diff --git a/include/desktop/view.h b/include/desktop/view.h index 1ba8510..d9f937a 100644 --- a/include/desktop/view.h +++ b/include/desktop/view.h @@ -28,6 +28,7 @@ enum kiwmi_view_type { struct kiwmi_view { struct wl_list link; + struct wl_list children; // struct kiwmi_view_child::link struct kiwmi_desktop *desktop; @@ -46,6 +47,8 @@ struct kiwmi_view { struct wl_listener unmap; struct wl_listener commit; struct wl_listener destroy; + struct wl_listener new_popup; + struct wl_listener new_subsurface; struct wl_listener request_move; struct wl_listener request_resize; @@ -68,9 +71,9 @@ struct kiwmi_view { struct kiwmi_view_impl { void (*close)(struct kiwmi_view *view); - void (*for_each_surface)( + void (*for_each_mapped_surface)( struct kiwmi_view *view, - wlr_surface_iterator_func_t iterator, + wlr_surface_iterator_func_t callback, void *user_data); pid_t (*get_pid)(struct kiwmi_view *view); void ( @@ -88,15 +91,51 @@ struct kiwmi_view_impl { double *sub_y); }; +enum kiwmi_view_child_type { + KIWMI_VIEW_CHILD_SUBSURFACE, + KIWMI_VIEW_CHILD_XDG_POPUP, +}; + +struct kiwmi_view_child { + struct wl_list link; + struct wl_list children; // struct kiwmi_view_child::link + + struct kiwmi_view *view; + struct kiwmi_view_child *parent; + + enum kiwmi_view_child_type type; + const struct kiwmi_view_child_impl *impl; + + struct wlr_surface *wlr_surface; + union { + struct wlr_subsurface *wlr_subsurface; + struct wlr_xdg_popup *wlr_xdg_popup; + }; + + bool mapped; + + struct wl_listener commit; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener new_popup; + struct wl_listener new_subsurface; + struct wl_listener extension_destroy; // the union'ed object destroy + struct wl_listener surface_destroy; // wlr_surface::events.destroy +}; + +struct kiwmi_view_child_impl { + void (*reconfigure)(struct kiwmi_view_child *child); +}; + struct kiwmi_request_resize_event { struct kiwmi_view *view; uint32_t edges; }; void view_close(struct kiwmi_view *view); -void view_for_each_surface( +void view_for_each_mapped_surface( struct kiwmi_view *view, - wlr_surface_iterator_func_t iterator, + wlr_surface_iterator_func_t callback, void *user_data); pid_t view_get_pid(struct kiwmi_view *view); void view_get_size(struct kiwmi_view *view, uint32_t *width, uint32_t *height); @@ -128,4 +167,20 @@ struct kiwmi_view *view_create( enum kiwmi_view_type type, const struct kiwmi_view_impl *impl); +void +view_init_subsurfaces(struct kiwmi_view_child *child, struct kiwmi_view *view); +bool view_child_is_mapped(struct kiwmi_view_child *child); +void view_child_damage(struct kiwmi_view_child *child); +void view_child_destroy(struct kiwmi_view_child *child); +struct kiwmi_view_child *view_child_create( + struct kiwmi_view_child *parent, + struct kiwmi_view *view, + struct wlr_surface *wlr_surface, + enum kiwmi_view_child_type type, + const struct kiwmi_view_child_impl *impl); +struct kiwmi_view_child *view_child_subsurface_create( + struct kiwmi_view_child *parent, + struct kiwmi_view *view, + struct wlr_subsurface *subsurface); + #endif /* KIWMI_DESKTOP_VIEW_H */ diff --git a/kiwmi/desktop/output.c b/kiwmi/desktop/output.c index 1df1b42..c327d59 100644 --- a/kiwmi/desktop/output.c +++ b/kiwmi/desktop/output.c @@ -172,7 +172,8 @@ output_frame_notify(struct wl_listener *listener, void *data) struct kiwmi_view *view; wl_list_for_each (view, &desktop->views, link) { - view_for_each_surface(view, send_frame_done_to_surface, &now); + view_for_each_mapped_surface( + view, send_frame_done_to_surface, &now); } if (!wlr_output_attach_render(wlr_output, NULL)) { @@ -229,7 +230,7 @@ output_frame_notify(struct wl_listener *listener, void *data) rdata.data = view; wl_signal_emit(&view->events.pre_render, &rdata); - view_for_each_surface(view, render_surface, &rdata); + view_for_each_mapped_surface(view, render_surface, &rdata); wl_signal_emit(&view->events.post_render, &rdata); } diff --git a/kiwmi/desktop/view.c b/kiwmi/desktop/view.c index 62023d0..17b392a 100644 --- a/kiwmi/desktop/view.c +++ b/kiwmi/desktop/view.c @@ -24,13 +24,13 @@ view_close(struct kiwmi_view *view) } void -view_for_each_surface( +view_for_each_mapped_surface( struct kiwmi_view *view, - wlr_surface_iterator_func_t iterator, + wlr_surface_iterator_func_t callback, void *user_data) { - if (view->impl->for_each_surface) { - view->impl->for_each_surface(view, iterator, user_data); + if (view->impl->for_each_mapped_surface) { + view->impl->for_each_mapped_surface(view, callback, user_data); } } @@ -86,6 +86,13 @@ view_set_size(struct kiwmi_view *view, uint32_t width, uint32_t height) if (view->impl->set_size) { view->impl->set_size(view, width, height); + struct kiwmi_view_child *child; + wl_list_for_each (child, &view->children, link) { + if (child->impl && child->impl->reconfigure) { + child->impl->reconfigure(child); + } + } + struct kiwmi_output *output; wl_list_for_each (output, &view->desktop->outputs, link) { output_damage(output); @@ -99,6 +106,13 @@ view_set_pos(struct kiwmi_view *view, uint32_t x, uint32_t y) view->x = x; view->y = y; + struct kiwmi_view_child *child; + wl_list_for_each (child, &view->children, link) { + if (child->impl && child->impl->reconfigure) { + child->impl->reconfigure(child); + } + } + struct kiwmi_output *output; wl_list_for_each (output, &view->desktop->outputs, link) { output_damage(output); @@ -229,6 +243,29 @@ view_resize(struct kiwmi_view *view, uint32_t edges) view_begin_interactive(view, KIWMI_CURSOR_RESIZE, edges); } +/** + * Creates a kiwmi_view_child for each subsurface of either the 'child' or the + * 'view'. 'child' can be NULL, 'view' should never be. + */ +void +view_init_subsurfaces(struct kiwmi_view_child *child, struct kiwmi_view *view) +{ + struct wlr_surface *surface = + child ? child->wlr_surface : view->wlr_surface; + if (!surface) { + wlr_log(WLR_ERROR, "Attempting to init_subsurfaces without a surface"); + return; + } + + struct wlr_subsurface *subsurface; + wl_list_for_each (subsurface, &surface->subsurfaces_below, parent_link) { + view_child_subsurface_create(child, view, subsurface); + } + wl_list_for_each (subsurface, &surface->subsurfaces_above, parent_link) { + view_child_subsurface_create(child, view, subsurface); + } +} + struct kiwmi_view * view_create( struct kiwmi_desktop *desktop, @@ -251,6 +288,8 @@ view_create( view->x = 0; view->y = 0; + wl_list_init(&view->children); + wl_signal_init(&view->events.unmap); wl_signal_init(&view->events.request_move); wl_signal_init(&view->events.request_resize); @@ -259,3 +298,200 @@ view_create( return view; } + +bool +view_child_is_mapped(struct kiwmi_view_child *child) +{ + if (!child->mapped) { + return false; + } + + struct kiwmi_view_child *parent = child->parent; + while (parent) { + if (!parent->mapped) { + return false; + } + parent = parent->parent; + } + + return child->view->mapped; +} + +void +view_child_damage(struct kiwmi_view_child *child) +{ + // Note for later: this is supposed to damage the child and all subchildren + struct kiwmi_output *output; + wl_list_for_each (output, &child->view->desktop->outputs, link) { + output_damage(output); + } +} + +void +view_child_destroy(struct kiwmi_view_child *child) +{ + bool visible = view_child_is_mapped(child) && !child->view->hidden; + if (visible) { + view_child_damage(child); + } + + wl_list_remove(&child->link); + child->parent = NULL; + + struct kiwmi_view_child *subchild, *tmpchild; + wl_list_for_each_safe (subchild, tmpchild, &child->children, link) { + subchild->mapped = false; + view_child_destroy(subchild); + } + + wl_list_remove(&child->commit.link); + wl_list_remove(&child->map.link); + wl_list_remove(&child->unmap.link); + wl_list_remove(&child->new_popup.link); + wl_list_remove(&child->new_subsurface.link); + wl_list_remove(&child->surface_destroy.link); + wl_list_remove(&child->extension_destroy.link); + + free(child); +} + +static void +view_child_subsurface_extension_destroy_notify( + struct wl_listener *listener, + void *UNUSED(data)) +{ + struct kiwmi_view_child *child = + wl_container_of(listener, child, extension_destroy); + view_child_destroy(child); +} + +static void +view_child_surface_destroy_notify( + struct wl_listener *listener, + void *UNUSED(data)) +{ + struct kiwmi_view_child *child = + wl_container_of(listener, child, surface_destroy); + view_child_destroy(child); +} + +static void +view_child_commit_notify(struct wl_listener *listener, void *UNUSED(data)) +{ + struct kiwmi_view_child *child = wl_container_of(listener, child, commit); + if (view_child_is_mapped(child)) { + view_child_damage(child); + } +} + +static void +view_child_new_subsurface_notify(struct wl_listener *listener, void *data) +{ + struct kiwmi_view_child *child = + wl_container_of(listener, child, new_subsurface); + struct wlr_subsurface *subsurface = data; + view_child_subsurface_create(child, child->view, subsurface); +} + +static void +view_child_map_notify(struct wl_listener *listener, void *UNUSED(data)) +{ + struct kiwmi_view_child *child = wl_container_of(listener, child, map); + child->mapped = true; + if (view_child_is_mapped(child)) { + view_child_damage(child); + } +} + +static void +view_child_unmap_notify(struct wl_listener *listener, void *UNUSED(data)) +{ + struct kiwmi_view_child *child = wl_container_of(listener, child, unmap); + if (view_child_is_mapped(child)) { + view_child_damage(child); + } + child->mapped = false; +} + +struct kiwmi_view_child * +view_child_create( + struct kiwmi_view_child *parent, + struct kiwmi_view *view, + struct wlr_surface *wlr_surface, + enum kiwmi_view_child_type type, + const struct kiwmi_view_child_impl *impl) +{ + struct kiwmi_view_child *child = calloc(1, sizeof(*child)); + if (!child) { + wlr_log(WLR_ERROR, "Failed to allocate view_child"); + return NULL; + } + + child->type = type; + child->impl = impl; + child->view = view; + child->wlr_surface = wlr_surface; + child->mapped = false; + + if (parent) { + child->parent = parent; + wl_list_insert(&parent->children, &child->link); + } else { + wl_list_insert(&view->children, &child->link); + } + + wl_list_init(&child->children); + + child->commit.notify = view_child_commit_notify; + wl_signal_add(&wlr_surface->events.commit, &child->commit); + + child->map.notify = view_child_map_notify; + child->unmap.notify = view_child_unmap_notify; + + // wlr_surface doesn't have these events, but its extensions usually do + wl_list_init(&child->map.link); + wl_list_init(&child->unmap.link); + + child->new_subsurface.notify = view_child_new_subsurface_notify; + wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); + + child->surface_destroy.notify = view_child_surface_destroy_notify; + wl_signal_add(&wlr_surface->events.destroy, &child->surface_destroy); + + // Possibly unused + wl_list_init(&child->new_popup.link); + wl_list_init(&child->extension_destroy.link); + + view_init_subsurfaces(child, child->view); + + return child; +} + +struct kiwmi_view_child * +view_child_subsurface_create( + struct kiwmi_view_child *parent, + struct kiwmi_view *view, + struct wlr_subsurface *subsurface) +{ + struct kiwmi_view_child *child = view_child_create( + parent, view, subsurface->surface, KIWMI_VIEW_CHILD_SUBSURFACE, NULL); + if (!child) { + return NULL; + } + + child->wlr_subsurface = subsurface; + child->mapped = subsurface->mapped; + + if (view_child_is_mapped(child)) { + view_child_damage(child); + } + + wl_signal_add(&subsurface->events.map, &child->map); + wl_signal_add(&subsurface->events.unmap, &child->unmap); + + child->extension_destroy.notify = + view_child_subsurface_extension_destroy_notify; + wl_signal_add(&subsurface->events.destroy, &child->extension_destroy); + + return child; +} diff --git a/kiwmi/desktop/xdg_shell.c b/kiwmi/desktop/xdg_shell.c index b0e7302..345f1f1 100644 --- a/kiwmi/desktop/xdg_shell.c +++ b/kiwmi/desktop/xdg_shell.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,151 @@ #include "input/seat.h" #include "server.h" +static struct kiwmi_view_child *view_child_popup_create( + struct kiwmi_view_child *parent, + struct kiwmi_view *view, + struct wlr_xdg_popup *wlr_popup); + +static void +popup_new_popup_notify(struct wl_listener *listener, void *data) +{ + struct kiwmi_view_child *popup = + wl_container_of(listener, popup, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + view_child_popup_create(popup, popup->view, wlr_popup); +} + +static void +popup_extension_destroy_notify(struct wl_listener *listener, void *UNUSED(data)) +{ + struct kiwmi_view_child *popup = + wl_container_of(listener, popup, extension_destroy); + view_child_destroy(popup); +} + +static void +popup_unconstrain(struct kiwmi_view_child *popup) +{ + if (popup->type != KIWMI_VIEW_CHILD_XDG_POPUP) { + wlr_log(WLR_ERROR, "Expected an xdg_popup kiwmi_view_child"); + return; + } + + struct kiwmi_view *view = popup->view; + + // Prefer output at view center + struct wlr_output *output = wlr_output_layout_output_at( + view->desktop->output_layout, + view->x + view->geom.width / 2, + view->y + view->geom.height / 2); + + if (!output) { + // Retry with view top-left corner (if its center is off-screen) + output = wlr_output_layout_output_at( + view->desktop->output_layout, view->x, view->y); + } + + if (!output) { + wlr_log( + WLR_ERROR, "View's output not found, popups may end up invisible"); + return; + } + + double view_ox = view->x; + double view_oy = view->y; + wlr_output_layout_output_coords( + view->desktop->output_layout, output, &view_ox, &view_oy); + + int output_width; + int output_height; + wlr_output_effective_resolution(output, &output_width, &output_height); + + // relative to the view + struct wlr_box output_box = { + .x = -view_ox, + .y = -view_oy, + .width = output_width, + .height = output_height, + }; + + wlr_xdg_popup_unconstrain_from_box(popup->wlr_xdg_popup, &output_box); +} + +static void +popup_reconfigure(struct kiwmi_view_child *popup) +{ + if (popup->type != KIWMI_VIEW_CHILD_XDG_POPUP) { + wlr_log(WLR_ERROR, "Expected an xdg_popup view_child"); + return; + } + + popup_unconstrain(popup); + + struct kiwmi_view_child *subchild; + wl_list_for_each (subchild, &popup->children, link) { + if (subchild->impl && subchild->impl->reconfigure) { + subchild->impl->reconfigure(subchild); + } + } +} + +static const struct kiwmi_view_child_impl xdg_popup_view_child_impl = { + .reconfigure = popup_reconfigure, +}; + +static struct kiwmi_view_child * +view_child_popup_create( + struct kiwmi_view_child *parent, + struct kiwmi_view *view, + struct wlr_xdg_popup *wlr_popup) +{ + struct kiwmi_view_child *child = view_child_create( + parent, + view, + wlr_popup->base->surface, + KIWMI_VIEW_CHILD_XDG_POPUP, + &xdg_popup_view_child_impl); + if (!child) { + return NULL; + } + + child->wlr_xdg_popup = wlr_popup; + child->mapped = wlr_popup->base->mapped; + + if (view_child_is_mapped(child)) { + view_child_damage(child); + } + + wl_signal_add(&wlr_popup->base->events.map, &child->map); + wl_signal_add(&wlr_popup->base->events.unmap, &child->unmap); + + child->new_popup.notify = popup_new_popup_notify; + wl_signal_add(&wlr_popup->base->events.new_popup, &child->new_popup); + + child->extension_destroy.notify = popup_extension_destroy_notify; + wl_signal_add(&wlr_popup->base->events.destroy, &child->extension_destroy); + + popup_unconstrain(child); + + return child; +} + +static void +xdg_surface_new_popup_notify(struct wl_listener *listener, void *data) +{ + struct kiwmi_view *view = wl_container_of(listener, view, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + view_child_popup_create(NULL, view, wlr_popup); +} + +static void +xdg_surface_new_subsurface_notify(struct wl_listener *listener, void *data) +{ + struct kiwmi_view *view = wl_container_of(listener, view, new_subsurface); + struct wlr_subsurface *subsurface = data; + view_child_subsurface_create(NULL, view, subsurface); +} + static void xdg_surface_map_notify(struct wl_listener *listener, void *UNUSED(data)) { @@ -80,11 +226,20 @@ xdg_surface_destroy_notify(struct wl_listener *listener, void *UNUSED(data)) seat->focused_view = NULL; } + struct kiwmi_view_child *child, *tmpchild; + wl_list_for_each_safe (child, tmpchild, &view->children, link) { + child->mapped = false; + view_child_destroy(child); + } + wl_list_remove(&view->link); + wl_list_remove(&view->children); wl_list_remove(&view->map.link); wl_list_remove(&view->unmap.link); wl_list_remove(&view->commit.link); wl_list_remove(&view->destroy.link); + wl_list_remove(&view->new_popup.link); + wl_list_remove(&view->new_subsurface.link); wl_list_remove(&view->request_move.link); wl_list_remove(&view->request_resize.link); @@ -126,12 +281,87 @@ xdg_shell_view_close(struct kiwmi_view *view) } static void -xdg_shell_view_for_each_surface( - struct kiwmi_view *view, - wlr_surface_iterator_func_t iterator, +surface_for_each_mapped_surface( + struct wlr_surface *surface, + int x, + int y, + wlr_surface_iterator_func_t callback, void *user_data) { - wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator, user_data); + struct wlr_subsurface *subsurface; + wl_list_for_each (subsurface, &surface->subsurfaces_below, parent_link) { + if (!subsurface->mapped) { + continue; + } + + surface_for_each_mapped_surface( + subsurface->surface, + x + subsurface->current.x, + y + subsurface->current.y, + callback, + user_data); + } + + callback(surface, x, y, user_data); + + wl_list_for_each (subsurface, &surface->subsurfaces_above, parent_link) { + if (!subsurface->mapped) { + continue; + } + + surface_for_each_mapped_surface( + subsurface->surface, + x + subsurface->current.x, + y + subsurface->current.y, + callback, + user_data); + } +} + +static void +xdg_surface_for_each_mapped_popup_surface( + struct wlr_xdg_surface *surface, + int x, + int y, + wlr_surface_iterator_func_t callback, + void *user_data) +{ + struct wlr_xdg_popup *popup; + wl_list_for_each (popup, &surface->popups, link) { + struct wlr_xdg_surface *popup_surface = popup->base; + if (!popup_surface->configured || !popup_surface->mapped) { + continue; + } + + double popup_sx, popup_sy; + wlr_xdg_popup_get_position(popup, &popup_sx, &popup_sy); + + surface_for_each_mapped_surface( + popup_surface->surface, + x + popup_sx, + y + popup_sy, + callback, + user_data); + xdg_surface_for_each_mapped_popup_surface( + popup_surface, x + popup_sx, y + popup_sy, callback, user_data); + } +} + +static void +xdg_shell_view_for_each_mapped_surface( + struct kiwmi_view *view, + wlr_surface_iterator_func_t callback, + void *user_data) +{ + if (!view->mapped) { + return; + } + + // Have to copy over the wlroots implementation with only small changes + surface_for_each_mapped_surface( + view->xdg_surface->surface, 0, 0, callback, user_data); + xdg_surface_for_each_mapped_popup_surface( + view->xdg_surface, 0, 0, callback, user_data); } static pid_t @@ -205,15 +435,15 @@ xdg_shell_view_surface_at( } static const struct kiwmi_view_impl xdg_shell_view_impl = { - .close = xdg_shell_view_close, - .for_each_surface = xdg_shell_view_for_each_surface, - .get_pid = xdg_shell_view_get_pid, - .get_size = xdg_shell_view_get_size, - .get_string_prop = xdg_shell_view_get_string_prop, - .set_activated = xdg_shell_view_set_activated, - .set_size = xdg_shell_view_set_size, - .set_tiled = xdg_shell_view_set_tiled, - .surface_at = xdg_shell_view_surface_at, + .close = xdg_shell_view_close, + .for_each_mapped_surface = xdg_shell_view_for_each_mapped_surface, + .get_pid = xdg_shell_view_get_pid, + .get_size = xdg_shell_view_get_size, + .get_string_prop = xdg_shell_view_get_string_prop, + .set_activated = xdg_shell_view_set_activated, + .set_size = xdg_shell_view_set_size, + .set_tiled = xdg_shell_view_set_tiled, + .surface_at = xdg_shell_view_surface_at, }; void @@ -259,6 +489,13 @@ xdg_shell_new_surface_notify(struct wl_listener *listener, void *data) view->destroy.notify = xdg_surface_destroy_notify; wl_signal_add(&xdg_surface->events.destroy, &view->destroy); + view->new_popup.notify = xdg_surface_new_popup_notify; + wl_signal_add(&xdg_surface->events.new_popup, &view->new_popup); + + view->new_subsurface.notify = xdg_surface_new_subsurface_notify; + wl_signal_add( + &xdg_surface->surface->events.new_subsurface, &view->new_subsurface); + view->request_move.notify = xdg_toplevel_request_move_notify; wl_signal_add( &xdg_surface->toplevel->events.request_move, &view->request_move); @@ -267,6 +504,8 @@ xdg_shell_new_surface_notify(struct wl_listener *listener, void *data) wl_signal_add( &xdg_surface->toplevel->events.request_resize, &view->request_resize); + view_init_subsurfaces(NULL, view); + wl_list_insert(&desktop->views, &view->link); }