summaryrefslogtreecommitdiff
path: root/lib/awful/client/focus.lua
blob: a8b04f2eeabb7413bcc7de51382bef556c4987ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
---------------------------------------------------------------------------
--- Keep track of the focused clients.
--
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @submodule client
---------------------------------------------------------------------------
local grect = require("gears.geometry").rectangle

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 = {list = {}}}

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(focus.history.list) do
        if v == c then
            table.remove(focus.history.list, 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(focus.history.list, 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(focus.history.list) 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 = grect.get_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 = grect.get_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