diff --git a/libswc/compositor.c b/libswc/compositor.c index 543615c..a988921 100644 --- a/libswc/compositor.c +++ b/libswc/compositor.c @@ -434,6 +434,45 @@ static const struct view_impl view_impl = { .move = move, }; +static void +restack_view_for_layer(struct compositor_view *view, bool raise) +{ + struct compositor_view *other; + struct wl_list *insert_after = &compositor.views; + bool found_same = false; + + wl_list_for_each (other, &compositor.views, link) { + if (other == view) + continue; + + if (other->stack_layer > view->stack_layer) { + insert_after = &other->link; + continue; + } + + if (other->stack_layer == view->stack_layer) { + found_same = true; + insert_after = raise ? other->link.prev : &other->link; + break; + } + + insert_after = other->link.prev; + break; + } + + if (!found_same && !raise && insert_after == &compositor.views) { + insert_after = compositor.views.prev; + if (insert_after == &view->link) + insert_after = insert_after->prev; + } + + if (insert_after == &view->link) + insert_after = insert_after->prev; + + wl_list_remove(&view->link); + wl_list_insert(insert_after, &view->link); +} + struct compositor_view * compositor_create_view(struct surface *surface) { @@ -450,6 +489,8 @@ compositor_create_view(struct surface *surface) view->window = NULL; view->parent = NULL; view->visible = false; + view->always_top = false; + view->stack_layer = STACK_LAYER_NORMAL; view->extents.x1 = 0; view->extents.y1 = 0; view->extents.x2 = 0; @@ -461,6 +502,7 @@ compositor_create_view(struct surface *surface) wl_signal_init(&view->destroy_signal); surface_set_view(surface, &view->base); wl_list_insert(&compositor.views, &view->link); + restack_view_for_layer(view, true); return view; } @@ -538,6 +580,34 @@ compositor_view_hide(struct compositor_view *view) } } +void +raise_window_top(struct compositor_view *view) +{ + view->stack_layer = STACK_LAYER_OVERLAY; + restack_view_for_layer(view, true); + damage_view(view); + update(&view->base); +} + +void +compositor_view_set_stack_layer(struct compositor_view *view, uint32_t layer, bool raise) +{ + if (view->stack_layer == layer) { + if (raise) { + restack_view_for_layer(view, true); + damage_view(view); + update(&view->base); + } + return; + } + + damage_view(view); + view->stack_layer = layer; + restack_view_for_layer(view, raise); + damage_view(view); + update(&view->base); +} + void compositor_view_set_border_width(struct compositor_view *view, uint32_t width) { diff --git a/libswc/compositor.h b/libswc/compositor.h index 9844bd9..cb6e6a2 100644 --- a/libswc/compositor.h +++ b/libswc/compositor.h @@ -55,6 +55,8 @@ struct compositor_view { /* Whether or not the view is visible (mapped). */ bool visible; + bool always_top; + uint32_t stack_layer; /* The box that the surface covers (including it's border). */ pixman_box32_t extents; @@ -87,6 +89,18 @@ void compositor_view_set_parent(struct compositor_view *view, struct compositor_ void compositor_view_show(struct compositor_view *view); void compositor_view_hide(struct compositor_view *view); +void raise_window_top(struct compositor_view *view); + +enum compositor_stack_layer { + STACK_LAYER_BACKGROUND = 0, + STACK_LAYER_BOTTOM = 1, + STACK_LAYER_NORMAL = 2, + STACK_LAYER_TOP = 3, + STACK_LAYER_OVERLAY = 4, +}; + +void compositor_view_set_stack_layer(struct compositor_view *view, uint32_t layer, bool raise); + void compositor_view_set_border_color(struct compositor_view *view, uint32_t color); void compositor_view_set_border_width(struct compositor_view *view, uint32_t width); diff --git a/libswc/internal.h b/libswc/internal.h index a704b6a..0f55c1a 100644 --- a/libswc/internal.h +++ b/libswc/internal.h @@ -47,6 +47,7 @@ struct swc { struct swc_drm *const drm; struct wl_global *data_device_manager; struct wl_global *kde_decoration_manager; + struct wl_global *layer_shell; struct wl_global *panel_manager; struct wl_global *shell; struct wl_global *subcompositor; diff --git a/libswc/layer_shell.c b/libswc/layer_shell.c new file mode 100644 index 0000000..caa34fc --- /dev/null +++ b/libswc/layer_shell.c @@ -0,0 +1,641 @@ +#include "layer_shell.h" + +#include "compositor.h" +#include "internal.h" +#include "keyboard.h" +#include "output.h" +#include "screen.h" +#include "seat.h" +#include "surface.h" +#include "util.h" +#include "view.h" + +#include "wlr-layer-shell-unstable-v1-server-protocol.h" +#include +#include +#include + +struct layer_surface { + struct wl_resource *resource; + struct wl_listener surface_destroy_listener; + struct wl_listener surface_commit_listener; + struct compositor_view *view; + struct view_handler view_handler; + struct screen *screen; + struct screen_modifier modifier; + struct layer_surface_state { + uint32_t layer; + uint32_t anchor; + int32_t exclusive_zone; + uint32_t exclusive_edge; + uint32_t keyboard_interactivity; + struct { + int32_t top, right, bottom, left; + } margin; + uint32_t desired_width, desired_height; + } current, pending; + uint32_t configure_serial; + bool configured; + bool mapped; +}; + +static bool +state_equal(const struct layer_surface_state *a, + const struct layer_surface_state *b) +{ + return a->layer == b->layer && a->anchor == b->anchor && + a->exclusive_zone == b->exclusive_zone && + a->exclusive_edge == b->exclusive_edge && + a->keyboard_interactivity == b->keyboard_interactivity && + a->margin.top == b->margin.top && + a->margin.right == b->margin.right && + a->margin.bottom == b->margin.bottom && + a->margin.left == b->margin.left && + a->desired_width == b->desired_width && + a->desired_height == b->desired_height; +} + +static int32_t +available_size(uint32_t size, int32_t start_margin, int32_t end_margin) +{ + int32_t available = (int32_t)size - start_margin - end_margin; + + return MAX(available, 0); +} + +static uint32_t +configure_size(uint32_t desired, uint32_t anchor, uint32_t first_anchor, + uint32_t second_anchor, uint32_t total_size, int32_t start_margin, + int32_t end_margin) +{ + if (desired != 0) { + return desired; + } + + if ((anchor & first_anchor) && (anchor & second_anchor)) { + return available_size(total_size, start_margin, end_margin); + } + + return 0; +} + +static uint32_t +exclusive_edge(struct layer_surface *surface) +{ + if (surface->current.exclusive_edge != 0) { + return surface->current.exclusive_edge; + } + + switch (surface->current.anchor) { + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: + return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: + return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + default: + return 0; + } +} + +static int32_t +exclusive_size(struct layer_surface *surface) +{ + switch (surface->current.exclusive_zone) { + case 0: + return 0; + case -1: + switch (exclusive_edge(surface)) { + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: + return (int32_t)surface->view->base.geometry.height + + surface->current.margin.top; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + return (int32_t)surface->view->base.geometry.height + + surface->current.margin.bottom; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: + return (int32_t)surface->view->base.geometry.width + + surface->current.margin.left; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + return (int32_t)surface->view->base.geometry.width + + surface->current.margin.right; + default: + return 0; + } + default: + return MAX(surface->current.exclusive_zone, 0); + } +} + +static void +update_usable_geometry(struct layer_surface *surface) +{ + if (!surface->screen) { + return; + } + + screen_update_usable_geometry(surface->screen); +} + +static void +restack_layer(struct layer_surface *surface) +{ + uint32_t stack_layer = STACK_LAYER_NORMAL; + bool always_top = false; + + switch (surface->current.layer) { + case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: + stack_layer = STACK_LAYER_BACKGROUND; + break; + case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: + stack_layer = STACK_LAYER_BOTTOM; + break; + case ZWLR_LAYER_SHELL_V1_LAYER_TOP: + stack_layer = STACK_LAYER_TOP; + always_top = true; + break; + case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: + stack_layer = STACK_LAYER_OVERLAY; + always_top = true; + break; + default: + break; + } + + surface->view->always_top = always_top; + compositor_view_set_stack_layer(surface->view, stack_layer, true); +} + +static void +update_position(struct layer_surface *surface) +{ + struct swc_rectangle *screen = &surface->screen->base.geometry; + struct swc_rectangle *view = &surface->view->base.geometry; + int32_t x, y; + + if ((surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) && + !(surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + x = screen->x + surface->current.margin.left; + } else if ((surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) && + !(surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + x = screen->x + (int32_t)screen->width - (int32_t)view->width - + surface->current.margin.right; + } else { + x = screen->x + ((int32_t)screen->width - (int32_t)view->width + + surface->current.margin.left - + surface->current.margin.right) / + 2; + } + + if ((surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) && + !(surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + y = screen->y + surface->current.margin.top; + } else if ((surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) && + !(surface->current.anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + y = screen->y + (int32_t)screen->height - (int32_t)view->height - + surface->current.margin.bottom; + } else { + y = screen->y + ((int32_t)screen->height - (int32_t)view->height + + surface->current.margin.top - + surface->current.margin.bottom) / + 2; + } + + view_move(&surface->view->base, x, y); +} + +static void +send_configure(struct layer_surface *surface) +{ + uint32_t width, height; + + if (!surface->screen) { + return; + } + + width = configure_size(surface->current.desired_width, surface->current.anchor, + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, + surface->screen->base.geometry.width, + surface->current.margin.left, + surface->current.margin.right); + height = + configure_size(surface->current.desired_height, surface->current.anchor, + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + surface->screen->base.geometry.height, + surface->current.margin.top, + surface->current.margin.bottom); + surface->configure_serial = wl_display_next_serial(swc.display); + zwlr_layer_surface_v1_send_configure(surface->resource, + surface->configure_serial, width, + height); +} + +static void +set_size(struct wl_client *client, struct wl_resource *resource, uint32_t width, + uint32_t height) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + surface->pending.desired_width = width; + surface->pending.desired_height = height; +} + +static void +set_anchor(struct wl_client *client, struct wl_resource *resource, + uint32_t anchor) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + surface->pending.anchor = anchor; +} + +static void +set_exclusive_zone(struct wl_client *client, struct wl_resource *resource, + int32_t zone) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + surface->pending.exclusive_zone = zone; +} + +static void +set_margin(struct wl_client *client, struct wl_resource *resource, int32_t top, + int32_t right, int32_t bottom, int32_t left) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + surface->pending.margin.top = top; + surface->pending.margin.right = right; + surface->pending.margin.bottom = bottom; + surface->pending.margin.left = left; +} + +static void +set_keyboard_interactivity(struct wl_client *client, struct wl_resource *resource, + uint32_t keyboard_interactivity) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + surface->pending.keyboard_interactivity = keyboard_interactivity; +} + +static void +get_popup(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *popup) +{ + (void)client; + (void)resource; + (void)popup; +} + +static void +ack_configure(struct wl_client *client, struct wl_resource *resource, + uint32_t serial) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + if (serial == surface->configure_serial) { + update_position(surface); + } +} + +static void +set_layer(struct wl_client *client, struct wl_resource *resource, uint32_t layer) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { + wl_resource_post_error(resource, + ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, + "invalid layer %" PRIu32, layer); + return; + } + + surface->pending.layer = layer; +} + +static void +set_exclusive_edge(struct wl_client *client, struct wl_resource *resource, + uint32_t edge) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + + (void)client; + + surface->pending.exclusive_edge = edge; +} + +static const struct zwlr_layer_surface_v1_interface layer_surface_impl = { + .set_size = set_size, + .set_anchor = set_anchor, + .set_exclusive_zone = set_exclusive_zone, + .set_margin = set_margin, + .set_keyboard_interactivity = set_keyboard_interactivity, + .get_popup = get_popup, + .ack_configure = ack_configure, + .destroy = destroy_resource, + .set_layer = set_layer, + .set_exclusive_edge = set_exclusive_edge, +}; + +static void +handle_attach(struct view_handler *handler) +{ + struct layer_surface *surface = wl_container_of(handler, surface, view_handler); + bool mapped = surface->view->base.buffer != NULL; + + if (mapped) { + update_position(surface); + restack_layer(surface); + if (!surface->mapped) { + compositor_view_show(surface->view); + if (surface->current.keyboard_interactivity != + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) { + keyboard_set_focus(swc.seat->keyboard, surface->view); + } + } + } else if (surface->mapped) { + compositor_view_hide(surface->view); + } + + surface->mapped = mapped; + update_usable_geometry(surface); +} + +static void +handle_resize(struct view_handler *handler, uint32_t old_width, + uint32_t old_height) +{ + struct layer_surface *surface = wl_container_of(handler, surface, view_handler); + + (void)old_width; + (void)old_height; + + update_position(surface); + update_usable_geometry(surface); +} + +static const struct view_handler_impl view_handler_impl = { + .attach = handle_attach, + .resize = handle_resize, +}; + +static void +handle_surface_commit(struct wl_listener *listener, void *data) +{ + struct layer_surface *surface = + wl_container_of(listener, surface, surface_commit_listener); + bool state_changed = !state_equal(&surface->current, &surface->pending); + + (void)data; + + if (state_changed) { + surface->current = surface->pending; + restack_layer(surface); + update_usable_geometry(surface); + } + + /* mke sure that the inital commit and also any later state change gets a fresh + * configure */ + if (!surface->configured || state_changed) { + send_configure(surface); + surface->configured = true; + } +} + +static void +modify(struct screen_modifier *modifier, const struct swc_rectangle *geom, + pixman_region32_t *usable) +{ + struct layer_surface *surface = wl_container_of(modifier, surface, modifier); + pixman_box32_t box = {.x1 = geom->x, + .y1 = geom->y, + .x2 = geom->x + geom->width, + .y2 = geom->y + geom->height}; + int32_t size; + + if (!surface->mapped) { + pixman_region32_reset(usable, &box); + return; + } + + size = exclusive_size(surface); + if (size <= 0) { + pixman_region32_reset(usable, &box); + return; + } + + /* shrink usuable area*/ + switch (exclusive_edge(surface)) { + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: + box.y1 = MAX(box.y1, geom->y + size); + break; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + box.y2 = MIN(box.y2, geom->y + geom->height - size); + break; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: + box.x1 = MAX(box.x1, geom->x + size); + break; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + box.x2 = MIN(box.x2, geom->x + geom->width - size); + break; + default: + break; + } + + pixman_region32_reset(usable, &box); +} + +static void +destroy_layer_surface(struct wl_resource *resource) +{ + struct layer_surface *surface = wl_resource_get_user_data(resource); + bool had_screen = surface->screen != NULL; + + wl_list_remove(&surface->surface_destroy_listener.link); + wl_list_remove(&surface->surface_commit_listener.link); + wl_list_remove(&surface->modifier.link); + compositor_view_destroy(surface->view); + if (had_screen) { + screen_update_usable_geometry(surface->screen); + } + free(surface); +} + +static void +handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct layer_surface *surface = + wl_container_of(listener, surface, surface_destroy_listener); + + (void)data; + + wl_resource_destroy(surface->resource); +} + +static struct layer_surface * +layer_surface_new(struct wl_client *client, uint32_t version, uint32_t id, + struct surface *surface, struct screen *screen, uint32_t layer) +{ + struct layer_surface *layer_surface; + + layer_surface = calloc(1, sizeof(*layer_surface)); + if (!layer_surface) { + goto error0; + } + + layer_surface->resource = + wl_resource_create(client, &zwlr_layer_surface_v1_interface, version, id); + if (!layer_surface->resource) { + goto error1; + } + + if (!(layer_surface->view = compositor_create_view(surface))) { + goto error2; + } + + if (!surface_set_role(surface, layer_surface->resource)) { + goto error3; + } + + layer_surface->screen = screen; + layer_surface->current.layer = layer; + layer_surface->pending = layer_surface->current; + layer_surface->current.exclusive_zone = 0; + layer_surface->current.exclusive_edge = 0; + layer_surface->current.keyboard_interactivity = + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; + layer_surface->pending = layer_surface->current; + layer_surface->surface_destroy_listener.notify = handle_surface_destroy; + layer_surface->surface_commit_listener.notify = handle_surface_commit; + layer_surface->view_handler.impl = &view_handler_impl; + layer_surface->modifier.modify = modify; + wl_list_init(&layer_surface->modifier.link); + wl_resource_set_implementation(layer_surface->resource, &layer_surface_impl, + layer_surface, destroy_layer_surface); + wl_resource_add_destroy_listener(surface->resource, + &layer_surface->surface_destroy_listener); + wl_signal_add(&surface->signal.commit, &layer_surface->surface_commit_listener); + wl_list_insert(&layer_surface->view->base.handlers, + &layer_surface->view_handler.link); + wl_list_insert(&screen->modifiers, &layer_surface->modifier.link); + restack_layer(layer_surface); + + return layer_surface; + +error3: + compositor_view_destroy(layer_surface->view); +error2: + wl_resource_destroy(layer_surface->resource); +error1: + free(layer_surface); +error0: + return NULL; +} + +static void +get_layer_surface(struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource, + struct wl_resource *output_resource, uint32_t layer, + const char *namespace_) +{ + struct surface *surface = wl_resource_get_user_data(surface_resource); + struct output *output; + struct screen *screen; + + (void)namespace_; + + if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { + wl_resource_post_error(resource, + ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, + "invalid layer %" PRIu32, layer); + return; + } + + if (surface->role) { + wl_resource_post_error(resource, ZWLR_LAYER_SHELL_V1_ERROR_ROLE, + "surface already has a role"); + return; + } + + if (surface_has_buffer(surface)) { + wl_resource_post_error(resource, + ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED, + "surface already has a buffer"); + return; + } + + if (output_resource) { + output = wl_resource_get_user_data(output_resource); + screen = output ? output->screen : NULL; + } else if (!wl_list_empty(&swc.screens)) { + screen = wl_container_of(swc.screens.next, screen, link); + } else { + screen = NULL; + } + + if (!screen || + !layer_surface_new(client, wl_resource_get_version(resource), id, surface, + screen, layer)) { + wl_client_post_no_memory(client); + } +} + +static const struct zwlr_layer_shell_v1_interface layer_shell_impl = { + .get_layer_surface = get_layer_surface, + .destroy = destroy_resource, +}; + +static void +bind_layer_shell(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct wl_resource *resource; + + (void)data; + + resource = + wl_resource_create(client, &zwlr_layer_shell_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &layer_shell_impl, NULL, NULL); +} + +struct wl_global * +layer_shell_create(struct wl_display *display) +{ + return wl_global_create(display, &zwlr_layer_shell_v1_interface, 5, NULL, + bind_layer_shell); +} diff --git a/libswc/layer_shell.h b/libswc/layer_shell.h new file mode 100644 index 0000000..76e5432 --- /dev/null +++ b/libswc/layer_shell.h @@ -0,0 +1,10 @@ +#ifndef SWC_LAYER_SHELL_H +#define SWC_LAYER_SHELL_H + +struct wl_display; +struct wl_global; + +struct wl_global * +layer_shell_create(struct wl_display *display); + +#endif diff --git a/libswc/local.mk b/libswc/local.mk index 90ceef9..aff3399 100644 --- a/libswc/local.mk +++ b/libswc/local.mk @@ -32,6 +32,7 @@ SWC_SOURCES = \ libswc/input.c \ libswc/kde_decoration.c \ libswc/keyboard.c \ + libswc/layer_shell.c \ libswc/launch.c \ libswc/mode.c \ libswc/output.c \ @@ -60,6 +61,7 @@ SWC_SOURCES = \ protocol/server-decoration-protocol.c \ protocol/swc-protocol.c \ protocol/wayland-drm-protocol.c \ + protocol/wlr-layer-shell-unstable-v1-protocol.c \ protocol/xdg-decoration-unstable-v1-protocol.c \ protocol/xdg-output-unstable-v1-protocol.c \ protocol/xdg-shell-protocol.c @@ -93,6 +95,7 @@ $(call objects,compositor panel_manager panel screen): protocol/swc-server-proto $(call objects,dmabuf): protocol/linux-dmabuf-unstable-v1-server-protocol.h $(call objects,drm drm_buffer): protocol/wayland-drm-server-protocol.h $(call objects,kde_decoration): protocol/server-decoration-server-protocol.h +$(call objects,layer_shell): protocol/wlr-layer-shell-unstable-v1-server-protocol.h $(call objects,xdg_decoration): protocol/xdg-decoration-unstable-v1-server-protocol.h $(call objects,xdg_output): protocol/xdg-output-unstable-v1-server-protocol.h $(call objects,xdg_shell): protocol/xdg-shell-server-protocol.h diff --git a/libswc/panel.c b/libswc/panel.c index 89623ea..7c26053 100644 --- a/libswc/panel.c +++ b/libswc/panel.c @@ -231,11 +231,14 @@ panel_new(struct wl_client *client, uint32_t version, uint32_t id, struct surfac panel->resource = wl_resource_create(client, &swc_panel_interface, version, id); if (!panel->resource) - goto error1; + goto error0; - if (!(panel->view = compositor_create_view(surface))) + if (!surface_set_role(surface, panel->resource)) goto error2; + if (!(panel->view = compositor_create_view(surface))) + goto error3; + wl_resource_set_implementation(panel->resource, &panel_impl, panel, &destroy_panel); panel->surface_destroy_listener.notify = &handle_surface_destroy; panel->view_handler.impl = &view_handler_impl; @@ -249,9 +252,9 @@ panel_new(struct wl_client *client, uint32_t version, uint32_t id, struct surfac return panel; -error2: +error3: wl_resource_destroy(panel->resource); -error1: +error2: free(panel); error0: return NULL; diff --git a/libswc/shell.c b/libswc/shell.c index f5ccad6..6ba7a40 100644 --- a/libswc/shell.c +++ b/libswc/shell.c @@ -23,6 +23,7 @@ #include "shell.h" #include "internal.h" +#include "surface.h" #include "shell_surface.h" #include @@ -33,6 +34,11 @@ get_shell_surface(struct wl_client *client, struct wl_resource *resource, uint32 struct surface *surface = wl_resource_get_user_data(surface_resource); struct shell_surface *shell_surface; + if (surface->role) { + wl_resource_post_error(resource, WL_SHELL_ERROR_ROLE, "surface already has a role"); + return; + } + shell_surface = shell_surface_new(client, wl_resource_get_version(resource), id, surface); if (!shell_surface) diff --git a/libswc/shell_surface.c b/libswc/shell_surface.c index 7f1952a..56ed73d 100644 --- a/libswc/shell_surface.c +++ b/libswc/shell_surface.c @@ -226,14 +226,19 @@ shell_surface_new(struct wl_client *client, uint32_t version, uint32_t id, struc if (!shell_surface->resource) goto error1; + if (!surface_set_role(surface, shell_surface->resource)) + goto error2; + if (!window_initialize(&shell_surface->window, &window_impl, surface)) + goto error2; wl_resource_set_implementation(shell_surface->resource, &shell_surface_implementation, shell_surface, &destroy_shell_surface); - window_initialize(&shell_surface->window, &window_impl, surface); shell_surface->surface_destroy_listener.notify = &handle_surface_destroy; wl_resource_add_destroy_listener(surface->resource, &shell_surface->surface_destroy_listener); return shell_surface; +error2: + wl_resource_destroy(shell_surface->resource); error1: free(shell_surface); error0: diff --git a/libswc/surface.c b/libswc/surface.c index 1a6fcc7..7ea0171 100644 --- a/libswc/surface.c +++ b/libswc/surface.c @@ -47,6 +47,16 @@ handle_buffer_destroy(struct wl_listener *listener, void *data) state->buffer = NULL; } +static void +handle_role_destroy(struct wl_listener *listener, void *data) +{ + struct surface *surface = wl_container_of(listener, surface, role_destroy_listener); + + (void)data; + + surface->role = NULL; +} + static void state_initialize(struct surface_state *state) { @@ -257,6 +267,7 @@ commit(struct wl_client *client, struct wl_resource *resource) } surface->pending.commit = 0; + wl_signal_emit(&surface->signal.commit, surface); } static void @@ -304,6 +315,8 @@ surface_destroy(struct wl_resource *resource) if (surface->view) wl_list_remove(&surface->view_handler.link); + if (surface->role) + wl_list_remove(&surface->role_destroy_listener.link); free(surface); } @@ -331,9 +344,12 @@ surface_new(struct wl_client *client, uint32_t version, uint32_t id) /* Initialize the surface. */ surface->pending.commit = 0; + wl_signal_init(&surface->signal.commit); surface->buffer = NULL; surface->view = NULL; surface->view_handler.impl = &view_handler_impl; + surface->role = NULL; + surface->role_destroy_listener.notify = &handle_role_destroy; state_initialize(&surface->state); state_initialize(&surface->pending.state); @@ -363,3 +379,22 @@ surface_set_view(struct surface *surface, struct view *view) view_update(view); } } + +bool +surface_set_role(struct surface *surface, struct wl_resource *role) +{ + if (surface->role) + return false; + + surface->role = role; + wl_resource_add_destroy_listener(role, &surface->role_destroy_listener); + return true; +} + +bool +surface_has_buffer(struct surface *surface) +{ + return surface->state.buffer + || ((surface->pending.commit & SURFACE_COMMIT_ATTACH) + && surface->pending.state.buffer); +} diff --git a/libswc/surface.h b/libswc/surface.h index d249e83..70622b7 100644 --- a/libswc/surface.h +++ b/libswc/surface.h @@ -55,6 +55,9 @@ struct surface_state { struct surface { struct wl_resource *resource; + struct { + struct wl_signal commit; + } signal; struct surface_state state; @@ -67,9 +70,13 @@ struct surface { struct wld_buffer *buffer; struct view *view; struct view_handler view_handler; + struct wl_resource *role; + struct wl_listener role_destroy_listener; }; struct surface *surface_new(struct wl_client *client, uint32_t version, uint32_t id); void surface_set_view(struct surface *surface, struct view *view); +bool surface_set_role(struct surface *surface, struct wl_resource *role); +bool surface_has_buffer(struct surface *surface); #endif diff --git a/libswc/swc.c b/libswc/swc.c index ae6499f..f0680fa 100644 --- a/libswc/swc.c +++ b/libswc/swc.c @@ -31,6 +31,7 @@ #include "launch.h" #include "kde_decoration.h" #include "keyboard.h" +#include "layer_shell.h" #include "panel_manager.h" #include "pointer.h" #include "screen.h" @@ -191,22 +192,28 @@ swc_initialize(struct wl_display *display, struct wl_event_loop *event_loop, con goto error12; } + swc.layer_shell = layer_shell_create(display); + if (!swc.layer_shell) { + ERROR("Could not initialize layer shell\n"); + goto error13; + } + swc.panel_manager = panel_manager_create(display); if (!swc.panel_manager) { ERROR("Could not initialize panel manager\n"); - goto error13; + goto error14; } swc.xdg_output_manager = xdg_output_manager_create(display); if (!swc.xdg_output_manager) { ERROR("Could not initialize XDG output manager\n"); - goto error14; + goto error15; } #ifdef ENABLE_XWAYLAND if (!xserver_initialize()) { ERROR("Could not initialize xwayland\n"); - goto error15; + goto error16; } #endif @@ -215,11 +222,13 @@ swc_initialize(struct wl_display *display, struct wl_event_loop *event_loop, con return true; #ifdef ENABLE_XWAYLAND -error15: +error16: wl_global_destroy(swc.xdg_output_manager); #endif -error14: +error15: wl_global_destroy(swc.panel_manager); +error14: + wl_global_destroy(swc.layer_shell); error13: wl_global_destroy(swc.kde_decoration_manager); error12: @@ -258,6 +267,7 @@ swc_finalize(void) #endif wl_global_destroy(swc.xdg_output_manager); wl_global_destroy(swc.panel_manager); + wl_global_destroy(swc.layer_shell); wl_global_destroy(swc.xdg_decoration_manager); wl_global_destroy(swc.xdg_shell); wl_global_destroy(swc.shell); diff --git a/libswc/xdg_output.c b/libswc/xdg_output.c index e00afa4..dec78ac 100644 --- a/libswc/xdg_output.c +++ b/libswc/xdg_output.c @@ -38,6 +38,7 @@ get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct output *output = wl_resource_get_user_data(output_resource); struct swc_rectangle *geom = &output->screen->base.geometry; + struct wl_resource *wl_output_resource; resource = wl_resource_create(client, &zxdg_output_v1_interface, wl_resource_get_version(resource), id); if (!resource) { @@ -48,12 +49,15 @@ get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, wl_resource_set_implementation(resource, &output_impl, NULL, NULL); zxdg_output_v1_send_logical_position(resource, geom->x, geom->y); zxdg_output_v1_send_logical_size(resource, geom->width, geom->height); - if (wl_resource_get_version(resource) >= 2) + if (wl_resource_get_version(resource) >= 2) { zxdg_output_v1_send_name(resource, output->name); - if (wl_resource_get_version(resource) < 3) - zxdg_output_v1_send_done(resource); - else - wl_output_send_done(output->resource); + zxdg_output_v1_send_description(resource, output->name); + } + zxdg_output_v1_send_done(resource); + + wl_output_resource = wl_resource_find_for_client(&output->resources, client); + if (wl_output_resource && wl_resource_get_version(wl_output_resource) >= 2) + wl_output_send_done(wl_output_resource); } static const struct zxdg_output_manager_v1_interface output_manager_impl = { diff --git a/libswc/xdg_shell.c b/libswc/xdg_shell.c index 97ad5fe..2ca9b09 100644 --- a/libswc/xdg_shell.c +++ b/libswc/xdg_shell.c @@ -457,13 +457,18 @@ xdg_toplevel_new(struct wl_client *client, uint32_t version, uint32_t id, struct toplevel->resource = wl_resource_create(client, &xdg_toplevel_interface, version, id); if (!toplevel->resource) goto error1; - window_initialize(&toplevel->window, &toplevel_window_impl, xdg_surface->surface); + if (!surface_set_role(xdg_surface->surface, toplevel->resource)) + goto error2; + if (!window_initialize(&toplevel->window, &toplevel_window_impl, xdg_surface->surface)) + goto error2; wl_array_init(&toplevel->states); wl_resource_set_implementation(toplevel->resource, &toplevel_impl, toplevel, &destroy_toplevel); window_manage(&toplevel->window); return toplevel; +error2: + wl_resource_destroy(toplevel->resource); error1: free(toplevel); error0: @@ -508,10 +513,12 @@ xdg_popup_new(struct wl_client *client, uint32_t version, uint32_t id, struct xd popup->resource = wl_resource_create(client, &xdg_popup_interface, version, id); if (!popup->resource) goto error1; - wl_resource_set_implementation(popup->resource, &popup_impl, popup, &destroy_popup); + if (!surface_set_role(xdg_surface->surface, popup->resource)) + goto error2; popup->view = compositor_create_view(xdg_surface->surface); if (!popup->view) - goto error2; + goto error3; + wl_resource_set_implementation(popup->resource, &popup_impl, popup, &destroy_popup); rect = calculate_position(positioner); compositor_view_set_parent(popup->view, parent_view); @@ -521,8 +528,9 @@ xdg_popup_new(struct wl_client *client, uint32_t version, uint32_t id, struct xd return popup; -error2: +error3: wl_resource_destroy(popup->resource); +error2: error1: free(popup); error0: @@ -540,6 +548,10 @@ get_toplevel(struct wl_client *client, struct wl_resource *resource, uint32_t id wl_resource_post_error(resource, XDG_WM_BASE_ERROR_ROLE, "surface already has a role"); return; } + if (xdg_surface->surface->role) { + wl_resource_post_error(resource, XDG_WM_BASE_ERROR_ROLE, "surface already has a role"); + return; + } toplevel = xdg_toplevel_new(client, wl_resource_get_version(resource), id, xdg_surface); if (!toplevel) { wl_client_post_no_memory(client); @@ -561,6 +573,10 @@ get_popup(struct wl_client *client, struct wl_resource *resource, uint32_t id, s wl_resource_post_error(resource, XDG_WM_BASE_ERROR_ROLE, "surface already has a role"); return; } + if (xdg_surface->surface->role) { + wl_resource_post_error(resource, XDG_WM_BASE_ERROR_ROLE, "surface already has a role"); + return; + } popup = xdg_popup_new(client, wl_resource_get_version(resource), id, xdg_surface, parent, positioner); if (!popup) { wl_client_post_no_memory(client); diff --git a/protocol/local.mk b/protocol/local.mk index ab3d38f..ed1e743 100644 --- a/protocol/local.mk +++ b/protocol/local.mk @@ -7,6 +7,7 @@ PROTOCOL_EXTENSIONS = \ $(dir)/server-decoration.xml\ $(dir)/swc.xml \ $(dir)/wayland-drm.xml \ + $(dir)/wlr-layer-shell-unstable-v1.xml \ $(wayland_protocols)/stable/xdg-shell/xdg-shell.xml \ $(wayland_protocols)/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml \ $(wayland_protocols)/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml \ @@ -32,4 +33,3 @@ install-$(dir): | $(DESTDIR)$(DATADIR)/swc install -m 644 protocol/swc.xml $(DESTDIR)$(DATADIR)/swc include common.mk - diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..e9f27e4 --- /dev/null +++ b/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,407 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + + + + + Requests an edge for the exclusive zone to apply. The exclusive + edge will be automatically deduced from anchor points when possible, + but when the surface is anchored to a corner, it will be necessary + to set it explicitly to disambiguate, as it is not possible to deduce + which one of the two corner edges should be used. + + The edge must be one the surface is anchored to, otherwise the + invalid_exclusive_edge protocol error will be raised. + + + + +