This patch adds support for _NET_WM_MOVERESIZE hint as defined in EWMH
which let a window without decorations to manage itself (moving/resizing).
Tested with VS Code and Google Chrome.
Specs available at
https://specifications.freedesktop.org/wm/1.5/ar01s04.html#id-1.5.4---
src/event.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++--
src/screen.h | 12 ++++++++
src/wmspec.c | 78 ++++++++++++++++++++++++++++-------------------
src/wmspec.h | 20 ++++++++++++
4 files changed, 162 insertions(+), 34 deletions(-)
diff --git a/src/event.c b/src/event.c
index 67d30098..4c45228a 100644
--- a/src/event.c
+++ b/src/event.c
@@ -3,7 +3,7 @@
* Window Maker window manager
*
* Copyright (c) 1997-2003 Alfredo K. Kojima
- * Copyright (c) 2014-2023 Window Maker Team
+ * Copyright (c) 2014-2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -91,6 +91,7 @@ static void handleMapNotify(XEvent *event);
static void handleUnmapNotify(XEvent *event);
static void handleButtonPress(XEvent *event);
static void handleExpose(XEvent *event);
+static void handleButtonRelease(XEvent *event);
static void handleDestroyNotify(XEvent *event);
static void handleConfigureRequest(XEvent *event);
static void handleMapRequest(XEvent *event);
@@ -233,6 +234,10 @@ void DispatchEvent(XEvent * event)
handleButtonPress(event);
break;
+
case ButtonRelease:
+
handleButtonRelease(event);
+
break;
+
case Expose:
handleExpose(event);
break;
@@ -886,6 +891,17 @@ static void handleButtonPress(XEvent * event)
}
}
+static void handleButtonRelease(XEvent * event)
+{
+
WScreen *scr = wScreenForRootWindow(event->xbutton.root);
+
+
if (scr && scr->moveresize.active && event->xbutton.button == Button1) {
+
XUngrabPointer(dpy, CurrentTime);
+
scr->moveresize.active = 0;
+
scr->moveresize.window = NULL;
+
}
+}
+
static void handleMapNotify(XEvent * event)
{
WWindow *wwin;
@@ -1966,8 +1982,74 @@ static void hotCornerDelay(void *data)
static void handleMotionNotify(XEvent *event)
{
+
WScreen *scr = wScreenForRootWindow(event->xmotion.root);
+
+
/* Handle move/resize for _NET_WM_MOVERESIZE */
+
if (scr && scr->moveresize.active && scr->moveresize.window) {
+
WWindow *wwin = scr->moveresize.window;
+
int dx = event->xmotion.x_root - scr->moveresize.start_x;
+
int dy = event->xmotion.y_root - scr->moveresize.start_y;
+
+
if (scr->moveresize.is_move) {
+
int new_x = scr->moveresize.orig_x + dx;
+
int new_y = scr->moveresize.orig_y + dy;
+
wWindowConfigure(wwin, new_x, new_y, wwin->client.width, wwin->client.height);
+
} else {
+
int new_x = scr->moveresize.orig_x;
+
int new_y = scr->moveresize.orig_y;
+
int new_w = scr->moveresize.orig_w;
+
int new_h = scr->moveresize.orig_h;
+
+
switch (scr->moveresize.resize_edge) {
+
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
+
new_x += dx;
+
new_y += dy;
+
new_w -= dx;
+
new_h -= dy;
+
break;
+
case _NET_WM_MOVERESIZE_SIZE_TOP:
+
new_y += dy;
+
new_h -= dy;
+
break;
+
case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
+
new_y += dy;
+
new_w += dx;
+
new_h -= dy;
+
break;
+
case _NET_WM_MOVERESIZE_SIZE_RIGHT:
+
new_w += dx;
+
break;
+
case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
+
new_w += dx;
+
new_h += dy;
+
break;
+
case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
+
new_h += dy;
+
break;
+
case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
+
new_x += dx;
+
new_w -= dx;
+
new_h += dy;
+
break;
+
case _NET_WM_MOVERESIZE_SIZE_LEFT:
+
new_x += dx;
+
new_w -= dx;
+
break;
+
}
+
+
if (new_w < 20)
+
new_w = 20;
+
if (new_h < 20)
+
new_h = 20;
+
+
wWindowConfigure(wwin, new_x, new_y,
+
new_w - (wwin->frame->core->width - wwin->client.width),
+
new_h - (wwin->frame->core->height - wwin->client.height));
+
}
+
return;
+
}
+
if (wPreferences.scrollable_menus || wPreferences.hot_corners) {
-
WScreen *scr = wScreenForRootWindow(event->xmotion.root);
WMPoint p = wmkpoint(event->xmotion.x_root, event->xmotion.y_root);
WMRect rect = wGetRectForHead(scr, wGetHeadForPoint(scr, p));
diff --git a/src/screen.h b/src/screen.h
index dac0be56..18f26488 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -2,6 +2,7 @@
* Window Maker window manager
*
* Copyright (c) 1997-2003 Alfredo K. Kojima
+ * Copyright (c) 2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -328,6 +329,17 @@ typedef struct _WScreen {
unsigned int ignore_focus_events:1;
unsigned int in_hot_corner:3;
} flags;
+
+ /* move/resize state for _NET_WM_MOVERESIZE support */
+ struct {
+ int active; /* 1 if move/resize in progress */
+ int is_move; /* 1 for move, 0 for resize */
+ struct WWindow *window; /* window being moved/resized */
+ int start_x, start_y; /* pointer position when started */
+ int orig_x, orig_y; /* original window position */
+ int orig_w, orig_h; /* original window size */
+ int resize_edge; /* which edge for resize (0-7) */
+ } moveresize;
} WScreen;
diff --git a/src/wmspec.c b/src/wmspec.c
index fe313410..9b50a87d 100644
--- a/src/wmspec.c
+++ b/src/wmspec.c
@@ -3,6 +3,7 @@
* Window Maker window manager
*
* Copyright (c) 1998-2003 Alfredo K. Kojima
+ * Copyright (c) 2026 Window Maker Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -71,7 +72,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;
@@ -226,35 +227,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 +266,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;
@@ -1740,6 +1710,50 @@ Bool wNETWMProcessClientMessage(XClientMessageEvent *event)
} else if (event->message_type == net_desktop_names) {
handleDesktopNames(scr);
return True;
+
+
} else if (event->message_type == net_wm_moveresize) {
+
wwin = wWindowFor(event->window);
+
if (!wwin || !wwin->frame)
+
return False;
+
+
int direction = event->data.l[2];
+
int x_root = event->data.l[0];
+
int y_root = event->data.l[1];
+
+
/* Check if already in progress */
+
if (scr->moveresize.active)
+
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;
+
}
+
+
if(XGrabPointer(dpy, scr->root_win, False,
+
ButtonReleaseMask | PointerMotionMask | ButtonPressMask,
+
GrabModeAsync, GrabModeAsync,
+
None, None, CurrentTime) != GrabSuccess)
+
return True;
+
+
/* Set up the move/resize state */
+
scr->moveresize.active = 1;
+
scr->moveresize.is_move = (direction == _NET_WM_MOVERESIZE_MOVE);
+
scr->moveresize.window = wwin;
+
scr->moveresize.start_x = x_root;
+
scr->moveresize.start_y = y_root;
+
scr->moveresize.orig_x = wwin->frame_x;
+
scr->moveresize.orig_y = wwin->frame_y;
+
scr->moveresize.orig_w = wwin->frame->core->width;
+
scr->moveresize.orig_h = wwin->frame->core->height;
+
scr->moveresize.resize_edge = direction;
+
+
return True;
}
}
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 <X11/Xlib.h>
+/*
+ * 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);
--
2.43.0