diff --git a/src/backend/backend.h b/src/backend/backend.h index 8da8ccb..f5eb4d9 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -811,6 +811,12 @@ typedef struct WMBackend { */ void (*client_remove_from_save_set)(WNativeWindow win); + /* + * Request a client window to close itself. + * On X11: XKillClient (forceful); on Wayland: xdg_toplevel_send_close. + */ + void (*client_close)(WNativeWindow win); + /* ------------------------------------------------------------------ */ /* Fake group-leader window (Phase 13) */ /* ------------------------------------------------------------------ */ diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 24588e2..c0a75fd 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -27,6 +27,9 @@ * so clients receive size changes from the WM. * Phase 45: pointer_query returns 1 (always on-screen); wWindowFocus/Unfocus * guarded on frame!=NULL so wSetFocusTo works without a frame window. + * Phase 46: client_close vtable slot (wlr_xdg_toplevel_send_close); + * title_listener updates wwin->wm_instance/wm_class on set_title signal; + * wClientSendProtocol/wClientKill guarded for Wayland. */ #ifdef HAVE_CONFIG_H @@ -75,6 +78,7 @@ struct wl_toplevel_view { struct wl_listener map_listener; struct wl_listener unmap_listener; struct wl_listener destroy_listener; + struct wl_listener title_listener; /* Phase 46: xdg_toplevel set_title */ WNativeWindow id; /* synthetic handle, >= 0x10000000 */ struct wl_list link; /* wl_state.toplevel_list */ }; @@ -159,6 +163,34 @@ wl_find_view_by_id(WNativeWindow id) return NULL; } +/* + * Phase 46: XDG toplevel title changed — update wwin->wm_instance. + */ +static void +handle_toplevel_title(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, title_listener); + (void)data; + const char *new_title = view->xdg_surface->toplevel->title; + + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (!desc || desc->parent_type != WCLASS_WINDOW) + return; + WWindow *wwin = (WWindow *)desc->parent; + + if (wwin->wm_instance) + wfree(wwin->wm_instance); + wwin->wm_instance = new_title ? strdup(new_title) : NULL; + + if (wwin->wm_class) + wfree(wwin->wm_class); + const char *app_id = (view->xdg_surface->toplevel->app_id) + ? view->xdg_surface->toplevel->app_id : "wayland-client"; + wwin->wm_class = strdup(app_id); + + wWindowUpdateName(wwin, new_title); +} + /* * Phase 38: toplevel map — surface is ready to be managed. */ @@ -226,6 +258,7 @@ handle_toplevel_destroy(struct wl_listener *listener, void *data) wl_list_remove(&view->map_listener.link); wl_list_remove(&view->unmap_listener.link); wl_list_remove(&view->destroy_listener.link); + wl_list_remove(&view->title_listener.link); wl_list_remove(&view->link); wl_state.managed_surface_count--; wfree(view); @@ -254,10 +287,12 @@ handle_xdg_new_surface(struct wl_listener *listener, void *data) view->map_listener.notify = handle_toplevel_map; view->unmap_listener.notify = handle_toplevel_unmap; view->destroy_listener.notify = handle_toplevel_destroy; + view->title_listener.notify = handle_toplevel_title; - wl_signal_add(&xdg_surface->events.map, &view->map_listener); - wl_signal_add(&xdg_surface->events.unmap, &view->unmap_listener); - wl_signal_add(&xdg_surface->events.destroy, &view->destroy_listener); + wl_signal_add(&xdg_surface->events.map, &view->map_listener); + wl_signal_add(&xdg_surface->events.unmap, &view->unmap_listener); + wl_signal_add(&xdg_surface->events.destroy, &view->destroy_listener); + wl_signal_add(&xdg_surface->toplevel->events.set_title, &view->title_listener); wl_list_insert(&wl_state.toplevel_list, &view->link); wl_state.managed_surface_count++; @@ -864,6 +899,15 @@ wl_client_synth_configure(WNativeWindow client_win, static void wl_client_add_to_save_set(WNativeWindow win) { (void)win; } static void wl_client_remove_from_save_set(WNativeWindow win) { (void)win; } +static void +wl_client_close(WNativeWindow win) +{ + /* Phase 46: ask the XDG toplevel to close itself. */ + struct wl_toplevel_view *v = wl_find_view_by_id(win); + if (v && v->xdg_surface) + wlr_xdg_toplevel_send_close(v->xdg_surface); +} + static void wl_client_query_shaped(WNativeWindow win, int *shaped_out) { (void)win; *shaped_out = 0; } @@ -1101,6 +1145,7 @@ const WMBackend wl_backend = { .client_synth_configure = wl_client_synth_configure, .client_add_to_save_set = wl_client_add_to_save_set, .client_remove_from_save_set = wl_client_remove_from_save_set, + .client_close = wl_client_close, .client_query_shaped = wl_client_query_shaped, .client_get_gnustep_attrs = wl_client_get_gnustep_attrs, .client_get_pid = wl_client_get_pid, diff --git a/src/backend/x11/x11_backend.c b/src/backend/x11/x11_backend.c index dd6c846..79f7191 100644 --- a/src/backend/x11/x11_backend.c +++ b/src/backend/x11/x11_backend.c @@ -963,6 +963,13 @@ x11_client_remove_from_save_set(WNativeWindow win) XRemoveFromSaveSet(dpy, WNW_TO_XID(win)); } +static void +x11_client_close(WNativeWindow win) +{ + XKillClient(dpy, WNW_TO_XID(win)); + XFlush(dpy); +} + /* -------------------------------------------------------------------- */ /* Fake group-leader window (Phase 13) */ /* -------------------------------------------------------------------- */ @@ -2458,6 +2465,7 @@ const WMBackend x11_backend = { .client_reparent = x11_client_reparent, .client_add_to_save_set = x11_client_add_to_save_set, .client_remove_from_save_set = x11_client_remove_from_save_set, + .client_close = x11_client_close, .fake_leader_create = x11_fake_leader_create, .fake_leader_destroy = x11_fake_leader_destroy, .icccm_set_class_hint = x11_icccm_set_class_hint, diff --git a/src/client.c b/src/client.c index 958e025..f2abdaf 100644 --- a/src/client.c +++ b/src/client.c @@ -250,6 +250,9 @@ void wClientSendProtocol(WWindow * wwin, Atom protocol, Time time) { XEvent event; + if (!dpy) + return; /* Wayland: no X11 ClientMessage */ + event.xclient.type = ClientMessage; event.xclient.message_type = w_global.atom.wm.protocols; event.xclient.format = 32; @@ -265,6 +268,11 @@ void wClientSendProtocol(WWindow * wwin, Atom protocol, Time time) void wClientKill(WWindow * wwin) { + if (!dpy) { + /* Wayland: request close via vtable */ + wm_backend->client_close(wwin->client_win); + return; + } XKillClient(dpy, wwin->client_win); XFlush(dpy);