diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 15e63b9..9dd2299 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -104,9 +104,13 @@ endif if USE_WAYLAND_CLIENT nodist_libWINGs_la_SOURCES = \ xdg-shell-protocol.c \ - xdg-shell-client-protocol.h -BUILT_SOURCES = xdg-shell-client-protocol.h xdg-shell-protocol.c -CLEANFILES = xdg-shell-client-protocol.h xdg-shell-protocol.c + xdg-shell-client-protocol.h \ + wmaker-popup-v1-protocol.c \ + wmaker-popup-v1-client-protocol.h +BUILT_SOURCES = xdg-shell-client-protocol.h xdg-shell-protocol.c \ + wmaker-popup-v1-client-protocol.h wmaker-popup-v1-protocol.c +CLEANFILES = xdg-shell-client-protocol.h xdg-shell-protocol.c \ + wmaker-popup-v1-client-protocol.h wmaker-popup-v1-protocol.c xdg-shell-client-protocol.h: $(WAYLAND_SCANNER) client-header \ @@ -115,6 +119,13 @@ xdg-shell-client-protocol.h: xdg-shell-protocol.c: xdg-shell-client-protocol.h $(WAYLAND_SCANNER) private-code \ $(WAYLAND_PROTOCOLS_DIR)/stable/xdg-shell/xdg-shell.xml $@ + +wmaker-popup-v1-client-protocol.h: $(top_srcdir)/protocols/wmaker-popup-v1.xml + $(WAYLAND_SCANNER) client-header $< $@ + +wmaker-popup-v1-protocol.c: $(top_srcdir)/protocols/wmaker-popup-v1.xml \ + wmaker-popup-v1-client-protocol.h + $(WAYLAND_SCANNER) private-code $< $@ endif libWUtil_la_SOURCES = \ diff --git a/WINGs/wclient_wl.c b/WINGs/wclient_wl.c index ce99a8e..70fe17d 100644 --- a/WINGs/wclient_wl.c +++ b/WINGs/wclient_wl.c @@ -37,6 +37,7 @@ #include #include "xdg-shell-client-protocol.h" +#include "wmaker-popup-v1-client-protocol.h" #include @@ -63,6 +64,8 @@ typedef struct WLClientCtx { struct wl_shm *shm; struct wl_seat *seat; struct xdg_wm_base *wm_base; + /* wmaker-popup-v1: client-driven popup positioning */ + struct wmaker_popup_manager_v1 *wmaker_popup_mgr; struct wl_pointer *pointer; struct wl_keyboard *keyboard; @@ -221,6 +224,8 @@ typedef struct WLClientToplevel { struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; + /* wmaker-popup-v1 wrapper for client-driven positioning (NULL if not popup) */ + struct wmaker_popup_v1 *wmaker_popup; WLClientShmBuffer *buf; /* current backing */ @@ -412,6 +417,7 @@ wl_client_view_destroy_backing(unsigned long id) { WLClientToplevel *top = wlc_top_find((WNativeWindow)id); if (!top) return; + if (top->wmaker_popup) wmaker_popup_v1_destroy(top->wmaker_popup); if (top->xdg_toplevel) xdg_toplevel_destroy(top->xdg_toplevel); if (top->xdg_surface) xdg_surface_destroy(top->xdg_surface); if (top->surface) wl_surface_destroy(top->surface); @@ -453,6 +459,16 @@ wl_client_toplevel_realize(struct W_View *view, int width, int height) if (view->flags.popup) { xdg_toplevel_set_app_id(top->xdg_toplevel, "wings-popup"); xdg_toplevel_set_title(top->xdg_toplevel, ""); + /* Wrap with wmaker-popup-v1 so we can drive position. */ + if (ctx->wmaker_popup_mgr) { + top->wmaker_popup = wmaker_popup_manager_v1_get_popup( + ctx->wmaker_popup_mgr, top->xdg_toplevel); + /* Apply any position recorded in view->pos before realize. */ + if (top->wmaker_popup && (view->pos.x || view->pos.y)) { + wmaker_popup_v1_set_position(top->wmaker_popup, + view->pos.x, view->pos.y); + } + } } else { xdg_toplevel_set_app_id(top->xdg_toplevel, "WINGs"); xdg_toplevel_set_title(top->xdg_toplevel, "WINGs Application"); @@ -515,6 +531,14 @@ W_WLClientSetMinMaxSize(W_View *view) wl_surface_commit(top->surface); } +void +W_WLClientPopupSetPosition(WNativeWindow window, int x, int y) +{ + WLClientToplevel *top = wlc_top_find(window); + if (!top || !top->wmaker_popup) return; + wmaker_popup_v1_set_position(top->wmaker_popup, x, y); +} + Bool W_WLClientBeginMove(WNativeWindow window) { @@ -1036,6 +1060,9 @@ registry_handle_global(void *data, struct wl_registry *registry, ctx->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, v); xdg_wm_base_add_listener(ctx->wm_base, &wm_base_listener, ctx); + } else if (strcmp(interface, wmaker_popup_manager_v1_interface.name) == 0) { + ctx->wmaker_popup_mgr = wl_registry_bind(registry, name, + &wmaker_popup_manager_v1_interface, 1); } } diff --git a/WINGs/wevent_wl.c b/WINGs/wevent_wl.c index 05e5b5b..6c3b365 100644 --- a/WINGs/wevent_wl.c +++ b/WINGs/wevent_wl.c @@ -216,8 +216,13 @@ static Bool wl_get_window_geometry(void *ectx, WNativeWindow w, if (width) *width = 0; if (height) *height = 0; return False; } +extern void W_WLClientPopupSetPosition(WNativeWindow w, int x, int y); + static void wl_move_window(void *ectx, WNativeWindow w, int x, int y) -{ (void)ectx; (void)w; (void)x; (void)y; } +{ + (void)ectx; + W_WLClientPopupSetPosition(w, x, y); +} /* Implemented in wclient_wl.c — calls xdg_toplevel_move on the client * connection so the compositor handles the drag. */ diff --git a/WINGs/wmaker-popup-v1-protocol.c b/WINGs/wmaker-popup-v1-protocol.c new file mode 100644 index 0000000..f9f7ff5 --- /dev/null +++ b/WINGs/wmaker-popup-v1-protocol.c @@ -0,0 +1,72 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright © 2026 Window Maker Team + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wmaker_popup_v1_interface; +extern const struct wl_interface xdg_toplevel_interface; + +static const struct wl_interface *wmaker_popup_v1_types[] = { + NULL, + NULL, + &wmaker_popup_v1_interface, + &xdg_toplevel_interface, +}; + +static const struct wl_message wmaker_popup_manager_v1_requests[] = { + { "destroy", "", wmaker_popup_v1_types + 0 }, + { "get_popup", "no", wmaker_popup_v1_types + 2 }, +}; + +WL_PRIVATE const struct wl_interface wmaker_popup_manager_v1_interface = { + "wmaker_popup_manager_v1", 1, + 2, wmaker_popup_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message wmaker_popup_v1_requests[] = { + { "destroy", "", wmaker_popup_v1_types + 0 }, + { "set_position", "ii", wmaker_popup_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wmaker_popup_v1_interface = { + "wmaker_popup_v1", 1, + 2, wmaker_popup_v1_requests, + 0, NULL, +}; + diff --git a/WINGs/wview_wl.c b/WINGs/wview_wl.c index 20e3912..582a1a0 100644 --- a/WINGs/wview_wl.c +++ b/WINGs/wview_wl.c @@ -316,6 +316,14 @@ static void wl_move(void *vctx, W_View *view, int x, int y) (void)vctx; view->pos.x = x; view->pos.y = y; + /* For popup-style toplevels, drive position via wmaker-popup-v1. + * The client function is a no-op if the toplevel hasn't been + * realized yet — pos.x/y is captured into view->pos for the + * realize-time call. */ + if (view->flags.topLevel && view->flags.popup && view->flags.realized) { + extern void W_WLClientPopupSetPosition(WNativeWindow w, int x, int y); + W_WLClientPopupSetPosition((WNativeWindow)view->window, x, y); + } if (view->parent && view->flags.mapped) wl_redisplay(vctx, view->parent); } diff --git a/src/Makefile.am b/src/Makefile.am index a16ad4f..a14c310 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,11 +1,27 @@ AUTOMAKE_OPTIONS = subdir-objects -BUILT_SOURCES = wconfig.h +BUILT_SOURCES = wconfig.h \ + wmaker-popup-v1-server-protocol.h \ + wmaker-popup-v1-protocol.c bin_PROGRAMS = wmaker EXTRA_DIST = +nodist_wmaker_SOURCES = \ + wmaker-popup-v1-protocol.c \ + wmaker-popup-v1-server-protocol.h + +CLEANFILES = wmaker-popup-v1-server-protocol.h \ + wmaker-popup-v1-protocol.c + +wmaker-popup-v1-server-protocol.h: $(top_srcdir)/protocols/wmaker-popup-v1.xml + $(WAYLAND_SCANNER) server-header $< $@ + +wmaker-popup-v1-protocol.c: $(top_srcdir)/protocols/wmaker-popup-v1.xml \ + wmaker-popup-v1-server-protocol.h + $(WAYLAND_SCANNER) private-code $< $@ + wmaker_SOURCES = \ GNUstep.h \ WindowMaker.h \ @@ -148,10 +164,10 @@ if USE_NESTED_FUNC wmaker_SOURCES += misc.c \ xmodifier.c else -nodist_wmaker_SOURCES = misc.hack_nf.c \ +nodist_wmaker_SOURCES += misc.hack_nf.c \ xmodifier.hack_nf.c -CLEANFILES = $(nodist_wmaker_SOURCES) +CLEANFILES += misc.hack_nf.c xmodifier.hack_nf.c misc.hack_nf.c: misc.c $(top_srcdir)/script/nested-func-to-macro.sh $(AM_V_GEN)$(top_srcdir)/script/nested-func-to-macro.sh \ diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 2b66994..5067f59 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -132,6 +132,7 @@ #include #include #include +#include "wmaker-popup-v1-server-protocol.h" #include #include /* Phase 17: XWayland support */ #include /* X_SetInputFocus and other request codes */ @@ -273,6 +274,9 @@ struct wl_toplevel_view { WNativeWindow frame_id; /* frame_buf that owns this view (0 = none) */ int frame_rel_x, frame_rel_y; /* offset within frame */ int x, y; /* Phase 55: WM-assigned position */ + /* wmaker-popup-v1: client-driven popup position (only valid if popup != NULL) */ + struct wl_resource *popup; /* wmaker_popup_v1 resource or NULL */ + int popup_x, popup_y; long *wm_state; /* Phase 56: saved WM state or NULL */ int wm_state_n; /* Phase 56: element count */ char **wm_command; /* Phase 57: WM_COMMAND argv or NULL */ @@ -362,6 +366,8 @@ struct wl_compositor_state { struct wlr_seat *seat; struct wlr_data_device_manager *data_device_mgr; struct wlr_xdg_shell *xdg_shell; + /* wmaker-popup-v1 protocol global — client-driven popup positioning */ + struct wl_global *wmaker_popup_manager; /* Phase 64: XDG decoration manager — tell clients to use CSD */ struct wlr_xdg_decoration_manager_v1 *decoration_manager; struct wl_listener decoration_new_listener; @@ -681,6 +687,8 @@ static int wl_client_get_pid(WNativeWindow win); /* Phase 13: forward declarations — defined in pointer section below. */ static void wl_update_pointer_focus(uint32_t time_msec); static void wl_pointer_warp_to(int x, int y); +/* wmaker-popup-v1 helper — defined in protocol section below. */ +static void wmaker_popup_apply(struct wl_toplevel_view *view); /* Phase 14: pointer event handler forward declarations. */ static void handle_pointer_motion(struct wl_listener *listener, void *data); /* Phase 24: click-to-focus helper — defined in Phase 13 pointer section. */ @@ -1165,6 +1173,10 @@ handle_toplevel_map(struct wl_listener *listener, void *data) /* Rebuild frame without decorations */ extern void wWindowConfigureBorders(WWindow *wwin); wWindowConfigureBorders(wwin); + /* If the client requested a position via wmaker-popup-v1, + * apply it now that the frame is set up. */ + if (view->popup) + wmaker_popup_apply(view); } int pid = wl_client_get_pid(view->id); /* Phase 62: focus delivered by wManageWindow via wSetFocusTo @@ -1366,6 +1378,136 @@ handle_new_toplevel_decoration(struct wl_listener *listener, void *data) } } +/* ------------------------------------------------------------------ */ +/* wmaker-popup-v1 protocol — client-driven popup positioning */ +/* ------------------------------------------------------------------ */ + +static void +wmaker_popup_handle_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + (void)client; + wl_resource_destroy(resource); +} + +static void +wmaker_popup_handle_set_position(struct wl_client *client, + struct wl_resource *resource, + int32_t x, int32_t y) +{ + (void)client; + struct wl_toplevel_view *view = wl_resource_get_user_data(resource); + if (!view) return; + view->popup_x = (int)x; + view->popup_y = (int)y; + wmaker_popup_apply(view); +} + +static const struct wmaker_popup_v1_interface wmaker_popup_impl = { + .destroy = wmaker_popup_handle_destroy, + .set_position = wmaker_popup_handle_set_position, +}; + +static void +wmaker_popup_resource_destroy(struct wl_resource *resource) +{ + struct wl_toplevel_view *view = wl_resource_get_user_data(resource); + if (view && view->popup == resource) + view->popup = NULL; +} + +static void +wmaker_popup_apply(struct wl_toplevel_view *view) +{ + if (!view || !view->popup) return; + + /* Update the canonical view position so subsequent compositor logic + * (frame moves, scene-node positioning) sees the new placement. */ + int dx = view->popup_x - view->x; + int dy = view->popup_y - view->y; + view->x = view->popup_x; + view->y = view->popup_y; + + /* If a frame_buf wraps this view, move it via the WM's window-move + * path so the frame and its tree node update consistently. */ + if (view->frame_id) { + struct wl_frame_buf *fb = frame_buf_find(view->frame_id); + if (fb) { + fb->x += dx; + fb->y += dy; + if (fb->tree) + wlr_scene_node_set_position(&fb->tree->node, fb->x, fb->y); + else if (fb->scene_buf) + wlr_scene_node_set_position(&fb->scene_buf->node, fb->x, fb->y); + view->frame_rel_x = 0; + view->frame_rel_y = 0; + } + } else if (view->scene_node) { + wlr_scene_node_set_position(&view->scene_node->node, + view->x, view->y); + } +} + +static void +wmaker_popup_manager_handle_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + (void)client; + wl_resource_destroy(resource); +} + +static void +wmaker_popup_manager_handle_get_popup(struct wl_client *client, + struct wl_resource *manager_resource, + uint32_t id, + struct wl_resource *toplevel_resource) +{ + (void)manager_resource; + struct wlr_xdg_toplevel *xdg_top = + wlr_xdg_toplevel_from_resource(toplevel_resource); + if (!xdg_top) { + wl_resource_post_error(manager_resource, 0, + "wmaker_popup: invalid xdg_toplevel resource"); + return; + } + + struct wl_toplevel_view *view = + wl_find_view_by_xdg_surface(xdg_top->base); + + struct wl_resource *resource = wl_resource_create(client, + &wmaker_popup_v1_interface, 1, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &wmaker_popup_impl, + view, wmaker_popup_resource_destroy); + + if (view) + view->popup = resource; +} + +static const struct wmaker_popup_manager_v1_interface wmaker_popup_manager_impl = { + .destroy = wmaker_popup_manager_handle_destroy, + .get_popup = wmaker_popup_manager_handle_get_popup, +}; + +static void +wmaker_popup_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + (void)data; + struct wl_resource *resource = wl_resource_create(client, + &wmaker_popup_manager_v1_interface, + version > 1 ? 1 : (int)version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &wmaker_popup_manager_impl, + NULL, NULL); +} + /* ------------------------------------------------------------------ */ /* Phase 17: XWayland surface lifecycle */ /* ------------------------------------------------------------------ */ @@ -2788,6 +2930,13 @@ wl_display_open(const char *name) wl_state.xdg_shell = wlr_xdg_shell_create(wl_state.display, 3); + /* Advertise wmaker-popup-v1 — clients use it to drive popup positioning. */ + wl_state.wmaker_popup_manager = wl_global_create(wl_state.display, + &wmaker_popup_manager_v1_interface, 1, NULL, + wmaker_popup_manager_bind); + if (wl_state.wmaker_popup_manager) + wwarning("wl_backend: wmaker-popup-v1 manager created"); + /* Phase 64: advertise zxdg_decoration_manager_v1 so clients that * support server-side decorations can negotiate; we respond with * CLIENT_SIDE since the Wayland path has no X11 frame rendering. */ diff --git a/src/wmaker-popup-v1-protocol.c b/src/wmaker-popup-v1-protocol.c new file mode 100644 index 0000000..f9f7ff5 --- /dev/null +++ b/src/wmaker-popup-v1-protocol.c @@ -0,0 +1,72 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright © 2026 Window Maker Team + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wmaker_popup_v1_interface; +extern const struct wl_interface xdg_toplevel_interface; + +static const struct wl_interface *wmaker_popup_v1_types[] = { + NULL, + NULL, + &wmaker_popup_v1_interface, + &xdg_toplevel_interface, +}; + +static const struct wl_message wmaker_popup_manager_v1_requests[] = { + { "destroy", "", wmaker_popup_v1_types + 0 }, + { "get_popup", "no", wmaker_popup_v1_types + 2 }, +}; + +WL_PRIVATE const struct wl_interface wmaker_popup_manager_v1_interface = { + "wmaker_popup_manager_v1", 1, + 2, wmaker_popup_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message wmaker_popup_v1_requests[] = { + { "destroy", "", wmaker_popup_v1_types + 0 }, + { "set_position", "ii", wmaker_popup_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wmaker_popup_v1_interface = { + "wmaker_popup_v1", 1, + 2, wmaker_popup_v1_requests, + 0, NULL, +}; +