From cd253ed815ac5ae79db59e0f1b8437cb6cd14718 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Oct 2019 18:29:59 -0400 Subject: [PATCH 01/22] client: Add an `activate` method. This method aims to provide a centralized, declarative API to focus clients. Currently, there is tons of code using "request::activate", including `rc.lua` and have extra boilerplate code around it to handle some corner case (such as minimization and clients already having the focus). This code takes room, is repetitive and force some imperative logic to be in `rc.lua`. --- lib/awful/client.lua | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 2a7c978d6..c7d49f6b7 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -14,6 +14,7 @@ local object = require("gears.object") local grect = require("gears.geometry").rectangle local gmath = require("gears.math") local gtable = require("gears.table") +local amousec = require("awful.mouse.client") local pairs = pairs local type = type local ipairs = ipairs @@ -1388,6 +1389,71 @@ function client.object.set_shape(self, shape) set_shape(self) end +--- Activate (focus) a client. +-- +-- This method is the correct way to focus a client. While +-- `client.focus = my_client` works and is commonly used in older code, it has +-- some drawbacks. The most obvious one is that it bypasses the activate +-- filters. It also doesn't handle minimized clients well and requires a lot +-- of boilerplate code to make work properly. +-- +-- The valid `args.actions` are: +-- +-- * **mouse_move**: Move the client when the mouse cursor moves until the +-- mouse buttons are release. +-- * **mouse_resize**: Resize the client when the mouse cursor moves until the +-- mouse buttons are release. +-- * **mouse_center**: Move the mouse cursor to the center of the client if it +-- isn't already within its geometry, +-- * **toggle_minimization**: If the client is already active, minimize it. +-- +-- @method activate +-- @tparam table args +-- @tparam[opt=other] string args.context Why was this activate called? +-- @tparam[opt=true] boolean args.raise Raise the client to the top of its layer. +-- @tparam[opt=false] boolean args.force Force the activation even for unfocusable +-- clients. +-- @tparam[opt=false] boolean args.switch_to_tags +-- @tparam[opt=false] boolean args.switch_to_tag +-- @tparam[opt=false] boolean args.action Once activated, perform an action. +-- @tparam[opt=false] boolean args.toggle_minimization +-- @see awful.ewmh.add_activate_filter +-- @see request::activate +function client.object.activate(c, args) + local new_args = setmetatable({}, {__index = args or {}}) + + -- Set the default arguments. + new_args.raise = new_args.raise == nil and true or args.raise + + if c == capi.client.focus and new_args.action == "toggle_minimization" then + c.minimized = true + else + c:emit_signal( + "request::activate", + new_args.context or "other", + new_args + ) + end + + if new_args.action and new_args.action == "mouse_move" then + amousec.move(c) + elseif new_args.action and new_args.action == "mouse_resize" then + amousec.resize(c) + elseif new_args.action and new_args.action == "mouse_center" then + local coords, geo = mouse.mouse.coords(), c:geometry() + coords.width, coords.height = 1,1 + + if not grect.area_intersect_area(geo, coords) then + -- Do not use `awful.placement` to avoid an useless circular + -- dependency. Centering is *very* simple. + mouse.mouse.coords { + x = geo.x + math.ceil(geo.width /2), + y = geo.y + math.ceil(geo.height/2) + } + end + end +end + -- Register standards signals --- The last geometry when client was floating. From 1434ea4c8b9acda161d4afe7af1415467b2aa0f9 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 19 Oct 2019 18:32:26 -0400 Subject: [PATCH 02/22] rc.lua: Get rid of the focus related imperative logic. --- awesomerc.lua | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index a40e977b5..a3c25be1b 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -157,22 +157,15 @@ screen.connect_signal("request::desktop_decoration", function(s) } } + -- @TASKLIST_BUTTON@ -- Create a tasklist widget s.mytasklist = awful.widget.tasklist { screen = s, filter = awful.widget.tasklist.filter.currenttags, buttons = { awful.button({ }, 1, function (c) - if c == client.focus then - c.minimized = true - else - c:emit_signal( - "request::activate", - "tasklist", - {raise = true} - ) - end - end), + c:activate { context = "tasklist", action = "toggle_minimization" } + end), awful.button({ }, 3, function() awful.menu.client_list { theme = { width = 250 } } end), awful.button({ }, 4, function() awful.client.focus.byidx( 1) end), awful.button({ }, 5, function() awful.client.focus.byidx(-1) end), @@ -286,9 +279,7 @@ awful.keyboard.append_global_keybindings({ local c = awful.client.restore() -- Focus restored client if c then - c:emit_signal( - "request::activate", "key.unminimize", {raise = true} - ) + c:activate { raise = true, context = "key.unminimize" } end end, {description = "restore minimized", group = "client"}), @@ -383,15 +374,13 @@ awful.keyboard.append_global_keybindings({ client.connect_signal("request::default_mousebindings", function() awful.mouse.append_client_mousebindings({ awful.button({ }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) + c:activate { context = "mouse_click" } end), awful.button({ modkey }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.move(c) + c:activate { context = "mouse_click", action = "mouse_move" } end), awful.button({ modkey }, 3, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.resize(c) + c:activate { context = "mouse_click", action = "mouse_resize"} end), }) end) @@ -528,12 +517,10 @@ client.connect_signal("request::titlebars", function(c) -- buttons for the titlebar local buttons = { awful.button({ }, 1, function() - c:emit_signal("request::activate", "titlebar", {raise = true}) - awful.mouse.client.move(c) + c:activate { context = "titlebar", action = "mouse_move" } end), awful.button({ }, 3, function() - c:emit_signal("request::activate", "titlebar", {raise = true}) - awful.mouse.client.resize(c) + c:activate { context = "titlebar", action = "mouse_resize"} end), } @@ -565,7 +552,7 @@ end) -- Enable sloppy focus, so that focus follows mouse. client.connect_signal("mouse::enter", function(c) - c:emit_signal("request::activate", "mouse_enter", {raise = false}) + c:activate { context = "mouse_enter", raise = false } end) -- @DOC_BORDER@ From 067bcaca60ed138f016388be34675e22d0421bc2 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 10 Nov 2019 01:12:43 -0500 Subject: [PATCH 03/22] client: Rename the `manage` and `unmanage` signals. They currently fit the general concept of a `request::` in the sense that they are not property related and have "request handlers". The commit also add deprecation for signals. The reason for this fits within the larger standardization project. Non-namespaced signals will eventually be renamed. This has started a long time ago. What is old is new again. Once upon a time, there was a `startup` parameter to the `manage` signal. It is now back in the form of a context. Finally, this commit removes the `manage` section of `rc.lua`. It no longer did anything worthy of being in the config. Each of its important parts have been moved out over the years and the last remaining bit is always required anyway. The code has been moved to `client.lua`. --- awesomerc.lua | 22 +---- docs/05-awesomerc.md.lua | 6 -- docs/config.ld | 11 ++- event.c | 6 +- ewmh.c | 6 +- lib/awful/autofocus.lua | 2 +- lib/awful/client.lua | 37 ++++++-- lib/awful/client/urgent.lua | 2 +- lib/awful/rules.lua | 2 +- lib/awful/spawn.lua | 2 +- lib/awful/titlebar.lua | 6 +- lib/awful/widget/taglist.lua | 2 +- lib/awful/widget/tasklist.lua | 2 +- objects/client.c | 95 ++++++++++++++++--- objects/client.h | 10 +- .../examples/awful/placement/no_offscreen.lua | 3 + tests/examples/shims/client.lua | 4 + tests/test-awful-rules.lua | 2 +- tests/test-leak-client.lua | 2 +- tests/test-spawn-snid.lua | 2 +- tests/test-urgent.lua | 2 +- 21 files changed, 161 insertions(+), 65 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index a3c25be1b..f60186497 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -435,7 +435,7 @@ end) -- }}} -- {{{ Rules --- Rules to apply to new clients (through the "manage" signal). +-- Rules to apply to new clients. -- @DOC_RULES@ awful.rules.rules = { -- @DOC_GLOBAL_RULE@ @@ -495,22 +495,7 @@ awful.rules.rules = { } -- }}} --- {{{ Signals --- Signal function to execute when a new client appears. --- @DOC_MANAGE_HOOK@ -client.connect_signal("manage", function (c) - -- Set the windows at the slave, - -- i.e. put it at the end of others instead of setting it master. - -- if not awesome.startup then awful.client.setslave(c) end - - if awesome.startup - and not c.size_hints.user_position - and not c.size_hints.program_position then - -- Prevent clients from being unreachable after screen count changes. - awful.placement.no_offscreen(c) - end -end) - +-- {{{ Titlebars -- @DOC_TITLEBARS@ -- Add a titlebar if titlebars_enabled is set to true in the rules. client.connect_signal("request::titlebars", function(c) @@ -549,6 +534,7 @@ client.connect_signal("request::titlebars", function(c) layout = wibox.layout.align.horizontal } end) +-- }}} -- Enable sloppy focus, so that focus follows mouse. client.connect_signal("mouse::enter", function(c) @@ -558,4 +544,4 @@ end) -- @DOC_BORDER@ client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end) client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end) --- }}} + diff --git a/docs/05-awesomerc.md.lua b/docs/05-awesomerc.md.lua index ffd0dbad0..a1d0c5bd0 100644 --- a/docs/05-awesomerc.md.lua +++ b/docs/05-awesomerc.md.lua @@ -208,12 +208,6 @@ sections.DOC_DIALOG_RULE = [[   ]] - -sections.DOC_MANAGE_HOOK = [[ -   -]] - - sections.DOC_TITLEBARS = [[   ]] diff --git a/docs/config.ld b/docs/config.ld index 3a906f563..c9686a040 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -70,6 +70,8 @@ new_type("deprecatedproperty", "Deprecated object properties", false, "Type cons new_type("method", "Object methods ", false, "Parameters") -- New type for signals new_type("signal", "Signals", false, "Arguments") +-- Deprecated signals. +new_type("deprecatedsignal", "Deprecated signals", false, "Arguments") -- New type for signals connections new_type("signalhandler", "Request handlers", false, "Arguments") -- Allow objects to define a set of beautiful properties affecting them @@ -736,6 +738,13 @@ local show_return = { deprecated = true, } +-- The different type of deprecation. +local is_deprecated = { + deprecated = true, + deprecatedproperty = true, + deprecatedsignal = true, +} + custom_display_name_handler = function(item, default_handler) init_custom_types(item) @@ -776,7 +785,7 @@ custom_display_name_handler = function(item, default_handler) end end - if item.type == "deprecated" or item.type == "deprecatedproperty" then + if is_deprecated[item.type] then return ret .. " [deprecated]" end diff --git a/event.c b/event.c index 989c0110c..030f86166 100644 --- a/event.c +++ b/event.c @@ -478,7 +478,7 @@ event_handle_destroynotify(xcb_destroy_notify_event_t *ev) client_t *c; if((c = client_getbywin(ev->window))) - client_unmanage(c, false); + client_unmanage(c, CLIENT_UNMANAGE_DESTROYED); else for(int i = 0; i < globalconf.embedded.len; i++) if(globalconf.embedded.tab[i].win == ev->window) @@ -856,7 +856,7 @@ event_handle_unmapnotify(xcb_unmap_notify_event_t *ev) client_t *c; if((c = client_getbywin(ev->window))) - client_unmanage(c, true); + client_unmanage(c, CLIENT_UNMANAGE_UNMAP); } /** The randr screen change notify event handler. @@ -993,7 +993,7 @@ event_handle_reparentnotify(xcb_reparent_notify_event_t *ev) /* Ignore reparents to the root window, they *might* be caused by * ourselves if a client quickly unmaps and maps itself again. */ if (ev->parent != globalconf.screen->root) - client_unmanage(c, true); + client_unmanage(c, CLIENT_UNMANAGE_REPARENT); } else if (ev->parent != globalconf.systray.window) { /* Embedded window moved elsewhere, end of embedding */ diff --git a/ewmh.c b/ewmh.c index d720a3043..429b96ee4 100644 --- a/ewmh.c +++ b/ewmh.c @@ -238,8 +238,8 @@ ewmh_init_lua(void) luaA_class_connect_signal(L, &client_class, "focus", ewmh_update_net_active_window); luaA_class_connect_signal(L, &client_class, "unfocus", ewmh_update_net_active_window); - luaA_class_connect_signal(L, &client_class, "manage", ewmh_update_net_client_list); - luaA_class_connect_signal(L, &client_class, "unmanage", ewmh_update_net_client_list); + luaA_class_connect_signal(L, &client_class, "request::manage", ewmh_update_net_client_list); + luaA_class_connect_signal(L, &client_class, "request::unmanage", ewmh_update_net_client_list); luaA_class_connect_signal(L, &client_class, "property::modal" , ewmh_client_update_hints); luaA_class_connect_signal(L, &client_class, "property::fullscreen" , ewmh_client_update_hints); luaA_class_connect_signal(L, &client_class, "property::maximized_horizontal" , ewmh_client_update_hints); @@ -256,7 +256,7 @@ ewmh_init_lua(void) luaA_class_connect_signal(L, &client_class, "property::titlebar_right" , ewmh_client_update_frame_extents); luaA_class_connect_signal(L, &client_class, "property::titlebar_left" , ewmh_client_update_frame_extents); luaA_class_connect_signal(L, &client_class, "property::border_width" , ewmh_client_update_frame_extents); - luaA_class_connect_signal(L, &client_class, "manage", ewmh_client_update_frame_extents); + luaA_class_connect_signal(L, &client_class, "request::manage", ewmh_client_update_frame_extents); /* NET_CURRENT_DESKTOP handling */ luaA_class_connect_signal(L, &client_class, "focus", ewmh_update_net_current_desktop); luaA_class_connect_signal(L, &client_class, "unfocus", ewmh_update_net_current_desktop); diff --git a/lib/awful/autofocus.lua b/lib/awful/autofocus.lua index 5647a9651..f3ea73c15 100644 --- a/lib/awful/autofocus.lua +++ b/lib/awful/autofocus.lua @@ -64,7 +64,7 @@ end tag.connect_signal("property::selected", function (t) timer.delayed_call(check_focus_tag, t) end) -client.connect_signal("unmanage", check_focus_delayed) +client.connect_signal("request::unmanage", check_focus_delayed) client.connect_signal("tagged", check_focus_delayed) client.connect_signal("untagged", check_focus_delayed) client.connect_signal("property::hidden", check_focus_delayed) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index c7d49f6b7..a37f9d3c1 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -843,7 +843,7 @@ capi.client.connect_signal("property::maximized_vertical", update_implicitly_flo capi.client.connect_signal("property::maximized_horizontal", update_implicitly_floating) capi.client.connect_signal("property::maximized", update_implicitly_floating) capi.client.connect_signal("property::size_hints", update_implicitly_floating) -capi.client.connect_signal("manage", update_implicitly_floating) +capi.client.connect_signal("request::manage", update_implicitly_floating) --- Toggle the floating state of a client between 'auto' and 'true'. -- Use `c.floating = not c.floating` @@ -1464,23 +1464,44 @@ end -- @tparam[opt=nil] string content The context (like "rules") -- @tparam[opt=nil] table hints Some hints. ---- The client marked signal (deprecated). --- @signal marked +--- The client marked signal. +-- @deprecatedsignal marked ---- The client unmarked signal (deprecated). --- @signal unmarked +--- The client unmarked signal. +-- @deprecatedsignal unmarked -- Add clients during startup to focus history. -- This used to happen through ewmh.activate, but that only handles visible -- clients now. -capi.client.connect_signal("manage", function (c) +capi.client.connect_signal("request::manage", function (c) + if capi.awesome.startup + and not c.size_hints.user_position + and not c.size_hints.program_position then + -- Prevent clients from being unreachable after screen count changes. + require("awful.placement").no_offscreen(c) + end + if awesome.startup then client.focus.history.add(c) end end) -capi.client.connect_signal("unmanage", client.focus.history.delete) +capi.client.connect_signal("request::unmanage", client.focus.history.delete) -capi.client.connect_signal("unmanage", client.floating.delete) +capi.client.connect_signal("request::unmanage", client.floating.delete) + +-- Print a warning when the old `manage` signal is used. +capi.client.connect_signal("manage::connected", function() + gdebug.deprecate( + "Use `request::manage` rather than `manage`", + {deprecated_in=5} + ) +end) +capi.client.connect_signal("unmanage::connected", function() + gdebug.deprecate( + "Use `request::unmanage` rather than `unmanage`", + {deprecated_in=5} + ) +end) -- Connect to "focus" signal, and allow to disable tracking. do diff --git a/lib/awful/client/urgent.lua b/lib/awful/client/urgent.lua index 8cdcc2cba..265a25fb3 100644 --- a/lib/awful/client/urgent.lua +++ b/lib/awful/client/urgent.lua @@ -83,7 +83,7 @@ end capi.client.connect_signal("property::urgent", urgent.add) capi.client.connect_signal("focus", urgent.delete) -capi.client.connect_signal("unmanage", urgent.delete) +capi.client.connect_signal("request::unmanage", urgent.delete) return urgent diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 9f4a6c401..9a659c140 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -657,7 +657,7 @@ function rules.completed_with_payload_callback(c, props, callbacks) rules.execute(c, props, callbacks) end -client.connect_signal("manage", rules.apply) +client.connect_signal("request::manage", rules.apply) --@DOC_rule_COMMON@ diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua index 8b54b4d89..b01c1c1af 100644 --- a/lib/awful/spawn.lua +++ b/lib/awful/spawn.lua @@ -736,7 +736,7 @@ end capi.awesome.connect_signal("spawn::canceled" , spawn.on_snid_cancel ) capi.awesome.connect_signal("spawn::timeout" , spawn.on_snid_cancel ) -capi.client.connect_signal ("manage" , spawn.on_snid_callback ) +capi.client.connect_signal ("request::manage" , spawn.on_snid_callback ) return setmetatable(spawn, { __call = function(_, ...) return spawn.spawn(...) end }) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua index 57bc3b011..0300c77a8 100644 --- a/lib/awful/titlebar.lua +++ b/lib/awful/titlebar.lua @@ -557,7 +557,9 @@ local function new(c, args) c:connect_signal("unfocus", update_colors) -- Inform the drawable when it becomes invisible - c:connect_signal("unmanage", function() ret:_inform_visible(false) end) + c:connect_signal("request::unmanage", function() + ret:_inform_visible(false) + end) else bars[position].args = args ret = bars[position].drawable @@ -832,7 +834,7 @@ function titlebar.widget.stickybutton(c) return widget end -client.connect_signal("unmanage", function(c) +client.connect_signal("request::unmanage", function(c) all_titlebars[c] = nil end) diff --git a/lib/awful/widget/taglist.lua b/lib/awful/widget/taglist.lua index 7e8b117c9..9fc153884 100644 --- a/lib/awful/widget/taglist.lua +++ b/lib/awful/widget/taglist.lua @@ -555,7 +555,7 @@ function taglist.new(args, filter, buttons, style, update_function, base_widget) end) capi.client.connect_signal("tagged", uc) capi.client.connect_signal("untagged", uc) - capi.client.connect_signal("unmanage", uc) + capi.client.connect_signal("request::unmanage", uc) capi.screen.connect_signal("removed", function(s) instances[get_screen(s)] = nil end) diff --git a/lib/awful/widget/tasklist.lua b/lib/awful/widget/tasklist.lua index d2fe7cfee..48e2adf79 100644 --- a/lib/awful/widget/tasklist.lua +++ b/lib/awful/widget/tasklist.lua @@ -621,7 +621,7 @@ function tasklist.new(args, filter, buttons, style, update_function, base_widget capi.client.connect_signal("property::hidden", u) capi.client.connect_signal("tagged", u) capi.client.connect_signal("untagged", u) - capi.client.connect_signal("unmanage", function(c) + capi.client.connect_signal("request::unmanage", function(c) u(c) for _, i in pairs(instances) do for _, tlist in pairs(i) do diff --git a/objects/client.c b/objects/client.c index 150bc0bfa..84d1d6740 100644 --- a/objects/client.c +++ b/objects/client.c @@ -63,7 +63,7 @@ * * To execute a callback when a new client is added, use the `manage` signal: * - * client.connect_signal("manage", function(c) + * client.connect_signal("request::manage", function(c) * -- do something * end) * @@ -164,7 +164,46 @@ */ /** When a new client appears and gets managed by Awesome. - * @signal manage + * + * This request should be implemented by code which track the client. It isn't + * recommended to use this to initialize the client content. This use case is + * a better fit for `ruled.client`, which has built-in dependency management. + * Using this request to mutate the client state will likely conflict with + * `ruled.client`. + * + * @signal request::manage + * @tparam client c The client. + * @tparam string context What created the client. It is currently either "new" + * or "startup". + * @tparam table hints More metadata (currently empty, it exists for compliance + * with the other `request::` signals). + */ + +/** When a client is going away. + * + * Each places which store `client` objects in non-weak table or whose state + * depend on the current client should answer this request. + * + * The contexts are: + * + * * **user**: `c:unmanage()` was called. + * * **reparented**: The window was reparented to another window. It is no + * longer a stand alone client. + * * **destroyed**: The window was closed. + * + * @signal request::unmanage + * @tparam client c The client. + * @tparam string context Why was the client unmanaged. + * @tparam table hints More metadata (currently empty, it exists for compliance + * with the other `request::` signals). + */ + +/** Use `request::manage`. + * @deprecatedsignal manage + */ + +/** Use `request::unmanage`. + * @deprecatedsignal unmanage */ /** @@ -275,10 +314,6 @@ * @signal unfocus */ -/** - * @signal unmanage - */ - /** When a client gets untagged. * @signal untagged * @tag t The tag object. @@ -1735,7 +1770,19 @@ client_manage(xcb_window_t w, xcb_get_geometry_reply_t *wgeom, xcb_get_window_at luaA_class_emit_signal(L, &client_class, "list", 0); + /* Add the context */ + if (globalconf.loop == NULL) + lua_pushstring(L, "startup"); + else + lua_pushstring(L, "new"); + + /* Hints */ + lua_newtable(L); + /* client is still on top of the stack; emit signal */ + luaA_object_emit_signal(L, -3, "request::manage", 2); + + /*TODO v6: remove this*/ luaA_object_emit_signal(L, -1, "manage", 0); xcb_generic_error_t *error = xcb_request_check(globalconf.connection, reparent_cookie); @@ -1744,7 +1791,7 @@ client_manage(xcb_window_t w, xcb_get_geometry_reply_t *wgeom, xcb_get_window_at NONULL(c->name), NONULL(c->class), NONULL(c->instance)); event_handle((xcb_generic_event_t *) error); p_delete(&error); - client_unmanage(c, true); + client_unmanage(c, CLIENT_UNMANAGE_FAILED); } /* pop client */ @@ -2354,10 +2401,10 @@ client_unban(client_t *c) /** Unmanage a client. * \param c The client. - * \param window_valid Is the client's window still valid? + * \param reason Why was the unmanage done. */ void -client_unmanage(client_t *c, bool window_valid) +client_unmanage(client_t *c, client_unmanage_t reason) { lua_State *L = globalconf_get_lua_State(); @@ -2384,6 +2431,28 @@ client_unmanage(client_t *c, bool window_valid) untag_client(c, globalconf.tags.tab[i]); luaA_object_push(L, c); + + /* Give the context to Lua */ + switch (reason) + { + break; + case CLIENT_UNMANAGE_USER: + lua_pushstring(L, "user"); + break; + case CLIENT_UNMANAGE_REPARENT: + lua_pushstring(L, "reparented"); + break; + case CLIENT_UNMANAGE_UNMAP: + case CLIENT_UNMANAGE_FAILED: + case CLIENT_UNMANAGE_DESTROYED: + lua_pushstring(L, "destroyed"); + break; + } + + /* Hints */ + lua_newtable(L); + + luaA_object_emit_signal(L, -3, "request::unmanage", 2); luaA_object_emit_signal(L, -1, "unmanage", 0); lua_pop(L, 1); @@ -2413,7 +2482,7 @@ client_unmanage(client_t *c, bool window_valid) /* Clear our event mask so that we don't receive any events from now on, * especially not for the following requests. */ - if(window_valid) + if(reason != CLIENT_UNMANAGE_DESTROYED) xcb_change_window_attributes(globalconf.connection, c->window, XCB_CW_EVENT_MASK, @@ -2423,7 +2492,7 @@ client_unmanage(client_t *c, bool window_valid) XCB_CW_EVENT_MASK, (const uint32_t []) { 0 }); - if(window_valid) + if(reason != CLIENT_UNMANAGE_DESTROYED) { xcb_unmap_window(globalconf.connection, c->window); xcb_reparent_window(globalconf.connection, c->window, globalconf.screen->root, @@ -2434,7 +2503,7 @@ client_unmanage(client_t *c, bool window_valid) window_array_append(&globalconf.destroy_later_windows, c->nofocus_window); window_array_append(&globalconf.destroy_later_windows, c->frame_window); - if(window_valid) + if(reason != CLIENT_UNMANAGE_DESTROYED) { /* Remove this window from the save set since this shouldn't be made visible * after a restart anymore. */ @@ -2823,7 +2892,7 @@ static int luaA_client_unmanage(lua_State *L) { client_t *c = luaA_checkudata(L, 1, &client_class); - client_unmanage(c, true); + client_unmanage(c, CLIENT_UNMANAGE_USER); return 0; } diff --git a/objects/client.h b/objects/client.h index 0c8193184..74a23131c 100644 --- a/objects/client.h +++ b/objects/client.h @@ -47,6 +47,14 @@ typedef enum { CLIENT_TITLEBAR_COUNT = 4 } client_titlebar_t; +typedef enum { + CLIENT_UNMANAGE_DESTROYED = 0, + CLIENT_UNMANAGE_USER = 1, + CLIENT_UNMANAGE_REPARENT = 2, + CLIENT_UNMANAGE_UNMAP = 3, + CLIENT_UNMANAGE_FAILED = 4 +} client_unmanage_t; + /* Special bit we invented to "fake" unset hints */ #define MWM_HINTS_AWESOME_SET (1L << 15) @@ -206,7 +214,7 @@ void client_ban_unfocus(client_t *); void client_unban(client_t *); void client_manage(xcb_window_t, xcb_get_geometry_reply_t *, xcb_get_window_attributes_reply_t *); bool client_resize(client_t *, area_t, bool); -void client_unmanage(client_t *, bool); +void client_unmanage(client_t *, client_unmanage_t); void client_kill(client_t *); void client_set_sticky(lua_State *, int, bool); void client_set_above(lua_State *, int, bool); diff --git a/tests/examples/awful/placement/no_offscreen.lua b/tests/examples/awful/placement/no_offscreen.lua index c7ac1038f..4b4054ef0 100644 --- a/tests/examples/awful/placement/no_offscreen.lua +++ b/tests/examples/awful/placement/no_offscreen.lua @@ -1,6 +1,9 @@ --DOC_GEN_OUTPUT --DOC_GEN_IMAGE --DOC_HIDE local awful = {placement = require("awful.placement")} --DOC_HIDE +--DOC_HIDE no_offscreen is auto-called when startup is true, avoid this. +awesome.startup = false -- luacheck: globals awesome.startup --DOC_HIDE + local c = client.gen_fake {x = -30, y = -30, width= 100, height=100} --DOC_HIDE print("Before:", "x="..c.x..", y="..c.y..", width="..c.width..", height="..c.height) --DOC_HIDE diff --git a/tests/examples/shims/client.lua b/tests/examples/shims/client.lua index f2067131f..c425c5379 100644 --- a/tests/examples/shims/client.lua +++ b/tests/examples/shims/client.lua @@ -287,7 +287,11 @@ function client.gen_fake(args) end }) + client.emit_signal("request::manage", ret) + + --TODO v6 remove this. client.emit_signal("manage", ret) + assert(not args.screen or (args.screen == ret.screen)) return ret diff --git a/tests/test-awful-rules.lua b/tests/test-awful-rules.lua index db89234d6..24c61c7e4 100644 --- a/tests/test-awful-rules.lua +++ b/tests/test-awful-rules.lua @@ -13,7 +13,7 @@ local tests = {} local tb_height = gears.math.round(beautiful.get_font_height() * 1.5) -- local border_width = beautiful.border_width --- Detect "manage" race conditions +-- Detect "request::manage" race conditions local real_apply = awful.rules.apply function awful.rules.apply(c) assert(#c:tags() == 0) diff --git a/tests/test-leak-client.lua b/tests/test-leak-client.lua index ca989b231..12b520e45 100644 --- a/tests/test-leak-client.lua +++ b/tests/test-leak-client.lua @@ -52,7 +52,7 @@ local function create_titlebar(c) end -- "Enable" titlebars (so that the titlebar can prevent garbage collection) -client.connect_signal("manage", function (c) +client.connect_signal("request::manage", function (c) create_titlebar(c) end) diff --git a/tests/test-spawn-snid.lua b/tests/test-spawn-snid.lua index 1a4530701..6e08c2f2d 100644 --- a/tests/test-spawn-snid.lua +++ b/tests/test-spawn-snid.lua @@ -5,7 +5,7 @@ local test_client = require("_client") local manage_called, c_snid -client.connect_signal("manage", function(c) +client.connect_signal("request::manage", function(c) manage_called = true c_snid = c.startup_id assert(c.machine == awesome.hostname, diff --git a/tests/test-urgent.lua b/tests/test-urgent.lua index f0558097d..b36a42710 100644 --- a/tests/test-urgent.lua +++ b/tests/test-urgent.lua @@ -16,7 +16,7 @@ client.connect_signal("property::urgent", function (c) end) local manage_cb_done -client.connect_signal("manage", function (c) +client.connect_signal("request::manage", function (c) manage_cb_done = true assert(c.class == "XTerm", "Client should be xterm!") end) From 58f3ea740f3780d5c413f06bf0b2305d19978579 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 10 Nov 2019 18:24:09 -0500 Subject: [PATCH 04/22] client: Add an `active` property to check if a client has focus. This follows in the footsteps of: * request::activate * awful.ewmh.add_activate_filter * c:activate{} --- lib/awful/client.lua | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index a37f9d3c1..3d17b38b5 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -1419,6 +1419,7 @@ end -- @tparam[opt=false] boolean args.toggle_minimization -- @see awful.ewmh.add_activate_filter -- @see request::activate +-- @see active function client.object.activate(c, args) local new_args = setmetatable({}, {__index = args or {}}) @@ -1454,6 +1455,62 @@ function client.object.activate(c, args) end end +--- Return true if the client is active (has focus). +-- +-- This property is **READ ONLY**. Use `c:activate { context = "myreason" }` +-- to change the focus. +-- +-- The reason for this is that directly setting the focus +-- (which can also be done using `client.focus = c`) will bypass the focus +-- stealing filters. This is easy at first, but as this gets called from more +-- and more places, it quickly become unmanageable. This coding style is +-- recommended for maintainable code: +-- +-- -- Check if a client has focus: +-- if c.active then +-- -- do something +-- end +-- +-- -- Check if there is a active (focused) client: +-- if client.focus ~= nil then +-- -- do something +-- end +-- +-- -- Get the active (focused) client: +-- local c = client.focus +-- +-- -- Set the focus: +-- c:activate { +-- context = "myreason", +-- switch_to_tag = true, +-- } +-- +-- @property active +-- @tparam boolean active +-- @see activate +-- @see request::activate +-- @see awful.ewmh.add_activate_filter + +function client.object.get_active(c) + return capi.client.focus == c +end + +function client.object.set_active(c, value) + if value then + -- Do it, but print an error popup. Setting the focus without a context + -- breaks the filters. This seems a good idea at first, then cause + -- endless pain. QtWidgets also enforces this. + c:activate() + assert(false, "You cannot set `active` directly, use `c:activate()`.") + else + -- Be permissive given the alternative is a bit convoluted. + capi.client.focus = nil + gdebug.print_warning( + "Consider using `client.focus = nil` instead of `c.active = false" + ) + end +end + -- Register standards signals --- The last geometry when client was floating. From ba5385dd40488bc1dbd24e61ed3bff76429e9169 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 10 Nov 2019 18:39:26 -0500 Subject: [PATCH 05/22] client: Update existing code to use `c.active`. --- lib/awful/client.lua | 2 +- lib/awful/titlebar.lua | 4 ++-- lib/awful/widget/tasklist.lua | 4 ++-- tests/test-awful-client.lua | 8 ++++---- tests/test-awful-tag.lua | 2 +- tests/test-current-desktop.lua | 2 +- tests/test-urgent.lua | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 3d17b38b5..9da7b44c6 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -536,7 +536,7 @@ function client.object.move_to_screen(self, s) end s = get_screen(s) if get_screen(self.screen) ~= s then - local sel_is_focused = self == capi.client.focus + local sel_is_focused = self.active self.screen = s screen.focus(s) diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua index 0300c77a8..d1a4070c3 100644 --- a/lib/awful/titlebar.lua +++ b/lib/awful/titlebar.lua @@ -443,7 +443,7 @@ local all_titlebars = setmetatable({}, { __mode = 'k' }) -- Get a color for a titlebar, this tests many values from the array and the theme local function get_color(name, c, args) local suffix = "_normal" - if capi.client.focus == c then + if c.active then suffix = "_focus" end local function get(array) @@ -707,7 +707,7 @@ function titlebar.widget.button(c, name, selector, action) end end local prefix = "normal" - if capi.client.focus == c then + if c.active then prefix = "focus" end if img ~= "" then diff --git a/lib/awful/widget/tasklist.lua b/lib/awful/widget/tasklist.lua index 48e2adf79..3c59d5db1 100644 --- a/lib/awful/widget/tasklist.lua +++ b/lib/awful/widget/tasklist.lua @@ -352,7 +352,7 @@ local function tasklist_label(c, args, tb) end end - local focused = capi.client.focus == c + local focused = c.active -- Handle transient_for: the first parent that does not skip the taskbar -- is considered to be focused, if the real client has skip_taskbar. if not focused and capi.client.focus and capi.client.focus.skip_taskbar @@ -723,7 +723,7 @@ end -- @filterfunction awful.tasklist.filter.focused function tasklist.filter.focused(c, screen) -- Only print client on the same screen as this widget - return get_screen(c.screen) == get_screen(screen) and capi.client.focus == c + return get_screen(c.screen) == get_screen(screen) and c.active end --- Get all the clients in an undefined order. diff --git a/tests/test-awful-client.lua b/tests/test-awful-client.lua index 92c423ff4..8f3db43d6 100644 --- a/tests/test-awful-client.lua +++ b/tests/test-awful-client.lua @@ -133,7 +133,7 @@ table.insert(steps, function() c1, c2 = client.get()[1], client.get()[2] -- This should still be the case - assert(client.focus == c1) + assert(c1.active) c2:emit_signal("request::activate", "i_said_so") @@ -143,7 +143,7 @@ end) -- Check if writing a focus stealing filter works. table.insert(steps, function() -- This should still be the case - assert(client.focus == c2) + assert(c2.active) original_count = #awful.ewmh.generic_activate_filters @@ -158,7 +158,7 @@ end) table.insert(steps, function() -- The request should have been denied - assert(client.focus == c2) + assert(c2.active) -- Test the remove function awful.ewmh.remove_activate_filter(function() end) @@ -171,7 +171,7 @@ table.insert(steps, function() c1:emit_signal("request::activate", "i_said_so") - return client.focus == c1 + return c1.active end) local has_error diff --git a/tests/test-awful-tag.lua b/tests/test-awful-tag.lua index 688ddd48b..14d493edc 100644 --- a/tests/test-awful-tag.lua +++ b/tests/test-awful-tag.lua @@ -64,7 +64,7 @@ local steps = { client.focus = client.get()[1] local c = client.focus - assert(c and client.focus == c) + assert(c and c.active) assert(beautiful.awesome_icon) local t = awful.tag.add("Test", {clients={c}, icon = beautiful.awesome_icon}) diff --git a/tests/test-current-desktop.lua b/tests/test-current-desktop.lua index c47a98478..bb6c6aa8b 100644 --- a/tests/test-current-desktop.lua +++ b/tests/test-current-desktop.lua @@ -76,7 +76,7 @@ local steps = { -- Killing the client means the first selected tag counts function(count) if count == 1 then - assert(client.focus == c) + assert(c.active) c:kill() c = nil return diff --git a/tests/test-urgent.lua b/tests/test-urgent.lua index b36a42710..169c2512c 100644 --- a/tests/test-urgent.lua +++ b/tests/test-urgent.lua @@ -56,7 +56,7 @@ local steps = { assert(#client.get() == 1) local c = client.get()[1] assert(not c.urgent, "Client is not urgent anymore.") - assert(c == client.focus, "Client is focused.") + assert(c.active, "Client is focused.") assert(awful.tag.getproperty(awful.screen.focused().tags[2], "urgent") == false) assert(awful.tag.getproperty(awful.screen.focused().tags[2], "urgent_count") == 0) return true From c10bdc3cfec1c86b11eb929f5e2564babade8206 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 10 Nov 2019 19:42:52 -0500 Subject: [PATCH 06/22] client: Add a property::active signal. --- lib/awful/client.lua | 20 ++++++++++++++++++++ objects/client.c | 8 +++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 9da7b44c6..d713d2ee1 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -1485,6 +1485,26 @@ end -- switch_to_tag = true, -- } -- +-- -- Get notified when a client gets or loses the focus: +-- c:connect_signal("property::active", function(c, is_active) +-- -- do something +-- end) +-- +-- -- Get notified when any client gets or loses the focus: +-- client.connect_signal("property::active", function(c, is_active) +-- -- do something +-- end) +-- +-- -- Get notified when any client gets the focus: +-- client.connect_signal("focus", function(c) +-- -- do something +-- end) +-- +-- -- Get notified when any client loses the focus: +-- client.connect_signal("unfocus", function(c) +-- -- do something +-- end) +-- -- @property active -- @tparam boolean active -- @see activate diff --git a/objects/client.c b/objects/client.c index 84d1d6740..893abf212 100644 --- a/objects/client.c +++ b/objects/client.c @@ -1252,6 +1252,9 @@ client_unfocus_internal(client_t *c) globalconf.focus.client = NULL; luaA_object_push(L, c); + + lua_pushboolean(L, false); + luaA_object_emit_signal(L, -2, "property::active", 1); luaA_object_emit_signal(L, -1, "unfocus", 0); lua_pop(L, 1); } @@ -1369,8 +1372,11 @@ client_focus_update(client_t *c) luaA_object_push(L, c); client_set_urgent(L, -1, false); - if(focused_new) + if(focused_new) { + lua_pushboolean(L, true); + luaA_object_emit_signal(L, -2, "property::active", 1); luaA_object_emit_signal(L, -1, "focus", 0); + } lua_pop(L, 1); From 04c757322c079a8b9f13a51d0dc4c37e5a5b7965 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 10 Nov 2019 20:47:43 -0500 Subject: [PATCH 07/22] client: Turn `rc.lua` logic into a new `request::border` signal. The default `rc.lua` was using the focus/unfocus signals to set the border color along with `awful.rules`. This logic block was no longer aligned with the rest of `rc.lua` since it was the only place where `beautiful` variables where only used by `rc.lua`. On top of this, the new request handler also has extra contexts for the urgent and floating/maximixed use cases. So it can be used by themes to implement much smarter borders than just focus based ones. They were previously limited by the fact most of the (un-monkey-patchable) logic was in `rc.lua`. Note that this commit also shuffle the awful.rules order between the titlebar and the border and changes the tests accordignly. After some consideration, I came to the conclusion the previous behavior was bogus and the fact that the placement tests required to know about the titlebar height is simply a proof of that. The change was required in this commit because since the border is no longer in the default rules, a new buggy edge case surfaced. --- awesomerc.lua | 9 +- docs/05-awesomerc.md.lua | 5 - docs/89-NEWS.md | 8 + docs/common/client_theme.ldoc | 492 ++++++++++++++++++++++++++++ lib/awful/client.lua | 52 ++- lib/awful/client/urgent.lua | 17 +- lib/awful/ewmh.lua | 153 +++++++++ lib/awful/menu.lua | 4 +- lib/awful/placement.lua | 2 +- lib/awful/rules.lua | 12 +- lib/awful/tooltip.lua | 2 +- lib/awful/widget/calendar_popup.lua | 2 +- lib/beautiful/init.lua | 17 +- lib/naughty/core.lua | 2 +- lib/naughty/notification.lua | 2 +- lib/wibox/init.lua | 23 +- objects/client.c | 42 +-- objects/window.c | 6 +- tests/examples/shims/client.lua | 2 +- tests/examples/shims/drawin.lua | 2 +- tests/test-awful-placement.lua | 20 +- tests/test-awful-rules.lua | 4 +- tests/test-focus.lua | 4 +- tests/test-geometry.lua | 4 +- themes/default/theme.lua | 10 +- themes/gtk/theme.lua | 6 +- themes/sky/theme.lua | 6 +- themes/xresources/theme.lua | 6 +- themes/zenburn/theme.lua | 6 +- 29 files changed, 810 insertions(+), 110 deletions(-) create mode 100644 docs/common/client_theme.ldoc diff --git a/awesomerc.lua b/awesomerc.lua index f60186497..13959fb10 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -441,9 +441,7 @@ awful.rules.rules = { -- @DOC_GLOBAL_RULE@ -- All clients will match this rule. { rule = { }, - properties = { border_width = beautiful.border_width, - border_color = beautiful.border_normal, - focus = awful.client.focus.filter, + properties = { focus = awful.client.focus.filter, raise = true, screen = awful.screen.preferred, placement = awful.placement.no_overlap+awful.placement.no_offscreen @@ -540,8 +538,3 @@ end) client.connect_signal("mouse::enter", function(c) c:activate { context = "mouse_enter", raise = false } end) - --- @DOC_BORDER@ -client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end) -client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end) - diff --git a/docs/05-awesomerc.md.lua b/docs/05-awesomerc.md.lua index a1d0c5bd0..dee0ab161 100644 --- a/docs/05-awesomerc.md.lua +++ b/docs/05-awesomerc.md.lua @@ -221,11 +221,6 @@ sections.DOC_CSD_TITLEBARS = [[ See `client.requests_no_titlebar` for more details. ]] - -sections.DOC_BORDER = [[ -   -]] - -- Ask ldoc to generate links local function add_links(line) diff --git a/docs/89-NEWS.md b/docs/89-NEWS.md index 73595bf79..7089f99fa 100644 --- a/docs/89-NEWS.md +++ b/docs/89-NEWS.md @@ -62,6 +62,14 @@ This document was last updated at commit v4.3-197-g9085ed631. objects. If you used these low level APIs to add keys and buttons dynamically, please migrate your code to the corresponding `:append_` and `:remove_` client methods. + * `beautiful.border_width` and `beautiful.border_color` are now honored even + when the part related to borders is removed from `rc.lua`. Set them + appropriately in your theme or disconnect the default `request::border` + handler. + * The order by which the client rules compute the geometry have changed + slightly. The border is now applied before the titlebar offset. This should + not affect most users unless you had mitigated the bug it fixes by adding + the titlebar offset in your rules. # Awesome window manager framework version 4.3 changes diff --git a/docs/common/client_theme.ldoc b/docs/common/client_theme.ldoc new file mode 100644 index 000000000..762138577 --- /dev/null +++ b/docs/common/client_theme.ldoc @@ -0,0 +1,492 @@ +*/ + + +/** + * The fallback border color when the client is floating. + * + * @beautiful beautiful.border_color_floating + * @param color + * @see request::border + * @see beautiful.border_color_floating_active + * @see beautiful.border_color_floating_normal + * @see beautiful.border_color_floating_urgent + * @see beautiful.border_color_floating_new + */ + +/** + * The fallback border color when the client is mazimized. + * + * @beautiful beautiful.border_color_mazimized + * @param color + * @see request::border + * @see beautiful.border_color_maximized_active + * @see beautiful.border_color_maximized_normal + * @see beautiful.border_color_maximized_urgent + * @see beautiful.border_color_maximized_new + */ + +/** + * The border color when the client is active. + * + * @beautiful beautiful.border_color_active + * @param color + * @see request::border + */ + +/** + * The border color when the client is not active. + * + * @beautiful beautiful.border_color_normal + * @param color + * @see request::border + */ + +/** + * The border color when the client has the urgent property set. + * + * @beautiful beautiful.border_color_urgent + * @param color + * @see request::border + */ + +/** + * The border color when the client is not active and new. + * + * @beautiful beautiful.border_color_new + * @param color + * @see request::border + */ + +/** + * The border color when the (floating) client is active. + * + * @beautiful beautiful.border_color_floating_active + * @param color + * @see request::border + */ + +/** + * The border color when the (floating) client is not active. + * + * @beautiful beautiful.border_color_floating_normal + * @param color + * @see request::border + */ + +/** + * The border color when the (floating) client has the urgent property set. + * + * @beautiful beautiful.border_color_floating_urgent + * @param color + * @see request::border + */ + +/** + * The border color when the (floating) client is not active and new. + * + * @beautiful beautiful.border_color_floating_new + * @param color + * @see request::border + */ + +/** + * The border color when the (maximized) client is active. + * + * @beautiful beautiful.border_color_maximized_active + * @param color + * @see request::border + */ + +/** + * The border color when the (maximized) client is not active. + * + * @beautiful beautiful.border_color_maximized_normal + * @param color + * @see request::border + */ + +/** + * The border color when the (maximized) client has the urgent property set. + * + * @beautiful beautiful.border_color_maximized_urgent + * @param color + * @see request::border + */ + +/** + * The border color when the (maximized) client is not active and new. + * + * @beautiful beautiful.border_color_maximized_new + * @param color + * @see request::border + */ + +/** + * The border color when the (fullscreen) client is active. + * + * @beautiful beautiful.border_color_fullscreen_active + * @param color + * @see request::border + */ + +/** + * The border color when the (fullscreen) client is not active. + * + * @beautiful beautiful.border_color_fullscreen_normal + * @param color + * @see request::border + */ + +/** + * The border color when the (fullscreen) client has the urgent property set. + * + * @beautiful beautiful.border_color_fullscreen_urgent + * @param color + * @see request::border + */ + +/** + * The border color when the (fullscreen) client is not active and new. + * + * @beautiful beautiful.border_color_fullscreen_new + * @param color + * @see request::border + */ + +/** + * The fallback border width when nothing else is set. + * + * @beautiful beautiful.border_width + * @param integer + * @see request::border + * @see beautiful.border_width_floating + * @see beautiful.border_width_mazimized + * @see beautiful.border_width_floating_active + * @see beautiful.border_width_floating_normal + * @see beautiful.border_width_floating_urgent + * @see beautiful.border_width_floating_new + * @see beautiful.border_width_maximized_active + * @see beautiful.border_width_maximized_normal + * @see beautiful.border_width_maximized_urgent + * @see beautiful.border_width_maximized_new + */ + +/** + * The fallback border width when the client is floating. + * + * @beautiful beautiful.border_width_floating + * @param integer + * @see request::border + * @see beautiful.border_width_floating_active + * @see beautiful.border_width_floating_normal + * @see beautiful.border_width_floating_urgent + * @see beautiful.border_width_floating_new + */ + +/** + * The fallback border width when the client is mazimized. + * + * @beautiful beautiful.border_width_mazimized + * @param integer + * @see request::border + * @see beautiful.border_width_maximized_active + * @see beautiful.border_width_maximized_normal + * @see beautiful.border_width_maximized_urgent + * @see beautiful.border_width_maximized_new + */ + +/** + * The client border width for the normal clients. + * + * @beautiful beautiful.border_width_normal + * @param integer + * @see request::border + */ + +/** + * The client border width for the active client. + * + * @beautiful beautiful.border_width_active + * @param integer + * @see request::border + */ + +/** + * The client border width for the urgent clients. + * + * @beautiful beautiful.border_width_urgent + * @param integer + * @see request::border + */ + +/** + * The client border width for the new clients. + * + * @beautiful beautiful.border_width_new + * @param integer + * @see request::border + */ + +/** + * The client border width for the normal floating clients. + * + * @beautiful beautiful.border_width_floating_normal + * @param integer + * @see request::border + */ + +/** + * The client border width for the active floating client. + * + * @beautiful beautiful.border_width_floating_active + * @param integer + * @see request::border + */ + +/** + * The client border width for the urgent floating clients. + * + * @beautiful beautiful.border_width_floating_urgent + * @param integer + * @see request::border + */ + +/** + * The client border width for the new floating clients. + * + * @beautiful beautiful.border_width_floating_new + * @param integer + * @see request::border + */ + +/** + * The client border width for the normal maximized clients. + * + * @beautiful beautiful.border_width_maximized_normal + * @param integer + * @see request::border + */ + +/** + * The client border width for the active maximized client. + * + * @beautiful beautiful.border_width_maximized_active + * @param integer + * @see request::border + */ + +/** + * The client border width for the urgent maximized clients. + * + * @beautiful beautiful.border_width_maximized_urgent + * @param integer + * @see request::border + */ + +/** + * The client border width for the new maximized clients. + * + * @beautiful beautiful.border_width_maximized_new + * @param integer + * @see request::border + */ + +/** + * The client border width for the normal fullscreen clients. + * + * @beautiful beautiful.border_width_fullscreen_normal + * @param integer + * @see request::border + */ + +/** + * The client border width for the active fullscreen client. + * + * @beautiful beautiful.border_width_fullscreen_active + * @param integer + * @see request::border + */ + +/** + * The client border width for the urgent fullscreen clients. + * + * @beautiful beautiful.border_width_fullscreen_urgent + * @param integer + * @see request::border + */ + +/** + * The client border width for the new fullscreen clients. + * + * @beautiful beautiful.border_width_fullscreen_new + * @param integer + * @see request::border + */ + +/** + * The client opacity for the normal clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_normal + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the active client. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_active + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the urgent clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_urgent + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the new clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_new + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the normal floating clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_floating_normal + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the active floating client. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_floating_active + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the urgent floating clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_floating_urgent + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the new floating clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_floating_new + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the normal maximized clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_maximized_normal + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the active maximized client. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_maximized_active + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the urgent maximized clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_maximized_urgent + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the new maximized clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_maximized_new + * @param[opt=1] number + * @see request::border + */ +/** + * The client opacity for the normal fullscreen clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_fullscreen_normal + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the active fullscreen client. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_fullscreen_active + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the urgent fullscreen clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_fullscreen_urgent + * @param[opt=1] number + * @see request::border + */ + +/** + * The client opacity for the new fullscreen clients. + * + * A number between 0 and 1. + * + * @beautiful beautiful.opacity_fullscreen_new + * @param[opt=1] number + * @see request::border + */ + +/** + * The marked clients border color. + * Note that only solid colors are supported. + * @beautiful beautiful.border_marked + * @param color + */ + +/* diff --git a/lib/awful/client.lua b/lib/awful/client.lua index d713d2ee1..530415400 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -688,6 +688,12 @@ function client.object.set_floating(c, s) c:geometry(client.property.get(c, "floating_geometry")) end c.screen = scr + + if s then + c:emit_signal("request::border", "floating", {}) + else + c:emit_signal("request::border", (c.active and "" or "in").."active", {}) + end end end @@ -834,6 +840,17 @@ local function update_implicitly_floating(c) if cur ~= new then client.property.set(c, "_implicitly_floating", new) c:emit_signal("property::floating") + + -- Don't send the border signals as they would be sent twice (with this + -- one having the wrong context). There is some `property::` signal + -- sent before `request::manage`. + if client.property.get(c, "_border_init") then + if cur then + c:emit_signal("request::border", "floating", {}) + else + c:emit_signal("request::border", (c.active and "" or "in").."active", {}) + end + end end end @@ -1389,6 +1406,19 @@ function client.object.set_shape(self, shape) set_shape(self) end +-- Proxy those properties to decorate their accessors with an extra flag to +-- define when they are set by the user. This allows to "manage" the value of +-- those properties internally until they are manually overridden. +for _, prop in ipairs { "border_width", "border_color", "opacity" } do + client.object["get_"..prop] = function(self) + return self["_"..prop] + end + client.object["set_"..prop] = function(self, value) + self._private["_user_"..prop] = true + self["_"..prop] = value + end +end + --- Activate (focus) a client. -- -- This method is the correct way to focus a client. While @@ -1531,7 +1561,9 @@ function client.object.set_active(c, value) end end --- Register standards signals +capi.client.connect_signal("property::active", function(c) + c:emit_signal("request::border", (c.active and "" or "in").."active", {}) +end) --- The last geometry when client was floating. -- @signal property::floating_geometry @@ -1547,6 +1579,21 @@ end --- The client unmarked signal. -- @deprecatedsignal unmarked +--- Emited when the border client might need to be update. +-- +-- The context are: +-- +-- * **added**: When a new client is created. +-- * **active**: When client gains the focus (or stop being urgent/floating +-- but is active). +-- * **inactive**: When client loses the focus (or stop being urgent/floating +-- and is not active. +-- * **urgent**: When a client becomes urgent. +-- * **floating**: When the floating or maximization state changes. +-- +-- @signal request::border +-- @see awful.ewmh.update_border + -- Add clients during startup to focus history. -- This used to happen through ewmh.activate, but that only handles visible -- clients now. @@ -1561,6 +1608,9 @@ capi.client.connect_signal("request::manage", function (c) if awesome.startup then client.focus.history.add(c) end + + client.property.set(c, "_border_init", true) + c:emit_signal("request::border", "added", {}) end) capi.client.connect_signal("request::unmanage", client.focus.history.delete) diff --git a/lib/awful/client/urgent.lua b/lib/awful/client/urgent.lua index 265a25fb3..f8f188284 100644 --- a/lib/awful/client/urgent.lua +++ b/lib/awful/client/urgent.lua @@ -63,9 +63,24 @@ end -- @client c The client object. -- @param prop The property which is updated. function urgent.add(c, prop) - if type(c) == "client" and prop == "urgent" and c.urgent then + assert( + c.urgent ~= nil, + "`awful.client.urgent.add()` takes a client as first parameter" + ) + + if prop == "urgent" and c.urgent then table.insert(data, c) end + + if c.urgent then + c:emit_signal("request::border", "urgent", {}) + else + c:emit_signal( + "request::border", + (c.active and "" or "in").."active", + {} + ) + end end --- Remove client from urgent stack. diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua index 842a05508..b8a2d3b78 100644 --- a/lib/awful/ewmh.lua +++ b/lib/awful/ewmh.lua @@ -17,6 +17,7 @@ local asuit = require("awful.layout.suit") local beautiful = require("beautiful") local alayout = require("awful.layout") local atag = require("awful.tag") +local gdebug = require("gears.debug") local ewmh = { generic_activate_filters = {}, @@ -453,6 +454,158 @@ ewmh.add_activate_filter(function(c) end end, "mouse_enter") +--- The default client `request::border` handler. +-- +-- To replace this handler with your own, use: +-- +-- client.disconnect_signal("request::border", awful.ewmh.update_border) +-- +-- The default implementation chooses from dozens beautiful theme variables +-- depending if the client is tiled, floating, maximized and then from its state +-- (urgent, new, active, normal) +-- +-- @signalhandler awful.ewmh.update_border +-- @usebeautiful beautiful.border_color_active +-- @usebeautiful beautiful.border_color_normal +-- @usebeautiful beautiful.border_color_new +-- @usebeautiful beautiful.border_color_urgent +-- @usebeautiful beautiful.border_color_floating +-- @usebeautiful beautiful.border_color_floating_active +-- @usebeautiful beautiful.border_color_floating_normal +-- @usebeautiful beautiful.border_color_floating_new +-- @usebeautiful beautiful.border_color_floating_urgent +-- @usebeautiful beautiful.border_color_maximized +-- @usebeautiful beautiful.border_color_maximized_active +-- @usebeautiful beautiful.border_color_maximized_normal +-- @usebeautiful beautiful.border_color_maximized_new +-- @usebeautiful beautiful.border_color_maximized_urgent +-- @usebeautiful beautiful.border_color_fullscreen +-- @usebeautiful beautiful.border_color_fullscreen_active +-- @usebeautiful beautiful.border_color_fullscreen_normal +-- @usebeautiful beautiful.border_color_fullscreen_new +-- @usebeautiful beautiful.border_color_fullscreen_urgent +-- @usebeautiful beautiful.border_width_active +-- @usebeautiful beautiful.border_width_normal +-- @usebeautiful beautiful.border_width_new +-- @usebeautiful beautiful.border_width_urgent +-- @usebeautiful beautiful.border_width_floating +-- @usebeautiful beautiful.border_width_floating_active +-- @usebeautiful beautiful.border_width_floating_normal +-- @usebeautiful beautiful.border_width_floating_new +-- @usebeautiful beautiful.border_width_floating_urgent +-- @usebeautiful beautiful.border_width_maximized +-- @usebeautiful beautiful.border_width_maximized_active +-- @usebeautiful beautiful.border_width_maximized_normal +-- @usebeautiful beautiful.border_width_maximized_new +-- @usebeautiful beautiful.border_width_maximized_urgent +-- @usebeautiful beautiful.border_width_fullscreen +-- @usebeautiful beautiful.border_width_fullscreen_active +-- @usebeautiful beautiful.border_width_fullscreen_normal +-- @usebeautiful beautiful.border_width_fullscreen_new +-- @usebeautiful beautiful.border_width_fullscreen_urgent +-- @usebeautiful beautiful.opacity_floating +-- @usebeautiful beautiful.opacity_floating_active +-- @usebeautiful beautiful.opacity_floating_normal +-- @usebeautiful beautiful.opacity_floating_new +-- @usebeautiful beautiful.opacity_floating_urgent +-- @usebeautiful beautiful.opacity_maximized +-- @usebeautiful beautiful.opacity_maximized_active +-- @usebeautiful beautiful.opacity_maximized_normal +-- @usebeautiful beautiful.opacity_maximized_new +-- @usebeautiful beautiful.opacity_maximized_urgent +-- @usebeautiful beautiful.opacity_fullscreen +-- @usebeautiful beautiful.opacity_fullscreen_active +-- @usebeautiful beautiful.opacity_fullscreen_normal +-- @usebeautiful beautiful.opacity_fullscreen_new +-- @usebeautiful beautiful.opacity_fullscreen_urgent +-- @usebeautiful beautiful.opacity_active +-- @usebeautiful beautiful.opacity_normal +-- @usebeautiful beautiful.opacity_new +-- @usebeautiful beautiful.opacity_urgent + +function ewmh.update_border(c, context) + local suffix, fallback = "", "" + + -- Add the sub-namespace. + if c.fullscreen then + suffix, fallback = "_fullscreen", "_fullscreen" + elseif c.maximized then + suffix, fallback = "_maximized", "_maximized" + elseif c.floating then + suffix, fallback = "_floating", "_floating" + end + + -- Add the state suffix. + if c.urgent then + suffix = suffix .. "_urgent" + elseif c.active then + suffix = suffix .. "_active" + elseif context == "added" then + suffix = suffix .. "_new" + else + suffix = suffix .. "_normal" + end + + if not c._private._user_border_width then + c._border_width = beautiful["border_width"..suffix] + or beautiful["border_width"..fallback] + or beautiful.border_width + end + + if not c._private._user_border_color then + -- First, check marked clients. This is a concept that should probably + -- never have been added to the core. The documentation claims it works, + -- even if it has been broken for 90% of AwesomeWM releases ever since + -- it was added. + if c.marked and beautiful.border_marked then + c._border_color = beautiful.border_marked + return + end + + local tv = beautiful["border_color"..suffix] + + if fallback ~= "" and not tv then + tv = beautiful["border_color"..fallback] + end + + -- The old theme variable did not have "color" in its name. + if (not tv) and beautiful.border_normal and (not c.active) then + gdebug.deprecate( + "Use `beautiful.border_color_normal` instead of `beautiful.border_normal`", + {deprecated_in=5} + ) + tv = beautiful.border_normal + elseif (not tv) and beautiful.border_focus then + gdebug.deprecate( + "Use `beautiful.border_color_active` instead of `beautiful.border_focus`", + {deprecated_in=5} + ) + tv = beautiful.border_focus + end + + if not tv then + tv = beautiful.border_color + end + + if tv then + c._border_color = tv + end + end + + if not c._private._user_opacity then + local tv = beautiful["opacity"..suffix] + + if fallback ~= "" and not tv then + tv = beautiful["opacity"..fallback] + end + + if tv then + c._opacity = tv + end + end +end + +client.connect_signal("request::border", ewmh.update_border) client.connect_signal("request::activate", ewmh.activate) client.connect_signal("request::tag", ewmh.tag) client.connect_signal("request::urgent", ewmh.urgent) diff --git a/lib/awful/menu.lua b/lib/awful/menu.lua index b17964a4d..369e63bd7 100644 --- a/lib/awful/menu.lua +++ b/lib/awful/menu.lua @@ -119,8 +119,8 @@ local function load_theme(a, b) local fallback = beautiful.get() if a.reset then b = fallback end if a == "reset" then a = fallback end - ret.border = a.border_color or b.menu_border_color or b.border_normal or - fallback.menu_border_color or fallback.border_normal + ret.border = a.border_color or b.menu_border_color or b.border_color_normal or + fallback.menu_border_color or fallback.border_color_normal ret.border_width= a.border_width or b.menu_border_width or b.border_width or fallback.menu_border_width or fallback.border_width or dpi(0) ret.fg_focus = a.fg_focus or b.menu_fg_focus or b.fg_focus or diff --git a/lib/awful/placement.lua b/lib/awful/placement.lua index 18f204b7d..354c81a41 100644 --- a/lib/awful/placement.lua +++ b/lib/awful/placement.lua @@ -708,7 +708,7 @@ local function get_relative_regions(geo, mode, is_absolute) -- Detect various types of geometry table and (try) to get rid of the -- differences so the code below don't have to care anymore. if geo.drawin then - bw, dgeo = geo.drawin.border_width, geo.drawin:geometry() + bw, dgeo = geo.drawin._border_width, geo.drawin:geometry() elseif geo.drawable and geo.drawable.get_wibox then bw = geo.drawable.get_wibox().border_width dgeo = geo.drawable.get_wibox():geometry() diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index 9a659c140..ba38cac82 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -538,6 +538,12 @@ crules._execute = function(_, c, props, callbacks) props.keys = props.keys or keys props.buttons = props.buttons or btns + -- Border width will also cause geometry related properties to fail + if props.border_width then + c.border_width = type(props.border_width) == "function" and + props.border_width(c, props) or props.border_width + end + -- This has to be done first, as it will impact geometry related props. if props.titlebars_enabled and (type(props.titlebars_enabled) ~= "function" or props.titlebars_enabled(c,props)) then @@ -545,12 +551,6 @@ crules._execute = function(_, c, props, callbacks) c._request_titlebars_called = true end - -- Border width will also cause geometry related properties to fail - if props.border_width then - c.border_width = type(props.border_width) == "function" and - props.border_width(c, props) or props.border_width - end - -- Size hints will be re-applied when setting width/height unless it is -- disabled first if props.size_hints_honor ~= nil then diff --git a/lib/awful/tooltip.lua b/lib/awful/tooltip.lua index 81674a3bc..b75a758be 100644 --- a/lib/awful/tooltip.lua +++ b/lib/awful/tooltip.lua @@ -694,7 +694,7 @@ function tooltip.new(args) or beautiful.bg_focus or "#ffcb60" local border_width = args.border_width or beautiful.tooltip_border_width or 0 local border_color = args.border_color or beautiful.tooltip_border_color - or beautiful.border_normal or "#ffcb60" + or beautiful.border_color_normal or "#ffcb60" -- Set wibox default properties self.wibox_properties = { diff --git a/lib/awful/widget/calendar_popup.lua b/lib/awful/widget/calendar_popup.lua index 3e3479d8d..3c9fa3fb0 100644 --- a/lib/awful/widget/calendar_popup.lua +++ b/lib/awful/widget/calendar_popup.lua @@ -131,7 +131,7 @@ local function parse_cell_options(cell, args) elseif prop == 'border_width' then default = beautiful.border_width or 0 elseif prop == 'border_color' then - default = beautiful.border_normal or beautiful.fg_normal + default = beautiful.border_color_normal or beautiful.fg_normal end -- Get default diff --git a/lib/beautiful/init.lua b/lib/beautiful/init.lua index 7731eddc5..9db96f254 100644 --- a/lib/beautiful/init.lua +++ b/lib/beautiful/init.lua @@ -132,23 +132,12 @@ local active_font -- @beautiful beautiful.useless_gap -- @param[opt=0] number ---- The client border width. +--- The fallback border width. -- @beautiful beautiful.border_width -- @param number ---- The default clients border color. --- Note that only solid colors are supported. --- @beautiful beautiful.border_normal --- @param color - ---- The focused client border color. --- Note that only solid colors are supported. --- @beautiful beautiful.border_focus --- @param color - ---- The marked clients border color. --- Note that only solid colors are supported. --- @beautiful beautiful.border_marked +--- The fallback border color. +-- @beautiful beautiful.border_color -- @param color --- The wallpaper path. diff --git a/lib/naughty/core.lua b/lib/naughty/core.lua index e65704b47..92342f9e3 100644 --- a/lib/naughty/core.lua +++ b/lib/naughty/core.lua @@ -630,7 +630,7 @@ end -- @string[opt=`beautiful.notification_fg` or `beautiful.fg_focus` or `'#ffffff'`] args.fg Foreground color. -- @string[opt=`beautiful.notification_fg` or `beautiful.bg_focus` or `'#535d6c'`] args.bg Background color. -- @int[opt=`beautiful.notification_border_width` or 1] args.border_width Border width. --- @string[opt=`beautiful.notification_border_color` or `beautiful.border_focus` or `'#535d6c'`] args.border_color Border color. +-- @string[opt=`beautiful.notification_border_color` or `beautiful.border_color_active` or `'#535d6c'`] args.border_color Border color. -- @tparam[opt=`beautiful.notification_shape`] gears.shape args.shape Widget shape. -- @tparam[opt=`beautiful.notification_opacity`] gears.opacity args.opacity Widget opacity. -- @tparam[opt=`beautiful.notification_margin`] gears.margin args.margin Widget margin. diff --git a/lib/naughty/notification.lua b/lib/naughty/notification.lua index f06d21c8d..5edb45e70 100644 --- a/lib/naughty/notification.lua +++ b/lib/naughty/notification.lua @@ -777,7 +777,7 @@ end -- @string[opt=`beautiful.notification_fg` or `beautiful.bg_focus` or `'#535d6c'`] args.bg Background color. -- @int[opt=`beautiful.notification_border_width` or 1] args.border_width Border width. -- @string[opt=`beautiful.notification_border_color` or --- `beautiful.border_focus` or `'#535d6c'`] args.border_color Border color. +-- `beautiful.border_color_active` or `'#535d6c'`] args.border_color Border color. -- @tparam[opt=`beautiful.notification_shape`] gears.shape args.shape Widget shape. -- @tparam[opt=`beautiful.notification_opacity`] gears.opacity args.opacity Widget opacity. -- @tparam[opt=`beautiful.notification_margin`] gears.margin args.margin Widget margin. diff --git a/lib/wibox/init.lua b/lib/wibox/init.lua index 6bf2b85fd..46f72d49c 100644 --- a/lib/wibox/init.lua +++ b/lib/wibox/init.lua @@ -221,7 +221,20 @@ function wibox:get_children_by_id(name) return {} end -for _, k in pairs{ "struts", "geometry", "get_xproperty", "set_xproperty" } do +-- Proxy those properties to decorate their accessors with an extra flag to +-- define when they are set by the user. This allows to "manage" the value of +-- those properties internally until they are manually overridden. +for _, prop in ipairs { "border_width", "border_color", "opacity" } do + wibox["get_"..prop] = function(self) + return self["_"..prop] + end + wibox["set_"..prop] = function(self, value) + self._private["_user_"..prop] = true + self["_"..prop] = value + end +end + +for _, k in ipairs{ "struts", "geometry", "get_xproperty", "set_xproperty" } do wibox[k] = function(self, ...) return self.drawin[k](self.drawin, ...) end @@ -362,6 +375,14 @@ local function new(args) ret.shape = args.shape end + if args.border_width then + ret.border_width = args.border_width + end + + if args.border_color then + ret.border_color = args.border_color + end + if args.input_passthrough then ret.input_passthrough = args.input_passthrough end diff --git a/objects/client.c b/objects/client.c index 893abf212..1b10f8a85 100644 --- a/objects/client.c +++ b/objects/client.c @@ -568,20 +568,19 @@ * The client border width. * @property border_width * @param integer + * @propemits false false + * @see request::border */ /** * The client border color. * - * **Signal:** - * - * * *property::border\_color* - * - * @see gears.color - * * @property border_color - * @param pattern Any string, gradients and patterns will be converted to a + * @param color Any string, gradients and patterns will be converted to a * cairo pattern. + * @propemits false false + * @see request::border + * @see gears.color */ /** @@ -593,6 +592,8 @@ * * @property urgent * @param boolean + * @propemits false false + * @see request::border */ /** @@ -619,6 +620,8 @@ * * @property opacity * @param number Between 0 (transparent) to 1 (opaque) + * @propemits false false + * @see request::border */ /** @@ -673,6 +676,8 @@ * * @property maximized * @param boolean + * @propemits false false + * @see request::border */ /** @@ -976,27 +981,6 @@ * @see client.geometry */ -/** - * The border color when the client is focused. - * - * @beautiful beautiful.border_focus - * @param string - */ - -/** - * The border color when the client is not focused. - * - * @beautiful beautiful.border_normal - * @param string - */ - -/** - * The client border width. - * - * @beautiful beautiful.border_width - * @param integer - */ - /** Return client struts (reserved space at the edge of the screen). * * @param struts A table with new strut values, or none. @@ -4095,4 +4079,6 @@ client_class_setup(lua_State *L) /* @DOC_cobject_COMMON@ */ +/* @DOC_client_theme_COMMON@ */ + // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/objects/window.c b/objects/window.c index 623cdbb6e..440970f3d 100644 --- a/objects/window.c +++ b/objects/window.c @@ -530,15 +530,15 @@ window_class_setup(lua_State *L) NULL, (lua_class_propfunc_t) luaA_window_get_window, NULL); - luaA_class_add_property(&window_class, "opacity", + luaA_class_add_property(&window_class, "_opacity", (lua_class_propfunc_t) luaA_window_set_opacity, (lua_class_propfunc_t) luaA_window_get_opacity, (lua_class_propfunc_t) luaA_window_set_opacity); - luaA_class_add_property(&window_class, "border_color", + luaA_class_add_property(&window_class, "_border_color", (lua_class_propfunc_t) luaA_window_set_border_color, (lua_class_propfunc_t) luaA_window_get_border_color, (lua_class_propfunc_t) luaA_window_set_border_color); - luaA_class_add_property(&window_class, "border_width", + luaA_class_add_property(&window_class, "_border_width", (lua_class_propfunc_t) luaA_window_set_border_width, (lua_class_propfunc_t) luaA_window_get_border_width, (lua_class_propfunc_t) luaA_window_set_border_width); diff --git a/tests/examples/shims/client.lua b/tests/examples/shims/client.lua index c425c5379..f554b76bd 100644 --- a/tests/examples/shims/client.lua +++ b/tests/examples/shims/client.lua @@ -70,7 +70,7 @@ function client.gen_fake(args) ret.type = "normal" ret.valid = true ret.size_hints = {} - ret.border_width = 1 + ret._border_width = 1 ret.icon_sizes = {{16,16}} ret.name = "Example Client" ret._private._struts = { top = 0, right = 0, left = 0, bottom = 0 } diff --git a/tests/examples/shims/drawin.lua b/tests/examples/shims/drawin.lua index 61ca495c8..8eb5d1bfe 100644 --- a/tests/examples/shims/drawin.lua +++ b/tests/examples/shims/drawin.lua @@ -15,7 +15,7 @@ local function new_drawin(_, args) ret.y=0 ret.width=1 ret.height=1 - ret.border_width=0 + ret._border_width=0 ret.ontop = false ret.below = false ret.above = false diff --git a/tests/test-awful-placement.lua b/tests/test-awful-placement.lua index 7761b3798..6aedf95c3 100644 --- a/tests/test-awful-placement.lua +++ b/tests/test-awful-placement.lua @@ -1,6 +1,4 @@ local awful = require("awful") -local gears = require("gears") -local beautiful = require("beautiful") local test_client = require("_client") local runner = require("_runner") @@ -14,8 +12,9 @@ end local tests = {} -local tb_height = gears.math.round(beautiful.get_font_height() * 1.5) -local border_width = beautiful.border_width +-- Set it to something different than the default to make sure it doesn't change +-- due to some request::border. +local border_width = 3 local class = "test-awful-placement" local rule = { @@ -24,6 +23,7 @@ local rule = { }, properties = { floating = true, + border_width = border_width, placement = awful.placement.no_overlap + awful.placement.no_offscreen } } @@ -40,7 +40,7 @@ end local function default_test(c, geometry) check_geometry(c, geometry.expected_x, geometry.expected_y, geometry.expected_width or geometry.width, - geometry.expected_height or (geometry.height + tb_height)) + geometry.expected_height or (geometry.height)) return true end @@ -242,7 +242,7 @@ for _, tag_num in ipairs{1, 2, 3} do width = wa.width - 50, height = 100, expected_x = wa.x, - expected_y = wa.y + tb_height + 2*border_width + 100 + expected_y = wa.y + 2*border_width + 100 } end } @@ -258,7 +258,7 @@ for _, tag_num in ipairs{1, 2, 3} do width = wa.width - 10, height = wa.height - 50, expected_x = wa.x, - expected_y = wa.y + 50 - 2*border_width - tb_height + expected_y = (wa.y + wa.height) - (wa.height - 50 + 2*border_width) } end } @@ -270,7 +270,7 @@ for _, tag_num in ipairs{1, 2, 3} do return { width = wa.width - 10, height = wa.height - 50, - expected_x = wa.x + 10 - 2*border_width, + expected_x = (wa.x + wa.width) - (wa.width - 10 + 2*border_width), expected_y = wa.y } end @@ -283,8 +283,8 @@ for _, tag_num in ipairs{1, 2, 3} do return { width = wa.width - 10, height = wa.height - 50, - expected_x = wa.x + 10 - 2*border_width, - expected_y = wa.y + 50 - 2*border_width - tb_height + expected_x = (wa.x + wa.width ) - (wa.width - 10 + 2*border_width), + expected_y = (wa.y + wa.height) - (wa.height - 50 + 2*border_width) } end } diff --git a/tests/test-awful-rules.lua b/tests/test-awful-rules.lua index 24c61c7e4..2482363da 100644 --- a/tests/test-awful-rules.lua +++ b/tests/test-awful-rules.lua @@ -1,6 +1,5 @@ local awful = require("awful") local gears = require("gears") -local beautiful = require("beautiful") local test_client = require("_client") local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) @@ -10,7 +9,6 @@ local message_printed = false -- Magic table to store tests local tests = {} -local tb_height = gears.math.round(beautiful.get_font_height() * 1.5) -- local border_width = beautiful.border_width -- Detect "request::manage" race conditions @@ -74,7 +72,7 @@ test_rule { -- The size should not have changed local geo = get_client_by_class(class):geometry() - assert(geo.width == 100 and geo.height == 100+tb_height) + assert(geo.width == 100 and geo.height == 100) return true end diff --git a/tests/test-focus.lua b/tests/test-focus.lua index e7fa09123..5a2fe90d8 100644 --- a/tests/test-focus.lua +++ b/tests/test-focus.lua @@ -5,8 +5,8 @@ local runner = require("_runner") local awful = require("awful") local beautiful = require("beautiful") -beautiful.border_normal = "#0000ff" -beautiful.border_focus = "#00ff00" +beautiful.border_color_normal = "#0000ff" +beautiful.border_color_active = "#00ff00" client.connect_signal("focus", function(c) c.border_color = "#ff0000" diff --git a/tests/test-geometry.lua b/tests/test-geometry.lua index c90d1b9d5..a97491a88 100644 --- a/tests/test-geometry.lua +++ b/tests/test-geometry.lua @@ -17,7 +17,7 @@ awful.rules.rules = { y = 0, width = 100, height = 100, - border_color = beautiful.border_normal + border_color = beautiful.border_color_normal } } } @@ -103,7 +103,7 @@ local steps = { function() local c = client.get()[1] - assert(not pcall(function() c.border_width = -2000 end)) + assert(not pcall(function() c._border_width = -2000 end)) assert(c.border_width==0) c.border_width = 125 diff --git a/themes/default/theme.lua b/themes/default/theme.lua index b0be48012..fd9f9c43c 100644 --- a/themes/default/theme.lua +++ b/themes/default/theme.lua @@ -24,11 +24,11 @@ theme.fg_focus = "#ffffff" theme.fg_urgent = "#ffffff" theme.fg_minimize = "#ffffff" -theme.useless_gap = dpi(0) -theme.border_width = dpi(1) -theme.border_normal = "#000000" -theme.border_focus = "#535d6c" -theme.border_marked = "#91231c" +theme.useless_gap = dpi(0) +theme.border_width = dpi(1) +theme.border_color_normal = "#000000" +theme.border_color_active = "#535d6c" +theme.border_marked = "#91231c" -- There are other variable sets -- overriding the default one when diff --git a/themes/gtk/theme.lua b/themes/gtk/theme.lua index ca31be8f0..392791ff3 100644 --- a/themes/gtk/theme.lua +++ b/themes/gtk/theme.lua @@ -120,9 +120,9 @@ theme.fg_minimize = mix(theme.wibar_fg, theme.wibar_bg, 0.9) theme.bg_systray = theme.wibar_bg -theme.border_normal = theme.gtk.wm_border_unfocused_color -theme.border_focus = theme.gtk.wm_border_focused_color -theme.border_marked = theme.gtk.success_color +theme.border_color_normal = theme.gtk.wm_border_unfocused_color +theme.border_color_active = theme.gtk.wm_border_focused_color +theme.border_color_marked = theme.gtk.success_color theme.border_width = dpi(theme.gtk.button_border_width or 1) theme.border_radius = theme.gtk.button_border_radius diff --git a/themes/sky/theme.lua b/themes/sky/theme.lua index 0aa7d1bd6..b724bad89 100644 --- a/themes/sky/theme.lua +++ b/themes/sky/theme.lua @@ -27,9 +27,9 @@ theme.fg_minimize = "#2e3436" theme.useless_gap = dpi(0) theme.border_width = dpi(2) -theme.border_normal = "#dae3e0" -theme.border_focus = "#729fcf" -theme.border_marked = "#eeeeec" +theme.border_color_normal = "#dae3e0" +theme.border_color_active = "#729fcf" +theme.border_color_marked = "#eeeeec" -- IMAGES theme.layout_fairh = themes_path .. "sky/layouts/fairh.png" diff --git a/themes/xresources/theme.lua b/themes/xresources/theme.lua index e11a83c9e..4168bcc07 100644 --- a/themes/xresources/theme.lua +++ b/themes/xresources/theme.lua @@ -29,9 +29,9 @@ theme.fg_minimize = theme.bg_normal theme.useless_gap = dpi(3) theme.border_width = dpi(2) -theme.border_normal = xrdb.color0 -theme.border_focus = theme.bg_focus -theme.border_marked = xrdb.color10 +theme.border_color_normal = xrdb.color0 +theme.border_color_active = theme.bg_focus +theme.border_color_marked = xrdb.color10 -- There are other variable sets -- overriding the default one when diff --git a/themes/zenburn/theme.lua b/themes/zenburn/theme.lua index 2b6fa08a8..ead4081b2 100644 --- a/themes/zenburn/theme.lua +++ b/themes/zenburn/theme.lua @@ -27,9 +27,9 @@ theme.bg_systray = theme.bg_normal -- {{{ Borders theme.useless_gap = dpi(0) theme.border_width = dpi(2) -theme.border_normal = "#3F3F3F" -theme.border_focus = "#6F6F6F" -theme.border_marked = "#CC9393" +theme.border_color_normal = "#3F3F3F" +theme.border_color_active = "#6F6F6F" +theme.border_color_marked = "#CC9393" -- }}} -- {{{ Titlebars From 55a097efc73ef9b841d14ed0a977212d3b6ecf51 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 10 Nov 2019 21:04:04 -0500 Subject: [PATCH 08/22] client: Update existing code to use `property::active`. --- lib/awful/titlebar.lua | 3 +-- lib/awful/widget/taglist.lua | 3 +-- lib/awful/widget/tasklist.lua | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua index d1a4070c3..a1b86597a 100644 --- a/lib/awful/titlebar.lua +++ b/lib/awful/titlebar.lua @@ -553,8 +553,7 @@ local function new(c, args) } -- Update the colors when focus changes - c:connect_signal("focus", update_colors) - c:connect_signal("unfocus", update_colors) + c:connect_signal("property::active", update_colors) -- Inform the drawable when it becomes invisible c:connect_signal("request::unmanage", function() diff --git a/lib/awful/widget/taglist.lua b/lib/awful/widget/taglist.lua index 9fc153884..5c35f2004 100644 --- a/lib/awful/widget/taglist.lua +++ b/lib/awful/widget/taglist.lua @@ -539,8 +539,7 @@ function taglist.new(args, filter, buttons, style, update_function, base_widget) end local uc = function (c) return u(c.screen) end local ut = function (t) return u(t.screen) end - capi.client.connect_signal("focus", uc) - capi.client.connect_signal("unfocus", uc) + capi.client.connect_signal("property::active", uc) tag.attached_connect_signal(nil, "property::selected", ut) tag.attached_connect_signal(nil, "property::icon", ut) tag.attached_connect_signal(nil, "property::hide", ut) diff --git a/lib/awful/widget/tasklist.lua b/lib/awful/widget/tasklist.lua index 3c59d5db1..8b49b6afe 100644 --- a/lib/awful/widget/tasklist.lua +++ b/lib/awful/widget/tasklist.lua @@ -630,8 +630,7 @@ function tasklist.new(args, filter, buttons, style, update_function, base_widget end end) capi.client.connect_signal("list", u) - capi.client.connect_signal("focus", u) - capi.client.connect_signal("unfocus", u) + capi.client.connect_signal("property::active", u) capi.screen.connect_signal("removed", function(s) instances[get_screen(s)] = nil end) From b51a20670b8f4025fc955467f0f0139323f6d61c Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 15 Dec 2019 01:59:13 -0500 Subject: [PATCH 09/22] tests: Test client.active. --- tests/test-focus.lua | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/test-focus.lua b/tests/test-focus.lua index 5a2fe90d8..6b5191f0d 100644 --- a/tests/test-focus.lua +++ b/tests/test-focus.lua @@ -3,6 +3,7 @@ local runner = require("_runner") local awful = require("awful") +local gdebug = require("gears.debug") local beautiful = require("beautiful") beautiful.border_color_normal = "#0000ff" @@ -64,8 +65,36 @@ local steps = { test("#123456") test("#12345678") test("#123456ff", "#123456") + return true - end + end, + + function() + assert(client.focus) + local called, called2 = false, false + gdebug.print_warning = function() called = true end + local c2 = client.focus + + client.focus.active = false + assert(called) + assert(not client.focus) + assert(not c2.active) + called = false + + local real_assert = assert + assert = function() called2 = true end --luacheck: globals assert + + c2.active = true + + assert = real_assert --luacheck: globals assert + + assert(called2) + assert(not called) + assert(c2.active) + assert(client.focus == c2) + + return true + end, } runner.run_steps(steps) From 8365dbc1b6a30b232e7a448bac32a72896234af2 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 15 Dec 2019 01:48:56 -0500 Subject: [PATCH 10/22] tests: Test rc.lua `c:activate()` actions. --- tests/test-awesomerc.lua | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/test-awesomerc.lua b/tests/test-awesomerc.lua index 07326e520..bf3601206 100644 --- a/tests/test-awesomerc.lua +++ b/tests/test-awesomerc.lua @@ -4,6 +4,7 @@ local hotkeys_widget = require("awful.hotkeys_popup").widget -- luacheck: globals modkey local old_c = nil +local called = false -- Get a tag and a client @@ -322,7 +323,50 @@ local steps = { end end end, + -- Test the `c:activate{}` keybindings. + function() + client.connect_signal("request::activate", function() + called = true + end) + old_c = client.focus + + root.fake_input("key_press", "Super_L") + awful.placement.centered(mouse, {parent=old_c}) + root.fake_input("button_press",1) + root.fake_input("button_release",1) + root.fake_input("key_release", "Super_L") + + return true + end, + function() + if not called then return end + + client.focus = nil + called = false + root.fake_input("key_press", "Super_L") + root.fake_input("button_press",1) + root.fake_input("button_release",1) + root.fake_input("key_release", "Super_L") + return true + end, + -- Test resize. + function() + if not called then return end + + called = false + root.fake_input("key_press", "Super_L") + root.fake_input("button_press",3) + root.fake_input("button_release",3) + root.fake_input("key_release", "Super_L") + + return true + end, + function() + if not called then return end + + return true + end } require("_runner").run_steps(steps) From 5ad0856fee6596f2b50a24e6a047aa7068f12268 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Mon, 11 Nov 2019 01:55:54 -0500 Subject: [PATCH 11/22] layout: Add a `request::default_layouts` to fill the list of layouts. This will allow the default client layout list to be manipulated by modules without the risk of overwriting each other. The commit also add a new `--{{{ Tag --}}}` section to `rc.lua`. It will be expanded once the tag rules get merged. --- awesomerc.lua | 46 ++++++------ lib/awful/layout/init.lua | 143 ++++++++++++++++++++++++++++++++------ objects/tag.c | 14 ++++ 3 files changed, 161 insertions(+), 42 deletions(-) diff --git a/awesomerc.lua b/awesomerc.lua index 13959fb10..733bc1840 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -49,27 +49,6 @@ editor_cmd = terminal .. " -e " .. editor -- I suggest you to remap Mod4 to another key using xmodmap or other tools. -- However, you can use another modifier like Mod1, but it may interact with others. modkey = "Mod4" - --- @DOC_LAYOUT@ --- Table of layouts to cover with awful.layout.inc, order matters. -awful.layout.layouts = { - awful.layout.suit.floating, - awful.layout.suit.tile, - awful.layout.suit.tile.left, - awful.layout.suit.tile.bottom, - awful.layout.suit.tile.top, - awful.layout.suit.fair, - awful.layout.suit.fair.horizontal, - awful.layout.suit.spiral, - awful.layout.suit.spiral.dwindle, - awful.layout.suit.max, - awful.layout.suit.max.fullscreen, - awful.layout.suit.magnifier, - awful.layout.suit.corner.nw, - -- awful.layout.suit.corner.ne, - -- awful.layout.suit.corner.sw, - -- awful.layout.suit.corner.se, -} -- }}} -- {{{ Menu @@ -95,10 +74,33 @@ mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon, menubar.utils.terminal = terminal -- Set the terminal for applications that require it -- }}} +-- {{{ Tag +-- @DOC_LAYOUT@ +-- Table of layouts to cover with awful.layout.inc, order matters. +tag.connect_signal("request::default_layouts", function() + awful.layout.append_default_layouts({ + awful.layout.suit.floating, + awful.layout.suit.tile, + awful.layout.suit.tile.left, + awful.layout.suit.tile.bottom, + awful.layout.suit.tile.top, + awful.layout.suit.fair, + awful.layout.suit.fair.horizontal, + awful.layout.suit.spiral, + awful.layout.suit.spiral.dwindle, + awful.layout.suit.max, + awful.layout.suit.max.fullscreen, + awful.layout.suit.magnifier, + awful.layout.suit.corner.nw, + }) +end) +-- }}} + +-- {{{ Wibar + -- Keyboard map indicator and switcher mykeyboardlayout = awful.widget.keyboardlayout() --- {{{ Wibar -- Create a textclock widget mytextclock = wibox.widget.textclock() diff --git a/lib/awful/layout/init.lua b/lib/awful/layout/init.lua index e9aeb0097..47bbcbf2b 100644 --- a/lib/awful/layout/init.lua +++ b/lib/awful/layout/init.lua @@ -31,26 +31,17 @@ end local layout = {} -layout.suit = require("awful.layout.suit") +-- Support `table.insert()` to avoid breaking old code. +local default_layouts = setmetatable({}, { + __newindex = function(self, key, value) + assert(key <= #self+1 and key > 0) -layout.layouts = { - layout.suit.floating, - layout.suit.tile, - layout.suit.tile.left, - layout.suit.tile.bottom, - layout.suit.tile.top, - layout.suit.fair, - layout.suit.fair.horizontal, - layout.suit.spiral, - layout.suit.spiral.dwindle, - layout.suit.max, - layout.suit.max.fullscreen, - layout.suit.magnifier, - layout.suit.corner.nw, - layout.suit.corner.ne, - layout.suit.corner.sw, - layout.suit.corner.se, -} + layout.append_default_layout(value) + end +}) + + +layout.suit = require("awful.layout.suit") --- The default list of layouts. -- @@ -256,6 +247,51 @@ function layout.arrange(screen) end) end +--- Append a layout to the list of default tag layouts. +-- +-- @staticfct awful.layout.append_default_layout +-- @tparam layout to_add A valid tag layout. +-- @see awful.layout.layouts +function layout.append_default_layout(to_add) + rawset(default_layouts, #default_layouts+1, to_add) + capi.tag.emit_signal("property::layouts") +end + +--- Remove a layout from the list of default layouts. +-- +-- @staticfct awful.layout.remove_default_layout +-- @tparam layout to_remove A valid tag layout. +-- @treturn boolean True if the layout was found and removed. +-- @see awful.layout.layouts +function layout.remove_default_layout(to_remove) + local ret, found = false, true + + -- Remove all instances, just in case. + while found do + found = false + for k, l in ipairs(default_layouts) do + if l == to_remove then + table.remove(default_layouts, k) + ret, found = true, true + break + end + end + end + + return ret +end + +--- Append many layouts to the list of default tag layouts. +-- +-- @staticfct awful.layout.append_default_layouts +-- @tparam table layouts A table of valid tag layout. +-- @see awful.layout.layouts +function layout.append_default_layouts(layouts) + for _, l in ipairs(layouts) do + rawset(default_layouts, #default_layouts+1, l) + end +end + --- Get the current layout name. -- @param _layout The layout. -- @return The layout name. @@ -365,6 +401,73 @@ capi.screen.connect_signal("property::geometry", function(s, old_geom) end end) -return layout +local init_layouts +init_layouts = function() + capi.tag.emit_signal("request::default_layouts") + capi.tag.disconnect_signal("new", init_layouts) + + -- Fallback. + if #default_layouts == 0 then + layout.append_default_layouts({ + layout.suit.floating, + layout.suit.tile, + layout.suit.tile.left, + layout.suit.tile.bottom, + layout.suit.tile.top, + layout.suit.fair, + layout.suit.fair.horizontal, + layout.suit.spiral, + layout.suit.spiral.dwindle, + layout.suit.max, + layout.suit.max.fullscreen, + layout.suit.magnifier, + layout.suit.corner.nw, + layout.suit.corner.ne, + layout.suit.corner.sw, + layout.suit.corner.se, + }) + end + + init_layouts = nil +end + +-- "new" is emited before "activate", do it should be the very last opportunity +-- generate the list of default layout. With dynamic tag, this can happen later +-- than the first event loop iteration. +capi.tag.connect_signal("new", init_layouts) + +-- Intercept the calls to `layouts` to both lazyload then and emit the proper +-- signals. +local mt = { + __index = function(_, key) + if key == "layouts" then + -- Lazy initialization to *at least* attempt to give modules a + -- chance to load before calling `request::default_layouts`. Note + -- that the old `rc.lua` called `awful.layout.layouts` in the global + -- context. If there was some module `require()` later in the code, + -- they will not get the signal. + if init_layouts then + init_layouts() + end + + return default_layouts + end + end, + __newindex = function(_, key, value) + if key == "layouts" then + gdebug.print_warning( + "`awful.layout.layouts` was set before `request::default_layouts` could ".. + "be called. Please use `awful.layout.append_default_layouts` to ".. + " avoid this problem" + ) + capi.tag.disconnect_signal("new", init_layouts) + init_layouts = nil + else + rawset(layout, key, value) + end + end +} + +return setmetatable(layout, mt) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/objects/tag.c b/objects/tag.c index 729cfe299..de8e7a8e9 100644 --- a/objects/tag.c +++ b/objects/tag.c @@ -207,6 +207,20 @@ * @signal request::select */ +/** + * This signal is emitted to fill the list of default layouts. + * + * It is emitted on the global `tag` class rather than individual tag objects. + * The default handler is part of `rc.lua`. New modules can also use this signal + * to dynamically add new layouts to the list of default layouts. + * + * @signal request::default_layouts + * @see awful.layout.layouts + * @see awful.layout.append_default_layout + * @see awful.layout.remove_default_layout + */ + + /** When a client gets tagged with this tag. * @signal tagged * @client c The tagged client. From efc42b1be16b4fdaeab6c4627114d5bcc4fe6021 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 16 Nov 2019 18:51:15 -0500 Subject: [PATCH 12/22] autofocus: Modify `awful.autofocus` to be a request::. This also pulls in part of the permission framework to ensure backward compatibility is kept. `awful.autofocus` was always weird. It is a module part of `awful`, but it was never part of `awful` `init.lua`. Rather, `rc.lua` was the sole place it was used. It behave exactly like a request, but predate them by years. As I cleanup the request:: API before the permissions API gets formalized, this has to be fixed now. It isn't deprecated in this commit because it makes too many tests fail. Another pull request will solve that by adding the "API level" concept to AwesomeWM so I can change the behavior without breaking existing configs. With that, the behavior of `autofocus` will be enabled by default with the permissions to disable it. --- awesomerc.lua | 5 -- docs/config.ld | 1 + lib/awful/autofocus.lua | 83 ++++---------------------- lib/awful/ewmh.lua | 96 +++++++++++++++++++++++++++++++ lib/awful/permissions/_common.lua | 56 ++++++++++++++++++ objects/client.c | 20 +++++++ spec/awful/ewmh_spec.lua | 3 + 7 files changed, 188 insertions(+), 76 deletions(-) create mode 100644 lib/awful/permissions/_common.lua diff --git a/awesomerc.lua b/awesomerc.lua index 733bc1840..578a9a37f 100644 --- a/awesomerc.lua +++ b/awesomerc.lua @@ -535,8 +535,3 @@ client.connect_signal("request::titlebars", function(c) } end) -- }}} - --- Enable sloppy focus, so that focus follows mouse. -client.connect_signal("mouse::enter", function(c) - c:activate { context = "mouse_enter", raise = false } -end) diff --git a/docs/config.ld b/docs/config.ld index c9686a040..6a1470a81 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -444,6 +444,7 @@ file = { '../lib/awful/screen/dpi.lua', '../lib/awful/startup_notification.lua', '../lib/awful/mouse/drag_to_tag.lua', + '../lib/awful/permissions/_common.lua', '../lib/gears/init.lua', '../lib/wibox/layout/init.lua', '../lib/wibox/container/init.lua', diff --git a/lib/awful/autofocus.lua b/lib/awful/autofocus.lua index f3ea73c15..c11d4831e 100644 --- a/lib/awful/autofocus.lua +++ b/lib/awful/autofocus.lua @@ -1,74 +1,15 @@ --------------------------------------------------------------------------- ---- Autofocus functions. --- --- When loaded, this module makes sure that there's always a client that will --- have focus on events such as tag switching, client unmanaging, etc. --- --- @author Julien Danjou <julien@danjou.info> --- @copyright 2009 Julien Danjou --- @module awful.autofocus +-- This module used to be a "require only" module which, when explicitly +-- required, would allow handle focus when switching tags and other useful +-- corner cases. This code has been migrated to a more standard request:: +-- API. The content itself is now in `awful.permissions`. This was required +-- to preserve backward compatibility since this module may or may not have +-- been loaded. --------------------------------------------------------------------------- +require("awful.permissions._common")._deprecated_autofocus_in_use() -local client = client -local aclient = require("awful.client") -local timer = require("gears.timer") - -local function filter_sticky(c) - return not c.sticky and aclient.focus.filter(c) -end - ---- Give focus when clients appear/disappear. --- --- @param obj An object that should have a .screen property. -local function check_focus(obj) - if (not obj.screen) or not obj.screen.valid then return end - -- When no visible client has the focus... - if not client.focus or not client.focus:isvisible() then - local c = aclient.focus.history.get(screen[obj.screen], 0, filter_sticky) - if not c then - c = aclient.focus.history.get(screen[obj.screen], 0, aclient.focus.filter) - end - if c then - c:emit_signal("request::activate", "autofocus.check_focus", - {raise=false}) - end - end -end - ---- Check client focus (delayed). --- @param obj An object that should have a .screen property. -local function check_focus_delayed(obj) - timer.delayed_call(check_focus, {screen = obj.screen}) -end - ---- Give focus on tag selection change. --- --- @tparam tag t A tag object -local function check_focus_tag(t) - local s = t.screen - if (not s) or (not s.valid) then return end - s = screen[s] - check_focus({ screen = s }) - if client.focus and screen[client.focus.screen] ~= s then - local c = aclient.focus.history.get(s, 0, filter_sticky) - if not c then - c = aclient.focus.history.get(s, 0, aclient.focus.filter) - end - if c then - c:emit_signal("request::activate", "autofocus.check_focus_tag", - {raise=false}) - end - end -end - -tag.connect_signal("property::selected", function (t) - timer.delayed_call(check_focus_tag, t) -end) -client.connect_signal("request::unmanage", check_focus_delayed) -client.connect_signal("tagged", check_focus_delayed) -client.connect_signal("untagged", check_focus_delayed) -client.connect_signal("property::hidden", check_focus_delayed) -client.connect_signal("property::minimized", check_focus_delayed) -client.connect_signal("property::sticky", check_focus_delayed) - --- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 +--require("gears.debug").deprecate( +-- "The `awful.autofocus` module is deprecated, remove the require() and ".. +-- "look at the new `rc.lua` granted permission section in the client rules", +-- {deprecated_in=5} +--) diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua index b8a2d3b78..c39fb2426 100644 --- a/lib/awful/ewmh.lua +++ b/lib/awful/ewmh.lua @@ -18,6 +18,7 @@ local beautiful = require("beautiful") local alayout = require("awful.layout") local atag = require("awful.tag") local gdebug = require("gears.debug") +local pcommon = require("awful.permissions._common") local ewmh = { generic_activate_filters = {}, @@ -75,6 +76,60 @@ local function repair_geometry(window) repair_geometry_lock = false end +local function filter_sticky(c) + return not c.sticky and aclient.focus.filter(c) +end + +--- Give focus when clients appear/disappear. +-- +-- @param obj An object that should have a .screen property. +local function check_focus(obj) + if (not obj.screen) or not obj.screen.valid then return end + -- When no visible client has the focus... + if not client.focus or not client.focus:isvisible() then + local c = aclient.focus.history.get(screen[obj.screen], 0, filter_sticky) + if not c then + c = aclient.focus.history.get(screen[obj.screen], 0, aclient.focus.filter) + end + if c then + c:emit_signal( + "request::autoactivate", + "history", + {raise=false} + ) + end + end +end + +--- Check client focus (delayed). +-- @param obj An object that should have a .screen property. +local function check_focus_delayed(obj) + timer.delayed_call(check_focus, {screen = obj.screen}) +end + +--- Give focus on tag selection change. +-- +-- @tparam tag t A tag object +local function check_focus_tag(t) + local s = t.screen + if (not s) or (not s.valid) then return end + s = screen[s] + check_focus({ screen = s }) + if client.focus and screen[client.focus.screen] ~= s then + local c = aclient.focus.history.get(s, 0, filter_sticky) + if not c then + c = aclient.focus.history.get(s, 0, aclient.focus.filter) + end + if c then + c:emit_signal( + "request::autoactivate", + "switch_tag", + {raise=false} + ) + end + end +end + --- Activate a window. -- -- This sets the focus only if the client is visible. @@ -605,6 +660,32 @@ function ewmh.update_border(c, context) end end +local activate_context_map = { + mouse_enter = "mouse.enter", + switch_tag = "autofocus.check_focus_tag", + history = "autofocus.check_focus" +} + +--- Default handler for the `request::autoactivate` signal. +-- +-- All it does is to emit `request::activate` with the following context +-- mapping: +-- +-- * mouse_enter: *mouse.enter* +-- * switch_tag : *autofocus.check_focus_tag* +-- * history : *autofocus.check_focus* +-- +-- @signalhandler awful.ewmh.autoactivate +function ewmh.autoactivate(c, context, args) + if not pcommon.check("client", "autoactivate", context) then return end + + local ctx = activate_context_map[context] and + activate_context_map[context] or context + + c:emit_signal("request::activate", ctx, args) +end + +client.connect_signal("request::autoactivate", ewmh.autoactivate) client.connect_signal("request::border", ewmh.update_border) client.connect_signal("request::activate", ewmh.activate) client.connect_signal("request::tag", ewmh.tag) @@ -614,12 +695,27 @@ 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) +client.connect_signal("request::unmanage", check_focus_delayed) +client.connect_signal("tagged", check_focus_delayed) +client.connect_signal("untagged", check_focus_delayed) +client.connect_signal("property::hidden", check_focus_delayed) +client.connect_signal("property::minimized", check_focus_delayed) +client.connect_signal("property::sticky", check_focus_delayed) +tag.connect_signal("property::selected", function (t) + timer.delayed_call(check_focus_tag, t) +end) + screen.connect_signal("property::workarea", function(s) for _, c in pairs(client.get(s)) do repair_geometry(c) end end) +-- Enable sloppy focus, so that focus follows mouse. +client.connect_signal("mouse::enter", function(c) + c:emit_signal("request::autoactivate", "mouse_enter", {raise=false}) +end) + return ewmh -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/awful/permissions/_common.lua b/lib/awful/permissions/_common.lua new file mode 100644 index 000000000..435033603 --- /dev/null +++ b/lib/awful/permissions/_common.lua @@ -0,0 +1,56 @@ +-- Common code for the permission framework. +-- It is in its own file to avoid circular dependencies. +-- +-- It is **NOT** a public API. + +local module = {} + +-- The default permissions for all requests. +-- If something is not in this list, then it is assumed it should be granted. +local default_permissions = { + client = { + autoactivate = { + -- To preserve the default from AwesomeWM 3.3-4.3, do not steal + -- focus by default for these contexts: + mouse_enter = false, + switch_tag = false, + history = false, + } + } +} + +function module.check(class, request, context) + if not default_permissions[class] then return true end + if not default_permissions[class][request] then return true end + if default_permissions[class][request][context] == nil then return true end + + return default_permissions[class][request][context] +end + +function module.set(class, request, context, granted) + assert(type(granted) == "boolean") + + if not default_permissions[class] then + default_permissions[class] = {} + end + + if not default_permissions[class][request] then + default_permissions[class][request] = {} + end + + default_permissions[class][request][context] = granted +end + +-- Awesome 3.3-4.3 had an `awful.autofocus` module. That module had no APIs, but +-- was simply "enabled" when you `require()`d it for the first time. This was +-- non-standard and was the only module in `awful` to only do things when +-- explicitly `require()`d. +-- +-- It is now a dummy module which will set the property to `true`. +function module._deprecated_autofocus_in_use() + module.set("client", "autoactivate", "mouse_enter", true) + module.set("client", "autoactivate", "switch_tag" , true) + module.set("client", "autoactivate", "history" , true) +end + +return module diff --git a/objects/client.c b/objects/client.c index 1b10f8a85..6385d232e 100644 --- a/objects/client.c +++ b/objects/client.c @@ -266,6 +266,26 @@ * @tparam[opt=false] boolean hints.raise should the client be raised? */ +/** When an event could lead to the client being activated. + * + * This is an layer "on top" of `request::activate` for event which are not + * actual request for activation/focus, but where "it would be nice" if the + * client got the focus. This includes the focus-follow-mouse model and focusing + * previous clients when the selected tag changes. + * + * This idea is that `request::autoactivate` will emit `request::activate`. + * However it is much easier to replace the handler for `request::autoactivate` + * than it is to replace the handler for `request::activate`. Thus it provides + * a nice abstraction to simplify handling the focus when switching tags or + * moving the mouse. + * + * @signal request::autoactivate + * @tparam string context The context where this signal was used. + * @tparam[opt] table hints A table with additional hints: + * @tparam[opt=false] boolean hints.raise should the client be raised? + * + */ + /** * @signal request::geometry * @tparam client c The client diff --git a/spec/awful/ewmh_spec.lua b/spec/awful/ewmh_spec.lua index 058b77781..67c0328fa 100644 --- a/spec/awful/ewmh_spec.lua +++ b/spec/awful/ewmh_spec.lua @@ -11,6 +11,9 @@ describe("awful.ewmh.client_geometry_requests", function() _G.screen = { connect_signal = function() end, } + _G.tag = { + connect_signal = function() end, + } local ewmh = require("awful.ewmh") From 5818de41ce181f36207bb0d7b090aff292737089 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Nov 2019 18:46:22 -0500 Subject: [PATCH 13/22] awful: Rename awful.ewmh to awful.permissions. It has nothing to do with EWMH since a long time. It was already used for the requests, so lets formalize this. --- build-utils/check_for_invalid_requires.lua | 3 + docs/90-FAQ.md | 8 +- docs/config.ld | 1 + lib/awful/client.lua | 8 +- lib/awful/ewmh.lua | 726 +----------------- lib/awful/init.lua | 19 +- lib/awful/permissions/init.lua | 724 +++++++++++++++++ .../{ewmh_spec.lua => permissions_spec.lua} | 16 +- .../examples/sequences/client/fullscreen.lua | 2 +- tests/examples/sequences/client/maximized.lua | 2 +- .../sequences/client/maximized_horizontal.lua | 2 +- .../sequences/client/maximized_vertical.lua | 2 +- tests/test-awful-client.lua | 12 +- tests/test-maximize.lua | 4 +- 14 files changed, 779 insertions(+), 750 deletions(-) create mode 100644 lib/awful/permissions/init.lua rename spec/awful/{ewmh_spec.lua => permissions_spec.lua} (71%) diff --git a/build-utils/check_for_invalid_requires.lua b/build-utils/check_for_invalid_requires.lua index dd33443b0..741d1ad37 100755 --- a/build-utils/check_for_invalid_requires.lua +++ b/build-utils/check_for_invalid_requires.lua @@ -32,6 +32,9 @@ local allowed_deps = { gears = true, lgi = true, wibox = true, + + -- Necessary to lazy-load the deprecated modules. + ["awful.*"] = true }, naughty = { awful = true, diff --git a/docs/90-FAQ.md b/docs/90-FAQ.md index 7e7d1c2b2..9257632df 100644 --- a/docs/90-FAQ.md +++ b/docs/90-FAQ.md @@ -315,17 +315,17 @@ You can call the `awful.layout.set()` function, here's an example: ### Why are new clients urgent by default? -You can change this by redefining `awful.ewmh.activate(c)` in your rc.lua. If +You can change this by redefining `awful.permissions.activate(c)` in your rc.lua. If you don't want new clients to be urgent by default put this in your rc.lua: - client.disconnect_signal("request::activate", awful.ewmh.activate) - function awful.ewmh.activate(c) + client.disconnect_signal("request::activate", awful.permissions.activate) + function awful.permissions.activate(c) if c:isvisible() then client.focus = c c:raise() end end - client.connect_signal("request::activate", awful.ewmh.activate) + client.connect_signal("request::activate", awful.permissions.activate) ## Usage diff --git a/docs/config.ld b/docs/config.ld index 6a1470a81..c4c6c3078 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -471,6 +471,7 @@ file = { '../lib/awful/widget/progressbar.lua', '../lib/awful/widget/textclock.lua', '../lib/awful/wibox.lua', + '../lib/awful/ewmh.lua', '../lib/wibox/layout/constraint.lua', '../lib/wibox/layout/margin.lua', '../lib/wibox/layout/mirror.lua', diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 530415400..fc06f6120 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -1447,7 +1447,7 @@ end -- @tparam[opt=false] boolean args.switch_to_tag -- @tparam[opt=false] boolean args.action Once activated, perform an action. -- @tparam[opt=false] boolean args.toggle_minimization --- @see awful.ewmh.add_activate_filter +-- @see awful.permissions.add_activate_filter -- @see request::activate -- @see active function client.object.activate(c, args) @@ -1539,7 +1539,7 @@ end -- @tparam boolean active -- @see activate -- @see request::activate --- @see awful.ewmh.add_activate_filter +-- @see awful.permissions.add_activate_filter function client.object.get_active(c) return capi.client.focus == c @@ -1592,10 +1592,10 @@ end) -- * **floating**: When the floating or maximization state changes. -- -- @signal request::border --- @see awful.ewmh.update_border +-- @see awful.permissions.update_border -- Add clients during startup to focus history. --- This used to happen through ewmh.activate, but that only handles visible +-- This used to happen through permissions.activate, but that only handles visible -- clients now. capi.client.connect_signal("request::manage", function (c) if capi.awesome.startup diff --git a/lib/awful/ewmh.lua b/lib/awful/ewmh.lua index c39fb2426..5cd71a368 100644 --- a/lib/awful/ewmh.lua +++ b/lib/awful/ewmh.lua @@ -1,721 +1,9 @@ ---------------------------------------------------------------------------- ---- Implements EWMH requests handling. --- --- @author Julien Danjou <julien@danjou.info> --- @copyright 2009 Julien Danjou --- @module awful.ewmh ---------------------------------------------------------------------------- - -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") -local asuit = require("awful.layout.suit") -local beautiful = require("beautiful") -local alayout = require("awful.layout") -local atag = require("awful.tag") local gdebug = require("gears.debug") -local pcommon = require("awful.permissions._common") -local ewmh = { - generic_activate_filters = {}, - contextual_activate_filters = {}, -} - ---- Honor the screen padding when maximizing. --- @beautiful beautiful.maximized_honor_padding --- @tparam[opt=true] boolean maximized_honor_padding - ---- Hide the border on fullscreen clients. --- @beautiful beautiful.fullscreen_hide_border --- @tparam[opt=true] boolean fullscreen_hide_border - ---- Hide the border on maximized clients. --- @beautiful beautiful.maximized_hide_border --- @tparam[opt=false] boolean maximized_hide_border - ---- The list of all registered generic request::activate (focus stealing) --- filters. If a filter is added to only one context, it will be in --- `ewmh.contextual_activate_filters`["context_name"]. --- @table[opt={}] generic_activate_filters --- @see ewmh.activate --- @see ewmh.add_activate_filter --- @see ewmh.remove_activate_filter - ---- The list of all registered contextual request::activate (focus stealing) --- filters. If a filter is added to only one context, it will be in --- `ewmh.generic_activate_filters`. --- @table[opt={}] contextual_activate_filters --- @see ewmh.activate --- @see ewmh.add_activate_filter --- @see ewmh.remove_activate_filter - ---- Update a client's settings when its geometry changes, skipping signals --- resulting from calls within. -local repair_geometry_lock = false - -local function repair_geometry(window) - if repair_geometry_lock then return end - repair_geometry_lock = true - - -- Re-apply the geometry locking properties to what they should be. - for _, prop in ipairs { - "fullscreen", "maximized", "maximized_vertical", "maximized_horizontal" - } do - if window[prop] then - window:emit_signal("request::geometry", prop, { - store_geometry = false - }) - break - end - end - - repair_geometry_lock = false -end - -local function filter_sticky(c) - return not c.sticky and aclient.focus.filter(c) -end - ---- Give focus when clients appear/disappear. --- --- @param obj An object that should have a .screen property. -local function check_focus(obj) - if (not obj.screen) or not obj.screen.valid then return end - -- When no visible client has the focus... - if not client.focus or not client.focus:isvisible() then - local c = aclient.focus.history.get(screen[obj.screen], 0, filter_sticky) - if not c then - c = aclient.focus.history.get(screen[obj.screen], 0, aclient.focus.filter) - end - if c then - c:emit_signal( - "request::autoactivate", - "history", - {raise=false} - ) - end - end -end - ---- Check client focus (delayed). --- @param obj An object that should have a .screen property. -local function check_focus_delayed(obj) - timer.delayed_call(check_focus, {screen = obj.screen}) -end - ---- Give focus on tag selection change. --- --- @tparam tag t A tag object -local function check_focus_tag(t) - local s = t.screen - if (not s) or (not s.valid) then return end - s = screen[s] - check_focus({ screen = s }) - if client.focus and screen[client.focus.screen] ~= s then - local c = aclient.focus.history.get(s, 0, filter_sticky) - if not c then - c = aclient.focus.history.get(s, 0, aclient.focus.filter) - end - if c then - c:emit_signal( - "request::autoactivate", - "switch_tag", - {raise=false} - ) - end - end -end - ---- Activate a window. --- --- This sets the focus only if the client is visible. --- --- It is the default signal handler for `request::activate` on a `client`. --- --- @signalhandler awful.ewmh.activate --- @client c A client to use --- @tparam string context The context where this signal was used. --- @tparam[opt] table hints A table with additional hints: --- @tparam[opt=false] boolean hints.raise should the client be raised? --- @tparam[opt=false] boolean hints.switch_to_tag should the client's first tag --- be selected if none of the client's tags are selected? --- @tparam[opt=false] boolean hints.switch_to_tags Select all tags associated --- with the client. -function ewmh.activate(c, context, hints) -- luacheck: no unused args - hints = hints or {} - - if c.focusable == false and not hints.force then - if hints.raise then - c:raise() - end - - return - end - - local found, ret = false - - -- Execute the filters until something handle the request - for _, tab in ipairs { - ewmh.contextual_activate_filters[context] or {}, - ewmh.generic_activate_filters - } do - for i=#tab, 1, -1 do - ret = tab[i](c, context, hints) - if ret ~= nil then found=true; break end - end - - if found then break end - end - - -- Minimized clients can be requested to have focus by, for example, 3rd - -- party toolbars and they might not try to unminimize it first. - if ret ~= false and hints.raise then - c.minimized = false - end - - if ret ~= false and c:isvisible() then - client.focus = c - elseif ret == false and not hints.force then - return - end - - if hints.raise then - c:raise() - if not awesome.startup and not c:isvisible() then - c.urgent = true - end - end - - -- The rules use `switchtotag`. For consistency and code re-use, support it, - -- but keep undocumented. --TODO v5 remove switchtotag - if hints.switchtotag or hints.switch_to_tag or hints.switch_to_tags then - atag.viewmore(c:tags(), c.screen, (not hints.switch_to_tags) and 0 or nil) - end -end - ---- Add an activate (focus stealing) filter function. --- --- The callback takes the following parameters: --- --- * **c** (*client*) The client requesting the activation --- * **context** (*string*) The activation context. --- * **hints** (*table*) Some additional hints (depending on the context) --- --- If the callback returns `true`, the client will be activated. If the callback --- returns `false`, the activation request is cancelled unless the `force` hint is --- set. If the callback returns `nil`, the previous callback will be executed. --- This will continue until either a callback handles the request or when it runs --- out of callbacks. In that case, the request will be granted if the client is --- visible. --- --- For example, to block Firefox from stealing the focus, use: --- --- awful.ewmh.add_activate_filter(function(c) --- if c.class == "Firefox" then return false end --- end, "ewmh") --- --- @tparam function f The callback --- @tparam[opt] string context The `request::activate` context --- @see generic_activate_filters --- @see contextual_activate_filters --- @see remove_activate_filter --- @staticfct awful.ewmh.add_activate_filter -function ewmh.add_activate_filter(f, context) - if not context then - table.insert(ewmh.generic_activate_filters, f) - else - ewmh.contextual_activate_filters[context] = - ewmh.contextual_activate_filters[context] or {} - - table.insert(ewmh.contextual_activate_filters[context], f) - end -end - ---- Remove an activate (focus stealing) filter function. --- This is an helper to avoid dealing with `ewmh.add_activate_filter` directly. --- @tparam function f The callback --- @tparam[opt] string context The `request::activate` context --- @treturn boolean If the callback existed --- @see generic_activate_filters --- @see contextual_activate_filters --- @see add_activate_filter --- @staticfct awful.ewmh.remove_activate_filter -function ewmh.remove_activate_filter(f, context) - local tab = context and (ewmh.contextual_activate_filters[context] or {}) - or ewmh.generic_activate_filters - - for k, v in ipairs(tab) do - if v == f then - table.remove(tab, k) - - -- In case the callback is there multiple time. - ewmh.remove_activate_filter(f, context) - - return true - end - end - - return false -end - --- Get tags that are on the same screen as the client. This should _almost_ --- always return the same content as c:tags(). -local function get_valid_tags(c, s) - local tags, new_tags = c:tags(), {} - - for _, t in ipairs(tags) do - if s == t.screen then - table.insert(new_tags, t) - end - end - - return new_tags -end - ---- Tag a window with its requested tag. --- --- It is the default signal handler for `request::tag` on a `client`. --- --- @signalhandler awful.ewmh.tag --- @client c A client to tag --- @tparam[opt] tag|boolean t A tag to use. If true, then the client is made sticky. --- @tparam[opt={}] table hints Extra information -function ewmh.tag(c, t, hints) --luacheck: no unused - -- There is nothing to do - if not t and #get_valid_tags(c, c.screen) > 0 then return end - - if not t then - if c.transient_for and not (hints and hints.reason == "screen") then - c.screen = c.transient_for.screen - if not c.sticky then - local tags = c.transient_for:tags() - c:tags(#tags > 0 and tags or c.transient_for.screen.selected_tags) - end - else - c:to_selected_tags() - end - elseif type(t) == "boolean" and t then - c.sticky = true - else - c.screen = t.screen - c:tags({ t }) - end -end - ---- Handle client urgent request --- @signalhandler awful.ewmh.urgent --- @client c A client --- @tparam boolean urgent If the client should be urgent -function ewmh.urgent(c, urgent) - if c ~= client.focus and not aclient.property.get(c,"ignore_urgent") then - c.urgent = urgent - end -end - --- Map the state to the action name -local context_mapper = { - maximized_vertical = "maximize_vertically", - maximized_horizontal = "maximize_horizontally", - maximized = "maximize", - fullscreen = "maximize" -} - ---- Move and resize the client. --- --- This is the default geometry request handler. --- --- @signalhandler awful.ewmh.geometry --- @tparam client c The client --- @tparam string context The context --- @tparam[opt={}] table hints The hints to pass to the handler -function ewmh.geometry(c, context, hints) - local layout = c.screen.selected_tag and c.screen.selected_tag.layout or nil - - -- Setting the geometry will not work unless the client is floating. - if (not c.floating) and (not layout == asuit.floating) then - return - end - - context = context or "" - - local original_context = context - - -- Now, map it to something useful - context = context_mapper[context] or context - - local props = gtable.clone(hints or {}, false) - props.store_geometry = props.store_geometry==nil and true or props.store_geometry - - -- If it is a known placement function, then apply it, otherwise let - -- other potential handler resize the client (like in-layout resize or - -- floating client resize) - if aplace[context] then - - -- Check if it corresponds to a boolean property. - local state = c[original_context] - - -- If the property is boolean and it corresponds to the undo operation, - -- restore the stored geometry. - if state == false then - local original = repair_geometry_lock - repair_geometry_lock = true - aplace.restore(c, {context=context}) - repair_geometry_lock = original - return - end - - local honor_default = original_context ~= "fullscreen" - - if props.honor_workarea == nil then - props.honor_workarea = honor_default - end - - if props.honor_padding == nil and props.honor_workarea and context:match("maximize") then - props.honor_padding = beautiful.maximized_honor_padding ~= false - end - - if (original_context == "fullscreen" and beautiful.fullscreen_hide_border ~= false) or - (original_context == "maximized" and beautiful.maximized_hide_border == true) then - props.ignore_border_width = true - props.zap_border_width = true - end - - local original = repair_geometry_lock - repair_geometry_lock = true - aplace[context](c, props) - repair_geometry_lock = original - 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() - -- Computes the actual X11 atoms before/after - local before_max_h = c.maximized or c.maximized_horizontal - local before_max_v = c.maximized or c.maximized_vertical - local after_max_h, after_max_v - if c._delayed_max_h ~= nil then - after_max_h = c._delayed_max_h - else - after_max_h = before_max_h - end - if c._delayed_max_v ~= nil then - after_max_v = c._delayed_max_v - else - after_max_v = before_max_v - end - -- Interprets the client's intention based on the client's view - if after_max_h and after_max_v then - c.maximized = true - elseif before_max_h and before_max_v then - -- At this point, c.maximized must be true, and the client is - -- trying to unmaximize the window, and potentially partial - -- maximized the window - c.maximized = false - if after_max_h ~= after_max_v then - c.maximized_horizontal = after_max_h - c.maximized_vertical = after_max_v - end - else - -- At this point, c.maximized must be false, and the client is - -- not trying to fully maximize the window - c.maximized_horizontal = after_max_h - c.maximized_vertical = after_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"] or 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 - if c.immobilized_horizontal then - hints = gtable.clone(hints) - hints.x = nil - hints.width = nil - end - if c.immobilized_vertical then - hints = gtable.clone(hints) - hints.y = nil - hints.height = nil - end - c:geometry(hints) - end -end - --- The magnifier layout doesn't work with focus follow mouse. -ewmh.add_activate_filter(function(c) - if alayout.get(c.screen) ~= alayout.suit.magnifier - and aclient.focus.filter(c) then - return nil - else - return false - end -end, "mouse_enter") - ---- The default client `request::border` handler. --- --- To replace this handler with your own, use: --- --- client.disconnect_signal("request::border", awful.ewmh.update_border) --- --- The default implementation chooses from dozens beautiful theme variables --- depending if the client is tiled, floating, maximized and then from its state --- (urgent, new, active, normal) --- --- @signalhandler awful.ewmh.update_border --- @usebeautiful beautiful.border_color_active --- @usebeautiful beautiful.border_color_normal --- @usebeautiful beautiful.border_color_new --- @usebeautiful beautiful.border_color_urgent --- @usebeautiful beautiful.border_color_floating --- @usebeautiful beautiful.border_color_floating_active --- @usebeautiful beautiful.border_color_floating_normal --- @usebeautiful beautiful.border_color_floating_new --- @usebeautiful beautiful.border_color_floating_urgent --- @usebeautiful beautiful.border_color_maximized --- @usebeautiful beautiful.border_color_maximized_active --- @usebeautiful beautiful.border_color_maximized_normal --- @usebeautiful beautiful.border_color_maximized_new --- @usebeautiful beautiful.border_color_maximized_urgent --- @usebeautiful beautiful.border_color_fullscreen --- @usebeautiful beautiful.border_color_fullscreen_active --- @usebeautiful beautiful.border_color_fullscreen_normal --- @usebeautiful beautiful.border_color_fullscreen_new --- @usebeautiful beautiful.border_color_fullscreen_urgent --- @usebeautiful beautiful.border_width_active --- @usebeautiful beautiful.border_width_normal --- @usebeautiful beautiful.border_width_new --- @usebeautiful beautiful.border_width_urgent --- @usebeautiful beautiful.border_width_floating --- @usebeautiful beautiful.border_width_floating_active --- @usebeautiful beautiful.border_width_floating_normal --- @usebeautiful beautiful.border_width_floating_new --- @usebeautiful beautiful.border_width_floating_urgent --- @usebeautiful beautiful.border_width_maximized --- @usebeautiful beautiful.border_width_maximized_active --- @usebeautiful beautiful.border_width_maximized_normal --- @usebeautiful beautiful.border_width_maximized_new --- @usebeautiful beautiful.border_width_maximized_urgent --- @usebeautiful beautiful.border_width_fullscreen --- @usebeautiful beautiful.border_width_fullscreen_active --- @usebeautiful beautiful.border_width_fullscreen_normal --- @usebeautiful beautiful.border_width_fullscreen_new --- @usebeautiful beautiful.border_width_fullscreen_urgent --- @usebeautiful beautiful.opacity_floating --- @usebeautiful beautiful.opacity_floating_active --- @usebeautiful beautiful.opacity_floating_normal --- @usebeautiful beautiful.opacity_floating_new --- @usebeautiful beautiful.opacity_floating_urgent --- @usebeautiful beautiful.opacity_maximized --- @usebeautiful beautiful.opacity_maximized_active --- @usebeautiful beautiful.opacity_maximized_normal --- @usebeautiful beautiful.opacity_maximized_new --- @usebeautiful beautiful.opacity_maximized_urgent --- @usebeautiful beautiful.opacity_fullscreen --- @usebeautiful beautiful.opacity_fullscreen_active --- @usebeautiful beautiful.opacity_fullscreen_normal --- @usebeautiful beautiful.opacity_fullscreen_new --- @usebeautiful beautiful.opacity_fullscreen_urgent --- @usebeautiful beautiful.opacity_active --- @usebeautiful beautiful.opacity_normal --- @usebeautiful beautiful.opacity_new --- @usebeautiful beautiful.opacity_urgent - -function ewmh.update_border(c, context) - local suffix, fallback = "", "" - - -- Add the sub-namespace. - if c.fullscreen then - suffix, fallback = "_fullscreen", "_fullscreen" - elseif c.maximized then - suffix, fallback = "_maximized", "_maximized" - elseif c.floating then - suffix, fallback = "_floating", "_floating" - end - - -- Add the state suffix. - if c.urgent then - suffix = suffix .. "_urgent" - elseif c.active then - suffix = suffix .. "_active" - elseif context == "added" then - suffix = suffix .. "_new" - else - suffix = suffix .. "_normal" - end - - if not c._private._user_border_width then - c._border_width = beautiful["border_width"..suffix] - or beautiful["border_width"..fallback] - or beautiful.border_width - end - - if not c._private._user_border_color then - -- First, check marked clients. This is a concept that should probably - -- never have been added to the core. The documentation claims it works, - -- even if it has been broken for 90% of AwesomeWM releases ever since - -- it was added. - if c.marked and beautiful.border_marked then - c._border_color = beautiful.border_marked - return - end - - local tv = beautiful["border_color"..suffix] - - if fallback ~= "" and not tv then - tv = beautiful["border_color"..fallback] - end - - -- The old theme variable did not have "color" in its name. - if (not tv) and beautiful.border_normal and (not c.active) then - gdebug.deprecate( - "Use `beautiful.border_color_normal` instead of `beautiful.border_normal`", - {deprecated_in=5} - ) - tv = beautiful.border_normal - elseif (not tv) and beautiful.border_focus then - gdebug.deprecate( - "Use `beautiful.border_color_active` instead of `beautiful.border_focus`", - {deprecated_in=5} - ) - tv = beautiful.border_focus - end - - if not tv then - tv = beautiful.border_color - end - - if tv then - c._border_color = tv - end - end - - if not c._private._user_opacity then - local tv = beautiful["opacity"..suffix] - - if fallback ~= "" and not tv then - tv = beautiful["opacity"..fallback] - end - - if tv then - c._opacity = tv - end - end -end - -local activate_context_map = { - mouse_enter = "mouse.enter", - switch_tag = "autofocus.check_focus_tag", - history = "autofocus.check_focus" -} - ---- Default handler for the `request::autoactivate` signal. --- --- All it does is to emit `request::activate` with the following context --- mapping: --- --- * mouse_enter: *mouse.enter* --- * switch_tag : *autofocus.check_focus_tag* --- * history : *autofocus.check_focus* --- --- @signalhandler awful.ewmh.autoactivate -function ewmh.autoactivate(c, context, args) - if not pcommon.check("client", "autoactivate", context) then return end - - local ctx = activate_context_map[context] and - activate_context_map[context] or context - - c:emit_signal("request::activate", ctx, args) -end - -client.connect_signal("request::autoactivate", ewmh.autoactivate) -client.connect_signal("request::border", ewmh.update_border) -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) -client.connect_signal("request::unmanage", check_focus_delayed) -client.connect_signal("tagged", check_focus_delayed) -client.connect_signal("untagged", check_focus_delayed) -client.connect_signal("property::hidden", check_focus_delayed) -client.connect_signal("property::minimized", check_focus_delayed) -client.connect_signal("property::sticky", check_focus_delayed) -tag.connect_signal("property::selected", function (t) - timer.delayed_call(check_focus_tag, t) -end) - -screen.connect_signal("property::workarea", function(s) - for _, c in pairs(client.get(s)) do - repair_geometry(c) - end -end) - --- Enable sloppy focus, so that focus follows mouse. -client.connect_signal("mouse::enter", function(c) - c:emit_signal("request::autoactivate", "mouse_enter", {raise=false}) -end) - -return ewmh - --- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 +-- It diverged over time to the point where it had nothing to do with EWMH. +return gdebug.deprecate_class( + require("awful.permissions"), + "awful.ewmh", + "awful.permissions", + { deprecated_in = 5} +) diff --git a/lib/awful/init.lua b/lib/awful/init.lua index 992590324..cff95db16 100644 --- a/lib/awful/init.lua +++ b/lib/awful/init.lua @@ -8,8 +8,11 @@ require("awful._compat") -return -{ +local deprecated = { + ewmh = true +} + +local ret = { client = require("awful.client"); completion = require("awful.completion"); layout = require("awful.layout"); @@ -30,11 +33,21 @@ return wibox = require("awful.wibox"); startup_notification = require("awful.startup_notification"); tooltip = require("awful.tooltip"); - ewmh = require("awful.ewmh"); + permissions = require("awful.permissions"); titlebar = require("awful.titlebar"); rules = require("awful.rules"); popup = require("awful.popup"); spawn = require("awful.spawn"); } +-- Lazy load deprecated modules to reduce the numbers of loop dependencies. +return setmetatable(ret,{ + __index = function(_, key) + if deprecated[key] then + rawset(ret, key, require("awful."..key)) + end + return rawget(ret, key) + end +}) + -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/awful/permissions/init.lua b/lib/awful/permissions/init.lua new file mode 100644 index 000000000..3b284b3cc --- /dev/null +++ b/lib/awful/permissions/init.lua @@ -0,0 +1,724 @@ +--------------------------------------------------------------------------- +--- Implements EWMH requests handling. +-- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2009 Julien Danjou +-- @module awful.permissions +--------------------------------------------------------------------------- + +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") +local asuit = require("awful.layout.suit") +local beautiful = require("beautiful") +local alayout = require("awful.layout") +local atag = require("awful.tag") +local gdebug = require("gears.debug") +local pcommon = require("awful.permissions._common") + +local permissions = { + generic_activate_filters = {}, + contextual_activate_filters = {}, +} + +--- Honor the screen padding when maximizing. +-- @beautiful beautiful.maximized_honor_padding +-- @tparam[opt=true] boolean maximized_honor_padding + +--- Hide the border on fullscreen clients. +-- @beautiful beautiful.fullscreen_hide_border +-- @tparam[opt=true] boolean fullscreen_hide_border + +--- Hide the border on maximized clients. +-- @beautiful beautiful.maximized_hide_border +-- @tparam[opt=false] boolean maximized_hide_border + +--- The list of all registered generic request::activate (focus stealing) +-- filters. If a filter is added to only one context, it will be in +-- `permissions.contextual_activate_filters`["context_name"]. +-- @table[opt={}] generic_activate_filters +-- @see permissions.activate +-- @see permissions.add_activate_filter +-- @see permissions.remove_activate_filter + +--- The list of all registered contextual request::activate (focus stealing) +-- filters. If a filter is added to only one context, it will be in +-- `permissions.generic_activate_filters`. +-- @table[opt={}] contextual_activate_filters +-- @see permissions.activate +-- @see permissions.add_activate_filter +-- @see permissions.remove_activate_filter + +--- Update a client's settings when its geometry changes, skipping signals +-- resulting from calls within. +local repair_geometry_lock = false + +local function repair_geometry(window) + if repair_geometry_lock then return end + repair_geometry_lock = true + + -- Re-apply the geometry locking properties to what they should be. + for _, prop in ipairs { + "fullscreen", "maximized", "maximized_vertical", "maximized_horizontal" + } do + if window[prop] then + window:emit_signal("request::geometry", prop, { + store_geometry = false + }) + break + end + end + + repair_geometry_lock = false +end + +local function filter_sticky(c) + return not c.sticky and aclient.focus.filter(c) +end + +--- Give focus when clients appear/disappear. +-- +-- @param obj An object that should have a .screen property. +local function check_focus(obj) + if (not obj.screen) or not obj.screen.valid then return end + -- When no visible client has the focus... + if not client.focus or not client.focus:isvisible() then + local c = aclient.focus.history.get(screen[obj.screen], 0, filter_sticky) + if not c then + c = aclient.focus.history.get(screen[obj.screen], 0, aclient.focus.filter) + end + if c then + c:emit_signal( + "request::autoactivate", + "history", + {raise=false} + ) + end + end +end + +--- Check client focus (delayed). +-- @param obj An object that should have a .screen property. +local function check_focus_delayed(obj) + timer.delayed_call(check_focus, {screen = obj.screen}) +end + +--- Give focus on tag selection change. +-- +-- @tparam tag t A tag object +local function check_focus_tag(t) + local s = t.screen + if (not s) or (not s.valid) then return end + s = screen[s] + check_focus({ screen = s }) + if client.focus and screen[client.focus.screen] ~= s then + local c = aclient.focus.history.get(s, 0, filter_sticky) + if not c then + c = aclient.focus.history.get(s, 0, aclient.focus.filter) + end + if c then + c:emit_signal( + "request::autoactivate", + "switch_tag", + {raise=false} + ) + end + end +end + +--- Activate a window. +-- +-- This sets the focus only if the client is visible. +-- +-- It is the default signal handler for `request::activate` on a `client`. +-- +-- @signalhandler awful.permissions.activate +-- @client c A client to use +-- @tparam string context The context where this signal was used. +-- @tparam[opt] table hints A table with additional hints: +-- @tparam[opt=false] boolean hints.raise should the client be raised? +-- @tparam[opt=false] boolean hints.switch_to_tag should the client's first tag +-- be selected if none of the client's tags are selected? +-- @tparam[opt=false] boolean hints.switch_to_tags Select all tags associated +-- with the client. +function permissions.activate(c, context, hints) -- luacheck: no unused args + hints = hints or {} + + if c.focusable == false and not hints.force then + if hints.raise then + c:raise() + end + + return + end + + local found, ret = false + + -- Execute the filters until something handle the request + for _, tab in ipairs { + permissions.contextual_activate_filters[context] or {}, + permissions.generic_activate_filters + } do + for i=#tab, 1, -1 do + ret = tab[i](c, context, hints) + if ret ~= nil then found=true; break end + end + + if found then break end + end + + -- Minimized clients can be requested to have focus by, for example, 3rd + -- party toolbars and they might not try to unminimize it first. + if ret ~= false and hints.raise then + c.minimized = false + end + + if ret ~= false and c:isvisible() then + client.focus = c + elseif ret == false and not hints.force then + return + end + + if hints.raise then + c:raise() + if not awesome.startup and not c:isvisible() then + c.urgent = true + end + end + + -- The rules use `switchtotag`. For consistency and code re-use, support it, + -- but keep undocumented. --TODO v5 remove switchtotag + if hints.switchtotag or hints.switch_to_tag or hints.switch_to_tags then + atag.viewmore(c:tags(), c.screen, (not hints.switch_to_tags) and 0 or nil) + end +end + +--- Add an activate (focus stealing) filter function. +-- +-- The callback takes the following parameters: +-- +-- * **c** (*client*) The client requesting the activation +-- * **context** (*string*) The activation context. +-- * **hints** (*table*) Some additional hints (depending on the context) +-- +-- If the callback returns `true`, the client will be activated. If the callback +-- returns `false`, the activation request is cancelled unless the `force` hint is +-- set. If the callback returns `nil`, the previous callback will be executed. +-- This will continue until either a callback handles the request or when it runs +-- out of callbacks. In that case, the request will be granted if the client is +-- visible. +-- +-- For example, to block Firefox from stealing the focus, use: +-- +-- awful.permissions.add_activate_filter(function(c) +-- if c.class == "Firefox" then return false end +-- end, "permissions") +-- +-- @tparam function f The callback +-- @tparam[opt] string context The `request::activate` context +-- @see generic_activate_filters +-- @see contextual_activate_filters +-- @see remove_activate_filter +-- @staticfct awful.permissions.add_activate_filter +function permissions.add_activate_filter(f, context) + if not context then + table.insert(permissions.generic_activate_filters, f) + else + permissions.contextual_activate_filters[context] = + permissions.contextual_activate_filters[context] or {} + + table.insert(permissions.contextual_activate_filters[context], f) + end +end + +--- Remove an activate (focus stealing) filter function. +-- This is an helper to avoid dealing with `permissions.add_activate_filter` directly. +-- @tparam function f The callback +-- @tparam[opt] string context The `request::activate` context +-- @treturn boolean If the callback existed +-- @see generic_activate_filters +-- @see contextual_activate_filters +-- @see add_activate_filter +-- @staticfct awful.permissions.remove_activate_filter +function permissions.remove_activate_filter(f, context) + local tab = context and (permissions.contextual_activate_filters[context] or {}) + or permissions.generic_activate_filters + + for k, v in ipairs(tab) do + if v == f then + table.remove(tab, k) + + -- In case the callback is there multiple time. + permissions.remove_activate_filter(f, context) + + return true + end + end + + return false +end + +-- Get tags that are on the same screen as the client. This should _almost_ +-- always return the same content as c:tags(). +local function get_valid_tags(c, s) + local tags, new_tags = c:tags(), {} + + for _, t in ipairs(tags) do + if s == t.screen then + table.insert(new_tags, t) + end + end + + return new_tags +end + +--- Tag a window with its requested tag. +-- +-- It is the default signal handler for `request::tag` on a `client`. +-- +-- @signalhandler awful.permissions.tag +-- @client c A client to tag +-- @tparam[opt] tag|boolean t A tag to use. If true, then the client is made sticky. +-- @tparam[opt={}] table hints Extra information +function permissions.tag(c, t, hints) --luacheck: no unused + -- There is nothing to do + if not t and #get_valid_tags(c, c.screen) > 0 then return end + + if not t then + if c.transient_for and not (hints and hints.reason == "screen") then + c.screen = c.transient_for.screen + if not c.sticky then + local tags = c.transient_for:tags() + c:tags(#tags > 0 and tags or c.transient_for.screen.selected_tags) + end + else + c:to_selected_tags() + end + elseif type(t) == "boolean" and t then + c.sticky = true + else + c.screen = t.screen + c:tags({ t }) + end +end + +--- Handle client urgent request +-- @signalhandler awful.permissions.urgent +-- @client c A client +-- @tparam boolean urgent If the client should be urgent +function permissions.urgent(c, urgent) + if c ~= client.focus and not aclient.property.get(c,"ignore_urgent") then + c.urgent = urgent + end +end + +-- Map the state to the action name +local context_mapper = { + maximized_vertical = "maximize_vertically", + maximized_horizontal = "maximize_horizontally", + maximized = "maximize", + fullscreen = "maximize" +} + +--- Move and resize the client. +-- +-- This is the default geometry request handler. +-- +-- @signalhandler awful.permissions.geometry +-- @tparam client c The client +-- @tparam string context The context +-- @tparam[opt={}] table hints The hints to pass to the handler +function permissions.geometry(c, context, hints) + local layout = c.screen.selected_tag and c.screen.selected_tag.layout or nil + + -- Setting the geometry will not work unless the client is floating. + if (not c.floating) and (not layout == asuit.floating) then + return + end + + context = context or "" + + local original_context = context + + -- Now, map it to something useful + context = context_mapper[context] or context + + local props = gtable.clone(hints or {}, false) + props.store_geometry = props.store_geometry==nil and true or props.store_geometry + + -- If it is a known placement function, then apply it, otherwise let + -- other potential handler resize the client (like in-layout resize or + -- floating client resize) + if aplace[context] then + + -- Check if it corresponds to a boolean property. + local state = c[original_context] + + -- If the property is boolean and it corresponds to the undo operation, + -- restore the stored geometry. + if state == false then + local original = repair_geometry_lock + repair_geometry_lock = true + aplace.restore(c, {context=context}) + repair_geometry_lock = original + return + end + + local honor_default = original_context ~= "fullscreen" + + if props.honor_workarea == nil then + props.honor_workarea = honor_default + end + + if props.honor_padding == nil and props.honor_workarea and context:match("maximize") then + props.honor_padding = beautiful.maximized_honor_padding ~= false + end + + if (original_context == "fullscreen" and beautiful.fullscreen_hide_border ~= false) or + (original_context == "maximized" and beautiful.maximized_hide_border == true) then + props.ignore_border_width = true + props.zap_border_width = true + end + + local original = repair_geometry_lock + repair_geometry_lock = true + aplace[context](c, props) + repair_geometry_lock = original + 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.permissions.merge_maximization +-- @tparam client c The client +-- @tparam string context The context +-- @tparam[opt={}] table hints The hints to pass to the handler +function permissions.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() + -- Computes the actual X11 atoms before/after + local before_max_h = c.maximized or c.maximized_horizontal + local before_max_v = c.maximized or c.maximized_vertical + local after_max_h, after_max_v + if c._delayed_max_h ~= nil then + after_max_h = c._delayed_max_h + else + after_max_h = before_max_h + end + if c._delayed_max_v ~= nil then + after_max_v = c._delayed_max_v + else + after_max_v = before_max_v + end + -- Interprets the client's intention based on the client's view + if after_max_h and after_max_v then + c.maximized = true + elseif before_max_h and before_max_v then + -- At this point, c.maximized must be true, and the client is + -- trying to unmaximize the window, and potentially partial + -- maximized the window + c.maximized = false + if after_max_h ~= after_max_v then + c.maximized_horizontal = after_max_h + c.maximized_vertical = after_max_v + end + else + -- At this point, c.maximized must be false, and the client is + -- not trying to fully maximize the window + c.maximized_horizontal = after_max_h + c.maximized_vertical = after_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"] or 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 `permissions`. +-- +-- @signalhandler awful.permissions.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 permissions.client_geometry_requests(c, context, hints) + if context == "ewmh" and hints then + if c.immobilized_horizontal then + hints = gtable.clone(hints) + hints.x = nil + hints.width = nil + end + if c.immobilized_vertical then + hints = gtable.clone(hints) + hints.y = nil + hints.height = nil + end + c:geometry(hints) + end +end + +-- The magnifier layout doesn't work with focus follow mouse. +permissions.add_activate_filter(function(c) + if alayout.get(c.screen) ~= alayout.suit.magnifier + and aclient.focus.filter(c) then + return nil + else + return false + end +end, "mouse_enter") + +--- The default client `request::border` handler. +-- +-- To replace this handler with your own, use: +-- +-- client.disconnect_signal("request::border", awful.ewmh.update_border) +-- +-- The default implementation chooses from dozens beautiful theme variables +-- depending if the client is tiled, floating, maximized and then from its state +-- (urgent, new, active, normal) +-- +-- @signalhandler awful.ewmh.update_border +-- @usebeautiful beautiful.border_color_active +-- @usebeautiful beautiful.border_color_normal +-- @usebeautiful beautiful.border_color_new +-- @usebeautiful beautiful.border_color_urgent +-- @usebeautiful beautiful.border_color_floating +-- @usebeautiful beautiful.border_color_floating_active +-- @usebeautiful beautiful.border_color_floating_normal +-- @usebeautiful beautiful.border_color_floating_new +-- @usebeautiful beautiful.border_color_floating_urgent +-- @usebeautiful beautiful.border_color_maximized +-- @usebeautiful beautiful.border_color_maximized_active +-- @usebeautiful beautiful.border_color_maximized_normal +-- @usebeautiful beautiful.border_color_maximized_new +-- @usebeautiful beautiful.border_color_maximized_urgent +-- @usebeautiful beautiful.border_color_fullscreen +-- @usebeautiful beautiful.border_color_fullscreen_active +-- @usebeautiful beautiful.border_color_fullscreen_normal +-- @usebeautiful beautiful.border_color_fullscreen_new +-- @usebeautiful beautiful.border_color_fullscreen_urgent +-- @usebeautiful beautiful.border_width_active +-- @usebeautiful beautiful.border_width_normal +-- @usebeautiful beautiful.border_width_new +-- @usebeautiful beautiful.border_width_urgent +-- @usebeautiful beautiful.border_width_floating +-- @usebeautiful beautiful.border_width_floating_active +-- @usebeautiful beautiful.border_width_floating_normal +-- @usebeautiful beautiful.border_width_floating_new +-- @usebeautiful beautiful.border_width_floating_urgent +-- @usebeautiful beautiful.border_width_maximized +-- @usebeautiful beautiful.border_width_maximized_active +-- @usebeautiful beautiful.border_width_maximized_normal +-- @usebeautiful beautiful.border_width_maximized_new +-- @usebeautiful beautiful.border_width_maximized_urgent +-- @usebeautiful beautiful.border_width_fullscreen +-- @usebeautiful beautiful.border_width_fullscreen_active +-- @usebeautiful beautiful.border_width_fullscreen_normal +-- @usebeautiful beautiful.border_width_fullscreen_new +-- @usebeautiful beautiful.border_width_fullscreen_urgent +-- @usebeautiful beautiful.opacity_floating +-- @usebeautiful beautiful.opacity_floating_active +-- @usebeautiful beautiful.opacity_floating_normal +-- @usebeautiful beautiful.opacity_floating_new +-- @usebeautiful beautiful.opacity_floating_urgent +-- @usebeautiful beautiful.opacity_maximized +-- @usebeautiful beautiful.opacity_maximized_active +-- @usebeautiful beautiful.opacity_maximized_normal +-- @usebeautiful beautiful.opacity_maximized_new +-- @usebeautiful beautiful.opacity_maximized_urgent +-- @usebeautiful beautiful.opacity_fullscreen +-- @usebeautiful beautiful.opacity_fullscreen_active +-- @usebeautiful beautiful.opacity_fullscreen_normal +-- @usebeautiful beautiful.opacity_fullscreen_new +-- @usebeautiful beautiful.opacity_fullscreen_urgent +-- @usebeautiful beautiful.opacity_active +-- @usebeautiful beautiful.opacity_normal +-- @usebeautiful beautiful.opacity_new +-- @usebeautiful beautiful.opacity_urgent + +function permissions.update_border(c, context) + if not pcommon.check(c, "client", "border", context) then return end + + local suffix, fallback = "", "" + + -- Add the sub-namespace. + if c.fullscreen then + suffix, fallback = "_fullscreen", "_fullscreen" + elseif c.maximized then + suffix, fallback = "_maximized", "_maximized" + elseif c.floating then + suffix, fallback = "_floating", "_floating" + end + + -- Add the state suffix. + if c.urgent then + suffix = suffix .. "_urgent" + elseif c.active then + suffix = suffix .. "_active" + elseif context == "added" then + suffix = suffix .. "_new" + else + suffix = suffix .. "_normal" + end + + if not c._private._user_border_width then + c._border_width = beautiful["border_width"..suffix] + or beautiful["border_width"..fallback] + or beautiful.border_width + end + + if not c._private._user_border_color then + -- First, check marked clients. This is a concept that should probably + -- never have been added to the core. The documentation claims it works, + -- even if it has been broken for 90% of AwesomeWM releases ever since + -- it was added. + if c.marked and beautiful.border_marked then + c._border_color = beautiful.border_marked + return + end + + local tv = beautiful["border_color"..suffix] + + if fallback ~= "" and not tv then + tv = beautiful["border_color"..fallback] + end + + -- The old theme variable did not have "color" in its name. + if (not tv) and beautiful.border_normal and (not c.active) then + gdebug.deprecate( + "Use `beautiful.border_color_normal` instead of `beautiful.border_normal`", + {deprecated_in=5} + ) + tv = beautiful.border_normal + elseif (not tv) and beautiful.border_focus then + gdebug.deprecate( + "Use `beautiful.border_color_active` instead of `beautiful.border_focus`", + {deprecated_in=5} + ) + tv = beautiful.border_focus + end + + if not tv then + tv = beautiful.border_color + end + + if tv then + c._border_color = tv + end + end + + if not c._private._user_opacity then + local tv = beautiful["opacity"..suffix] + + if fallback ~= "" and not tv then + tv = beautiful["opacity"..fallback] + end + + if tv then + c._opacity = tv + end + end +end + +local activate_context_map = { + mouse_enter = "mouse.enter", + switch_tag = "autofocus.check_focus_tag", + history = "autofocus.check_focus" +} + +--- Default handler for the `request::autoactivate` signal. +-- +-- All it does is to emit `request::activate` with the following context +-- mapping: +-- +-- * mouse_enter: *mouse.enter* +-- * switch_tag : *autofocus.check_focus_tag* +-- * history : *autofocus.check_focus* +-- +-- @signalhandler awful.permissions.autoactivate +function permissions.autoactivate(c, context, args) + if not pcommon.check("client", "autoactivate", context) then return end + + local ctx = activate_context_map[context] and + activate_context_map[context] or context + + c:emit_signal("request::activate", ctx, args) +end + +client.connect_signal("request::autoactivate" , permissions.autoactivate) +client.connect_signal("request::border" , permissions.update_border) +client.connect_signal("request::activate" , permissions.activate) +client.connect_signal("request::tag" , permissions.tag) +client.connect_signal("request::urgent" , permissions.urgent) +client.connect_signal("request::geometry" , permissions.geometry) +client.connect_signal("request::geometry" , permissions.merge_maximization) +client.connect_signal("request::geometry" , permissions.client_geometry_requests) +client.connect_signal("property::border_width" , repair_geometry) +client.connect_signal("property::screen" , repair_geometry) +client.connect_signal("request::unmanage" , check_focus_delayed) +client.connect_signal("tagged" , check_focus_delayed) +client.connect_signal("untagged" , check_focus_delayed) +client.connect_signal("property::hidden" , check_focus_delayed) +client.connect_signal("property::minimized" , check_focus_delayed) +client.connect_signal("property::sticky" , check_focus_delayed) + +tag.connect_signal("property::selected", function (t) + timer.delayed_call(check_focus_tag, t) +end) + +screen.connect_signal("property::workarea", function(s) + for _, c in pairs(client.get(s)) do + repair_geometry(c) + end +end) + +-- Enable sloppy focus, so that focus follows mouse. +client.connect_signal("mouse::enter", function(c) + c:emit_signal("request::autoactivate", "mouse_enter", {raise=false}) +end) + +return permissions + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/spec/awful/ewmh_spec.lua b/spec/awful/permissions_spec.lua similarity index 71% rename from spec/awful/ewmh_spec.lua rename to spec/awful/permissions_spec.lua index 67c0328fa..fb58f3f93 100644 --- a/spec/awful/ewmh_spec.lua +++ b/spec/awful/permissions_spec.lua @@ -1,4 +1,4 @@ -describe("awful.ewmh.client_geometry_requests", function() +describe("awful.permissions.client_geometry_requests", function() package.loaded["awful.client"] = {} package.loaded["awful.layout"] = {} package.loaded["awful.screen"] = {} @@ -15,35 +15,35 @@ describe("awful.ewmh.client_geometry_requests", function() connect_signal = function() end, } - local ewmh = require("awful.ewmh") + local permissions = require("awful.permissions") it("removes x/y/width/height when immobilized", function() local c = {} local s = stub.new(c, "geometry") - ewmh.client_geometry_requests(c, "ewmh", {}) + permissions.client_geometry_requests(c, "ewmh", {}) assert.stub(s).was_called_with(c, {}) - ewmh.client_geometry_requests(c, "ewmh", {x=0, width=400}) + permissions.client_geometry_requests(c, "ewmh", {x=0, width=400}) assert.stub(s).was_called_with(c, {x=0, width=400}) c.immobilized_horizontal = true c.immobilized_vertical = false - ewmh.client_geometry_requests(c, "ewmh", {x=0, width=400}) + permissions.client_geometry_requests(c, "ewmh", {x=0, width=400}) assert.stub(s).was_called_with(c, {}) - ewmh.client_geometry_requests(c, "ewmh", {x=0, width=400, y=0}) + permissions.client_geometry_requests(c, "ewmh", {x=0, width=400, y=0}) assert.stub(s).was_called_with(c, {y=0}) c.immobilized_horizontal = true c.immobilized_vertical = true - ewmh.client_geometry_requests(c, "ewmh", {x=0, width=400, y=0}) + permissions.client_geometry_requests(c, "ewmh", {x=0, width=400, y=0}) assert.stub(s).was_called_with(c, {}) c.immobilized_horizontal = false c.immobilized_vertical = true local hints = {x=0, width=400, y=0} - ewmh.client_geometry_requests(c, "ewmh", hints) + permissions.client_geometry_requests(c, "ewmh", hints) assert.stub(s).was_called_with(c, {x=0, width=400}) -- Table passed as argument should not have been modified. assert.is.same(hints, {x=0, width=400, y=0}) diff --git a/tests/examples/sequences/client/fullscreen.lua b/tests/examples/sequences/client/fullscreen.lua index 3ac6744b6..c7eea1d16 100644 --- a/tests/examples/sequences/client/fullscreen.lua +++ b/tests/examples/sequences/client/fullscreen.lua @@ -2,7 +2,7 @@ local module = ... --DOC_HIDE local awful = {tag = require("awful.tag"), layout = require("awful.layout")} --DOC_HIDE awful.placement = require("awful.placement") --DOC_HIDE -require("awful.ewmh") --DOC_HIDE +require("awful.permissions") --DOC_HIDE screen[1]:fake_resize(0, 0, 1024/2, 768/2) --DOC_HIDE screen.fake_add(1034/2, 0, 1024/2, 768/2).outputs = {["eVGA1"] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE screen.fake_add(2074/2, 0, 1024/2, 768/2).outputs = {["DVI1" ] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE diff --git a/tests/examples/sequences/client/maximized.lua b/tests/examples/sequences/client/maximized.lua index c6370605d..7a778f2bd 100644 --- a/tests/examples/sequences/client/maximized.lua +++ b/tests/examples/sequences/client/maximized.lua @@ -2,7 +2,7 @@ local module = ... --DOC_HIDE local awful = {tag = require("awful.tag"), layout = require("awful.layout")} --DOC_HIDE awful.placement = require("awful.placement") --DOC_HIDE -require("awful.ewmh") --DOC_HIDE +require("awful.permissions") --DOC_HIDE screen[1]:fake_resize(0, 0, 1024/2, 768/2) --DOC_HIDE screen.fake_add(1034/2, 0, 1024/2, 768/2).outputs = {["eVGA1"] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE screen.fake_add(2074/2, 0, 1024/2, 768/2).outputs = {["DVI1" ] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE diff --git a/tests/examples/sequences/client/maximized_horizontal.lua b/tests/examples/sequences/client/maximized_horizontal.lua index fe642fab2..a655de52b 100644 --- a/tests/examples/sequences/client/maximized_horizontal.lua +++ b/tests/examples/sequences/client/maximized_horizontal.lua @@ -2,7 +2,7 @@ local module = ... --DOC_HIDE local awful = {tag = require("awful.tag"), layout = require("awful.layout")} --DOC_HIDE awful.placement = require("awful.placement") --DOC_HIDE -require("awful.ewmh") --DOC_HIDE +require("awful.permissions") --DOC_HIDE screen[1]:fake_resize(0, 0, 1024/2, 768/2) --DOC_HIDE screen.fake_add(1034/2, 0, 1024/2, 768/2).outputs = {["eVGA1"] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE screen.fake_add(2074/2, 0, 1024/2, 768/2).outputs = {["DVI1" ] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE diff --git a/tests/examples/sequences/client/maximized_vertical.lua b/tests/examples/sequences/client/maximized_vertical.lua index 8573eaa2f..8a15a6c02 100644 --- a/tests/examples/sequences/client/maximized_vertical.lua +++ b/tests/examples/sequences/client/maximized_vertical.lua @@ -2,7 +2,7 @@ local module = ... --DOC_HIDE local awful = {tag = require("awful.tag"), layout = require("awful.layout")} --DOC_HIDE awful.placement = require("awful.placement") --DOC_HIDE -require("awful.ewmh") --DOC_HIDE +require("awful.permissions") --DOC_HIDE screen[1]:fake_resize(0, 0, 1024/2, 768/2) --DOC_HIDE screen.fake_add(1034/2, 0, 1024/2, 768/2).outputs = {["eVGA1"] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE screen.fake_add(2074/2, 0, 1024/2, 768/2).outputs = {["DVI1" ] = {mm_height=60/2, mm_width=80/2 }} --DOC_HIDE diff --git a/tests/test-awful-client.lua b/tests/test-awful-client.lua index 8f3db43d6..e69a7ca03 100644 --- a/tests/test-awful-client.lua +++ b/tests/test-awful-client.lua @@ -145,9 +145,9 @@ table.insert(steps, function() -- This should still be the case assert(c2.active) - original_count = #awful.ewmh.generic_activate_filters + original_count = #awful.permissions.generic_activate_filters - awful.ewmh.add_activate_filter(function(c) + awful.permissions.add_activate_filter(function(c) if c == c1 then return false end end) @@ -161,13 +161,13 @@ table.insert(steps, function() assert(c2.active) -- Test the remove function - awful.ewmh.remove_activate_filter(function() end) + awful.permissions.remove_activate_filter(function() end) - awful.ewmh.add_activate_filter(awful.ewmh.generic_activate_filters[1]) + awful.permissions.add_activate_filter(awful.permissions.generic_activate_filters[1]) - awful.ewmh.remove_activate_filter(awful.ewmh.generic_activate_filters[1]) + awful.permissions.remove_activate_filter(awful.permissions.generic_activate_filters[1]) - assert(original_count == #awful.ewmh.generic_activate_filters) + assert(original_count == #awful.permissions.generic_activate_filters) c1:emit_signal("request::activate", "i_said_so") diff --git a/tests/test-maximize.lua b/tests/test-maximize.lua index f3f249e8e..8f28b25d1 100644 --- a/tests/test-maximize.lua +++ b/tests/test-maximize.lua @@ -358,8 +358,8 @@ gears.table.merge(steps, { -- 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.disconnect_signal("request::geometry", awful.permissions.geometry) + client.disconnect_signal("request::geometry", awful.permissions.merge_maximization) client.connect_signal("request::geometry", geometry_handler) test_client(nil,nil,nil,nil,nil,{maximize_after=true}) From 668ed6135c0c344b65b9950870278a4a0aeafb24 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 17 Nov 2019 19:48:25 -0500 Subject: [PATCH 14/22] client: Add a `:grant()` and `:deny()` method for permissions. This is a lower level API than what most people will end up using (the rules), but it is useful enough to expose to the public API. --- lib/awful/client.lua | 17 +++++++++++++++++ lib/awful/permissions/_common.lua | 28 +++++++++++++++++++++++++++- lib/awful/permissions/init.lua | 10 +++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index fc06f6120..574e3dc1e 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -15,6 +15,7 @@ local grect = require("gears.geometry").rectangle local gmath = require("gears.math") local gtable = require("gears.table") local amousec = require("awful.mouse.client") +local pcommon = require("awful.permissions._common") local pairs = pairs local type = type local ipairs = ipairs @@ -1485,6 +1486,22 @@ function client.object.activate(c, args) end end +--- Grant a permission for a client. +-- +-- @method grant +-- @tparam string permission The permission name (just the name, no `request::`). +-- @tparam string context The reason why this permission is requested. +-- @see awful.permissions + +--- Deny a permission for a client. +-- +-- @method deny +-- @tparam string permission The permission name (just the name, no `request::`). +-- @tparam string context The reason why this permission is requested. +-- @see awful.permissions + +pcommon.setup_grant(client.object, "client") + --- Return true if the client is active (has focus). -- -- This property is **READ ONLY**. Use `c:activate { context = "myreason" }` diff --git a/lib/awful/permissions/_common.lua b/lib/awful/permissions/_common.lua index 435033603..368c95da6 100644 --- a/lib/awful/permissions/_common.lua +++ b/lib/awful/permissions/_common.lua @@ -19,11 +19,19 @@ local default_permissions = { } } -function module.check(class, request, context) +function module.check(object, class, request, context) if not default_permissions[class] then return true end if not default_permissions[class][request] then return true end if default_permissions[class][request][context] == nil then return true end + local ret = nil + + if object._private.permissions and object._private.permissions[request] then + ret = object._private.permissions[request][context] + end + + if ret ~= nil then return ret end + return default_permissions[class][request][context] end @@ -53,4 +61,22 @@ function module._deprecated_autofocus_in_use() module.set("client", "autoactivate", "history" , true) end +local function set_object_permission_common(self, request, context, v) + self._private.permissions = self._private.permissions or {} + if not self._private.permissions[request] then + self._private.permissions[request] = {} + end + self._private.permissions[request][context] = v +end + +-- Add the grant and deny methods to the objects. +function module.setup_grant(class, classname) -- luacheck: no unused + function class.grant(self, request, context) + set_object_permission_common(self, request, context, true) + end + function class.deny(self, request, context) + set_object_permission_common(self, request, context, false) + end +end + return module diff --git a/lib/awful/permissions/init.lua b/lib/awful/permissions/init.lua index 3b284b3cc..cf4fcab65 100644 --- a/lib/awful/permissions/init.lua +++ b/lib/awful/permissions/init.lua @@ -146,6 +146,8 @@ end -- @tparam[opt=false] boolean hints.switch_to_tags Select all tags associated -- with the client. function permissions.activate(c, context, hints) -- luacheck: no unused args + if not pcommon.check(c, "client", "activate", context) then return end + hints = hints or {} if c.focusable == false and not hints.force then @@ -333,6 +335,8 @@ local context_mapper = { -- @tparam string context The context -- @tparam[opt={}] table hints The hints to pass to the handler function permissions.geometry(c, context, hints) + if not pcommon.check(c, "client", "geometry", context) then return end + local layout = c.screen.selected_tag and c.screen.selected_tag.layout or nil -- Setting the geometry will not work unless the client is floating. @@ -403,6 +407,8 @@ end -- @tparam string context The context -- @tparam[opt={}] table hints The hints to pass to the handler function permissions.merge_maximization(c, context, hints) + if not pcommon.check(c, "client", "geometry", context) then return end + if context ~= "client_maximize_horizontal" and context ~= "client_maximize_vertical" then return end @@ -484,6 +490,8 @@ end -- @tparam string context The context -- @tparam[opt={}] table hints The hints to pass to the handler function permissions.client_geometry_requests(c, context, hints) + if not pcommon.check(c, "client", "geometry", context) then return end + if context == "ewmh" and hints then if c.immobilized_horizontal then hints = gtable.clone(hints) @@ -679,7 +687,7 @@ local activate_context_map = { -- -- @signalhandler awful.permissions.autoactivate function permissions.autoactivate(c, context, args) - if not pcommon.check("client", "autoactivate", context) then return end + if not pcommon.check(c, "client", "autoactivate", context) then return end local ctx = activate_context_map[context] and activate_context_map[context] or context From 6b427e73a8ec203c15ca42dc17090da175e0d553 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 30 Nov 2019 20:11:39 -0500 Subject: [PATCH 15/22] tag: Add a request::layouts signal and append/remove layout. --- lib/awful/layout/init.lua | 30 ++++++-- lib/awful/tag.lua | 70 +++++++++++++++++++ objects/tag.c | 9 +++ tests/examples/text/awful/layout/remove.lua | 23 ++++++ .../text/awful/layout/remove.output.txt | 5 ++ 5 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 tests/examples/text/awful/layout/remove.lua create mode 100644 tests/examples/text/awful/layout/remove.output.txt diff --git a/lib/awful/layout/init.lua b/lib/awful/layout/init.lua index 47bbcbf2b..c32b3286c 100644 --- a/lib/awful/layout/init.lua +++ b/lib/awful/layout/init.lua @@ -259,6 +259,8 @@ end --- Remove a layout from the list of default layouts. -- +-- @DOC_text_awful_layout_remove_EXAMPLE@ +-- -- @staticfct awful.layout.remove_default_layout -- @tparam layout to_remove A valid tag layout. -- @treturn boolean True if the layout was found and removed. @@ -455,13 +457,27 @@ local mt = { end, __newindex = function(_, key, value) if key == "layouts" then - gdebug.print_warning( - "`awful.layout.layouts` was set before `request::default_layouts` could ".. - "be called. Please use `awful.layout.append_default_layouts` to ".. - " avoid this problem" - ) - capi.tag.disconnect_signal("new", init_layouts) - init_layouts = nil + assert(type(value) == "table", "`awful.layout.layouts` needs a table.") + + -- Do not ask for layouts if they were already provided. + if init_layouts then + gdebug.print_warning( + "`awful.layout.layouts` was set before `request::default_layouts` could ".. + "be called. Please use `awful.layout.append_default_layouts` to ".. + " avoid this problem" + ) + + capi.tag.disconnect_signal("new", init_layouts) + init_layouts = nil + elseif #default_layouts > 0 then + gdebug.print_warning( + "`awful.layout.layouts` was set after `request::default_layouts` was ".. + "used to get the layouts. This is probably an accident. Use ".. + "`awful.layout.remove_default_layout` to get rid of this warning." + ) + end + + default_layouts = value else rawset(layout, key, value) end diff --git a/lib/awful/tag.lua b/lib/awful/tag.lua index 0d8867046..69123a4a9 100644 --- a/lib/awful/tag.lua +++ b/lib/awful/tag.lua @@ -867,6 +867,22 @@ function tag.object.get_layouts(self) local cls = custom_layouts(self) + -- Request some layouts. Maybe a new module was added? + if #cls == 0 and not tag.getproperty(self, "_layouts_requested") then + tag.setproperty(self, "_layouts_requested", true) + local old_count = #cls + self:emit_signal("request::layouts", "awful", {}) + + -- When request::layouts is used, assume it takes precedence over + -- the fallback. + if #cls > old_count then + tag.setproperty(self, "_layouts", gtable.clone(cls, false)) + return tag.getproperty(self, "_layouts") + end + + return tag.object.get_layouts(self) + end + -- Without the clone, the custom_layouts would grow return #cls > 0 and gtable.merge(gtable.clone(cls, false), alayout.layouts) or alayout.layouts @@ -882,6 +898,60 @@ function tag.object.set_layouts(self, layouts) self:emit_signal("property::layouts") end +function tag.object.append_layout(self, layout) + -- If the layouts are manually modified, don't request more. + tag.setproperty(self, "_layouts_requested", true) + + local cls = tag.getproperty(self, "_layouts") + + if not cls then + cls = custom_layouts(self) + end + + table.insert(cls, layout) + self:emit_signal("property::layouts") +end + +function tag.object.append_layouts(self, layouts) + -- If the layouts are manually modified, don't request more. + tag.setproperty(self, "_layouts_requested", true) + + local cls = tag.getproperty(self, "_layouts") + + if not cls then + cls = custom_layouts(self) + end + + for _, l in ipairs(layouts) do + table.insert(cls, l) + end + self:emit_signal("property::layouts") +end + +function tag.object.remove_layout(self, layout) + local cls = tag.getproperty(self, "_layouts") + + if not cls then + cls = custom_layouts(self) + end + + local pos = {} + for k, l in ipairs(cls) do + if l == layout then + table.insert(pos, k) + end + end + + if #pos > 0 then + for i=#pos, 1, -1 do + table.remove(cls, i) + end + self:emit_signal("property::layouts") + end + + return #pos > 0 +end + function tag.object.get_layout(t) local l = tag.getproperty(t, "layout") if l then return l end diff --git a/objects/tag.c b/objects/tag.c index de8e7a8e9..e0dbc6ff1 100644 --- a/objects/tag.c +++ b/objects/tag.c @@ -220,6 +220,15 @@ * @see awful.layout.remove_default_layout */ +/** This signals is emitted when a tag needs layouts for the first time. + * + * If no handler implement it, it will fallback to the content added by + * `request::default_layouts` + * + * @signal request::layouts + * @tparam string context The context (currently always "awful"). + * @tparam table hints A, currently empty, table with hints. + */ /** When a client gets tagged with this tag. * @signal tagged diff --git a/tests/examples/text/awful/layout/remove.lua b/tests/examples/text/awful/layout/remove.lua new file mode 100644 index 000000000..500f52435 --- /dev/null +++ b/tests/examples/text/awful/layout/remove.lua @@ -0,0 +1,23 @@ +--DOC_GEN_OUTPUT --DOC_HIDE +local awful = { layout = require("awful.layout"), --DOC_HIDE + suit= require("awful.layout.suit")} --DOC_HIDE + +awful.layout.append_default_layouts({ + awful.layout.suit.floating, + awful.layout.suit.tile, + awful.layout.suit.max, +}) + +for _, l in ipairs(awful.layout.layouts) do + print("Before:", l.name) +end + +--DOC_NEWLINE + +awful.layout.remove_default_layout(awful.layout.suit.tile) + +--DOC_NEWLINE + +for _, l in ipairs(awful.layout.layouts) do + print("After:", l.name) +end diff --git a/tests/examples/text/awful/layout/remove.output.txt b/tests/examples/text/awful/layout/remove.output.txt new file mode 100644 index 000000000..40d6558c0 --- /dev/null +++ b/tests/examples/text/awful/layout/remove.output.txt @@ -0,0 +1,5 @@ +Before: floating +Before: tile +Before: max +After: floating +After: max From 839dddee258cc7eaa3f7a3e4023f2b6df4c0cc67 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 30 Nov 2019 20:12:05 -0500 Subject: [PATCH 16/22] tests: Test the tag `request::layouts`. --- tests/test-awful-tag.lua | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test-awful-tag.lua b/tests/test-awful-tag.lua index 14d493edc..91ad2b443 100644 --- a/tests/test-awful-tag.lua +++ b/tests/test-awful-tag.lua @@ -1,5 +1,6 @@ local awful = require("awful") local gtable = require("gears.table") +local gdebug = require("gears.debug") local beautiful = require("beautiful") local function check_order() @@ -196,6 +197,26 @@ local steps = { return true end, + + -- Test adding and removing layouts. + function() + local t = mouse.screen.tags[9] + local count = #t.layouts + t:append_layout(awful.layout.suit.floating) + assert(#t.layouts == count + 1) + + t:append_layouts({ + awful.layout.suit.floating, + awful.layout.suit.floating, + }) + + assert(#t.layouts == count + 3) + + t:remove_layout(awful.layout.suit.floating) + assert(#t.layouts == count) + + return true + end } local multi_screen_steps = {} @@ -313,6 +334,27 @@ local ms = require("_multi_screen") ms.disable_wibox() ms(steps, multi_screen_steps) +-- Check deprecation. +table.insert(steps, function() + assert(#awful.layout.layouts > 0) + + local called1, called2 = false, false + gdebug.deprecate = function() called1 = true end + gdebug.print_warning = function() called2 = true end + + awful.layout.layouts = {} + + assert(called2) + assert(not called1) + assert(#awful.layout.layouts == 0) + + -- Test the random property setter. + awful.layout.foo = "bar" + assert(awful.layout.foo == "bar") + + return true +end) + require("_runner").run_steps(steps) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 From c5202a48708585cc33528065af8d1b1d28b1a6e0 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sat, 30 Nov 2019 23:57:47 -0500 Subject: [PATCH 17/22] tag: Add a context to request::select. --- ewmh.c | 3 ++- objects/tag.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ewmh.c b/ewmh.c index 429b96ee4..8a1dfcf1c 100644 --- a/ewmh.c +++ b/ewmh.c @@ -462,7 +462,8 @@ ewmh_process_client_message(xcb_client_message_event_t *ev) { lua_State *L = globalconf_get_lua_State(); luaA_object_push(L, globalconf.tags.tab[idx]); - luaA_object_emit_signal(L, -1, "request::select", 0); + lua_pushstring(L, "ewmh"); + luaA_object_emit_signal(L, -1, "request::select", 1); lua_pop(L, 1); } } diff --git a/objects/tag.c b/objects/tag.c index e0dbc6ff1..0bc37c706 100644 --- a/objects/tag.c +++ b/objects/tag.c @@ -203,8 +203,9 @@ #include "ewmh.h" #include "luaa.h" -/** +/** When a tag requests to be selected. * @signal request::select + * @tparam string context The reason why it was called. */ /** From e77dd01e5af040c756218fcac348af17347bd268 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 1 Dec 2019 00:01:05 -0500 Subject: [PATCH 18/22] Add more TOVOv5 for unfixable APIs. Another pull request at some point will add proper API levels, it will then become possible to fix these without breaking the API for everybody. However right now there is no way around the problems. --- ewmh.c | 5 +++++ lib/awful/tag.lua | 2 ++ property.c | 1 + 3 files changed, 8 insertions(+) diff --git a/ewmh.c b/ewmh.c index 8a1dfcf1c..ad7f51078 100644 --- a/ewmh.c +++ b/ewmh.c @@ -412,14 +412,17 @@ ewmh_process_state_atom(client_t *c, xcb_atom_t state, int set) { if(set == _NET_WM_STATE_REMOVE) { lua_pushboolean(L, false); + /*TODO v5: Add a context */ luaA_object_emit_signal(L, -2, "request::urgent", 1); } else if(set == _NET_WM_STATE_ADD) { lua_pushboolean(L, true); + /*TODO v5: Add a context */ luaA_object_emit_signal(L, -2, "request::urgent", 1); } else if(set == _NET_WM_STATE_TOGGLE) { lua_pushboolean(L, !c->urgent); + /*TODO v5: Add a context */ luaA_object_emit_signal(L, -2, "request::urgent", 1); } } @@ -436,6 +439,7 @@ ewmh_process_desktop(client_t *c, uint32_t desktop) { luaA_object_push(L, c); lua_pushboolean(L, true); + /*TODO v5: Move the context argument to arg1 */ luaA_object_emit_signal(L, -2, "request::tag", 1); /* Pop the client, arguments are already popped */ lua_pop(L, 1); @@ -444,6 +448,7 @@ ewmh_process_desktop(client_t *c, uint32_t desktop) { luaA_object_push(L, c); luaA_object_push(L, globalconf.tags.tab[idx]); + /*TODO v5: Move the context argument to arg1 */ luaA_object_emit_signal(L, -2, "request::tag", 1); /* Pop the client, arguments are already popped */ lua_pop(L, 1); diff --git a/lib/awful/tag.lua b/lib/awful/tag.lua index 69123a4a9..afbc02dcd 100644 --- a/lib/awful/tag.lua +++ b/lib/awful/tag.lua @@ -1697,6 +1697,7 @@ capi.client.connect_signal("property::screen", function(c) end if #new_tags == 0 then + --TODO v5: Add a context as first param c:emit_signal("request::tag", nil, {reason="screen"}) elseif #new_tags < #tags then c:tags(new_tags) @@ -1785,6 +1786,7 @@ capi.screen.connect_signal("removed", function(s) end -- Give other code yet another change to save clients for _, c in pairs(capi.client.get(s)) do + --TODO v5: Add a context as first param c:emit_signal("request::tag", nil, { reason = "screen-removed" }) end -- Then force all clients left to go somewhere random diff --git a/property.c b/property.c index a585ce915..7dee8eb2a 100644 --- a/property.c +++ b/property.c @@ -199,6 +199,7 @@ property_update_wm_hints(client_t *c, xcb_get_property_cookie_t cookie) luaA_object_push(L, c); + /*TODO v5: Add a context */ lua_pushboolean(L, xcb_icccm_wm_hints_get_urgency(&wmh)); luaA_object_emit_signal(L, -2, "request::urgent", 1); From 7705ef9f1e8c59b142a0a6d75da35222822bed60 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 1 Dec 2019 00:36:08 -0500 Subject: [PATCH 19/22] doc: Add a @classsignal tag. To be used to denote when a signal only exists on a class, rahter than on instances + class. --- docs/config.ld | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/config.ld b/docs/config.ld index c4c6c3078..5e835e644 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -353,6 +353,12 @@ add_custom_tag { hidden = true } +-- Define when a signal is only emitted on a class rather than on objects. +add_custom_tag { + name = "classsignal", + hidden = true, +} + -- Specify when this an item was deprecated. -- @deprecatedin 4.4 Optional message. add_custom_tag { From 257556793afe4fd0371c9f15568a4a2984470c59 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 1 Dec 2019 00:41:06 -0500 Subject: [PATCH 20/22] doc: Document the client request::default_keybindings signal. --- objects/client.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/objects/client.c b/objects/client.c index 6385d232e..9d31d2b64 100644 --- a/objects/client.c +++ b/objects/client.c @@ -312,6 +312,7 @@ * @signal request::default_mousebindings * @tparam string context The reason why the signal was sent (currently always * `startup`). + * @classsignal */ /** Emitted during startup to gather the default client keybindings. @@ -323,6 +324,19 @@ * @signal request::default_keybindings * @tparam string context The reason why the signal was sent (currently always * `startup`). + * @classsignal + */ + +/** Sent once when AwesomeWM starts to add default keybindings. + * + * Keybindings can be set directly on clients. Actually, older version of + * AwesomeWM did that through the rules. However this makes it impossible for + * auto-configured modules to add their own keybindings. Using the signals, + * `rc.lua` or any module can cleanly manage keybindings. + * + * @signal request::default_keybindings + * @tparam string context The context (currently always "startup"). + * @classsignal */ /** When a client gets tagged. From e208b81763c13ae8520c9e33d90fa9b75e0daf32 Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 1 Dec 2019 01:48:58 -0500 Subject: [PATCH 21/22] doc: Add a @request tag to document the request:: contexts. --- docs/config.ld | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/config.ld b/docs/config.ld index 5e835e644..35faea832 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -402,6 +402,20 @@ add_custom_tag { }, } +add_custom_tag { + name = "request", + title = "Requested actions or permissions", + params = { + { name = "class" }, + { name = "type" }, + { name = "context" }, + { name = "default" }, + }, + table = { + "Class", "Permission", "Context", "Default", "Description" + }, +} + -- More fitting section names kind_names={topic='Documentation', module='Libraries', script='Sample files'} @@ -507,12 +521,14 @@ local summarize = { emits = {index = 1, title = "signals" }, propemits = {index = 2, title = "signals" }, usebeautiful = {index = 3, title = "theme variables"}, - propbeautiful = {index = 4, title = "theme variables"} + propbeautiful = {index = 4, title = "theme variables"}, + request = {index = 5, title = "permissions" }, } local delimiter_for_tag = { usebeautiful = { "table class='widget_list' border=1", "table", "tr", "tr", {"Theme variable", "Usage"}}, propbeautiful = { "table class='widget_list' border=1", "table", "tr", "tr", {"Theme variable", "Usage"}}, + request = { "table class='widget_list' border=1", "table", "tr", "tr", {"Class", "Permission", "Context", "Default", "Description"}}, } -- Use the first word of the subtag content to map it to its tag. From 6ecab5f2f16db34dbe6d27bbb273c45b8b499e9d Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee Date: Sun, 1 Dec 2019 01:53:12 -0500 Subject: [PATCH 22/22] doc: Add documentation in each objects which emit request:: signals. --- lib/awful/client.lua | 19 +++++++- lib/awful/client/focus.lua | 8 ++++ lib/awful/client/urgent.lua | 5 +++ lib/awful/layout/init.lua | 2 +- lib/awful/menu.lua | 2 + lib/awful/mouse/client.lua | 6 ++- lib/awful/mouse/resize.lua | 1 - lib/awful/rules.lua | 2 + lib/awful/screen.lua | 20 ++++++--- lib/awful/spawn.lua | 2 + lib/awful/tag.lua | 5 ++- lib/awful/titlebar.lua | 15 ++++--- objects/client.c | 86 ++++++++++++++----------------------- objects/screen.c | 2 + objects/tag.c | 7 +++ 15 files changed, 114 insertions(+), 68 deletions(-) diff --git a/lib/awful/client.lua b/lib/awful/client.lua index 574e3dc1e..b142a3aa8 100644 --- a/lib/awful/client.lua +++ b/lib/awful/client.lua @@ -171,6 +171,8 @@ end -- first tag additionally) when the client is not visible. -- If it is a function, it will be called with the client and its first -- tag as arguments. +-- @request client activate client.jumpto granted When a client is activated +-- because `c:jump_to()` is called. function client.object.jump_to(self, merge) local s = get_screen(screen.focused()) -- focus the screen @@ -302,6 +304,8 @@ end -- @staticfct awful.client.swap.global_bydirection -- @param dir The direction, can be either "up", "down", "left" or "right". -- @client[opt] sel The client. +-- @request client activate client.swap.global_bydirection granted When a client +-- could be activated because `awful.client.swap.global_bydirection` was called. function client.swap.global_bydirection(dir, sel) sel = sel or capi.client.focus local scr = get_screen(sel and sel.screen or screen.focused()) @@ -463,6 +467,8 @@ end --- Move a client to a tag. -- @method move_to_tag -- @tparam tag target The tag to move the client to. +-- @request client activate client.movetotag granted When a client could be +-- activated because `c:move_to_tag()` was called. function client.object.move_to_tag(self, target) local s = target.screen if self and s then @@ -526,6 +532,8 @@ end -- @tparam[opt=c.screen.index+1] screen s The screen, default to current + 1. -- @see screen -- @see request::activate +-- @request client activate client.movetoscreen granted When a client could be +-- activated because `c:move_to_screen()` was called. function client.object.move_to_screen(self, s) if self then local sc = capi.screen.count() @@ -798,6 +806,7 @@ function client.floating.get(c) end --- The client floating state. +-- -- If the client is part of the tiled layout or free floating. -- -- Note that some windows might be floating even if you @@ -809,7 +818,13 @@ end -- * *property::floating* -- -- @property floating --- @param boolean The floating state +-- @tparam boolean floating The floating state. +-- @request client border floating granted When a border update is required +-- because the client focus status changed. +-- @request client border active granted When a client becomes active and is not +-- floating. +-- @request client border inactive granted When a client stop being active and +-- is not floating. function client.object.get_floating(c) c = c or capi.client.focus @@ -1554,6 +1569,8 @@ pcommon.setup_grant(client.object, "client") -- -- @property active -- @tparam boolean active +-- @request client border active granted When a client becomes active. +-- @request client border inactive granted When a client stop being active. -- @see activate -- @see request::activate -- @see awful.permissions.add_activate_filter diff --git a/lib/awful/client/focus.lua b/lib/awful/client/focus.lua index a8b04f2ee..4ba75221a 100644 --- a/lib/awful/client/focus.lua +++ b/lib/awful/client/focus.lua @@ -60,6 +60,8 @@ end -- @function awful.client.focus.byidx -- @param i The index. -- @client[opt] c The client. +-- @request client activate client.focus.byidx granted When `awful.focus.byidx` +-- is called. function focus.byidx(i, c) local target = client.next(i, c) if target then @@ -141,6 +143,8 @@ end --- Focus the previous client in history. -- @function awful.client.focus.history.previous +-- @request client activate client.focus.history.previous granted When +-- `awful.focus.history.previous` is called. function focus.history.previous() local sel = capi.client.focus local s = sel and sel.screen or screen.focused() @@ -158,6 +162,8 @@ end -- @client[opt] c The client. -- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom) -- @function awful.client.focus.bydirection +-- @request client activate client.focus.bydirection granted When +-- `awful.focus.bydirection` is called. function focus.bydirection(dir, c, stacked) local sel = c or capi.client.focus if sel then @@ -183,6 +189,8 @@ end -- @client[opt] c The client. -- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom) -- @function awful.client.focus.global_bydirection +-- @request client activate client.focus.global_bydirection granted When +-- `awful.client.focus.global_bydirection` is called. function focus.global_bydirection(dir, c, stacked) local sel = c or capi.client.focus local scr = get_screen(sel and sel.screen or screen.focused()) diff --git a/lib/awful/client/urgent.lua b/lib/awful/client/urgent.lua index f8f188284..dd1602d7a 100644 --- a/lib/awful/client/urgent.lua +++ b/lib/awful/client/urgent.lua @@ -62,6 +62,11 @@ end -- @function awful.urgent.add -- @client c The client object. -- @param prop The property which is updated. +-- @request client border active granted When a client becomes active and is no +-- longer urgent. +-- @request client border inactive granted When a client stop being active and +-- is no longer urgent. +-- @request client border urgent granted When a client stop becomes urgent. function urgent.add(c, prop) assert( c.urgent ~= nil, diff --git a/lib/awful/layout/init.lua b/lib/awful/layout/init.lua index c32b3286c..fb5e9fd5c 100644 --- a/lib/awful/layout/init.lua +++ b/lib/awful/layout/init.lua @@ -405,7 +405,7 @@ end) local init_layouts init_layouts = function() - capi.tag.emit_signal("request::default_layouts") + capi.tag.emit_signal("request::default_layouts", "startup") capi.tag.disconnect_signal("new", init_layouts) -- Fallback. diff --git a/lib/awful/menu.lua b/lib/awful/menu.lua index 369e63bd7..9cfe6b10c 100644 --- a/lib/awful/menu.lua +++ b/lib/awful/menu.lua @@ -521,6 +521,8 @@ end -- included in the menu. -- @return The menu. -- @constructorfct awful.menu.clients +-- @request client activate menu.clients granted When clicking on a clients menu +-- element. function menu.clients(args, item_args, filter) local cls_t = {} for c in client_iterate(filter or function() return true end) do diff --git a/lib/awful/mouse/client.lua b/lib/awful/mouse/client.lua index 0b1536d98..d6191b723 100644 --- a/lib/awful/mouse/client.lua +++ b/lib/awful/mouse/client.lua @@ -15,7 +15,9 @@ local module = {} -- @staticfct awful.mouse.client.move -- @param c The client to move, or the focused one if nil. -- @param snap The pixel to snap clients. --- @param finished_cb Deprecated, do not use +-- @param finished_cb Deprecated, do not use. +-- @request client geometry mouse.move granted When `awful.mouse.client.move` is +-- called. function module.move(c, snap, finished_cb) --luacheck: no unused args if finished_cb then gdebug.deprecate("The mouse.client.move `finished_cb` argument is no longer".. @@ -99,6 +101,8 @@ end -- @tparam string corner The corner to grab on resize. Auto detected by default. -- @tparam[opt={}] table args A set of `awful.placement` arguments -- @treturn string The corner (or side) name +-- @request client geometry mouse.resize granted When `awful.mouse.client.resize` +-- is called. function module.resize(c, corner, args) c = c or capi.client.focus diff --git a/lib/awful/mouse/resize.lua b/lib/awful/mouse/resize.lua index 16542e4e1..98981d33f 100644 --- a/lib/awful/mouse/resize.lua +++ b/lib/awful/mouse/resize.lua @@ -103,7 +103,6 @@ end -- @tparam client client A client. -- @tparam[default=mouse.resize] string context The resizing context. -- @tparam[opt={}] table args A set of `awful.placement` arguments. - local function handler(_, client, context, args) --luacheck: no unused_args args = args or {} context = context or "mouse.resize" diff --git a/lib/awful/rules.lua b/lib/awful/rules.lua index ba38cac82..2fe0ad249 100644 --- a/lib/awful/rules.lua +++ b/lib/awful/rules.lua @@ -529,6 +529,8 @@ end -- @tab props Properties to apply. -- @tab[opt] callbacks Callbacks to apply. -- @staticfct awful.rules.execute +-- @request client titlebars rules granted The `titlebars_enabled` is set in the +-- rules. crules._execute = function(_, c, props, callbacks) diff --git a/lib/awful/screen.lua b/lib/awful/screen.lua index 5c6a2c0f1..a1dfda78c 100644 --- a/lib/awful/screen.lua +++ b/lib/awful/screen.lua @@ -89,6 +89,8 @@ end -- or keeps its position relative to the current focused screen. -- @staticfct awful.screen.focus -- @screen _screen Screen number (defaults / falls back to mouse.screen). +-- @request client activate screen.focus granted The most recent focused client +-- for this screen should be re-activated. function screen.focus(_screen) client = client or require("awful.client") if type(_screen) == "number" and _screen > capi.screen.count() then _screen = screen.focused() end @@ -771,7 +773,9 @@ end -- The only default implementation is the one provided by `rc.lua`. -- -- @signal request::desktop_decoration --- @tparam screen s The screen object. +-- @tparam string context The context. +-- @request screen wallpaper added granted When the decorations needs to be +-- added to a new screen. --- Emitted when a new screen needs a wallpaper. -- @@ -782,7 +786,13 @@ end -- The only default implementation is the one provided by `rc.lua`. -- -- @signal request::wallpaper --- @tparam screen s The screen object. +-- @tparam string context The context. +-- @request screen wallpaper added granted When the wallpaper needs to be +-- added to a new screen. +-- @request screen wallpaper geometry granted When the wallpaper needs to be +-- updated because the resolution changed. +-- @request screen wallpaper dpi granted When the wallpaper needs to be +-- updated because the DPI changed. --- When a new (physical) screen area has been added. -- @@ -965,15 +975,15 @@ capi.screen.connect_signal("_added", function(s) -- metadata. Thus, the DPI may be wrong when setting the wallpaper. if s._managed ~= "Lua" then s:emit_signal("added") - s:emit_signal("request::desktop_decoration") - s:emit_signal("request::wallpaper") + s:emit_signal("request::desktop_decoration", "added") + s:emit_signal("request::wallpaper", "added") end end) -- Resize the wallpaper(s) for _, prop in ipairs {"geometry", "dpi" } do capi.screen.connect_signal("property::"..prop, function(s) - s:emit_signal("request::wallpaper") + s:emit_signal("request::wallpaper", prop) end) end diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua index b01c1c1af..2899636f3 100644 --- a/lib/awful/spawn.lua +++ b/lib/awful/spawn.lua @@ -712,6 +712,8 @@ local raise_rules = {focus = true, switch_to_tags = true, raise = true} -- @see awful.rules -- @treturn client The client if it already exists. -- @staticfct awful.spawn.raise_or_spawn +-- @request client activate spawn.raise_or_spawn granted Activate a client when +-- `awful.spawn.raise_or_spawn` is called and the client exists. function spawn.raise_or_spawn(cmd, rules, matcher, unique_id, callback) local hash = unique_id or hash_command(cmd, rules) diff --git a/lib/awful/tag.lua b/lib/awful/tag.lua index afbc02dcd..fe831453c 100644 --- a/lib/awful/tag.lua +++ b/lib/awful/tag.lua @@ -815,6 +815,8 @@ end -- -- @property layouts -- @param table +-- @request tag layouts awful granted When the `layouts` property is first called +-- and there is no layouts, then that signal is called. -- @see awful.layout.layouts -- @see layout @@ -1758,6 +1760,7 @@ capi.tag.connect_signal("request::select", tag.object.view_only) -- this, an handler for this request must simply set a new screen -- for the tag. -- @signal request::screen +-- @tparam string context Why it was called. --- Emitted after `request::screen` if no new screen has been set. -- The tag will be deleted, this is a last chance to move its clients @@ -1777,7 +1780,7 @@ end) capi.screen.connect_signal("removed", function(s) -- First give other code a chance to move the tag to another screen for _, t in pairs(s.tags) do - t:emit_signal("request::screen") + t:emit_signal("request::screen", "removed") end -- Everything that's left: Tell everyone that these tags go away (other code -- could e.g. save clients) diff --git a/lib/awful/titlebar.lua b/lib/awful/titlebar.lua index a1b86597a..734aecb27 100644 --- a/lib/awful/titlebar.lua +++ b/lib/awful/titlebar.lua @@ -470,12 +470,13 @@ end -- when `titlebars_enabled` is not set in the rules. -- @tparam client c The client. -- @tparam[opt=false] boolean hide_all Hide all titlebars except `keep` --- @tparam string keep Keep the titlebar at this position +-- @tparam string keep Keep the titlebar at this position. +-- @tparam string context The reason why this was called. -- @treturn boolean If the titlebars were loaded -local function load_titlebars(c, hide_all, keep) +local function load_titlebars(c, hide_all, keep, context) if c._request_titlebars_called then return false end - c:emit_signal("request::titlebars", "awful.titlebar", {}) + c:emit_signal("request::titlebars", context, {}) if hide_all then -- Don't bother checking if it has been created, `.hide` don't works @@ -582,9 +583,11 @@ end -- @param[opt] position The position of the titlebar. Must be one of "left", -- "right", "top", "bottom". Default is "top". -- @staticfct awful.titlebar.show +-- @request client titlebars show granted Called when `awful.titlebar.show` is +-- called. function titlebar.show(c, position) position = position or "top" - if load_titlebars(c, true, position) then return end + if load_titlebars(c, true, position, "show") then return end local bars = all_titlebars[c] local data = bars and bars[position] local args = data and data.args @@ -606,9 +609,11 @@ end -- @param[opt] position The position of the titlebar. Must be one of "left", -- "right", "top", "bottom". Default is "top". -- @staticfct awful.titlebar.toggle +-- @request client titlebars toggle granted Called when `awful.titlebar.toggle` is +-- called. function titlebar.toggle(c, position) position = position or "top" - if load_titlebars(c, true, position) then return end + if load_titlebars(c, true, position, "toggle") then return end local _, size = get_titlebar_function(c, position)(c) if size == 0 then titlebar.show(c, position) diff --git a/objects/client.c b/objects/client.c index 9d31d2b64..4178235b0 100644 --- a/objects/client.c +++ b/objects/client.c @@ -177,6 +177,8 @@ * or "startup". * @tparam table hints More metadata (currently empty, it exists for compliance * with the other `request::` signals). + * @request client border added granted When a new client needs a its initial + * border settings. */ /** When a client is going away. @@ -264,6 +266,7 @@ * @tparam string context The context where this signal was used. * @tparam[opt] table hints A table with additional hints: * @tparam[opt=false] boolean hints.raise should the client be raised? + * @request client activate ewmh granted When the client asks to be activated. */ /** When an event could lead to the client being activated. @@ -286,13 +289,18 @@ * */ -/** +/** When something request a client geometry to be modified. + * * @signal request::geometry * @tparam client c The client * @tparam string context Why and what to resize. This is used for the * handlers to know if they are capable of applying the new geometry. * @tparam[opt={}] table Additional arguments. Each context handler may * interpret this differently. + * @request client geometry client_maximize_horizontal granted When a client + * (programmatically) asks for the maximization to be changed. + * @request client geometry client_maximize_vertical granted When a client + * (programmatically) asks for the maximization to be changed. */ /** @@ -337,6 +345,7 @@ * @signal request::default_keybindings * @tparam string context The context (currently always "startup"). * @classsignal + * @request client default_keybindings startup granted Sent when AwesomeWM starts. */ /** When a client gets tagged. @@ -691,12 +700,11 @@ * * @DOC_sequences_client_fullscreen_EXAMPLE@ * - * **Signal:** - * - * * *property::fullscreen* - * * @property fullscreen - * @param boolean + * @tparam boolean fullscreen + * @propemits false false + * @request client geometry fullscreen granted When the client must be resized + * because it became (or stop being) fullscreen. */ /** @@ -704,13 +712,11 @@ * * @DOC_sequences_client_maximized_EXAMPLE@ * - * **Signal:** - * - * * *property::maximized* - * * @property maximized - * @param boolean + * @tparam boolean maximized * @propemits false false + * @request client geometry maximized granted When the client must be resized + * because it became (or stop being) maximized. * @see request::border */ @@ -719,12 +725,11 @@ * * @DOC_sequences_client_maximized_horizontal_EXAMPLE@ * - * **Signal:** - * - * * *property::maximized\_horizontal* - * * @property maximized_horizontal - * @param boolean + * @tparam boolean maximized_horizontal + * @propemits false false + * @request client geometry maximized_horizontal granted When the client must be resized + * because it became (or stop being) maximized horizontally. */ /** @@ -732,34 +737,27 @@ * * @DOC_sequences_client_maximized_vertical_EXAMPLE@ * - * **Signal:** - * - * * *property::maximized\_vertical* - * * @property maximized_vertical - * @param boolean + * @tparam boolean maximized_vertical + * @propemits false false + * @request client geometry maximized_vertical granted When the client must be resized + * because it became (or stop being) maximized vertically. */ /** * The client the window is transient for. * - * **Signal:** - * - * * *property::transient\_for* - * * @property transient_for * @param client + * @propemits false false */ /** * Window identification unique to a group of windows. * - * **Signal:** - * - * * *property::group\_window* - * * @property group_window * @param client + * @propemits false false */ /** @@ -771,10 +769,6 @@ /** * A table with size hints of the client. * - * **Signal:** - * - * * *property::size\_hints* - * * @property size_hints * @param table * @tfield integer table.user_position @@ -787,6 +781,7 @@ * @tfield integer table.min_height * @tfield integer table.width_inc * @tfield integer table.height_inc + * @propemits false false * @see size_hints_honor */ @@ -801,10 +796,6 @@ * "resize" and "all" are set, this means that all but the resize function * should be enabled. * - * **Signal:** - * - * * *property::motif\_wm\_hints* - * * @property motif_wm_hints * @param table * @tfield[opt] table table.functions @@ -825,51 +816,40 @@ * @tfield[opt] string table.input_mode * @tfield[opt] table table.status * @tfield[opt] boolean table.status.tearoff_window + * @propemits false false */ /** * Set the client sticky, i.e. available on all tags. * - * **Signal:** - * - * * *property::sticky* - * * @property sticky * @param boolean + * @propemits false false */ /** * Indicate if the client is modal. * - * **Signal:** - * - * * *property::modal* - * * @property modal * @param boolean + * @propemits false false */ /** * True if the client can receive the input focus. * - * **Signal:** - * - * * *property::focusable* - * * @property focusable * @param boolean + * @propemits false false */ /** * The client's bounding shape as set by awesome as a (native) cairo surface. * - * **Signal:** - * - * * *property::shape\_bounding* - * * @see gears.surface.apply_shape_bounding * @property shape_bounding * @param surface + * @propemits false false */ /** diff --git a/objects/screen.c b/objects/screen.c index fc3417c89..230683684 100644 --- a/objects/screen.c +++ b/objects/screen.c @@ -115,6 +115,8 @@ /** * This signal is emitted when a screen is removed from the setup. * @signal removed + * @request tag screen removed granted When a screen is removed, `request::screen` + * is called on all screen tags to try to relocate them. */ /** This signal is emitted when the list of available screens changes. diff --git a/objects/tag.c b/objects/tag.c index 0bc37c706..f4747071b 100644 --- a/objects/tag.c +++ b/objects/tag.c @@ -206,6 +206,9 @@ /** When a tag requests to be selected. * @signal request::select * @tparam string context The reason why it was called. + * @request tag select ewmh granted When the client request to be moved to a + * specific virtual desktop. AwesomeWM interprets virtual desktop as indexed + * tags. */ /** @@ -216,6 +219,10 @@ * to dynamically add new layouts to the list of default layouts. * * @signal request::default_layouts + * @tparam string context The context (currently always "startup"). + * @classsignal + * @request tag default_layouts startup granted When AwesomeWM starts, it queries + * for default layout using this request. * @see awful.layout.layouts * @see awful.layout.append_default_layout * @see awful.layout.remove_default_layout