summaryrefslogtreecommitdiffstats
path: root/.config/awesome/bowl.lua
blob: bf6af7b63a624ee2b20e51fa1ed492779be5ec9e (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
-- -*- coding: utf-8 -*-
--------------------------------------------------------------------------------
-- @author Nicolas Berthier <nberthier@gmail.com>
-- @copyright 2010 Nicolas Berthier
--------------------------------------------------------------------------------
-- 
-- Bowls are kind of helpers that can be drawn (at the bottom --- for now) of an
-- area, and displaying the current key prefix. It is inspired by emacs'
-- behavior, that prints prefix keys in the minibuffer after a certain time.
--
-- I call it `bowl' as a reference to the bowl that one might have at home,
-- where one puts its actual keys... A more serious name would be `hint' or
-- `tooltip' (but they do not fit well for this usage).
--
-- Example usage: see `rc.lua' file.
--
--------------------------------------------------------------------------------

--{{{ Grab environment (mostly aliases)
local setmetatable = setmetatable
local ipairs = ipairs
local type = type
local pairs = pairs
local string = string
local print = print
local error = error

local capi = capi
local client = client
local awesome = awesome
local root = root
local timer = timer

local infoline = require ("infoline")
--}}}

module ("bowl")

-- Privata data: we use weak keys in order to allow collection of private data
-- if keys (clients) are collected (i.e., no longer used, after having been
-- killed for instance)
local data = setmetatable ({}, { __mode = 'k' })

--{{{ Default values

--- Default modifier filter
local modfilter = {
    ["Mod1"]    = "M",
    ["Mod4"]    = "S",
    ["Control"] = "C",
    ["Shift"]   = string.upper,
}

-- Timers configuration
local use_timers = true
local timeout = 2.0

--}}}

--{{{ Keychain pretty-printing

function mod_to_string (mods, k)
    local ret, k = "", k
    for _, mod in ipairs (mods) do
        if modfilter[mod] then
            local t = type (modfilter[mod])
            if t == "function" then
                k = modfilter[mod](k)
            elseif t == "string" then
                ret = ret .. modfilter[mod] .. "-"
            else
                error ("Invalid modifier key filter: got a " .. t)
            end
        else
            ret = ret .. mod .. "-"
        end
    end
    return ret, k
end

function ks_to_string (m, k)
    local m, k = mod_to_string (m, k)
    return m .. k
end

--}}}

--{{{ Timer management

local function delete_timer_maybe (d)
    if d.timer then             -- stop and remove the timer
        d.timer:remove_signal ("timeout", d.timer_function)
        d.timer:stop ()
        d.timer = nil
        d.timer_expired = true
    end
end

local function delayed_call_maybe (d, f)
    if use_timers then
        if not d.timer_expired and not d.timer then
            -- create and start the timer
            d.timer = timer ({ timeout = timeout })
            d.timer_function = function () f (); delete_timer_maybe (d) end
            d.timer:add_signal ("timeout", d.timer_function)
            d.timer:start ()
            d.timer_expired = false
        elseif not d.timer_expired then
            -- restart the timer...

            -- XXX: What is the actual semantics of the call to `start' (ie,
            -- does it restart the timer with the initial timeout)?
            d.timer:stop ()
            d.timer.timeout = timeout -- reset timeout
            d.timer:start ()
        end
    else                        -- timers disabled
        f ()                    -- call the given function directly
    end
end

--}}}

--{{{ Infoline management

function dispose (w)
    local d = data[w]
    if d.bowl then          -- if bowl was enabled... (should always be true...)
        infoline.dispose (d.bowl)
        d.bowl = nil
    end
    delete_timer_maybe (d)
    data[w] = nil
end

function append (w, m, k)
    local d = data[w]
    local pretty_ks = ks_to_string (m, k) .. " "
    infoline.set_text (d.bowl, infoline.get_text (d.bowl) .. pretty_ks)

    local function enable_bowl ()
        -- XXX: is there a possible bad interleaving that could make
        -- this function execute while the bowl has already been
        -- disposed of? in which case the condition should be checked
        -- first...

        -- if d.bowl then
        infoline.attach (d.bowl, w)
        -- end
    end

    delayed_call_maybe (d, enable_bowl)
end

function create (w)
    -- XXX: Note the prefix text could be customizable...
    data[w] = { bowl = infoline.new (" ") }
end

--}}}


--- Initializes the bowl module, with given properties; should be called before
--- ANY other function of this module.
-- Configurations fields include:
--
-- `use_timers', `timeout': A boolean defining whether bowls drawing should be
-- delayed, along with a number being this time shift, in seconds (Default
-- values are `true' and `2').
--
-- `modfilter': A table associating modifiers (Mod1, Mod4, Control, Shift, etc.)
-- with either a string (in this case it will replace the modifier when printed
-- in heplers) or functions (in this case the key string will be repaced by a
-- call to this function with the key string as parameter). Default value is:
-- { ["Mod1"] = "M", ["Mod4"] = "S", ["Control"] = "C", ["Shift"] =
-- string.upper }
--
-- @param c The table of properties.
function init (c)
    local c = c or { }
    modfilter = c.modfilter and c.modfilter or modfilter
    if c.use_timers ~= nil then use_timers = c.use_timers end
    if use_timers then
        timeout = c.timeout ~= nil and c.timeout or timeout
    end
end

--- Setup signal listeners, that trigger appropriate functions for a default
--- behavior.
function default_setup ()
    local function to_root (f) return function (...) f (root, ...) end end
    client.add_signal ("keychain::enter", create)
    client.add_signal ("keychain::append", append)
    client.add_signal ("keychain::leave", dispose)
    awesome.add_signal ("keychain::enter", to_root (create))
    awesome.add_signal ("keychain::append", to_root (append))
    awesome.add_signal ("keychain::leave", to_root (dispose))
end

-- Local variables:
-- indent-tabs-mode: nil
-- fill-column: 80
-- lua-indent-level: 4
-- End:
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80