diff --git a/event.c b/event.c index 23096668..4fb13810 100644 --- a/event.c +++ b/event.c @@ -341,6 +341,8 @@ event_handle_configurerequest(xcb_configure_request_event_t *ev) uint16_t deco_bottom = bw + tb_bottom; int16_t diff_w = 0, diff_h = 0, diff_border = 0; + lua_State *L = globalconf_get_lua_State(); + if(ev->value_mask & XCB_CONFIG_WINDOW_X) { int16_t diff = 0; @@ -373,8 +375,6 @@ event_handle_configurerequest(xcb_configure_request_event_t *ev) } if(ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { - lua_State *L = globalconf_get_lua_State(); - diff_border = ev->border_width - bw; diff_h += diff_border; diff_w += diff_border; @@ -396,7 +396,33 @@ event_handle_configurerequest(xcb_configure_request_event_t *ev) } c->got_configure_request = true; - client_resize(c, geometry, false); + + /* Request the changes to be applied */ + luaA_object_push(L, c); + lua_pushstring(L, "ewmh"); /* context */ + lua_newtable(L); /* props */ + + /* area, it needs to be directly in the `hints` table to comply with + the "protocol" + */ + lua_pushstring(L, "x"); + lua_pushinteger(L, geometry.x); + lua_rawset(L, -3); + + lua_pushstring(L, "y"); + lua_pushinteger(L, geometry.y); + lua_rawset(L, -3); + + lua_pushstring(L, "width"); + lua_pushinteger(L, geometry.width); + lua_rawset(L, -3); + + lua_pushstring(L, "height"); + lua_pushinteger(L, geometry.height); + lua_rawset(L, -3); + + luaA_object_emit_signal(L, -3, "request::geometry", 2); + lua_pop(L, 1); } else if (xembed_getbywin(&globalconf.embedded, ev->window)) { diff --git a/ewmh.c b/ewmh.c index c2811bb1..af7a23a4 100644 --- a/ewmh.c +++ b/ewmh.c @@ -207,6 +207,28 @@ ewmh_init(void) father, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &i); } +static void +ewmh_update_maximize(bool h, bool status, bool toggle) +{ + lua_State *L = globalconf_get_lua_State(); + + if (h) + lua_pushstring(L, "client_maximize_horizontal"); + else + lua_pushstring(L, "client_maximize_vertical"); + + /* Create table argument with raise=true. */ + lua_newtable(L); + lua_pushstring(L, "toggle"); + lua_pushboolean(L, toggle); + lua_settable(L, -3); + lua_pushstring(L, "status"); + lua_pushboolean(L, status); + lua_settable(L, -3); + + luaA_object_emit_signal(L, -3, "request::geometry", 2); +} + void ewmh_init_lua(void) { @@ -333,20 +355,20 @@ ewmh_process_state_atom(client_t *c, xcb_atom_t state, int set) else if(state == _NET_WM_STATE_MAXIMIZED_HORZ) { if(set == _NET_WM_STATE_REMOVE) - client_set_maximized_horizontal(L, -1, false); + ewmh_update_maximize(true, false, false); else if(set == _NET_WM_STATE_ADD) - client_set_maximized_horizontal(L, -1, true); + ewmh_update_maximize(true, true, false); else if(set == _NET_WM_STATE_TOGGLE) - client_set_maximized_horizontal(L, -1, !c->maximized_horizontal); + ewmh_update_maximize(true, false, true); } else if(state == _NET_WM_STATE_MAXIMIZED_VERT) { if(set == _NET_WM_STATE_REMOVE) - client_set_maximized_vertical(L, -1, false); + ewmh_update_maximize(false, false, false); else if(set == _NET_WM_STATE_ADD) - client_set_maximized_vertical(L, -1, true); + ewmh_update_maximize(false, true, false); else if(set == _NET_WM_STATE_TOGGLE) - client_set_maximized_vertical(L, -1, !c->maximized_vertical); + ewmh_update_maximize(false, false, true); } else if(state == _NET_WM_STATE_ABOVE) { @@ -554,6 +576,8 @@ ewmh_client_check_hints(client_t *c) void *data = NULL; xcb_get_property_cookie_t c0, c1, c2; xcb_get_property_reply_t *reply; + bool is_h_max = false; + bool is_v_max = false; /* Send the GetProperty requests which will be processed later */ c0 = xcb_get_property_unchecked(globalconf.connection, false, c->window, @@ -578,7 +602,34 @@ ewmh_client_check_hints(client_t *c) { state = (xcb_atom_t *) data; for(int i = 0; i < xcb_get_property_value_length(reply) / ssizeof(xcb_atom_t); i++) - ewmh_process_state_atom(c, state[i], _NET_WM_STATE_ADD); + if (state[i] == _NET_WM_STATE_MAXIMIZED_HORZ) + is_h_max = true; + else if (state[i] == _NET_WM_STATE_MAXIMIZED_VERT) + is_v_max = true; + else + ewmh_process_state_atom(c, state[i], _NET_WM_STATE_ADD); + } + + /* Check maximization manually */ + if (is_h_max && is_v_max) { + lua_State *L = globalconf_get_lua_State(); + luaA_object_push(L, c); + client_set_maximized(L, -1, true); + lua_pop(L, 1); + } + else if(is_h_max) + { + lua_State *L = globalconf_get_lua_State(); + luaA_object_push(L, c); + client_set_maximized_horizontal(L, -1, true); + lua_pop(L, 1); + } + else if(is_v_max) + { + lua_State *L = globalconf_get_lua_State(); + luaA_object_push(L, c); + client_set_maximized_vertical(L, -1, true); + lua_pop(L, 1); } p_delete(&reply); diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua index 2b7861d9..45002c4b 100644 --- a/lib/awful/ewmh.lua +++ b/lib/awful/ewmh.lua @@ -9,6 +9,7 @@ local client = client local screen = screen local ipairs = ipairs +local timer = require("gears.timer") local gtable = require("gears.table") local aclient = require("awful.client") local aplace = require("awful.placement") @@ -302,10 +303,88 @@ function ewmh.geometry(c, context, hints) end end +--- Merge the 2 requests sent by clients wanting to be maximized. +-- +-- The X clients set 2 flags (atoms) when they want to be maximized. This caused +-- 2 request::geometry to be sent. This code gives some time for them to arrive +-- and send a new `request::geometry` (through the property change) with the +-- combined state. +-- +-- @signalhandler awful.ewmh.merge_maximization +-- @tparam client c The client +-- @tparam string context The context +-- @tparam[opt={}] table hints The hints to pass to the handler +function ewmh.merge_maximization(c, context, hints) + if context ~= "client_maximize_horizontal" and context ~= "client_maximize_vertical" then + return + end + + if not c._delay_maximization then + c._delay_maximization = function() + -- This ignores unlikely corner cases like mismatching toggles. + -- That's likely to be an accident anyway. + if c._delayed_max_h and c._delayed_max_v then + c.maximized = c._delayed_max_h or c._delayed_max_v + elseif c._delayed_max_h then + c.maximized_horizontal = c._delayed_max_h + elseif c._delayed_max_v then + c.maximized_vertical = c._delayed_max_v + end + end + + timer { + timeout = 1/60, + autostart = true, + single_shot = true, + callback = function() + if not c.valid then return end + + c._delay_maximization(c) + c._delay_maximization = nil + c._delayed_max_h = nil + c._delayed_max_v = nil + end + } + end + + local function get_value(suffix, long_suffix) + if hints.toggle and c["_delayed_max_"..suffix] ~= nil then + return not c["_delayed_max_"..suffix] + elseif hints.toggle then + return not c["maximized_"..long_suffix] + else + return hints.status + end + end + + if context == "client_maximize_horizontal" then + c._delayed_max_h = get_value("h", "horizontal") + elseif context == "client_maximize_vertical" then + c._delayed_max_v = get_value("v", "vertical") + end +end + +--- Allow the client to move itself. +-- +-- This is the default geometry request handler when the context is `ewmh`. +-- +-- @signalhandler awful.ewmh.client_geometry_requests +-- @tparam client c The client +-- @tparam string context The context +-- @tparam[opt={}] table hints The hints to pass to the handler +function ewmh.client_geometry_requests(c, context, hints) + if context == "ewmh" and hints then + c:geometry(hints) + end +end + + client.connect_signal("request::activate", ewmh.activate) client.connect_signal("request::tag", ewmh.tag) client.connect_signal("request::urgent", ewmh.urgent) client.connect_signal("request::geometry", ewmh.geometry) +client.connect_signal("request::geometry", ewmh.merge_maximization) +client.connect_signal("request::geometry", ewmh.client_geometry_requests) client.connect_signal("property::border_width", repair_geometry) client.connect_signal("property::screen", repair_geometry) screen.connect_signal("property::workarea", function(s) diff --git a/tests/_client.lua b/tests/_client.lua index 684c17cd..c88dd160 100644 --- a/tests/_client.lua +++ b/tests/_client.lua @@ -32,8 +32,19 @@ local function open_window(class, title, options) } window:set_geometry_hints(nil, geom, Gdk.WindowHints.RESIZE_INC) end + if options.maximize_before then + window:maximize() + end window:set_wmclass(class, class) window:show_all() + if options.maximize_after then + window:maximize() + end + if options.resize_after_width and options.resize_after_height then + window:resize( + tonumber(options.resize_after_width), tonumber(options.resize_after_height) + ) + end end local function parse_options(options) @@ -124,10 +135,26 @@ return function(class, title, sn_rules, callback, resize_increment, args) if resize_increment then options = options .. "resize_increment," end + if args.maximize_before then + options = options .. "maximize_before," + end + if args.maximize_after then + options = options .. "maximize_after," + end + if args.resize then + options = table.concat { + options, + "resize_after_width=", + args.resize.height, ",", + "resize_after_height=", + args.resize.width, "," + } + end if args.gravity then assert(type(args.gravity)=="number","Use `lgi.Gdk.Gravity.NORTH_WEST`") options = options .. "gravity=" .. args.gravity .. "," end + local data = class .. "\n" .. title .. "\n" .. options .. "\n" local success, msg = pipe:write_all(data) assert(success, tostring(msg)) diff --git a/tests/test-maximize.lua b/tests/test-maximize.lua index 8d5aa711..48fc01f7 100644 --- a/tests/test-maximize.lua +++ b/tests/test-maximize.lua @@ -12,6 +12,17 @@ end local original_geo = nil +local counter = 0 + +local function geometry_handler(c, context, hints) + hints = hints or {} + assert(type(c) == "client") + assert(type(context) == "string") + assert(type(hints.toggle) == "boolean") + assert(type(hints.status) == "boolean") + counter = counter + 1 +end + local steps = { function(count) if count == 1 then @@ -142,6 +153,92 @@ local steps = { assert(geo_to_str(original_geo) == geo_to_str(new_geo), geo_to_str(original_geo) .. " == " .. geo_to_str(new_geo)) + c:kill() + + return true + end, + -- Now, start some clients maximized + function() + if #client.get() > 0 then return end + + test_client(nil,nil,nil,nil,nil,{maximize_before=true}) + + return true + end, + function() + local c = client.get()[1] + + if not c then return end + + assert(not c.maximized_horizontal) + assert(not c.maximized_vertical) + assert(c.maximized) + + c:kill() + + return true + end, + function() + if #client.get() > 0 then return end + + test_client(nil,nil,nil,nil,nil,{maximize_after=true}) + + return true + end, + function() + local c = client.get()[1] + + if not c then return end + + assert(not c.maximized_horizontal) + assert(not c.maximized_vertical) + + -- It might happen in the second try + if not c.maximized then return end + + c:kill() + + return true + end, + function() + if #client.get() > 0 then return end + + -- Test if resizing requests work + test_client(nil,nil,nil,nil,nil,{resize={width=400, height=400}}) + + return true + end, + function() + if #client.get() ~= 1 then return end + + local c = client.get()[1] + local _, size = c:titlebar_top() + + if c.width ~= 400 or c.height ~= 400+size then return end + + c:kill() + + return true + end, + function() + if #client.get() > 0 then return end + + -- Remove the default handler and replace it with a testing one. + -- **WARNING**: add tests **BEFORE** this function if you want them + -- to be relevant. + client.disconnect_signal("request::geometry", awful.ewmh.geometry) + client.disconnect_signal("request::geometry", awful.ewmh.merge_maximization) + client.connect_signal("request::geometry", geometry_handler) + + test_client(nil,nil,nil,nil,nil,{maximize_after=true}) + + return true + end, + function() + local c = client.get()[1] + + if not c or counter ~= 2 then return end + return true end }