client: Rewrite the client stacking code in Lua.

This adds 2 new requests:

 * request::raise: Helps to decouple ewmh and client class
    internally plus allow some focus stealing policies.
 * reqest::restack: Send *why* something is restacked to Lua. It
    allows to do things like sending a client to the back of a
    layout rather than use the master area.

This is mostly a 1:1 port of the C code. The idea is to use this as
a starting point to have a stack per `tag` rather than something
global. It also paves the way for stacking wibox among clients
in a predictable way. None of that is exposed in the public API
as part of this commit. The point is to get enough going so the
wibox desktop layer PR can be implemented on top of this.
This commit is contained in:
Emmanuel Lepage Vallee 2022-12-11 23:07:57 -08:00
parent 1239cdf4bc
commit c6a9148d0b
9 changed files with 236 additions and 152 deletions

10
event.c
View File

@ -830,8 +830,14 @@ event_handle_maprequest(xcb_map_request_event_t *ev)
luaA_object_push(L, c);
client_set_minimized(L, -1, false);
lua_pop(L, 1);
/* it will be raised, so just update ourself */
client_raise(c);
lua_pushstring(L, "maprequest");
lua_newtable(L);
lua_pushstring(L, "client");
luaA_object_push(L, c);
lua_rawset(L, -3);
luaA_object_emit_signal(L, -3, "request::raise", 2);
}
}
else

View File

@ -46,7 +46,6 @@ awesome_refresh(void)
drawin_refresh();
client_refresh();
banning_refresh();
stack_refresh();
client_destroy_later();
return xcb_flush(globalconf.connection);
}

View File

@ -34,11 +34,13 @@
local ipairs = ipairs
local type = type
local capi = {
screen = screen,
mouse = mouse,
screen = screen,
mouse = mouse,
awesome = awesome,
client = client,
tag = tag
client = client,
drawin = drawin,
tag = tag,
root = root,
}
local tag = require("awful.tag")
local client = require("awful.client")
@ -55,6 +57,9 @@ end
local layout = {}
-- Avoid restacking the clients and drawins too often.
local need_restack = true
-- Support `table.insert()` to avoid breaking old code.
local default_layouts = setmetatable({}, {
__newindex = function(self, key, value)
@ -64,6 +69,17 @@ local default_layouts = setmetatable({}, {
end
})
local x11_layers_ordered, x11_layers_keys = {
"WINDOW_LAYER_IGNORE",
"WINDOW_LAYER_DESKTOP",
"WINDOW_LAYER_BELOW",
"WINDOW_LAYER_NORMAL",
"WINDOW_LAYER_ABOVE",
"WINDOW_LAYER_FULLSCREEN",
"WINDOW_LAYER_ONTOP"
}, {}
for k, v in ipairs(x11_layers_ordered) do x11_layers_keys[v] = k end
layout.suit = require("awful.layout.suit")
@ -108,6 +124,27 @@ local arrange_lock = false
-- Delay one arrange call per screen.
local delayed_arrange = {}
local function client_to_layer(o)
if o.type == "desktop" then
return x11_layers_keys.WINDOW_LAYER_DESKTOP
elseif o.ontop then
-- first deal with user set attributes
return x11_layers_keys.WINDOW_LAYER_ONTOP;
elseif o.fullscreen and capi.client.focus == o then
-- Fullscreen windows only get their own layer when they have the focus
return x11_layers_keys.WINDOW_LAYER_FULLSCREEN;
elseif o.above then
return x11_layers_keys.WINDOW_LAYER_ABOVE;
elseif o.below then
return x11_layers_keys.WINDOW_LAYER_BELOW;
elseif o.transient_for then
-- check for transient attr
return x11_layers_keys.WINDOW_LAYER_IGNORE;
else
return x11_layers_keys.WINDOW_LAYER_NORMAL
end
end
--- Get the current layout.
-- @tparam screen screen The screen.
-- @return The layout function.
@ -237,6 +274,7 @@ end
-- @tparam screen screen The screen to arrange.
-- @noreturn
-- @staticfct awful.layout.arrange
-- @see restack
function layout.arrange(screen)
screen = get_screen(screen)
if not screen or delayed_arrange[screen] then return end
@ -417,8 +455,77 @@ function layout.move_handler(c, context, hints) --luacheck: no unused args
end
end
-- [UNDOCUMENTED] Handler for `request::restack`.
--
-- @signalhandler awful.layout.move_handler
-- @tparam string context The context
-- @tparam table hints Additional hints
-- @tparam[opt=nil] client|nil hints.client The client
-- @tparam[opt=nil] drawin|nil hints.drawin Additional hints
function layout._restack_handler(context, hints) -- luacheck: no unused args
--TODO Support permissions
need_restack = true
end
--- Arrange the clients on the Z axis.
-- @staticfct awful.layout.restack
-- @noreturn
-- @see arrange
function layout.restack()
local layers = {}
local function append(o)
local layer = client_to_layer(o)
layers[layer] = layers[layer] or {}
table.insert(layers[layer], 1, o.drawin and o.drawin or o)
end
local drawins, clients = capi.drawin.get(), capi.client.get(nil, true)
for _, c in ipairs(clients) do append(c) end
for i=#drawins, 1, -1 do append(drawins[i].get_wibox()) end
local result = {}
for i = 1, #x11_layers_ordered do
if layers[i] then
for _, v in ipairs(layers[i] or {}) do
table.insert(result, v)
end
end
end
capi.root.set_stacking_order(result)
end
capi.client.connect_signal("request::geometry", layout.move_handler)
-- Translate the `request::raise`, which will trigger a `"request::restack"`.
capi.client.connect_signal("request::raise", function(c, context, hints) --luacheck: no unused args
hints.client:raise()
end)
capi.client.connect_signal("request::restack", layout._restack_handler)
-- Check if the type is `"desktop"`, which goes below everything.
for _, class in ipairs(capi.client, capi.drawin) do
class.connect_signal("property::type", function(o)
capi.client.emit_signal("request::restack", "type", {
client = o.modal ~= nil and o or nil,
drawin = o.modal == nil and o or nil,
})
end)
end
-- Place the clients and drawin on top of each other.
capi.awesome.connect_signal("refresh", function()
if need_restack then
layout.restack()
need_restack = false
end
end)
-- When a screen is moved, make (floating) clients follow it
capi.screen.connect_signal("property::geometry", function(s, old_geom)
local geom = s.geometry

View File

@ -2241,7 +2241,7 @@ client_manage(xcb_window_t w, xcb_get_geometry_reply_t *wgeom, xcb_get_window_at
ewmh_client_check_hints(c);
/* Push client in stack */
stack_client_push(c);
stack_client_push(L, c, "manage");
/* Request our response */
xcb_get_property_reply_t *reply =
@ -2717,7 +2717,7 @@ client_set_fullscreen(lua_State *L, int cidx, bool s)
luaA_object_emit_signal(L, abs_cidx, "property::fullscreen", 0);
/* Force a client resize, so that titlebars get shown/hidden */
client_resize_do(c, c->geometry);
stack_windows();
stack_windows(L, "fullscreen", c, NULL);
}
}
@ -2768,7 +2768,7 @@ client_set_maximized_common(lua_State *L, int cidx, bool s, const char* type, co
if(max_before != c->maximized)
luaA_object_emit_signal(L, abs_cidx, "property::maximized", 0);
stack_windows();
stack_windows(L, "maximized", c, NULL);
}
}
@ -2816,7 +2816,7 @@ client_set_above(lua_State *L, int cidx, bool s)
client_set_fullscreen(L, cidx, false);
}
c->above = s;
stack_windows();
stack_windows(L, "above", c, NULL);
luaA_object_emit_signal(L, cidx, "property::above", 0);
}
}
@ -2841,7 +2841,7 @@ client_set_below(lua_State *L, int cidx, bool s)
client_set_fullscreen(L, cidx, false);
}
c->below = s;
stack_windows();
stack_windows(L, "below", c, NULL);
luaA_object_emit_signal(L, cidx, "property::below", 0);
}
}
@ -2859,7 +2859,7 @@ client_set_modal(lua_State *L, int cidx, bool s)
if(c->modal != s)
{
c->modal = s;
stack_windows();
stack_windows(L, "modal", c, NULL);
luaA_object_emit_signal(L, cidx, "property::modal", 0);
}
}
@ -2884,7 +2884,7 @@ client_set_ontop(lua_State *L, int cidx, bool s)
client_set_fullscreen(L, cidx, false);
}
c->ontop = s;
stack_windows();
stack_windows(L, "ontop", c, NULL);
luaA_object_emit_signal(L, cidx, "property::ontop", 0);
}
}
@ -2942,7 +2942,7 @@ client_unmanage(client_t *c, client_unmanage_t reason)
client_array_remove(&globalconf.clients, elem);
break;
}
stack_client_remove(c);
stack_client_remove(L, c, false, "unmanage");
for(int i = 0; i < globalconf.tags.len; i++)
untag_client(c, globalconf.tags.tab[i]);
@ -3402,7 +3402,29 @@ luaA_client_raise(lua_State *L)
)
return 0;
client_raise(c);
client_t *tc = c;
int counter = 0;
/* Find number of transient layers. */
for(counter = 0; tc->transient_for; counter++)
tc = tc->transient_for;
/* Push them in reverse order. */
for(; counter > 0; counter--)
{
tc = c;
for(int i = 0; i < counter; i++)
tc = tc->transient_for;
stack_client_append(L, tc, "raise");
}
/* Push c on top of the stack. */
stack_client_append(L, c, "raise");
/* Notify the listeners */
luaA_object_push(L, c);
luaA_object_emit_signal(L, -1, "raised", 0);
lua_pop(L, 1);
return 0;
}
@ -3426,11 +3448,11 @@ luaA_client_lower(lua_State *L)
if (globalconf.stack.len && globalconf.stack.tab[0] == c)
return 0;
stack_client_push(c);
stack_client_push(L, c, "lower");
/* Traverse all transient layers. */
for(client_t *tc = c->transient_for; tc; tc = tc->transient_for)
stack_client_push(tc);
stack_client_push(L, tc, "lower");
/* Notify the listeners */
luaA_object_push(L, c);

View File

@ -258,38 +258,6 @@ drawable_t *client_get_drawable(client_t *, int, int);
drawable_t *client_get_drawable_offset(client_t *, int *, int *);
area_t client_get_undecorated_geometry(client_t *);
/** Put client on top of the stack.
* \param c The client to raise.
*/
static inline void
client_raise(client_t *c)
{
client_t *tc = c;
int counter = 0;
/* Find number of transient layers. */
for(counter = 0; tc->transient_for; counter++)
tc = tc->transient_for;
/* Push them in reverse order. */
for(; counter > 0; counter--)
{
tc = c;
for(int i = 0; i < counter; i++)
tc = tc->transient_for;
stack_client_append(tc);
}
/* Push c on top of the stack. */
stack_client_append(c);
/* Notify the listeners */
lua_State *L = globalconf_get_lua_State();
luaA_object_push(L, c);
luaA_object_emit_signal(L, -1, "raised", 0);
lua_pop(L, 1);
}
/** Check if a client has fixed size.
* \param c A client.
* \return A boolean value, true if the client has a fixed size.

View File

@ -336,7 +336,7 @@ drawin_map(lua_State *L, int widx)
/* Deactivate BMA */
client_restore_enterleave_events();
/* Stack this drawin correctly */
stack_windows();
stack_windows(L, "append", NULL, drawin);
/* Add it to the list of visible drawins */
drawin_array_append(&globalconf.drawins, drawin);
/* Make sure it has a surface */
@ -591,7 +591,7 @@ luaA_drawin_set_ontop(lua_State *L, drawin_t *drawin)
if(b != drawin->ontop)
{
drawin->ontop = b;
stack_windows();
stack_windows(L, "ontop", NULL, drawin);
luaA_object_emit_signal(L, -3, "property::ontop", 0);
}
return 0;

2
root.c
View File

@ -42,6 +42,7 @@
#include "objects/button.h"
#include "common/luaclass.h"
#include "xwindow.h"
#include "stack.h"
#include "math.h"
@ -653,6 +654,7 @@ const struct luaL_Reg awesome_root_methods[] =
{ "tags", luaA_root_tags },
{ "__index", luaA_root_index },
{ "__newindex", luaA_root_newindex },
{ "set_stacking_order", luaA_set_stacking_order},
{ "set_index_miss_handler", luaA_root_set_index_miss_handler},
{ "set_call_handler", luaA_root_set_call_handler},
{ "set_newindex_miss_handler", luaA_root_set_newindex_miss_handler},

167
stack.c
View File

@ -21,11 +21,9 @@
#include "stack.h"
#include "ewmh.h"
#include "objects/client.h"
#include "objects/drawin.h"
void
stack_client_remove(client_t *c)
stack_client_remove(lua_State *L, client_t *c, bool silent, const char *context)
{
foreach(client, globalconf.stack)
if(*client == c)
@ -34,39 +32,65 @@ stack_client_remove(client_t *c)
break;
}
ewmh_update_net_client_list_stacking();
stack_windows();
if (!silent)
stack_windows(L, context, c, NULL);
}
/** Push the client at the beginning of the client stack.
* \param L The Lua context.
* \param c The client to push.
* \param context An human readable reason of why this was done.
*/
void
stack_client_push(client_t *c)
stack_client_push(lua_State *L, client_t *c, const char *context)
{
stack_client_remove(c);
stack_client_remove(L, c, true, "");
client_array_push(&globalconf.stack, c);
ewmh_update_net_client_list_stacking();
stack_windows();
stack_windows(L, context, c, NULL);
}
/** Push the client at the end of the client stack.
* \param L The Lua context.
* \param c The client to push.
* \param context An human readable reason of why this was done.
*/
void
stack_client_append(client_t *c)
stack_client_append(lua_State *L, client_t *c, const char *context)
{
stack_client_remove(c);
stack_client_remove(L, c, true, "");
client_array_append(&globalconf.stack, c);
ewmh_update_net_client_list_stacking();
stack_windows();
stack_windows(L, context, c, NULL);
}
static bool need_stack_refresh = false;
void
stack_windows(void)
stack_windows(lua_State *L, const char *context, client_t *c, drawin_t *d)
{
need_stack_refresh = true;
/* Context */
lua_pushstring(L, context);
/* Create hints table */
lua_newtable(L);
lua_pushstring(L, "client");
if (c)
luaA_object_push(L, c);
else
lua_pushnil(L);
lua_settable(L, -3);
lua_pushstring(L, "drawin");
if (d)
luaA_object_push(L, d);
else
lua_pushnil(L);
lua_settable(L, -3);
luaA_class_emit_signal(L, &client_class, "request::restack", 2);
}
/** Stack a window above another window, without causing errors.
@ -107,95 +131,48 @@ stack_client_above(client_t *c, xcb_window_t previous)
return previous;
}
/** Stacking layout layers */
typedef enum
{
/** This one is a special layer */
WINDOW_LAYER_IGNORE,
WINDOW_LAYER_DESKTOP,
WINDOW_LAYER_BELOW,
WINDOW_LAYER_NORMAL,
WINDOW_LAYER_ABOVE,
WINDOW_LAYER_FULLSCREEN,
WINDOW_LAYER_ONTOP,
/** This one only used for counting and is not a real layer */
WINDOW_LAYER_COUNT
} window_layer_t;
/** Get the real layer of a client according to its attribute (fullscreen, …)
* \param c The client.
* \return The real layer.
/**
* Allow Lua to define the stacking order of clients and wiboxes.
*
* The table must contain `client` and `wibox` object. Index `1` is the closest
* to the root (wallpaper) and the last index is the closest to the top.
*
* @staticfct root.set_stacking_order
* @tparam table stacking_order
*/
static window_layer_t
client_layer_translator(client_t *c)
{
/* first deal with user set attributes */
if(c->ontop)
return WINDOW_LAYER_ONTOP;
/* Fullscreen windows only get their own layer when they have the focus */
else if(c->fullscreen && globalconf.focus.client == c)
return WINDOW_LAYER_FULLSCREEN;
else if(c->above)
return WINDOW_LAYER_ABOVE;
else if(c->below)
return WINDOW_LAYER_BELOW;
/* check for transient attr */
else if(c->transient_for)
return WINDOW_LAYER_IGNORE;
/* then deal with windows type */
switch(c->type)
{
case WINDOW_TYPE_DESKTOP:
return WINDOW_LAYER_DESKTOP;
default:
break;
}
return WINDOW_LAYER_NORMAL;
}
/** Restack clients.
* \todo It might be worth stopping to restack everyone and only stack `c'
* relatively to the first matching in the list.
*/
void
stack_refresh()
{
if(!need_stack_refresh)
return;
int
luaA_set_stacking_order(lua_State *L) {
xcb_window_t next = XCB_NONE;
/* stack desktop windows */
for(window_layer_t layer = WINDOW_LAYER_DESKTOP; layer < WINDOW_LAYER_BELOW; layer++)
foreach(node, globalconf.stack)
if(client_layer_translator(*node) == layer)
next = stack_client_above(*node, next);
if(lua_gettop(L) == 1)
{
luaA_checktable(L, 1);
/* first stack not ontop drawin window */
foreach(drawin, globalconf.drawins)
if(!(*drawin)->ontop)
lua_pushnil(L);
while(lua_next(L, 1))
{
stack_window_above((*drawin)->window, next);
next = (*drawin)->window;
if (luaA_class_get(L, -1) == &client_class)
{
client_t *c = luaA_object_ref_class(L, -1, &client_class);
next = stack_client_above(c, next);
luaA_object_unref(L, c);
}
else if (luaA_class_get(L, -1) == &drawin_class)
{
drawin_t *d = luaA_object_ref_class(L, -1, &drawin_class);
stack_window_above(d->window, next);
next = d->window;
luaA_object_unref(L, d);
}
else
return luaL_error(L, "set_stacking_order only works on clients and drawins");
}
/* then stack clients */
for(window_layer_t layer = WINDOW_LAYER_BELOW; layer < WINDOW_LAYER_COUNT; layer++)
foreach(node, globalconf.stack)
if(client_layer_translator(*node) == layer)
next = stack_client_above(*node, next);
lua_pop(L, 1);
}
/* then stack ontop drawin window */
foreach(drawin, globalconf.drawins)
if((*drawin)->ontop)
{
stack_window_above((*drawin)->window, next);
next = (*drawin)->window;
}
need_stack_refresh = false;
return 0;
}

13
stack.h
View File

@ -21,14 +21,17 @@
#ifndef AWESOME_STACK_H
#define AWESOME_STACK_H
#include "luaa.h"
#include "objects/client.h"
#include "objects/drawin.h"
typedef struct client_t client_t;
void stack_client_remove(client_t *);
void stack_client_push(client_t *);
void stack_client_append(client_t *);
void stack_windows(void);
void stack_refresh(void);
void stack_client_remove(lua_State *, client_t *, bool, const char *);
void stack_client_push(lua_State *, client_t *, const char *);
void stack_client_append(lua_State *, client_t *, const char *);
void stack_windows(lua_State *, const char *, client_t *, drawin_t *);
int luaA_set_stacking_order(lua_State *);
#endif
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80