Skip to content

Commit 1b80f2d

Browse files
committed
Implement support for EWMH _NET_WM_MOVERESIZE client messages
1 parent aa8c7c6 commit 1b80f2d

File tree

6 files changed

+264
-13
lines changed

6 files changed

+264
-13
lines changed

common/atoms.list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ _NET_DESKTOP_NAMES
88
_NET_ACTIVE_WINDOW
99
_NET_SUPPORTING_WM_CHECK
1010
_NET_CLOSE_WINDOW
11+
_NET_WM_MOVERESIZE
1112
_NET_FRAME_EXTENTS
1213
_NET_WM_NAME
1314
_NET_WM_STRUT_PARTIAL

ewmh.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
#define _NET_WM_STATE_ADD 1
3636
#define _NET_WM_STATE_TOGGLE 2
3737

38+
#define _NET_WM_MOVERESIZE_MOVE 8
39+
#define _NET_WM_MOVERESIZE_CANCEL 11
40+
3841
#define ALL_DESKTOPS 0xffffffff
3942

4043
/** Update client EWMH hints.
@@ -141,6 +144,7 @@ ewmh_init(void)
141144
_NET_DESKTOP_NAMES,
142145
_NET_ACTIVE_WINDOW,
143146
_NET_CLOSE_WINDOW,
147+
_NET_WM_MOVERESIZE,
144148
_NET_FRAME_EXTENTS,
145149
_NET_WM_NAME,
146150
_NET_WM_STRUT_PARTIAL,
@@ -455,6 +459,34 @@ ewmh_process_desktop(client_t *c, uint32_t desktop)
455459
}
456460
}
457461

462+
const char *moveresize_size_dir_map[] = {
463+
"top_left", "top", "top_right", "right",
464+
"bottom_right", "bottom", "bottom_left", "left"
465+
};
466+
467+
static void
468+
push_moveresize_data(lua_State *L, const uint32_t data[5])
469+
{
470+
lua_newtable(L);
471+
472+
lua_pushstring(L, "mouse_pos");
473+
lua_newtable(L);
474+
475+
lua_pushstring(L, "x");
476+
lua_pushnumber(L, data[0]);
477+
lua_settable(L, -3);
478+
479+
lua_pushstring(L, "y");
480+
lua_pushnumber(L, data[1]);
481+
lua_settable(L, -3);
482+
483+
lua_settable(L, -3);
484+
485+
lua_pushstring(L, "button");
486+
lua_pushnumber(L, data[3]);
487+
lua_settable(L, -3);
488+
}
489+
458490
int
459491
ewmh_process_client_message(xcb_client_message_event_t *ev)
460492
{
@@ -511,6 +543,47 @@ ewmh_process_client_message(xcb_client_message_event_t *ev)
511543
lua_pop(L, 1);
512544
}
513545
}
546+
else if(ev->type == _NET_WM_MOVERESIZE)
547+
{
548+
if((c = client_getbywin(ev->window)))
549+
{
550+
lua_State *L = globalconf_get_lua_State();
551+
uint32_t dir = ev->data.data32[2];
552+
if(dir < 8) /* It's _NET_WM_MOVERESIZE_SIZE_* */
553+
{
554+
luaA_object_push(L, c);
555+
lua_pushstring(L, "ewmh");
556+
push_moveresize_data(L, ev->data.data32);
557+
558+
lua_pushstring(L, "corner");
559+
lua_pushstring(L, moveresize_size_dir_map[dir]);
560+
lua_settable(L, -3);
561+
562+
luaA_object_emit_signal(L, -3, "request::mouse_resize", 2);
563+
lua_pop(L, 1);
564+
}
565+
else
566+
{
567+
switch(dir)
568+
{
569+
case _NET_WM_MOVERESIZE_MOVE:
570+
luaA_object_push(L, c);
571+
lua_pushstring(L, "ewmh");
572+
push_moveresize_data(L, ev->data.data32);
573+
luaA_object_emit_signal(L, -3, "request::mouse_move", 2);
574+
lua_pop(L, 1);
575+
break;
576+
case _NET_WM_MOVERESIZE_CANCEL:
577+
luaA_object_push(L, c);
578+
lua_pushstring(L, "ewmh");
579+
luaA_object_emit_signal(L, -2, "request::mouse_cancel", 1);
580+
lua_pop(L, 1);
581+
break;
582+
/* Simply ignore the _NET_WM_MOVERESIZE_*_KEYBOARD cases like i3 does */
583+
}
584+
}
585+
}
586+
}
514587

515588
return 0;
516589
}

lib/awful/mouse/resize.lua

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,14 @@ local function handler(_, client, context, args) --luacheck: no unused_args
188188
end
189189

190190
-- Quit when the button is released
191-
for _,v in pairs(coords.buttons) do
192-
if v then return true end
191+
if args.mouse_buttons and #args.mouse_buttons > 0 then
192+
for _,v in pairs(args.mouse_buttons) do
193+
if coords.buttons[v] then return true end
194+
end
195+
else
196+
for _,v in pairs(coords.buttons) do
197+
if v then return true end
198+
end
193199
end
194200

195201
-- Only resize after the mouse is released, this avoids losing content

lib/awful/permissions/init.lua

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ local ipairs = ipairs
2626
local timer = require("gears.timer")
2727
local gtable = require("gears.table")
2828
local aclient = require("awful.client")
29+
local mresize = require("awful.mouse.resize")
2930
local aplace = require("awful.placement")
3031
local asuit = require("awful.layout.suit")
3132
local beautiful = require("beautiful")
@@ -567,6 +568,101 @@ function permissions.client_geometry_requests(c, context, hints)
567568
end
568569
end
569570

571+
--- Begins moving a client with the mouse.
572+
--
573+
-- This is the default handler for `request::mouse_move`. When a request is
574+
-- received, it uses `awful.mouse.resize` to initiate a mouse movement transaction
575+
-- that lasts so long as the specified mouse button is held.
576+
--
577+
-- @signalhandler awful.permissions.client_mouse_move
578+
-- @tparam client c The client
579+
-- @tparam string context The context
580+
-- @tparam table args Additional information describing the movement.
581+
-- @tparam number args.mouse_pos.x The x coordinate of the mouse when grabbed.
582+
-- @tparam number args.mouse_pos.y The y coordinate of the mouse when grabbed.
583+
-- @tparam number args.button The mouse button that initiated the movement.
584+
-- @sourcesignal client request::mouse_move
585+
function permissions.client_mouse_move(c, context, args)
586+
if not pcommon.check(c, "client", "mouse_move", context) then return end
587+
588+
if not c
589+
or c.fullscreen
590+
or c.maximized
591+
or c.type == "desktop"
592+
or c.type == "splash"
593+
or c.type == "dock" then
594+
return
595+
end
596+
597+
local center_pos = aplace.centered(mouse, {parent=c, pretend=true})
598+
mresize(c, "mouse.move", {
599+
placement = aplace.under_mouse,
600+
offset = {
601+
x = center_pos.x - args.mouse_pos.x,
602+
y = center_pos.y - args.mouse_pos.y
603+
},
604+
mouse_buttons = {args.button}
605+
})
606+
end
607+
608+
--- Begins resizing a client with the mouse.
609+
--
610+
-- This is the default handler for `request::mouse_resize`. When a request is
611+
-- received, it uses `awful.mouse.resize` to initiate a mouse resizing transaction
612+
-- that lasts so long as the specified mouse button is held.
613+
--
614+
-- @signalhandler awful.permissions.client_mouse_resize
615+
-- @tparam client c The client
616+
-- @tparam string context The context
617+
-- @tparam table args Additional information describing the resizing.
618+
-- @tparam number args.mouse_pos.x The x coordinate of the mouse when grabbed.
619+
-- @tparam number args.mouse_pos.y The y coordinate of the mouse when grabbed.
620+
-- @tparam number args.button The mouse button that initiated the resizing.
621+
-- @tparam string args.corner The corner/side of the window being resized.
622+
-- @sourcesignal client request::mouse_resize
623+
function permissions.client_mouse_resize(c, context, args)
624+
if not pcommon.check(c, "client", "mouse_resize", context) then return end
625+
626+
if not c
627+
or c.fullscreen
628+
or c.maximized
629+
or c.type == "desktop"
630+
or c.type == "splash"
631+
or c.type == "dock" then
632+
return
633+
end
634+
635+
local corner_pos = aplace[args.corner](mouse, {parent = c, pretend = true})
636+
mresize(c, "mouse.resize", {
637+
corner = args.corner,
638+
corner_lock = true,
639+
mouse_offset = {
640+
x = args.mouse_pos.x - corner_pos.x,
641+
y = args.mouse_pos.y - corner_pos.y
642+
},
643+
mouse_buttons = {args.button}
644+
})
645+
end
646+
647+
--- Cancels a mouse movement/resizing operation.
648+
--
649+
-- This is the default handler for `request::mouse_cancel`. It simply ends any
650+
-- ongoing `mousegrabber` transaction.
651+
--
652+
-- @signalhandler awful.permissions.client_mouse_cancel
653+
-- @tparam client c The client
654+
-- @tparam string context The context
655+
-- @sourcesignal client request::mouse_cancel
656+
function permissions.client_mouse_cancel(c, context)
657+
if not pcommon.check(c, "client", "mouse_cancel", context) then return end
658+
659+
-- This will also stop other mouse grabber transactions, but that's probably fine;
660+
-- a well-behaved client should only raise this signal when a mouse movement or
661+
-- resizing operation is in progress, and so no other mouse grabber transactions
662+
-- should be happening at this time.
663+
mousegrabber.stop()
664+
end
665+
570666
-- The magnifier layout doesn't work with focus follow mouse.
571667
permissions.add_activate_filter(function(c)
572668
if alayout.get(c.screen) ~= alayout.suit.magnifier
@@ -799,6 +895,9 @@ client.connect_signal("request::urgent" , permissions.urgent)
799895
client.connect_signal("request::geometry" , permissions.geometry)
800896
client.connect_signal("request::geometry" , permissions.merge_maximization)
801897
client.connect_signal("request::geometry" , permissions.client_geometry_requests)
898+
client.connect_signal("request::mouse_move" , permissions.client_mouse_move)
899+
client.connect_signal("request::mouse_resize" , permissions.client_mouse_resize)
900+
client.connect_signal("request::mouse_cancel" , permissions.client_mouse_cancel)
802901
client.connect_signal("property::border_width" , repair_geometry)
803902
client.connect_signal("property::screen" , repair_geometry)
804903
client.connect_signal("request::unmanage" , check_focus_delayed)

lib/awful/placement.lua

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,11 @@ end
11231123
--
11241124
-- * *axis*: The axis (vertical or horizontal). If none is
11251125
-- specified, then the drawable will be resized on both axis.
1126+
-- * *corner*: The corner to resize from. If not specified, then
1127+
-- the corner nearest to the cursor is used.
1128+
-- * *corner_lock*: Whether to lock the corner to *corner* or not.
1129+
-- if not set, then the corner may be updated to the one nearest
1130+
-- to the cursor in the middle of resizing operation.
11261131
--
11271132
--@DOC_awful_placement_resize_to_mouse_EXAMPLE@
11281133
-- @tparam drawable d A drawable (like `client`, `mouse` or `wibox`)
@@ -1138,25 +1143,38 @@ function placement.resize_to_mouse(d, args)
11381143
local h_only = args.axis == "horizontal"
11391144
local v_only = args.axis == "vertical"
11401145

1141-
-- To support both growing and shrinking the drawable, it is necessary
1142-
-- to decide to use either "north or south" and "east or west" directions.
1143-
-- Otherwise, the result will always be 1x1
1144-
local _, closest_corner = placement.closest_corner(capi.mouse, {
1145-
parent = d,
1146-
pretend = true,
1147-
include_sides = args.include_sides or false,
1148-
})
1146+
if args.mouse_offset then
1147+
coords = {
1148+
x = coords.x - args.mouse_offset.x,
1149+
y = coords.y - args.mouse_offset.y
1150+
}
1151+
end
1152+
1153+
local corner
1154+
if args.corner and args.corner_lock then
1155+
corner = args.corner
1156+
else
1157+
-- To support both growing and shrinking the drawable, it is necessary
1158+
-- to decide to use either "north or south" and "east or west" directions.
1159+
-- Otherwise, the result will always be 1x1
1160+
local _, closest_corner = placement.closest_corner(capi.mouse, {
1161+
parent = d,
1162+
pretend = true,
1163+
include_sides = args.include_sides or false,
1164+
})
1165+
corner = closest_corner
1166+
end
11491167

11501168
-- Given "include_sides" wasn't set, it will always return a name
11511169
-- with the 2 axis. If only one axis is needed, adjust the result
11521170
if h_only then
1153-
closest_corner = closest_corner:match("left") or closest_corner:match("right")
1171+
corner = corner:match("left") or corner:match("right")
11541172
elseif v_only then
1155-
closest_corner = closest_corner:match("top") or closest_corner:match("bottom")
1173+
corner = corner:match("top") or corner:match("bottom")
11561174
end
11571175

11581176
-- Use p0 (mouse), p1 and p2 to create a rectangle
1159-
local pts = resize_to_point_map[closest_corner]
1177+
local pts = resize_to_point_map[corner]
11601178
local p1 = pts.p1 and rect_to_point(ngeo, pts.p1[1], pts.p1[2]) or coords
11611179
local p2 = pts.p2 and rect_to_point(ngeo, pts.p2[1], pts.p2[2]) or coords
11621180

objects/client.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,60 @@ lua_class_t client_class;
315315
* @classsignal
316316
*/
317317

318+
/** Emitted when something requests for a client to be moved by the mouse.
319+
*
320+
* This is used to allow clients to manage their own "grabbable" areas, such
321+
* as in custom title bars, but to then delegate the task of actually moving
322+
* the client to the window manager.
323+
*
324+
* **Contexts are:**
325+
* * *ewmh*: When the client requests the movement (via _NET_WM_MOVERESIZE)
326+
*
327+
* @signal request::mouse_move
328+
* @tparam client c The client.
329+
* @tparam string context Why the mouse movement was requested.
330+
* @tparam table args Additional information describing the movement.
331+
* @tparam number args.mouse_pos.x The x coordinate of the mouse when grabbed.
332+
* @tparam number args.mouse_pos.y The y coordinate of the mouse when grabbed.
333+
* @tparam number args.button The mouse button that initiated the movement.
334+
* @classsignal
335+
*/
336+
337+
/** Emitted when something requests for a client to be resized by the mouse.
338+
*
339+
* This is used to allow clients to manage their own "grabbable" areas, such
340+
* as in custom window frames, but to then delegate the task of actually
341+
* resizing the client to the window manager.
342+
*
343+
* **Contexts are:**
344+
* * *ewmh*: When the client requests the resizing (via _NET_WM_MOVERESIZE)
345+
*
346+
* @signal request::mouse_resize
347+
* @tparam client c The client.
348+
* @tparam string context Why the mouse resizing was requested.
349+
* @tparam table args Additional information describing the resizing.
350+
* @tparam number args.mouse_pos.x The x coordinate of the mouse when grabbed.
351+
* @tparam number args.mouse_pos.y The y coordinate of the mouse when grabbed.
352+
* @tparam number args.button The mouse button that initiated the resizing.
353+
* @tparam string args.corner The corner/side of the window being resized.
354+
* @classsignal
355+
*/
356+
357+
/** Emitted when something requests for a grabbed client to be released.
358+
*
359+
* This is used to allow clients to cancel a mouse movement or resizing
360+
* operation that may have been started by an earlier `request::mouse_move`
361+
* or `request::mouse_resize` signal.
362+
*
363+
* **Contexts are:**
364+
* * *ewmh*: When the client requests the release (via _NET_WM_MOVERESIZE)
365+
*
366+
* @signal request::mouse_cancel
367+
* @tparam client c The client
368+
* @tparam string context Why the mouse release was requested.
369+
* @classsignal
370+
*/
371+
318372
/** Emitted when a client requests to be moved to a tag or needs a new tag.
319373
*
320374
* @signal request::tag

0 commit comments

Comments
 (0)