diff --git a/include/rootston/config.h b/include/rootston/config.h index 97a8baab2e..50e549eed9 100644 --- a/include/rootston/config.h +++ b/include/rootston/config.h @@ -10,7 +10,6 @@ struct roots_output_config { char *name; bool enable; enum wl_output_transform transform; - int x, y; float scale; struct wl_list link; struct { @@ -19,6 +18,18 @@ struct roots_output_config { } mode; }; +struct roots_layout_config { + struct wl_list rules; +}; + +struct roots_layout_rule_config { + char *output_name; + struct wl_list link; + enum wlr_output_layout_output_configuration configuration; + char *reference_output; + int x, y; +}; + struct roots_device_config { char *name; char *seat; @@ -68,6 +79,7 @@ struct roots_config { struct wl_list keyboards; struct wl_list cursors; + struct roots_layout_config layout; char *config_path; char *startup_cmd; bool debug_damage_tracking; diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h index bb7d2084d5..d7b2d9f457 100644 --- a/include/rootston/desktop.h +++ b/include/rootston/desktop.h @@ -26,6 +26,7 @@ #include "rootston/config.h" #include "rootston/output.h" #include "rootston/view.h" +#include "rootston/layout.h" struct roots_desktop { struct wl_list views; // roots_view::link @@ -35,8 +36,8 @@ struct roots_desktop { struct roots_server *server; struct roots_config *config; + struct roots_layout *layout; - struct wlr_output_layout *layout; struct wlr_xcursor_manager *xcursor_manager; struct wlr_compositor *compositor; diff --git a/include/rootston/layout.h b/include/rootston/layout.h new file mode 100644 index 0000000000..80ac77f6dc --- /dev/null +++ b/include/rootston/layout.h @@ -0,0 +1,29 @@ +#ifndef ROOTSTON_LAYOUT_H +#define ROOTSTON_LAYOUT_H + +#include +#include "rootston/config.h" +#include "rootston/output.h" + +struct roots_layout_rule { + struct roots_layout_rule_config *config; + struct roots_output *output; + struct wl_list link; + bool configured; +}; + +struct roots_layout { + struct wlr_output_layout *wlr_layout; + struct roots_layout_config *current_config; + struct wl_list rules; +}; + +struct roots_layout* roots_layout_create(struct roots_layout_config *config); +void roots_layout_destroy(struct roots_layout *layout); +void roots_layout_add_output(struct roots_layout *layout, + struct roots_output *output); +void roots_layout_remove_output(struct roots_layout *layout, + struct roots_output *output); +void roots_layout_reflow(struct roots_layout *layout); + +#endif diff --git a/include/wlr/types/wlr_output_layout.h b/include/wlr/types/wlr_output_layout.h index 759c8ccf4f..95616cdab9 100644 --- a/include/wlr/types/wlr_output_layout.h +++ b/include/wlr/types/wlr_output_layout.h @@ -23,10 +23,24 @@ struct wlr_output_layout { struct wlr_output_layout_output_state; +enum wlr_output_layout_output_configuration { + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED, + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_AUTO, + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_LEFT_OF, + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_RIGHT_OF, + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_BELOW, + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_ABOVE, + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_SAME_AS, +}; + struct wlr_output_layout_output { struct wlr_output *output; int x, y; + enum wlr_output_layout_output_configuration configuration; + struct wlr_output_layout_output *reference; + struct wl_list link; + struct wlr_output_layout_output_state *state; struct { @@ -88,16 +102,28 @@ struct wlr_box *wlr_output_layout_get_box( struct wlr_output_layout *layout, struct wlr_output *reference); /** -* Add an auto configured output to the layout. This will place the output in a -* sensible location in the layout. The coordinates of the output in the layout -* may adjust dynamically when the layout changes. If the output is already in -* the layout, it will become auto configured. If the position of the output is -* set such as with `wlr_output_layout_move()`, the output will become manually -* configured. -*/ + * Add an auto configured output to the layout. This will place the output in a + * sensible location in the layout. The coordinates of the output in the layout + * may adjust dynamically when the layout changes. If the output is already in + * the layout, it will become auto configured. Relative layouts are not supported + * with this kind of output. + */ void wlr_output_layout_add_auto(struct wlr_output_layout *layout, struct wlr_output *output); + +/** + * Add an auto configured output to the layout relative to another output. + * This will place the relative output next to the border of the reference + * output. The output layout will adjust dynamically to keep the relative + * output in this position when the absolute output changes coordinates. + * If the position of the relative output is set to absolute coordinates, + * it will become manually configured. +*/ +void wlr_output_layout_add_relative(struct wlr_output_layout *layout, + struct wlr_output *output, struct wlr_output *reference, + enum wlr_output_layout_output_configuration configuration); + /** * Get the output closest to the center of the layout extents. */ diff --git a/rootston/config.c b/rootston/config.c index 9a4d77fd89..a11b3882cc 100644 --- a/rootston/config.c +++ b/rootston/config.c @@ -228,6 +228,89 @@ static void config_handle_keyboard(struct roots_config *config, } } +static void config_handle_layout(struct roots_config *config, const char *name, + const char *value) { + struct roots_layout_config *lc = &config->layout; + + char *mod_value = strdup(value); + char *strstart = mod_value; + char *saveptr; + mod_value = strtok_r(mod_value, " ", &saveptr); + + struct roots_layout_rule_config *rule = + calloc(1, sizeof(struct roots_layout_rule_config)); + + if (strcmp(mod_value, "fixed") == 0) { + mod_value = strtok_r(NULL, ",", &saveptr); + if (!mod_value) { + goto invalid_config; + } + rule->x = strtol(mod_value, NULL, 10); + mod_value = strtok_r(NULL, " ", &saveptr); + if (!mod_value) { + goto invalid_config; + } + rule->y = strtol(mod_value, NULL, 10); + rule->configuration = + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED; + + } else if (strcmp(mod_value, "left-of") == 0) { + mod_value = strtok_r(NULL, " ", &saveptr); + if (!mod_value) { + goto invalid_config; + } + rule->reference_output = strdup(mod_value); + rule->configuration = + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_LEFT_OF; + } else if (strcmp(mod_value, "right-of") == 0) { + mod_value = strtok_r(NULL, " ", &saveptr); + if (!mod_value) { + goto invalid_config; + } + rule->reference_output = strdup(mod_value); + rule->configuration = + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_RIGHT_OF; + } else if (strcmp(mod_value, "below") == 0) { + mod_value = strtok_r(NULL, " ", &saveptr); + if (!mod_value) { + goto invalid_config; + } + rule->reference_output = strdup(mod_value); + rule->configuration = + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_BELOW; + } else if (strcmp(mod_value, "above") == 0) { + mod_value = strtok_r(NULL, " ", &saveptr); + if (!mod_value) { + goto invalid_config; + } + rule->reference_output = strdup(mod_value); + rule->configuration = + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_ABOVE; + } else if (strcmp(mod_value, "same-as") == 0) { + mod_value = strtok_r(NULL, " ", &saveptr); + if (!mod_value) { + goto invalid_config; + } + rule->reference_output = strdup(mod_value); + rule->configuration = + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_SAME_AS; + } else { + goto invalid_config; + } + + rule->output_name = strdup(name); + wl_list_insert(&lc->rules, &rule->link); + + free(strstart); + + return; + +invalid_config: + wlr_log(L_ERROR, "got invalid config for layout: %s = %s", name, value); + free(strstart); + free(rule); +} + static const char *output_prefix = "output:"; static const char *device_prefix = "device:"; static const char *keyboard_prefix = "keyboard:"; @@ -280,10 +363,6 @@ static int config_ini_handler(void *user, const char *section, const char *name, } else { wlr_log(L_ERROR, "got invalid output enable value: %s", value); } - } else if (strcmp(name, "x") == 0) { - oc->x = strtol(value, NULL, 10); - } else if (strcmp(name, "y") == 0) { - oc->y = strtol(value, NULL, 10); } else if (strcmp(name, "scale") == 0) { oc->scale = strtof(value, NULL); assert(oc->scale > 0); @@ -376,6 +455,8 @@ static int config_ini_handler(void *user, const char *section, const char *name, section, strlen(keyboard_prefix)) == 0) { const char *device_name = section + strlen(keyboard_prefix); config_handle_keyboard(config, device_name, name, value); + } else if (strcmp(section, "layout") == 0) { + config_handle_layout(config, name, value); } else if (strcmp(section, "bindings") == 0) { add_binding_config(&config->bindings, name, value); } else { @@ -398,6 +479,7 @@ struct roots_config *roots_config_create_from_args(int argc, char *argv[]) { wl_list_init(&config->keyboards); wl_list_init(&config->cursors); wl_list_init(&config->bindings); + wl_list_init(&config->layout.rules); int c; while ((c = getopt(argc, argv, "C:E:hD")) != -1) { @@ -500,6 +582,13 @@ void roots_config_destroy(struct roots_config *config) { free(bc); } + struct roots_layout_rule_config *lrc, *lrtmp = NULL; + wl_list_for_each_safe(lrc, lrtmp, &config->layout.rules, link) { + free(lrc->output_name); + free(lrc->reference_output); + free(lrc); + } + free(config->config_path); free(config); } diff --git a/rootston/desktop.c b/rootston/desktop.c index a6f9e9a078..d050dac5b2 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -114,9 +114,9 @@ static void view_update_output(const struct roots_view *view, struct roots_output *output; wl_list_for_each(output, &desktop->outputs, link) { bool intersected = before != NULL && wlr_output_layout_intersects( - desktop->layout, output->wlr_output, before); - bool intersects = wlr_output_layout_intersects(desktop->layout, - output->wlr_output, &box); + desktop->layout->wlr_layout, output->wlr_output, before); + bool intersects = wlr_output_layout_intersects( + desktop->layout->wlr_layout, output->wlr_output, &box); if (intersected && !intersects) { wlr_surface_send_leave(view->wlr_surface, output->wlr_output); } @@ -185,12 +185,12 @@ static struct wlr_output *view_get_output(struct roots_view *view) { view_get_box(view, &view_box); double output_x, output_y; - wlr_output_layout_closest_point(view->desktop->layout, NULL, + wlr_output_layout_closest_point(view->desktop->layout->wlr_layout, NULL, view->x + (double)view_box.width/2, view->y + (double)view_box.height/2, &output_x, &output_y); - return wlr_output_layout_output_at(view->desktop->layout, output_x, - output_y); + return wlr_output_layout_output_at(view->desktop->layout->wlr_layout, + output_x, output_y); } void view_arrange_maximized(struct roots_view *view) { @@ -200,7 +200,7 @@ void view_arrange_maximized(struct roots_view *view) { struct wlr_output *output = view_get_output(view); struct roots_output *roots_output = output->data; struct wlr_box *output_box = - wlr_output_layout_get_box(view->desktop->layout, output); + wlr_output_layout_get_box(view->desktop->layout->wlr_layout, output); struct wlr_box usable_area; memcpy(&usable_area, &roots_output->usable_area, sizeof(struct wlr_box)); @@ -275,7 +275,7 @@ void view_set_fullscreen(struct roots_view *view, bool fullscreen, view->saved.height = view_box.height; struct wlr_box *output_box = - wlr_output_layout_get_box(view->desktop->layout, output); + wlr_output_layout_get_box(view->desktop->layout->wlr_layout, output); view_move_resize(view, output_box->x, output_box->y, output_box->width, output_box->height); view_rotate(view, 0); @@ -333,7 +333,7 @@ bool view_center(struct roots_view *view) { } struct wlr_output *output = - wlr_output_layout_output_at(desktop->layout, + wlr_output_layout_output_at(desktop->layout->wlr_layout, seat->cursor->cursor->x, seat->cursor->cursor->y); if (!output) { @@ -342,7 +342,7 @@ bool view_center(struct roots_view *view) { } const struct wlr_output_layout_output *l_output = - wlr_output_layout_get(desktop->layout, output); + wlr_output_layout_get(desktop->layout->wlr_layout, output); int width, height; wlr_output_effective_resolution(output, &width, &height); @@ -616,7 +616,7 @@ static struct roots_view *desktop_view_at(struct roots_desktop *desktop, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { struct wlr_output *wlr_output = - wlr_output_layout_output_at(desktop->layout, lx, ly); + wlr_output_layout_output_at(desktop->layout->wlr_layout, lx, ly); if (wlr_output != NULL) { struct roots_output *output = desktop_output_from_wlr_output(desktop, wlr_output); @@ -661,7 +661,7 @@ struct wlr_surface *desktop_surface_at(struct roots_desktop *desktop, struct roots_view **view) { struct wlr_surface *surface = NULL; struct wlr_output *wlr_output = - wlr_output_layout_output_at(desktop->layout, lx, ly); + wlr_output_layout_output_at(desktop->layout->wlr_layout, lx, ly); struct roots_output *roots_output = NULL; double ox = lx, oy = ly; if (view) { @@ -670,7 +670,7 @@ struct wlr_surface *desktop_surface_at(struct roots_desktop *desktop, if (wlr_output) { roots_output = wlr_output->data; - wlr_output_layout_output_coords(desktop->layout, wlr_output, &ox, &oy); + wlr_output_layout_output_coords(desktop->layout->wlr_layout, wlr_output, &ox, &oy); if ((surface = layer_surface_at(roots_output, &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], @@ -712,13 +712,13 @@ static void handle_layout_change(struct wl_listener *listener, void *data) { wl_container_of(listener, desktop, layout_change); struct wlr_output *center_output = - wlr_output_layout_get_center_output(desktop->layout); + wlr_output_layout_get_center_output(desktop->layout->wlr_layout); if (center_output == NULL) { return; } struct wlr_box *center_output_box = - wlr_output_layout_get_box(desktop->layout, center_output); + wlr_output_layout_get_box(desktop->layout->wlr_layout, center_output); double center_x = center_output_box->x + center_output_box->width/2; double center_y = center_output_box->y + center_output_box->height/2; @@ -727,7 +727,8 @@ static void handle_layout_change(struct wl_listener *listener, void *data) { struct wlr_box box; view_get_box(view, &box); - if (wlr_output_layout_intersects(desktop->layout, NULL, &box)) { + if (wlr_output_layout_intersects(desktop->layout->wlr_layout, NULL, + &box)) { continue; } @@ -772,10 +773,10 @@ struct roots_desktop *desktop_create(struct roots_server *server, desktop->server = server; desktop->config = config; - desktop->layout = wlr_output_layout_create(); - wlr_xdg_output_manager_create(server->wl_display, desktop->layout); + desktop->layout = roots_layout_create(&config->layout); + wlr_xdg_output_manager_create(server->wl_display, desktop->layout->wlr_layout); desktop->layout_change.notify = handle_layout_change; - wl_signal_add(&desktop->layout->events.change, &desktop->layout_change); + wl_signal_add(&desktop->layout->wlr_layout->events.change, &desktop->layout_change); desktop->compositor = wlr_compositor_create(server->wl_display, server->renderer); diff --git a/rootston/keyboard.c b/rootston/keyboard.c index b5dac51c8a..376125751e 100644 --- a/rootston/keyboard.c +++ b/rootston/keyboard.c @@ -13,6 +13,7 @@ #include "rootston/input.h" #include "rootston/keyboard.h" #include "rootston/seat.h" +#include "rootston/layout.h" static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms, xkb_keysym_t keysym) { diff --git a/rootston/layer_shell.c b/rootston/layer_shell.c index db0aeb596b..3ba02d18a9 100644 --- a/rootston/layer_shell.c +++ b/rootston/layer_shell.c @@ -392,14 +392,14 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) { struct roots_seat *seat = input_last_active_seat(input); assert(seat); // Technically speaking we should handle this case struct wlr_output *output = - wlr_output_layout_output_at(desktop->layout, + wlr_output_layout_output_at(desktop->layout->wlr_layout, seat->cursor->cursor->x, seat->cursor->cursor->y); if (!output) { wlr_log(L_ERROR, "Couldn't find output at (%.0f,%.0f)", seat->cursor->cursor->x, seat->cursor->cursor->y); - output = wlr_output_layout_get_center_output(desktop->layout); + output = wlr_output_layout_get_center_output(desktop->layout->wlr_layout); } if (output) { layer_surface->output = output; diff --git a/rootston/layout.c b/rootston/layout.c new file mode 100644 index 0000000000..50e16d0421 --- /dev/null +++ b/rootston/layout.c @@ -0,0 +1,247 @@ +#include +#include +#include +#include "rootston/output.h" +#include "rootston/layout.h" + +static struct roots_layout_rule *get_rule(struct roots_layout *layout, char *name) { + struct roots_layout_rule *rule; + wl_list_for_each(rule, &layout->rules, link) { + if (strcmp(name, rule->output->wlr_output->name) == 0) { + return rule; + } + } + + return NULL; +} + +struct roots_layout* roots_layout_create(struct roots_layout_config *config) { + struct roots_layout *layout = calloc(1, sizeof(struct roots_layout)); + + if (!layout) { + return NULL; + } + + wl_list_init(&layout->rules); + layout->wlr_layout = wlr_output_layout_create(); + layout->current_config = config; + + return layout; +} + +void roots_layout_destroy(struct roots_layout *layout) { + if (layout) { + struct roots_layout_rule *rule, *tmp; + wl_list_for_each_safe(rule, tmp, &layout->rules, link) { + wl_list_remove(&rule->link); + free(rule); + } + + free(layout); + } +} + +static void apply_rule(struct roots_layout *layout, + struct roots_layout_rule *rule) { + + rule->configured = false; + + if (rule->config) { + if (rule->config->configuration != + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED) { + struct roots_layout_rule *ref = + get_rule(layout, rule->config->reference_output); + if (ref) { + wlr_output_layout_add_relative(layout->wlr_layout, + rule->output->wlr_output, ref->output->wlr_output, + rule->config->configuration); + rule->configured = true; + } else { + wlr_output_layout_add(layout->wlr_layout, + rule->output->wlr_output, 0, 0); + } + } else { + wlr_output_layout_add(layout->wlr_layout, rule->output->wlr_output, + rule->config->x, rule->config->y); + rule->configured = true; + } + } else { + wlr_output_layout_add(layout->wlr_layout, + rule->output->wlr_output, 0, 0); + } + + struct roots_layout_rule *ref_rule; + wl_list_for_each(ref_rule, &layout->rules, link) { + if (!ref_rule->configured && ref_rule->config) { + if (strcmp(rule->output->wlr_output->name, + ref_rule->config->reference_output) == 0) { + wlr_output_layout_add_relative(layout->wlr_layout, + ref_rule->output->wlr_output, rule->output->wlr_output, + ref_rule->config->configuration); + ref_rule->configured = true; + } + } + } +} + +void roots_layout_add_output(struct roots_layout *layout, + struct roots_output *output) { + + struct roots_layout_rule *new_rule; + + wl_list_for_each(new_rule, &layout->rules, link) { + if (new_rule->output == output) { + return; + } + } + + new_rule = calloc(1, sizeof(struct roots_layout_rule)); + + if (!new_rule) { + wlr_log(L_ERROR, "Could not allocate layout rule"); + return; + } + + new_rule->output = output; + + struct roots_layout_rule_config *c_rule; + wl_list_for_each(c_rule, &layout->current_config->rules, link) { + if (strcmp(output->wlr_output->name, c_rule->output_name) == 0) { + new_rule->config = c_rule; + break; + } + } + + wl_list_insert(&layout->rules, &new_rule->link); + apply_rule(layout, new_rule); +} + +void roots_layout_remove_output(struct roots_layout *layout, + struct roots_output *output) { + struct roots_layout_rule *rule, *tmp; + + wl_list_for_each_safe(rule, tmp, &layout->rules, link) { + if (rule->output == output) { + wl_list_remove(&rule->link); + free(rule); + } else if (rule->config && rule->config->reference_output && + strcmp(output->wlr_output->name, + rule->config->reference_output) == 0) { + rule->configured = false; + } + } +} + +/* + * roots_layout_reflow makes sure that outputs that could not be properly + * configured, do not cause gaps or overlaps. First the extents of the + * outputs that are fully configured are calculated. These include outputs + * that have no active reference and are not fixed, and their children. + * The remaining unconfigured outputs are placed to the right of the fully + * configured outputs, taking into account that the unconfigred outputs + * can have children. + */ +void roots_layout_reflow(struct roots_layout *layout) { + int max_x = INT_MIN; + int max_x_y = 0; + // Configured is always initialized before use, since the first output + // in the layout is always fixed (rootston specifc, since it does not + // use auto outputs) + bool configured; + + // XXX: requires specific ordering of the outputs in layout->outputs, + // specifically, output must follow their reference immediately. + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->wlr_layout->outputs, link) { + if (l_output->configuration == + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED) { + struct roots_layout_rule *rule; + wl_list_for_each(rule, &layout->rules, link) { + if (rule->output->wlr_output == l_output->output) { + configured = rule->configured; + break; + } + } + } + + if (configured) { + struct wlr_box *box = + wlr_output_layout_get_box(layout->wlr_layout, l_output->output); + if (max_x < box->x + box->width) { + max_x = box->x + box->width; + max_x_y = box->y; + } + } + } + + if (max_x == INT_MIN) { // In case there are no configured layouts + max_x = 0; + } + + struct wlr_output *unconfigured_output = NULL; + int auto_min_x = INT_MAX; + int auto_min_x_y = 0; + int auto_max_x = INT_MIN; + int auto_max_x_y = 0; + + wl_list_for_each(l_output, &layout->wlr_layout->outputs, link) { + if (l_output->configuration == + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED) { + if (unconfigured_output) { + struct wlr_box *box = + wlr_output_layout_get_box(layout->wlr_layout, + unconfigured_output); + wlr_output_layout_add(layout->wlr_layout, unconfigured_output, + box->x - auto_min_x + max_x, + box->y - auto_min_x_y + max_x_y); + max_x += auto_max_x - auto_min_x; + max_x_y += auto_max_x_y - auto_min_x_y; + auto_min_x = INT_MAX; + auto_min_x_y = 0; + auto_max_x = INT_MIN; + auto_max_x_y = 0; + unconfigured_output = NULL; + } + struct roots_layout_rule *rule; + wl_list_for_each(rule, &layout->rules, link) { + if (rule->output->wlr_output == l_output->output) { + configured = rule->configured; + if (!configured) { + unconfigured_output = l_output->output; + } + break; + } + } + } + + if (!configured) { + struct wlr_box *box = + wlr_output_layout_get_box(layout->wlr_layout, l_output->output); + if (auto_max_x < box->x + box->width) { + auto_max_x = box->x + box->width; + auto_max_x_y = box->y; + } + + if (auto_min_x > box->x) { + auto_min_x = box->x; + auto_min_x_y = box->y; + } + } + } + + if (unconfigured_output) { + struct wlr_box *box = + wlr_output_layout_get_box(layout->wlr_layout, + unconfigured_output); + wlr_output_layout_add(layout->wlr_layout, unconfigured_output, + box->x - auto_min_x + max_x, + box->y - auto_min_x_y + max_x_y); + max_x += auto_max_x - auto_min_x; + max_x_y += auto_max_x_y - auto_min_x_y; + } + + struct roots_layout_rule *rule; + wl_list_for_each(rule, &layout->rules, link) { + output_damage_whole(rule->output); + } +} diff --git a/rootston/meson.build b/rootston/meson.build index 8ab872b598..0069e10637 100644 --- a/rootston/meson.build +++ b/rootston/meson.build @@ -13,6 +13,7 @@ sources = [ 'wl_shell.c', 'xdg_shell_v6.c', 'xdg_shell.c', + 'layout.c' ] if get_option('enable-xwayland') sources += ['xwayland.c'] diff --git a/rootston/output.c b/rootston/output.c index faa808d176..9cb9c2bb7c 100644 --- a/rootston/output.c +++ b/rootston/output.c @@ -201,8 +201,9 @@ static void render_surface(struct wlr_surface *surface, int sx, int sy, get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); struct wlr_box box; - bool intersects = surface_intersect_output(surface, output->desktop->layout, - output->wlr_output, lx, ly, rotation, &box); + bool intersects = surface_intersect_output(surface, + output->desktop->layout->wlr_layout, output->wlr_output, + lx, ly, rotation, &box); if (!intersects) { return; } @@ -252,7 +253,8 @@ static void get_decoration_box(struct roots_view *view, double x = sx + view->x; double y = sy + view->y; - wlr_output_layout_output_coords(output->desktop->layout, wlr_output, &x, &y); + wlr_output_layout_output_coords(output->desktop->layout->wlr_layout, + wlr_output, &x, &y); box->x = x * wlr_output->scale; box->y = y * wlr_output->scale; @@ -346,7 +348,7 @@ static void surface_send_frame_done(struct wlr_surface *surface, int sx, int sy, double lx, ly; get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); - if (!surface_intersect_output(surface, output->desktop->layout, + if (!surface_intersect_output(surface, output->desktop->layout->wlr_layout, output->wlr_output, lx, ly, rotation, NULL)) { return; } @@ -403,7 +405,7 @@ static void render_output(struct roots_output *output) { float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f}; const struct wlr_box *output_box = - wlr_output_layout_get_box(desktop->layout, wlr_output); + wlr_output_layout_get_box(desktop->layout->wlr_layout, wlr_output); // Check if we can delegate the fullscreen surface to the output if (output->fullscreen_view != NULL && @@ -608,8 +610,9 @@ static void damage_whole_surface(struct wlr_surface *surface, int sx, int sy, wlr_output_transformed_resolution(output->wlr_output, &ow, &oh); struct wlr_box box; - bool intersects = surface_intersect_output(surface, output->desktop->layout, - output->wlr_output, lx, ly, rotation, &box); + bool intersects = surface_intersect_output(surface, + output->desktop->layout->wlr_layout, output->wlr_output, + lx, ly, rotation, &box); if (!intersects) { return; } @@ -622,7 +625,7 @@ static void damage_whole_surface(struct wlr_surface *surface, int sx, int sy, void output_damage_whole_local_surface(struct roots_output *output, struct wlr_surface *surface, double ox, double oy, float rotation) { struct wlr_output_layout_output *layout = wlr_output_layout_get( - output->desktop->layout, output->wlr_output); + output->desktop->layout->wlr_layout, output->wlr_output); struct damage_data data = { .output = output }; surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0, &data.layout, damage_whole_surface, &data); @@ -679,7 +682,7 @@ static void damage_from_surface(struct wlr_surface *surface, int sx, int sy, wlr_output_transformed_resolution(wlr_output, &ow, &oh); struct wlr_box box; - surface_intersect_output(surface, output->desktop->layout, + surface_intersect_output(surface, output->desktop->layout->wlr_layout, wlr_output, lx, ly, rotation, &box); int center_x = box.x + box.width/2; @@ -704,7 +707,7 @@ static void damage_from_surface(struct wlr_surface *surface, int sx, int sy, void output_damage_from_local_surface(struct roots_output *output, struct wlr_surface *surface, double ox, double oy, float rotation) { struct wlr_output_layout_output *layout = wlr_output_layout_get( - output->desktop->layout, output->wlr_output); + output->desktop->layout->wlr_layout, output->wlr_output); struct damage_data data = { .output = output }; surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0, &data.layout, damage_from_surface, &data); @@ -752,6 +755,8 @@ static void output_destroy(struct roots_output *output) { // TODO: cursor //example_config_configure_cursor(sample->config, sample->cursor, // sample->compositor); + roots_layout_remove_output(output->desktop->layout, output); + roots_layout_reflow(output->desktop->layout); wl_list_remove(&output->link); wl_list_remove(&output->destroy.link); @@ -784,12 +789,14 @@ static void output_damage_handle_destroy(struct wl_listener *listener, static void output_handle_mode(struct wl_listener *listener, void *data) { struct roots_output *output = wl_container_of(listener, output, mode); + roots_layout_reflow(output->desktop->layout); arrange_layers(output); } static void output_handle_transform(struct wl_listener *listener, void *data) { struct roots_output *output = wl_container_of(listener, output, transform); + roots_layout_reflow(output->desktop->layout); arrange_layers(output); } @@ -846,13 +853,14 @@ void handle_new_output(struct wl_listener *listener, void *data) { } wlr_output_set_scale(wlr_output, output_config->scale); wlr_output_set_transform(wlr_output, output_config->transform); - wlr_output_layout_add(desktop->layout, wlr_output, output_config->x, - output_config->y); + roots_layout_add_output(desktop->layout, output); + roots_layout_reflow(desktop->layout); } else { wlr_output_enable(wlr_output, false); } } else { - wlr_output_layout_add_auto(desktop->layout, wlr_output); + roots_layout_add_output(desktop->layout, output); + roots_layout_reflow(desktop->layout); } struct roots_seat *seat; diff --git a/rootston/rootston.ini.example b/rootston/rootston.ini.example index 556cbefba5..ffcabfe2fc 100644 --- a/rootston/rootston.ini.example +++ b/rootston/rootston.ini.example @@ -7,10 +7,6 @@ xwayland=false # Single output configuration. String after colon must match output's name. [output:VGA-1] -# Set logical (layout) coordinates for this screen -x = 1920 -y = 0 - # Screen transformation # possible values are: # '90', '180' or '270' - rotate output by specified angle clockwise @@ -19,6 +15,14 @@ y = 0 # and rotate by specified angle rotate = 90 +# Output position configuration +[layout] +# Put the output with name VGA-1 at (0,0) +VGA-1 = fixed 0,0 +# Put the output with name VGA-2 to the left of the output with name VGA-1 +VGA-2 = left-of VGA-1 +# Other possibilities include: right-of, above, below, mirror + [cursor] # Restrict cursor movements to single output map-to-output = VGA-1 diff --git a/rootston/seat.c b/rootston/seat.c index b137ff1185..0173b3dcda 100644 --- a/rootston/seat.c +++ b/rootston/seat.c @@ -215,7 +215,7 @@ static void roots_seat_init_cursor(struct roots_seat *seat) { seat->cursor->seat = seat; struct wlr_cursor *wlr_cursor = seat->cursor->cursor; struct roots_desktop *desktop = seat->input->server->desktop; - wlr_cursor_attach_output_layout(wlr_cursor, desktop->layout); + wlr_cursor_attach_output_layout(wlr_cursor, desktop->layout->wlr_layout); roots_seat_configure_cursor(seat); roots_seat_configure_xcursor(seat); @@ -762,7 +762,7 @@ void roots_seat_set_focus(struct roots_seat *seat, struct roots_view *view) { if (output->fullscreen_view && output->fullscreen_view != view && wlr_output_layout_intersects( - desktop->layout, + desktop->layout->wlr_layout, output->wlr_output, &box)) { view_set_fullscreen(output->fullscreen_view, false, NULL); diff --git a/rootston/xdg_shell.c b/rootston/xdg_shell.c index 805fb8742c..b7f04139ca 100644 --- a/rootston/xdg_shell.c +++ b/rootston/xdg_shell.c @@ -61,7 +61,7 @@ static void popup_unconstrain(struct roots_xdg_popup *popup) { } struct roots_view *view = popup->view_child.view; - struct wlr_output_layout *layout = view->desktop->layout; + struct wlr_output_layout *layout = view->desktop->layout->wlr_layout; struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; int anchor_lx, anchor_ly; diff --git a/rootston/xdg_shell_v6.c b/rootston/xdg_shell_v6.c index 02ad867d36..5429c282ea 100644 --- a/rootston/xdg_shell_v6.c +++ b/rootston/xdg_shell_v6.c @@ -63,7 +63,7 @@ static void popup_unconstrain(struct roots_xdg_popup_v6 *popup) { } struct roots_view *view = popup->view_child.view; - struct wlr_output_layout *layout = view->desktop->layout; + struct wlr_output_layout *layout = view->desktop->layout->wlr_layout; struct wlr_xdg_popup_v6 *wlr_popup = popup->wlr_popup; int anchor_lx, anchor_ly; diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c index bb1d399a3e..6498d139b4 100644 --- a/types/wlr_output_layout.c +++ b/types/wlr_output_layout.c @@ -17,7 +17,6 @@ struct wlr_output_layout_output_state { struct wlr_output_layout_output *l_output; struct wlr_box _box; // should never be read directly, use the getter - bool auto_configured; struct wl_listener mode; struct wl_listener scale; @@ -45,9 +44,24 @@ struct wlr_output_layout *wlr_output_layout_create(void) { return layout; } +static void output_detach_children( + struct wlr_output_layout_output *l_output) { + struct wlr_output_layout_output *l_output_rel; + wl_list_for_each(l_output_rel, &l_output->state->layout->outputs, link) { + if (l_output_rel->reference == l_output) { + l_output_rel->reference = NULL; + l_output_rel->configuration = + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED; + } + } +} + static void output_layout_output_destroy( struct wlr_output_layout_output *l_output) { wlr_signal_emit_safe(&l_output->events.destroy, l_output); + + output_detach_children(l_output); + wlr_output_destroy_global(l_output->output); wl_list_remove(&l_output->state->mode.link); wl_list_remove(&l_output->state->scale.link); @@ -93,38 +107,63 @@ static struct wlr_box *output_layout_output_get_box( * the rightmost output in the layout in a horizontal line. */ static void output_layout_reconfigure(struct wlr_output_layout *layout) { - int max_x = INT_MIN; - int max_x_y = INT_MIN; // y value for the max_x output - // find the rightmost x coordinate occupied by a manually configured output - // in the layout struct wlr_output_layout_output *l_output; + int max_x = INT_MIN; + int max_x_y = 0; + wl_list_for_each(l_output, &layout->outputs, link) { - if (l_output->state->auto_configured) { - continue; - } struct wlr_box *box = output_layout_output_get_box(l_output); - if (box->x + box->width > max_x) { - max_x = box->x + box->width; - max_x_y = box->y; + struct wlr_box *ref_box; + + // Requires the outputs to be ordered such that outputs follow + // immediately after their parents + switch(l_output->configuration) { + case WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED: + break; + case WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_LEFT_OF: + ref_box =output_layout_output_get_box(l_output->reference); + l_output->x = ref_box->x - box->width; + l_output->y = ref_box->y; + break; + case WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_RIGHT_OF: + ref_box =output_layout_output_get_box(l_output->reference); + l_output->x = ref_box->x + ref_box->width; + l_output->y = ref_box->y; + break; + case WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_BELOW: + ref_box =output_layout_output_get_box(l_output->reference); + l_output->x = ref_box->x; + l_output->y = ref_box->y + box->height; + break; + case WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_ABOVE: + ref_box =output_layout_output_get_box(l_output->reference); + l_output->x = ref_box->x; + l_output->y = ref_box->y - ref_box->height; + break; + case WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_RELATIVE_SAME_AS: + ref_box =output_layout_output_get_box(l_output->reference); + l_output->x = ref_box->x; + l_output->y = ref_box->y; + break; + case WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_AUTO: + if (max_x == INT_MIN) { + max_x = 0; + } + l_output->x = max_x; + l_output->y = max_x_y; + break; } - } - - if (max_x == INT_MIN) { - // there are no manually configured outputs - max_x = 0; - max_x_y = 0; - } - wl_list_for_each(l_output, &layout->outputs, link) { - if (!l_output->state->auto_configured) { - continue; + if (max_x < (l_output->x + box->width)) { + max_x = l_output->x + box->width; + max_x_y = l_output->y; } - struct wlr_box *box = output_layout_output_get_box(l_output); - l_output->x = max_x; - l_output->y = max_x_y; - max_x += box->width; + + struct wlr_output *output = l_output->output; + + wlr_log(L_DEBUG, "%s - %dx%d+%d+%d", output->name, output->width, output->height, l_output->x, l_output->y); } wl_list_for_each(l_output, &layout->outputs, link) { @@ -176,7 +215,6 @@ static struct wlr_output_layout_output *output_layout_output_create( l_output->state->layout = layout; l_output->output = output; wl_signal_init(&l_output->events.destroy); - wl_list_insert(&layout->outputs, &l_output->link); wl_signal_add(&output->events.mode, &l_output->state->mode); l_output->state->mode.notify = handle_output_mode; @@ -190,6 +228,42 @@ static struct wlr_output_layout_output *output_layout_output_create( return l_output; } +void relocate_output_with_children(struct wlr_output_layout *layout, + struct wlr_output_layout_output *output, + struct wl_list *ref) { + + if (output->link.prev == ref) { + return; + } + + struct wlr_output_layout_output *child_output, *tmp; + struct wlr_output_layout_output *parent_output = + output; + bool started = false; + + wl_list_for_each_safe(child_output, tmp, + &layout->outputs, link) { + if (!started) { + if (child_output == parent_output) { + started = true; + wl_list_remove(&child_output->link); + wl_list_insert(ref, &child_output->link); + } + continue; + } + + while (child_output->reference != parent_output) { + if (parent_output == output) { + return; + } + parent_output = parent_output->reference; + } + + wl_list_remove(&child_output->link); + wl_list_insert(&parent_output->link, &child_output->link); + } +} + void wlr_output_layout_add(struct wlr_output_layout *layout, struct wlr_output *output, int lx, int ly) { struct wlr_output_layout_output *l_output = @@ -200,15 +274,43 @@ void wlr_output_layout_add(struct wlr_output_layout *layout, wlr_log(L_ERROR, "Failed to create wlr_output_layout_output"); return; } + wl_list_insert(&layout->outputs, &l_output->link); + } else if (l_output->configuration != + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED) { + relocate_output_with_children(layout, l_output, &layout->outputs); } + l_output->x = lx; l_output->y = ly; - l_output->state->auto_configured = false; + l_output->configuration = WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED; + output_layout_reconfigure(layout); wlr_output_create_global(output); wlr_signal_emit_safe(&layout->events.add, l_output); } +void wlr_output_layout_move(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + + if (!l_output) { + wlr_log(L_ERROR, "output not found in this layout: %s", output->name); + return; + } + + if (l_output->configuration != + WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED) { + relocate_output_with_children(layout, l_output, &layout->outputs); + } + + l_output->x = lx; + l_output->y = ly; + l_output->configuration = WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED; + + output_layout_reconfigure(layout); +} + struct wlr_output_layout_output *wlr_output_layout_get( struct wlr_output_layout *layout, struct wlr_output *reference) { struct wlr_output_layout_output *l_output; @@ -270,20 +372,6 @@ struct wlr_output *wlr_output_layout_output_at(struct wlr_output_layout *layout, return NULL; } -void wlr_output_layout_move(struct wlr_output_layout *layout, - struct wlr_output *output, int lx, int ly) { - struct wlr_output_layout_output *l_output = - wlr_output_layout_get(layout, output); - if (l_output) { - l_output->x = lx; - l_output->y = ly; - l_output->state->auto_configured = false; - output_layout_reconfigure(layout); - } else { - wlr_log(L_ERROR, "output not found in this layout: %s", output->name); - } -} - void wlr_output_layout_remove(struct wlr_output_layout *layout, struct wlr_output *output) { struct wlr_output_layout_output *l_output = @@ -405,9 +493,65 @@ void wlr_output_layout_add_auto(struct wlr_output_layout *layout, wlr_log(L_ERROR, "Failed to create wlr_output_layout_output"); return; } + } else { + output_detach_children(l_output); + wl_list_remove(&l_output->link); + } + + l_output->configuration = WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_AUTO; + wl_list_insert(layout->outputs.prev, &l_output->link); // Insert at end + output_layout_reconfigure(layout); + wlr_output_create_global(output); + wlr_signal_emit_safe(&layout->events.add, l_output); +} + +void wlr_output_layout_add_relative(struct wlr_output_layout *layout, + struct wlr_output *output, struct wlr_output *reference, + enum wlr_output_layout_output_configuration configuration) { + assert(layout && reference); + + if (output == reference) { + wlr_log(L_ERROR, "Cannot self reference outputs"); + return; + } + + struct wlr_output_layout_output *l_output_reference = + wlr_output_layout_get(layout, reference); + + if (!l_output_reference) { + wlr_output_layout_add_auto(layout, reference); + l_output_reference = wlr_output_layout_get(layout, reference); + } + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + if (!l_output) { + l_output = output_layout_output_create(layout, output); + if (!l_output) { + wlr_log(L_ERROR, "Failed to create wlr_output_layout_output"); + return; + } + wl_list_insert(&l_output_reference->link, &l_output->link); + } else { + // Prevent loops + struct wlr_output_layout_output *parent = l_output_reference; + + while (parent) { + if (parent->reference == l_output) { + parent->configuration = WLR_OUTPUT_LAYOUT_OUTPUT_CONFIGURATION_FIXED; + parent->reference = NULL; + relocate_output_with_children(layout, parent, &layout->outputs); + break; + } + + parent = parent->reference; + } + + relocate_output_with_children(layout, l_output, &l_output_reference->link); } - l_output->state->auto_configured = true; + l_output->configuration = configuration; + l_output->reference = l_output_reference; + output_layout_reconfigure(layout); wlr_output_create_global(output); wlr_signal_emit_safe(&layout->events.add, l_output); @@ -430,7 +574,6 @@ struct wlr_output *wlr_output_layout_get_center_output( return wlr_output_layout_output_at(layout, dest_x, dest_y); } - struct wlr_output *wlr_output_layout_adjacent_output( struct wlr_output_layout *layout, enum wlr_direction direction, struct wlr_output *reference, double ref_lx, double ref_ly) {