diff --git a/include/waybox/output.h b/include/waybox/output.h index 4cde0fd..d117dce 100644 --- a/include/waybox/output.h +++ b/include/waybox/output.h @@ -5,7 +5,6 @@ #define _POSIX_C_SOURCE 200112L #endif -#include #include #include @@ -21,6 +20,8 @@ struct wb_output { struct wlr_xdg_output_manager_v1 *manager; + struct wl_list layers[4]; + struct wl_listener destroy; struct wl_listener frame; diff --git a/include/waybox/server.h b/include/waybox/server.h index e6238bd..e253100 100644 --- a/include/waybox/server.h +++ b/include/waybox/server.h @@ -27,6 +27,7 @@ #include "waybox/cursor.h" #include "decoration.h" +#include "layer_shell.h" #include "waybox/output.h" #include "waybox/seat.h" @@ -36,7 +37,7 @@ struct wb_server { struct wlr_allocator *allocator; struct wlr_backend *backend; struct wlr_compositor *compositor; - struct wlr_output_layout *layout; + struct wlr_output_layout *output_layout; struct wlr_renderer *renderer; struct wb_cursor *cursor; @@ -48,7 +49,10 @@ struct wb_server { uint32_t resize_edges; struct wl_list views; + struct wlr_layer_shell_v1 *layer_shell; struct wlr_xdg_shell *xdg_shell; + + struct wl_listener new_layer_surface; struct wl_listener new_xdg_surface; struct wl_listener new_xdg_decoration; diff --git a/waybox/cursor.c b/waybox/cursor.c index 2ccc41a..8655540 100644 --- a/waybox/cursor.c +++ b/waybox/cursor.c @@ -199,7 +199,7 @@ struct wb_cursor *wb_cursor_create(struct wb_server *server) { wl_signal_add(&server->seat->seat->events.request_set_cursor, &cursor->request_cursor); - wlr_cursor_attach_output_layout(cursor->cursor, server->layout); + wlr_cursor_attach_output_layout(cursor->cursor, server->output_layout); return cursor; } diff --git a/waybox/layer_shell.c b/waybox/layer_shell.c new file mode 100644 index 0000000..991aa17 --- /dev/null +++ b/waybox/layer_shell.c @@ -0,0 +1,294 @@ +#include +#include "waybox/xdg_shell.h" + +static void apply_exclusive(struct wlr_box *usable_area, + uint32_t anchor, int32_t exclusive, + int32_t margin_top, int32_t margin_right, + int32_t margin_bottom, int32_t margin_left) { + if (exclusive <= 0) { + return; + } + struct { + uint32_t anchors; + int *positive_axis; + int *negative_axis; + int margin; + } edges[] = { + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, + .positive_axis = &usable_area->y, + .negative_axis = &usable_area->height, + .margin = margin_top, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->height, + .margin = margin_bottom, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = &usable_area->x, + .negative_axis = &usable_area->width, + .margin = margin_left, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->width, + .margin = margin_right, + }, + }; + for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) { + if ((anchor & edges[i].anchors) == edges[i].anchors) { + if (edges[i].positive_axis) { + *edges[i].positive_axis += exclusive + edges[i].margin; + } + if (edges[i].negative_axis) { + *edges[i].negative_axis -= exclusive + edges[i].margin; + } + } + } +} + +static void arrange_layer(struct wlr_output *output, + struct wl_list *list /* struct *wb_layer_surface */, + struct wlr_box *usable_area, bool exclusive) { + struct wb_layer_surface *wb_surface; + struct wlr_box full_area = { 0 }; + wlr_output_effective_resolution(output, + &full_area.width, &full_area.height); + wl_list_for_each_reverse(wb_surface, list, link) { + struct wlr_layer_surface_v1 *layer = wb_surface->layer_surface; + struct wlr_layer_surface_v1_state *state = &layer->current; + if (exclusive != (state->exclusive_zone > 0)) { + continue; + } + struct wlr_box bounds; + if (state->exclusive_zone == -1) { + bounds = full_area; + } else { + bounds = *usable_area; + } + struct wlr_box box = { + .width = state->desired_width, + .height = state->desired_height + }; + /* Horizontal axis */ + const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if ((state->anchor & both_horiz) && box.width == 0) { + box.x = bounds.x; + box.width = bounds.width; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x = bounds.x; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x = bounds.x + (bounds.width - box.width); + } else { + box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); + } + /* Vertical axis */ + const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + if ((state->anchor & both_vert) && box.height == 0) { + box.y = bounds.y; + box.height = bounds.height; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y = bounds.y; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y = bounds.y + (bounds.height - box.height); + } else { + box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); + } + /* Margin */ + if ((state->anchor & both_horiz) == both_horiz) { + box.x += state->margin.left; + box.width -= state->margin.left + state->margin.right; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x += state->margin.left; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x -= state->margin.right; + } + if ((state->anchor & both_vert) == both_vert) { + box.y += state->margin.top; + box.height -= state->margin.top + state->margin.bottom; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y += state->margin.top; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y -= state->margin.bottom; + } + if (box.width < 0 || box.height < 0) { + wlr_layer_surface_v1_destroy(layer); + continue; + } + + /* Apply */ + wb_surface->geo = box; + apply_exclusive(usable_area, state->anchor, state->exclusive_zone, + state->margin.top, state->margin.right, + state->margin.bottom, state->margin.left); + wlr_layer_surface_v1_configure(layer, box.width, box.height); + } +} + +void arrange_layers(struct wb_output *output) { + struct wlr_box usable_area = { 0 }; + wlr_output_effective_resolution(output->wlr_output, + &usable_area.width, &usable_area.height); + + /* Arrange exclusive surfaces from top->bottom */ + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], + &usable_area, true); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], + &usable_area, true); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], + &usable_area, true); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + &usable_area, true); + + /* Arrange non-exlusive surfaces from top->bottom */ + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], + &usable_area, false); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], + &usable_area, false); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], + &usable_area, false); + arrange_layer(output->wlr_output, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + &usable_area, false); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); + struct wb_layer_surface *layer, *topmost = NULL; + for (size_t i = 0; i < nlayers; ++i) { + wl_list_for_each_reverse(layer, + &output->layers[layers_above_shell[i]], link) { + if (layer->layer_surface->current.keyboard_interactive) { + topmost = layer; + break; + } + } + if (topmost != NULL) { + break; + } + } + + /* Focus the topmost layer */ + if (topmost != NULL) + { + struct wb_view *view = + wl_container_of(output->server->views.next, view, link); + focus_view(view, view->xdg_surface->surface); + } +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct wb_layer_surface *layer = + wl_container_of(listener, layer, output_destroy); + layer->layer_surface->output = NULL; + wl_list_remove(&layer->output_destroy.link); + wlr_layer_surface_v1_destroy(layer->layer_surface); +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct wb_layer_surface *layer = + wl_container_of(listener, layer, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; + struct wlr_output *wlr_output = layer_surface->output; + if (wlr_output != NULL) { + struct wb_output *output = wlr_output->data; + arrange_layers(output); + } +} + +static void handle_destroy(struct wl_listener *listener, void *data) { + struct wb_layer_surface *layer = wl_container_of( + listener, layer, destroy); + wl_list_remove(&layer->link); + wl_list_remove(&layer->destroy.link); + wl_list_remove(&layer->map.link); + wl_list_remove(&layer->surface_commit.link); + if (layer->layer_surface->output) { + wl_list_remove(&layer->output_destroy.link); + arrange_layers((struct wb_output *)layer->layer_surface->output->data); + } + free(layer); +} + +static void handle_map(struct wl_listener *listener, void *data) { + struct wlr_layer_surface_v1 *layer_surface = data; + wlr_surface_send_enter(layer_surface->surface, layer_surface->output); +} + +void server_new_layer_surface(struct wl_listener *listener, void *data) { + struct wb_server *server = wl_container_of( + listener, server, new_layer_surface); + struct wlr_layer_surface_v1 *layer_surface = data; + if (!layer_surface->output) { + struct wlr_output *output = wlr_output_layout_output_at( + server->output_layout, server->cursor->cursor->x, server->cursor->cursor->y); + layer_surface->output = output; + } + + struct wb_output *output = layer_surface->output->data; + struct wb_layer_surface *wb_surface = + calloc(1, sizeof(struct wb_layer_surface)); + if (!wb_surface) { + return; + } + wb_surface->layer_surface = layer_surface; + layer_surface->data = wb_surface; + wb_surface->server = server; + + wb_surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&layer_surface->surface->events.commit, + &wb_surface->surface_commit); + wb_surface->output_destroy.notify = handle_output_destroy; + wl_signal_add(&layer_surface->output->events.destroy, + &wb_surface->output_destroy); + wb_surface->destroy.notify = handle_destroy; + wl_signal_add(&layer_surface->events.destroy, &wb_surface->destroy); + wb_surface->map.notify = handle_map; + wl_signal_add(&layer_surface->events.map, &wb_surface->map); + /* TODO: popups */ + + /* TODO: Listen for subsurfaces */ + wl_list_insert(&output->layers[layer_surface->pending.layer], &wb_surface->link); + /* Temporarily set the layer's current state to pending + * So that we can easily arrange it */ + struct wlr_layer_surface_v1_state old_state = layer_surface->current; + layer_surface->current = layer_surface->pending; + arrange_layers(output); + layer_surface->current = old_state; +} + +void init_layer_shell(struct wb_server *server) +{ + server->layer_shell = wlr_layer_shell_v1_create(server->wl_display); + server->new_layer_surface.notify = server_new_layer_surface; + wl_signal_add(&server->layer_shell->events.new_surface, + &server->new_layer_surface); +} diff --git a/waybox/layer_shell.h b/waybox/layer_shell.h new file mode 100644 index 0000000..22b9a7a --- /dev/null +++ b/waybox/layer_shell.h @@ -0,0 +1,22 @@ +#ifndef _WB_LAYERS_H +#define _WB_LAYERS_H +#include + +struct wb_server; + +struct wb_layer_surface { + struct wlr_layer_surface_v1 *layer_surface; + struct wb_server *server; + struct wl_list link; + + struct wl_listener destroy; + struct wl_listener map; + struct wl_listener surface_commit; + struct wl_listener output_destroy; + + struct wlr_box geo; +}; + +void init_layer_shell(struct wb_server *server); + +#endif diff --git a/waybox/meson.build b/waybox/meson.build index 9cddd5f..4e12aed 100644 --- a/waybox/meson.build +++ b/waybox/meson.build @@ -1,6 +1,7 @@ wb_src = files( 'cursor.c', 'decoration.c', + 'layer_shell.c', 'main.c', 'output.c', 'seat.c', diff --git a/waybox/output.c b/waybox/output.c index c7563a4..b48a6d7 100644 --- a/waybox/output.c +++ b/waybox/output.c @@ -31,7 +31,7 @@ static void render_surface(struct wlr_surface *surface, * output-local coordinates, or (2000 - 1920). */ double ox = 0, oy = 0; wlr_output_layout_output_coords( - view->server->layout, output, &ox, &oy); + view->server->output_layout, output, &ox, &oy); ox += view->x + sx, oy += view->y + sy; /* We also have to apply the scale factor for HiDPI outputs. This is only @@ -68,6 +68,43 @@ static void render_surface(struct wlr_surface *surface, wlr_surface_send_frame_done(surface, rdata->when); } +static void render_layer_surface(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct wb_layer_surface *layer_surface = data; + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + return; + } + struct wlr_output *output = layer_surface->layer_surface->output; + double ox = 0, oy = 0; + wlr_output_layout_output_coords( + layer_surface->server->output_layout, output, &ox, &oy); + ox += layer_surface->geo.x + sx, oy += layer_surface->geo.y + sy; + float matrix[9]; + enum wl_output_transform transform = + wlr_output_transform_invert(surface->current.transform); + struct wlr_box box; + memcpy(&box, &layer_surface->geo, sizeof(struct wlr_box)); + wlr_matrix_project_box(matrix, &box, transform, 0, + output->transform_matrix); + wlr_render_texture_with_matrix(layer_surface->server->renderer, + texture, matrix, 1); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_surface_send_frame_done(surface, &now); +} + +static void render_layer( + struct wb_output *output, struct wl_list *layer_surfaces) { + struct wb_layer_surface *layer_surface; + wl_list_for_each(layer_surface, layer_surfaces, link) { + struct wlr_layer_surface_v1 *wlr_layer_surface_v1 = + layer_surface->layer_surface; + wlr_surface_for_each_surface(wlr_layer_surface_v1->surface, + render_layer_surface, layer_surface); + } +} + void output_frame_notify(struct wl_listener *listener, void *data) { struct wb_output *output = wl_container_of(listener, output, frame); struct wlr_renderer *renderer = output->server->renderer; @@ -86,6 +123,9 @@ void output_frame_notify(struct wl_listener *listener, void *data) { float color[4] = {0.4f, 0.4f, 0.4f, 1.0f}; wlr_renderer_clear(renderer, color); + render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); + render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); + struct wb_view *view; wl_list_for_each_reverse(view, &output->server->views, link) { if (!view->mapped) @@ -103,6 +143,9 @@ void output_frame_notify(struct wl_listener *listener, void *data) { render_surface, &rdata); } + render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); + render_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); + wlr_output_render_software_cursors(output->wlr_output, NULL); wlr_renderer_end(renderer); wlr_output_commit(output->wlr_output); @@ -110,7 +153,7 @@ void output_frame_notify(struct wl_listener *listener, void *data) { void output_destroy_notify(struct wl_listener *listener, void *data) { struct wb_output *output = wl_container_of(listener, output, destroy); - wlr_output_layout_remove(output->server->layout, output->wlr_output); + wlr_output_layout_remove(output->server->output_layout, output->wlr_output); wl_list_remove(&output->link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->frame.link); @@ -142,16 +185,22 @@ void new_output_notify(struct wl_listener *listener, void *data) { struct wb_output *output = calloc(1, sizeof(struct wb_output)); output->server = server; output->wlr_output = wlr_output; + wlr_output->data = output; wl_list_insert(&server->outputs, &output->link); + wl_list_init(&output->layers[0]); + wl_list_init(&output->layers[1]); + wl_list_init(&output->layers[2]); + wl_list_init(&output->layers[3]); + output->destroy.notify = output_destroy_notify; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->frame.notify = output_frame_notify; wl_signal_add(&wlr_output->events.frame, &output->frame); - wlr_output_layout_add_auto(server->layout, wlr_output); + wlr_output_layout_add_auto(server->output_layout, wlr_output); wlr_output_create_global(wlr_output); output->manager = wlr_xdg_output_manager_v1_create(server->wl_display, - server->layout); + server->output_layout); } diff --git a/waybox/server.c b/waybox/server.c index 921c2d8..dc1331f 100644 --- a/waybox/server.c +++ b/waybox/server.c @@ -34,7 +34,7 @@ bool wb_create_backend(struct wb_server* server) { server->compositor = wlr_compositor_create(server->wl_display, server->renderer); - server->layout = wlr_output_layout_create(); + server->output_layout = wlr_output_layout_create(); server->seat = wb_seat_create(server); server->cursor = wb_cursor_create(server); @@ -70,6 +70,7 @@ bool wb_start_server(struct wb_server* server) { wlr_data_device_manager_create(server->wl_display); wl_list_init(&server->views); init_xdg_decoration(server); + init_layer_shell(server); init_xdg_shell(server); return true; @@ -81,7 +82,7 @@ bool wb_terminate(struct wb_server* server) { wb_seat_destroy(server->seat); wl_display_destroy_clients(server->wl_display); wl_display_destroy(server->wl_display); - wlr_output_layout_destroy(server->layout); + wlr_output_layout_destroy(server->output_layout); wlr_log(WLR_INFO, "%s", _("Display destroyed"));