From 6d0953bc229c8b6e8ed0cd251bd80280acf3e7e8 Mon Sep 17 00:00:00 2001 From: David Maciejak Date: Mon, 2 Feb 2026 22:22:12 -0500 Subject: [PATCH] wmaker: add support for _NET_WM_MOVERESIZE This patch adds support for _NET_WM_MOVERESIZE hint as defined in EWMH which allows a window without decorations to manage itself (moving/resizing). The purpose is to fix the issue mentioned at https://github.com/window-maker/wmaker/issues/20 Tested with VS Code, Google Chrome, Steam and Discord. Specs are available at https://specifications.freedesktop.org/wm/1.5/ar01s04.html#id-1.5.4 --- src/moveres.c | 46 ++++++++++++++++++-- src/window.h | 6 +++ src/wmspec.c | 113 +++++++++++++++++++++++++++++++++++--------------- src/wmspec.h | 20 +++++++++ 4 files changed, 148 insertions(+), 37 deletions(-) diff --git a/src/moveres.c b/src/moveres.c index 24a1bb70..c7dbf867 100644 --- a/src/moveres.c +++ b/src/moveres.c @@ -19,6 +19,7 @@ */ #include "wconfig.h" +#include "wmspec.h" #include #include @@ -1916,7 +1917,7 @@ int wMouseMoveWindow(WWindow * wwin, XEvent * ev) break; case ButtonRelease: - if (event.xbutton.button != ev->xbutton.button) + if (!wwin->moveresize.active && (event.xbutton.button != ev->xbutton.button)) break; if (started) { @@ -1964,6 +1965,10 @@ int wMouseMoveWindow(WWindow * wwin, XEvent * ev) WMUnmapWidget(scr->gview); } } + if (wwin->moveresize.active) { + XUngrabPointer(dpy, CurrentTime); + wwin->moveresize.active = 0; + } done = True; break; @@ -2101,8 +2106,13 @@ void wMouseResizeWindow(WWindow * wwin, XEvent * ev) wwarning("internal error: tryein"); return; } - orig_x = ev->xbutton.x_root; - orig_y = ev->xbutton.y_root; + if (ev->type == MotionNotify) { + orig_x = ev->xmotion.x_root; + orig_y = ev->xmotion.y_root; + } else { + orig_x = ev->xbutton.x_root; + orig_y = ev->xbutton.y_root; + } started = 0; wUnselectWindows(scr); @@ -2113,6 +2123,29 @@ void wMouseResizeWindow(WWindow * wwin, XEvent * ev) shiftl = XKeysymToKeycode(dpy, XK_Shift_L); shiftr = XKeysymToKeycode(dpy, XK_Shift_R); + if (wwin->moveresize.active) { + int direction = wwin->moveresize.resize_edge; + + res = 0; + is_resizebar = 0; + if (direction == _NET_WM_MOVERESIZE_SIZE_TOP) + res |= UP; + else if (direction == _NET_WM_MOVERESIZE_SIZE_BOTTOM) + res |= DOWN; + else if (direction == _NET_WM_MOVERESIZE_SIZE_LEFT) + res |= LEFT; + else if (direction == _NET_WM_MOVERESIZE_SIZE_RIGHT) + res |= RIGHT; + else if (direction == _NET_WM_MOVERESIZE_SIZE_TOPLEFT) + res |= (UP | LEFT); + else if (direction == _NET_WM_MOVERESIZE_SIZE_TOPRIGHT) + res |= (UP | RIGHT); + else if (direction == _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT) + res |= (DOWN | LEFT); + else if (direction == _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT) + res |= (DOWN | RIGHT); + } + while (1) { WMMaskEvent(dpy, KeyPressMask | ButtonMotionMask | ButtonReleaseMask | PointerMotionHintMask | ButtonPressMask | ExposureMask, &event); @@ -2269,7 +2302,7 @@ void wMouseResizeWindow(WWindow * wwin, XEvent * ev) break; case ButtonRelease: - if (event.xbutton.button != ev->xbutton.button) + if (!wwin->moveresize.active && (event.xbutton.button != ev->xbutton.button)) break; if (started) { @@ -2291,6 +2324,11 @@ void wMouseResizeWindow(WWindow * wwin, XEvent * ev) wWindowConfigure(wwin, fx, fy, fw, fh - vert_border); wWindowSynthConfigureNotify(wwin); } + if (wwin->moveresize.active) { + XUngrabPointer(dpy, CurrentTime); + wwin->moveresize.active = 0; + } + return; default: diff --git a/src/window.h b/src/window.h index 1e678f7b..0481b46b 100644 --- a/src/window.h +++ b/src/window.h @@ -237,6 +237,12 @@ typedef struct WWindow { int cmap_window_no; Window *cmap_windows; + /* move/resize state for _NET_WM_MOVERESIZE support */ + struct { + int active; /* 1 if move/resize is in progress */ + int resize_edge; /* which edge for resize (0-7) */ + } moveresize; + /* protocols */ WProtocols protocols; /* accepted WM_PROTOCOLS */ diff --git a/src/wmspec.c b/src/wmspec.c index 1fa01aa7..4af9ab13 100644 --- a/src/wmspec.c +++ b/src/wmspec.c @@ -71,7 +71,7 @@ static Atom net_showing_desktop; /* Other Root Window Messages */ static Atom net_close_window; static Atom net_moveresize_window; /* TODO */ -static Atom net_wm_moveresize; /* TODO */ +static Atom net_wm_moveresize; /* Application Window Properties */ static Atom net_wm_name; @@ -125,7 +125,7 @@ static Atom net_wm_strut; static Atom net_wm_strut_partial; /* TODO: doesn't really fit into the current strut scheme */ static Atom net_wm_icon_geometry; /* FIXME: should work together with net_wm_handled_icons, gnome-panel-2.2.0.1 doesn't use _NET_WM_HANDLED_ICONS, thus present situation. */ static Atom net_wm_icon; -static Atom net_wm_pid; /* TODO */ +static Atom net_wm_pid; static Atom net_wm_handled_icons; /* FIXME: see net_wm_icon_geometry */ static Atom net_wm_window_opacity; @@ -226,35 +226,6 @@ static atomitem_t atomNames[] = { #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 -#if 0 -/* - * These constant provide information on the kind of window move/resize when - * it is initiated by the application instead of by WindowMaker. They are - * parameter for the client message _NET_WM_MOVERESIZE, as defined by the - * FreeDesktop wm-spec standard: - * http://standards.freedesktop.org/wm-spec/1.5/ar01s04.html - * - * Today, WindowMaker does not support this at all (the corresponding Atom - * is not added to the list in setSupportedHints), probably because there is - * nothing it needs to do about it, the application is assumed to know what - * it is doing, and WindowMaker won't get in the way. - * - * The definition of the constants (taken from the standard) are disabled to - * avoid a spurious warning (-Wunused-macros). - */ -#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 -#define _NET_WM_MOVERESIZE_SIZE_TOP 1 -#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 -#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 -#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 -#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 -#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 -#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 -#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ -#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ -#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ -#endif - static void observer(void *self, WMNotification *notif); static void wsobserver(void *self, WMNotification *notif); @@ -294,9 +265,7 @@ static void setSupportedHints(WScreen *scr) atom[i++] = net_workarea; atom[i++] = net_supporting_wm_check; atom[i++] = net_showing_desktop; -#if 0 atom[i++] = net_wm_moveresize; -#endif atom[i++] = net_wm_desktop; #ifdef USE_XINERAMA atom[i++] = net_wm_fullscreen_monitors; @@ -1814,6 +1783,84 @@ Bool wNETWMProcessClientMessage(XClientMessageEvent *event) } return True; + } else if (event->message_type == net_wm_moveresize) { + XEvent fake_event; + int direction = event->data.l[2]; + int x_root = event->data.l[0]; + int y_root = event->data.l[1]; + int button = event->data.l[3]; + int junk; + Window junkw; + unsigned int mask; + + if (direction == _NET_WM_MOVERESIZE_CANCEL) { + if (wwin->moveresize.active) { + memset(&fake_event, 0, sizeof(XEvent)); + fake_event.type = ButtonRelease; + fake_event.xbutton.window = wwin->frame->core->window; + fake_event.xbutton.x_root = x_root; + fake_event.xbutton.y_root = y_root; + fake_event.xbutton.button = event->data.l[3]; + XSendEvent(dpy, wwin->frame->core->window, False, ButtonReleaseMask, &fake_event); + } + return True; + } + + /* Check if already in progress */ + if (wwin->moveresize.active) + return True; + + /* Check if the initiating button is actually pressed */ + if (!XQueryPointer(dpy, wwin->screen_ptr->root_win, &junkw, &junkw, + &junk, &junk, &junk, &junk, &mask)) + return True; + + if (button > 0) { + unsigned int expected = + button == 1 ? Button1Mask : + button == 2 ? Button2Mask : + button == 3 ? Button3Mask : + 0; + + if (expected && !(mask & expected)) + /* Race: button already released */ + return True; + } + + /* Check if operation is allowed */ + if (direction == _NET_WM_MOVERESIZE_MOVE) { + if (WFLAGP(wwin, no_movable)) + return True; + } else if (direction <= _NET_WM_MOVERESIZE_SIZE_LEFT) { + if (WFLAGP(wwin, no_resizable)) + return True; + } else { + return True; + } + + /* Grab the pointer for the operation */ + if (XGrabPointer(dpy, wwin->frame->core->window, False, + ButtonReleaseMask | PointerMotionMask | ButtonPressMask, + GrabModeAsync, GrabModeAsync, + None, None, CurrentTime) != GrabSuccess) + return True; + + /* Set up the move/resize state */ + wwin->moveresize.active = 1; + wwin->moveresize.resize_edge = direction; + + memset(&fake_event, 0, sizeof(XEvent)); + fake_event.type = MotionNotify; + fake_event.xmotion.x_root = x_root; + fake_event.xmotion.y_root = y_root; + fake_event.xmotion.window = wwin->frame->core->window; + if (direction == _NET_WM_MOVERESIZE_MOVE) + wMouseMoveWindow(wwin, &fake_event); + else + wMouseResizeWindow(wwin, &fake_event); + + return True; + #ifdef USE_XINERAMA } else if (event->message_type == net_wm_fullscreen_monitors) { unsigned long top, bottom, left, right, src_indication; diff --git a/src/wmspec.h b/src/wmspec.h index 6e171145..b0713ef0 100644 --- a/src/wmspec.h +++ b/src/wmspec.h @@ -28,6 +28,26 @@ #include "window.h" #include +/* + * These constant provide information on the kind of window move/resize when + * it is initiated by the application instead of by WindowMaker. They are + * parameter for the client message _NET_WM_MOVERESIZE, as defined by the + * FreeDesktop wm-spec standard: + * http://standards.freedesktop.org/wm-spec/1.5/ar01s04.html + */ +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + void wNETWMInitStuff(WScreen *scr); void wNETWMCleanup(WScreen *scr); void wNETWMUpdateWorkarea(WScreen *scr);