diff --git a/include/config/config.h b/include/config/config.h index 337e6e48..72af9107 100644 --- a/include/config/config.h +++ b/include/config/config.h @@ -52,6 +52,7 @@ struct config { ANCHOR_RIGHT, ANCHOR_BOTTOMLEFT, ANCHOR_BOTTOMRIGHT, + ANCHOR_SEPARATE, ANCHOR_NONE, } ninb_anchor; diff --git a/include/server/ui.h b/include/server/ui.h index 151b09ad..9024df6c 100644 --- a/include/server/ui.h +++ b/include/server/ui.h @@ -28,6 +28,16 @@ struct server_ui { struct wl_subsurface *subsurface; } tree; + struct { + struct wl_surface *surface; + struct xdg_toplevel *top_level; + struct xdg_surface *xdg_surface; + bool is_focused; + bool window_opened; + int32_t width; + int32_t height; + } ninbot; + struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zxdg_toplevel_decoration_v1 *xdg_decoration; @@ -48,7 +58,7 @@ struct server_ui { struct server_ui_config { struct wl_buffer *background; bool tearing; - + bool xwayland_toplevel; uint32_t ninb_opacity; }; @@ -101,6 +111,8 @@ void server_ui_destroy(struct server_ui *ui); void server_ui_hide(struct server_ui *ui); void server_ui_set_fullscreen(struct server_ui *ui, bool fullscreen); void server_ui_show(struct server_ui *ui); +void xwayland_toplevel_show(struct server_ui *ui); +void xwayland_toplevel_hide(struct server_ui *ui); void server_ui_use_config(struct server_ui *ui, struct server_ui_config *config); struct server_ui_config *server_ui_config_create(struct server_ui *ui, struct config *cfg); @@ -117,6 +129,9 @@ void server_view_set_pos(struct server_view *view, uint32_t x, uint32_t y); void server_view_set_size(struct server_view *view, uint32_t width, uint32_t height); void server_view_set_visible(struct server_view *view, bool visible); +void xwayland_server_view_attach(struct server_view *view); +void xwayland_server_view_detach(struct server_view *view); + struct server_view *server_view_create(struct server_ui *ui, struct server_surface *surface, const struct server_view_impl *impl, void *impl_data); void server_view_destroy(struct server_view *view); diff --git a/waywall/config/config.c b/waywall/config/config.c index bb9c41ea..40b76915 100644 --- a/waywall/config/config.c +++ b/waywall/config/config.c @@ -645,6 +645,7 @@ process_config_theme(struct config *cfg) { [ANCHOR_RIGHT] = "right", [ANCHOR_BOTTOMLEFT] = "bottomleft", [ANCHOR_BOTTOMRIGHT] = "bottomright", + [ANCHOR_SEPARATE] = "separate", }; for (size_t i = 0; i < STATIC_ARRLEN(anchor_names); i++) { diff --git a/waywall/server/ui.c b/waywall/server/ui.c index b99afd8b..beb15af8 100644 --- a/waywall/server/ui.c +++ b/waywall/server/ui.c @@ -69,7 +69,11 @@ layout_floating(struct server_view *view) { wl_fixed_from_int(-1), wl_fixed_from_int(-1)); wp_viewport_set_destination(view->viewport, -1, -1); - wl_surface_commit(view->ui->tree.surface); + if (view->ui->ninbot.surface == NULL) { + wl_surface_commit(view->ui->tree.surface); + } else { + wl_surface_commit(view->ui->ninbot.surface); + } } static void @@ -140,6 +144,49 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { .wm_capabilities = on_xdg_toplevel_wm_capabilities, }; +void ninbot_toplevel_configure_handler +( + void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states +) { + struct server_ui *ui = data; + ui->ninbot.width = width; + ui->ninbot.height = height; +} + +void xwayland_toplevel_hide(struct server_ui *ui) { + wl_surface_attach(ui->ninbot.surface, NULL, 0, 0); + wl_surface_commit(ui->ninbot.surface); + ui->ninbot.window_opened = false; +} + + +void ninbot_toplevel_close_handler +( + void *data, + struct xdg_toplevel *xdg_toplevel +) { + struct server_ui *ui = data; + + struct server_view *view; + struct server_view *tmp_view; + wl_list_for_each_safe(view, tmp_view, &ui->views, link) { + // Can we nest native Wayland apps except Minecraft? If so this is problematic + if (strcmp(view->impl->name, "xwayland") == 0) { + wl_resource_destroy(view->surface->resource); + } + } + xwayland_toplevel_hide(ui); +} + +static const struct xdg_toplevel_listener ninbot_toplevel_listener = { + .configure = ninbot_toplevel_configure_handler, + .close = ninbot_toplevel_close_handler +}; + static void on_xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { struct server_ui *ui = data; @@ -163,6 +210,19 @@ static const struct xdg_surface_listener xdg_surface_listener = { .configure = on_xdg_surface_configure, }; +static void xdg_surface_configure_handler (void *data, struct xdg_surface *xdg_surface, uint32_t serial) { + struct server_ui *ui = data; + if (ui->ninbot.width != 0 && ui->ninbot.height != 0) { + xdg_surface_set_window_geometry(xdg_surface, 0, 0, ui->ninbot.width, ui->ninbot.height); + } + xdg_surface_ack_configure(xdg_surface, serial); + wl_surface_commit(ui->ninbot.surface); +} + +static const struct xdg_surface_listener ninbot_surface_listener = { + .configure = xdg_surface_configure_handler, +}; + static void on_view_surface_commit(struct wl_listener *listener, void *data) { struct server_view *view = wl_container_of(listener, view, on_surface_commit); @@ -188,6 +248,29 @@ on_view_surface_commit(struct wl_listener *listener, void *data) { } } +void xwayland_toplevel_create(struct server_ui *ui) { + ui->ninbot.surface = wl_compositor_create_surface(ui->server->backend->compositor); + check_alloc(ui->ninbot.surface); + + ui->ninbot.xdg_surface = xdg_wm_base_get_xdg_surface(ui->server->backend->xdg_wm_base, ui->ninbot.surface); + check_alloc(ui->ninbot.xdg_surface); + xdg_surface_add_listener(ui->ninbot.xdg_surface, &ninbot_surface_listener, ui); + + ui->ninbot.top_level = xdg_surface_get_toplevel(ui->ninbot.xdg_surface); + check_alloc(ui->ninbot.top_level); + xdg_toplevel_add_listener(ui->ninbot.top_level, &ninbot_toplevel_listener, ui); +} + +void xwayland_toplevel_destroy(struct server_ui *ui) { + xdg_toplevel_destroy(ui->ninbot.top_level); + xdg_surface_destroy(ui->ninbot.xdg_surface); + wl_surface_destroy(ui->ninbot.surface); + ui->ninbot.top_level = NULL; + ui->ninbot.xdg_surface = NULL; + ui->ninbot.surface = NULL; + ui->ninbot.window_opened = false; +} + struct server_ui * server_ui_create(struct server *server, struct config *cfg) { struct server_ui *ui = zalloc(1, sizeof(*ui)); @@ -289,6 +372,7 @@ server_ui_destroy(struct server_ui *ui) { zxdg_toplevel_decoration_v1_destroy(ui->xdg_decoration); } + xwayland_toplevel_destroy(ui); xdg_toplevel_destroy(ui->xdg_toplevel); xdg_surface_destroy(ui->xdg_surface); wl_subsurface_destroy(ui->tree.subsurface); @@ -310,6 +394,7 @@ server_ui_hide(struct server_ui *ui) { ui->mapped = false; wl_signal_emit_mutable(&ui->server->events.map_status, &ui->mapped); + xwayland_toplevel_hide(ui); } void @@ -349,6 +434,22 @@ server_ui_show(struct server_ui *ui) { wl_signal_emit_mutable(&ui->server->events.map_status, &ui->mapped); } +void xwayland_toplevel_show(struct server_ui *ui) { + struct wl_display *display = ui->server->backend->display; + + wl_surface_attach(ui->ninbot.surface, NULL, 0, 0); + wl_surface_commit(ui->ninbot.surface); + wl_display_roundtrip(display); + + wl_surface_attach(ui->ninbot.surface, ui->config->background, 0, 0); + wl_surface_commit(ui->ninbot.surface); + wl_display_roundtrip(display); + + xdg_toplevel_set_title(ui->ninbot.top_level, "NinjabrainBot Wrapper"); + xdg_toplevel_set_app_id(ui->ninbot.top_level, "NinjabrainBot Wrapper"); + ui->ninbot.window_opened = true; +} + void server_ui_use_config(struct server_ui *ui, struct server_ui_config *config) { if (ui->config) { @@ -367,6 +468,12 @@ server_ui_use_config(struct server_ui *ui, struct server_ui_config *config) { wl_surface_damage_buffer(ui->root.surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(ui->root.surface); } + + if (config->xwayland_toplevel && ui->ninbot.surface == NULL) { + xwayland_toplevel_create(ui); + }else if (ui->ninbot.surface != NULL && !config->xwayland_toplevel) { + xwayland_toplevel_destroy(ui); + } } struct server_ui_config * @@ -386,6 +493,12 @@ server_ui_config_create(struct server_ui *ui, struct config *cfg) { return NULL; } + if (cfg->theme.ninb_anchor == ANCHOR_SEPARATE) { + config->xwayland_toplevel = true; + } else { + config->xwayland_toplevel = false; + } + config->tearing = cfg->experimental.tearing; config->ninb_opacity = cfg->theme.ninb_opacity * UINT32_MAX; @@ -452,10 +565,11 @@ server_view_commit(struct server_view *view) { if (visibility_changed && view->current.visible) { ww_assert(!view->subsurface); - view->subsurface = - wl_subcompositor_get_subsurface(view->ui->server->backend->subcompositor, - view->surface->remote, view->ui->tree.surface); - check_alloc(view->subsurface); + if (view->current.centered || view->ui->ninbot.surface == NULL) { + xwayland_server_view_detach(view); + } else { + xwayland_server_view_attach(view); + } wl_subsurface_set_desync(view->subsurface); } else if (visibility_changed && !view->current.visible) { @@ -476,6 +590,21 @@ server_view_commit(struct server_view *view) { view_state_reset(&view->pending); } +void +xwayland_server_view_attach(struct server_view *view) { + view->subsurface = + wl_subcompositor_get_subsurface(view->ui->server->backend->subcompositor, view->surface->remote, view->ui->ninbot.surface); + check_alloc(view->subsurface); + wl_surface_commit(view->ui->ninbot.surface); +} + +void +xwayland_server_view_detach(struct server_view *view) { + view->subsurface = + wl_subcompositor_get_subsurface(view->ui->server->backend->subcompositor, view->surface->remote, view->ui->tree.surface); + check_alloc(view->subsurface); +} + void server_view_refresh(struct server_view *view) { if (view->subsurface && view->current.centered) { diff --git a/waywall/server/wl_seat.c b/waywall/server/wl_seat.c index a4c9fafb..8d66efbb 100644 --- a/waywall/server/wl_seat.c +++ b/waywall/server/wl_seat.c @@ -744,9 +744,14 @@ on_pointer_enter(void *data, struct wl_pointer *wl, uint32_t serial, struct wl_s wl_fixed_t surface_x, wl_fixed_t surface_y) { struct server_seat *seat = data; seat->last_serial = serial; + seat->server->ui->ninbot.is_focused = false; + + if (surface == seat->server->ui->ninbot.surface) { + seat->server->ui->ninbot.is_focused = true; + return; + } if (surface != seat->server->ui->root.surface) { - ww_log(LOG_WARN, "received wl_pointer.enter for unknown surface"); return; } diff --git a/waywall/wrap.c b/waywall/wrap.c index 42be82c9..d2d38faa 100644 --- a/waywall/wrap.c +++ b/waywall/wrap.c @@ -153,6 +153,11 @@ floating_update_anchored(struct wrap *wrap) { x = 0; y = wrap->height - win_height; break; + case ANCHOR_SEPARATE: + x = 0; + y = 0; + server_view_set_visible(fview->view, true); + break; case ANCHOR_BOTTOMRIGHT: x = wrap->width - win_width; y = wrap->height - win_height; @@ -217,6 +222,12 @@ floating_view_create(struct wrap *wrap, struct server_view *view) { server_view_set_centered(view, false); floating_update_anchored(wrap); + if (wrap->cfg->theme.ninb_anchor == ANCHOR_SEPARATE) { + if (!wrap->server->ui->ninbot.window_opened) { + xwayland_toplevel_show(wrap->server->ui); + } + floating_set_visible(wrap, true); + } } } @@ -251,6 +262,14 @@ floating_view_destroy(struct wrap *wrap, struct server_view *view) { floating_find_anchored(wrap); floating_update_anchored(wrap); } + + if (wl_list_length(&wrap->floating.views) == 0 && view->ui->ninbot.window_opened) { + xwayland_toplevel_hide(view->ui); + if (view->ui->ninbot.is_focused) { + server_set_input_focus(wrap->server, wrap->view); + view->ui->ninbot.is_focused = false; + } + } return; } @@ -448,7 +467,7 @@ on_button(void *data, uint32_t button, bool pressed) { // Check to see if the input focus should be changed to a new window. If the user did not click // on any floating window, then input focus should be given back to the Minecraft instance. struct floating_view *fview = floating_view_at(wrap, wrap->input.x, wrap->input.y); - if (!fview) { + if (!fview || (wrap->cfg->theme.ninb_anchor == ANCHOR_SEPARATE && !wrap->server->ui->ninbot.is_focused)) { ww_assert(wrap->view); server_set_input_focus(wrap->server, wrap->view); return false; @@ -632,6 +651,24 @@ wrap_set_config(struct wrap *wrap, struct config *cfg) { config_vm_set_wrap(cfg->vm, wrap); wrap->cfg = cfg; + + if (wrap->cfg->theme.ninb_anchor == ANCHOR_SEPARATE) { + struct floating_view *fview; + wl_list_for_each(fview, &wrap->floating.views, link) { + wl_subsurface_destroy(fview->view->subsurface); + xwayland_server_view_attach(fview->view); + } + if (wl_list_length(&wrap->floating.views) > 0) { + xwayland_toplevel_show(wrap->server->ui); + } + } else { + struct floating_view *fview; + wl_list_for_each(fview, &wrap->floating.views, link) { + wl_subsurface_destroy(fview->view->subsurface); + xwayland_server_view_detach(fview->view); + } + } + if (wrap->cfg->theme.ninb_anchor == ANCHOR_NONE) { // If anchoring has been disabled, ensure there is no anchored view. if (wrap->floating.anchored) {