awesome/lib/awful/client/focus.lua

219 lines
6.5 KiB
Lua

---------------------------------------------------------------------------
--- Keep track of the focused clients.
--
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @release @AWESOME_VERSION@
-- @submodule client
---------------------------------------------------------------------------
local util = require("awful.util")
local capi =
{
screen = screen,
client = client,
}
-- We use a metatable to prevent circular dependency loops.
local screen
do
screen = setmetatable({}, {
__index = function(_, k)
screen = require("awful.screen")
return screen[k]
end,
__newindex = error -- Just to be sure in case anything ever does this
})
end
local client
do
client = setmetatable({}, {
__index = function(_, k)
client = require("awful.client")
return client[k]
end,
__newindex = error -- Just to be sure in case anything ever does this
})
end
local focus = {history = {}}
local internal = {}
local function get_screen(s)
return s and capi.screen[s]
end
--- Remove a client from the focus history
--
-- @client c The client that must be removed.
-- @function awful.client.focus.history.delete
function focus.history.delete(c)
for k, v in ipairs(internal) do
if v == c then
table.remove(internal, k)
break
end
end
end
--- Focus a client by its relative index.
--
-- @function awful.client.focus.byidx
-- @param i The index.
-- @client[opt] c The client.
function focus.byidx(i, c)
local target = client.next(i, c)
if target then
target:emit_signal("request::activate", "client.focus.byidx",
{raise=true})
end
end
--- Filter out window that we do not want handled by focus.
-- This usually means that desktop, dock and splash windows are
-- not registered and cannot get focus.
--
-- @client c A client.
-- @return The same client if it's ok, nil otherwise.
-- @function awful.client.focus.filter
function focus.filter(c)
if c.type == "desktop"
or c.type == "dock"
or c.type == "splash"
or not c.focusable then
return nil
end
return c
end
--- Update client focus history.
--
-- @client c The client that has been focused.
-- @function awful.client.focus.history.add
function focus.history.add(c)
-- Remove the client if its in stack
focus.history.delete(c)
-- Record the client has latest focused
table.insert(internal, 1, c)
end
--- Get the latest focused client for a screen in history.
--
-- @tparam int|screen s The screen to look for.
-- @tparam int idx The index: 0 will return first candidate,
-- 1 will return second, etc.
-- @tparam function filter An optional filter. If no client is found in the
-- first iteration, `awful.client.focus.filter` is used by default to get any
-- client.
-- @treturn client.object A client.
-- @function awful.client.focus.history.get
function focus.history.get(s, idx, filter)
s = get_screen(s)
-- When this counter is equal to idx, we return the client
local counter = 0
local vc = client.visible(s, true)
for _, c in ipairs(internal) do
if get_screen(c.screen) == s then
if not filter or filter(c) then
for _, vcc in ipairs(vc) do
if vcc == c then
if counter == idx then
return c
end
-- We found one, increment the counter only.
counter = counter + 1
break
end
end
end
end
end
-- Argh nobody found in history, give the first one visible if there is one
-- that passes the filter.
filter = filter or focus.filter
if counter == 0 then
for _, v in ipairs(vc) do
if filter(v) then
return v
end
end
end
end
--- Focus the previous client in history.
-- @function awful.client.focus.history.previous
function focus.history.previous()
local sel = capi.client.focus
local s = sel and sel.screen or screen.focused()
local c = focus.history.get(s, 1)
if c then
c:emit_signal("request::activate", "client.focus.history.previous",
{raise=false})
end
end
--- Focus a client by the given direction.
--
-- @tparam string dir The direction, can be either
-- `"up"`, `"down"`, `"left"` or `"right"`.
-- @client[opt] c The client.
-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
-- @function awful.client.focus.bydirection
function focus.bydirection(dir, c, stacked)
local sel = c or capi.client.focus
if sel then
local cltbl = client.visible(sel.screen, stacked)
local geomtbl = {}
for i,cl in ipairs(cltbl) do
geomtbl[i] = cl:geometry()
end
local target = util.get_rectangle_in_direction(dir, geomtbl, sel:geometry())
-- If we found a client to focus, then do it.
if target then
cltbl[target]:emit_signal("request::activate",
"client.focus.bydirection", {raise=false})
end
end
end
--- Focus a client by the given direction. Moves across screens.
--
-- @param dir The direction, can be either "up", "down", "left" or "right".
-- @client[opt] c The client.
-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
-- @function awful.client.focus.global_bydirection
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())
-- change focus inside the screen
focus.bydirection(dir, sel)
-- if focus not changed, we must change screen
if sel == capi.client.focus then
screen.focus_bydirection(dir, scr)
if scr ~= get_screen(screen.focused()) then
local cltbl = client.visible(screen.focused(), stacked)
local geomtbl = {}
for i,cl in ipairs(cltbl) do
geomtbl[i] = cl:geometry()
end
local target = util.get_rectangle_in_direction(dir, geomtbl, scr.geometry)
if target then
cltbl[target]:emit_signal("request::activate",
"client.focus.global_bydirection",
{raise=false})
end
end
end
end
return focus
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80