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.
This commit is contained in:
Emmanuel Lepage Vallee 2019-11-16 18:51:15 -05:00 committed by Emmanuel Lepage-Vallee
parent 5ad0856fee
commit efc42b1be1
7 changed files with 188 additions and 76 deletions

View File

@ -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)

View File

@ -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',

View File

@ -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}
--)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")