naughty: new option 'config.hover_timeout'
[awesome.git] / lib / naughty.lua.in
blob5775476d90c85d96d12b1dfab7664a51b5ae004e
1 ----------------------------------------------------------------------------
2 -- @author koniu <gkusnierz@gmail.com>
3 -- @copyright 2008 koniu
4 -- @release @AWESOME_VERSION@
5 ----------------------------------------------------------------------------
6 --
7 -- Usage:
8 --
9 -- require("naughty")
10 -- naughty.notify({ text = "notification",
11 -- title = "title",
12 -- position = "top_left"|"top_right"|"bottom_left"|"bottom_right",
13 -- timeout = 5,
14 -- icon="/path/to/image",
15 -- fg="#ffggcc",
16 -- bg="#bbggcc",
17 -- screen = 1 })
19 -- Package environment
20 local pairs = pairs
21 local table = table
22 local wibox = wibox
23 local image = image
24 local hooks = require("awful.hooks")
25 local string = string
26 local widget = widget
27 local button = button
28 local capi = { screen = screen }
29 local bt = require("awful.beautiful")
30 local beautiful = bt.get()
32 --- Notification library
33 module("naughty")
35 --- Naughty configuration - a table containing common/default popup settings.
36 -- You can override some of these for individual popups using args to notify().
37 -- @name config
38 -- @field timeout Number of seconds after which popups disappear.
39 -- Set to 0 for no timeout. Default: 5
40 -- @field screen Screen on which the popups will appear number. Default: 1
41 -- @field position Corner of the workarea the popups will appear.
42 -- Valid values: 'top_right', 'top_left', 'bottom_right', 'bottom_left'.
43 -- Default: 'top_right'
44 -- @field margin Space between popups and edge of the workarea. Default: 4
45 -- @field height Height of a single-line popup. Default: 16
46 -- @field width Width of a popup. Default: 300
47 -- @field gap Spacing between popups. Default: 1
48 -- @field ontop Boolean forcing popups to display on top. Default: true
49 -- @field font Popup font. Default: beautiful.font or "Verdana 8"
50 -- @field icon Popup icon. Default: nil
51 -- @field icon_size Size of the icon in pixels. Default: 16
52 -- @field fg Foreground color. Default: beautiful.fg_focus or '#ffffff'
53 -- @field bg Background color. Default: beautiful.bg_focus or '#535d6c'
54 -- @field border_color Border color.
55 -- Default: beautiful.border_focus or '#535d6c'
56 -- @field border_width Border width. Default: 1
57 -- @field hover_timeout Delay in seconds after which hovered popup disappears.
58 -- Default: nil
59 -- @class table
61 config = {}
62 config.timeout = 5
63 config.screen = 1
64 config.position = "top_right"
65 config.margin = 4
66 config.height = 16
67 config.width = 300
68 config.gap = 1
69 config.ontop = true
70 config.font = beautiful.font or "Verdana 8"
71 config.icon = nil
72 config.icon_size = 16
73 config.fg = beautiful.fg_focus or '#ffffff'
74 config.bg = beautiful.bg_focus or '#535d6c'
75 config.border_color = beautiful.border_focus or '#535d6c'
76 config.border_width = 1
77 config.hover_timeout = nil
79 --- Index of notifications. See config table for valid 'position' values.
80 -- Each element is a table consisting of:
81 -- @field box Wibox object containing the popup
82 -- @field lines Number of lines in title..text
83 -- @field timer Function to be executed on timeout
84 -- @name notifications[position]
85 -- @class table
87 notifications = {
88 top_left = {},
89 top_right = {},
90 bottom_left = {},
91 bottom_right = {},
94 local ws = capi.screen[config.screen].workarea
95 local ss = capi.screen[config.screen].geometry
97 --- Evaluate desired position of the notification by index - internal
98 -- @param idx Index of the notification
99 -- @param position top_right | top_left | bottom_right | bottom_left
100 -- @param lines Number of text lines in the notification
101 -- @return Absolute position in {x, y} dictionary
103 local function get_offset(idx, position, lines)
104 local v = {}
106 -- calculate x
107 if position:match("left") then
108 v.x = ws.x + config.margin
109 else
110 v.x = ws.x + ws.width - (config.width + config.border_width*2 + config.margin)
113 -- calculate existing popups' height
114 local existing = 0
115 for i = 1, idx-1, 1 do
116 existing = existing + config.height*notifications[position][i].lines + config.gap + config.border_width*2
119 -- calculate y
120 if position:match("top") then
121 v.y = ws.y + config.margin + existing
122 else
123 v.y = ws.y + ws.height - (config.margin + config.border_width + config.height*lines + existing)
126 -- if positioned outside workarea, destroy oldest popup and recalculate
127 if v.y + config.height*lines > ws.y + ws.height or v.y < ws.y then
128 idx = idx - 1
129 destroy(notifications[position][1])
130 v = get_offset(idx, position, lines)
133 return v
136 --- Re-arrange notifications according to their position and index - internal
137 -- @return None
138 local function arrange()
139 for p,pos in pairs(notifications) do
140 for i,notification in pairs(notifications[p]) do
141 local offset = get_offset(i, p, notification.lines)
142 notification.box:geometry({ x = offset.x, y = offset.y, width = config.width, height = notification.lines * config.height })
143 notification.idx = i
148 --- Destroy notification by index
149 -- @param idx Index of the notification
150 -- @param position One of 4 keys in notification dictionary: top_right, top_left, bottom_right, bottom_left
151 -- @return True if the popup was successfully destroyed, nil otherwise
152 function destroy(notification)
153 if notification then
154 notification.box.screen = nil
155 hooks.timer.unregister(notification.timer)
156 table.remove(notifications[notification.position], notification.idx)
157 arrange()
158 return true
162 --- Create notification. args is a dictionary of optional arguments. For more information and defaults see respective fields in config table.
163 -- @param text Text of the notification
164 -- @param timeout Time in seconds after which popup expires
165 -- @param title Title of the notification
166 -- @param position Corner of the workarea the popups will appear
167 -- @param icon Path to icon
168 -- @param icon_size Desired icon size in px
169 -- @param fg Foreground color
170 -- @param bg Background color
171 -- @param screen Target screen for the notification
172 -- @param ontop Target screen for the notification
173 -- @usage naughty.notify({ title = 'Achtung!', text = 'You\'re idling', timeout = 0 })
174 function notify(args)
175 -- gather settings together
176 local timeout = args.timeout or config.timeout
177 local position = args.position or config.position
178 local icon = args.icon or config.icon
179 local icon_size = args.icon_size or config.icon_size
180 local text = args.text or ""
181 local screen = args.screen or config.screen
182 local ontop = args.ontop or config.ontop
184 local title = ""
185 if args.title then
186 title = " " .. args.title .. "\n"
189 local lines = 1
190 for i in string.gmatch(title..text, "\n") do
191 lines = lines + 1
194 -- create the container wibox
195 local idx = #notifications[position] + 1
196 local box = wibox({ name = "not" .. idx,
197 position = "floating",
198 fg = args.fg or config.fg,
199 bg = args.bg or config.bg,
200 border_color = config.border_color,
201 border_width = config.border_width })
203 -- position the wibox
204 local offset = get_offset(idx, position, lines)
205 box:geometry({ width = config.width,
206 height = config.height * lines,
207 x = offset.x,
208 y = offset.y })
210 box.ontop = ontop
211 box.screen = screen
213 local notification = {
214 box = box,
215 lines = lines,
216 position = position,
217 idx = idx
220 local timer = function () destroy(notification) end
221 hooks.timer.register(timeout, timer)
222 notification.timer = timer
224 local hover_destroy = function ()
225 if config.hover_timeout == 0 then destroy(notification)
226 else hooks.timer.register(config.hover_timeout, timer) end
229 -- populate the wibox with widgets
230 local textbox = widget({ type = "textbox", name = "text", align = "flex" })
231 textbox:buttons({ button({ }, 1, function () destroy(notification) end) })
232 textbox.text = string.format('<span font_desc="%s"><b>%s</b> %s</span>',
233 config.font, title, text)
234 if config.hover_timeout then textbox.mouse_enter = hover_destroy end
236 local iconbox = nil
237 if icon then
238 iconbox = widget({ type = "imagebox", name = "icon", align = "left" })
239 iconbox:buttons({ button({ }, 1, function () destroy(notification) end) })
240 local img = image(icon)
241 if icon_size then img:crop_and_scale(0,0,img.height,img.width,icon_size,icon_size) end
242 iconbox.image = img
243 if config.hover_timeout then iconbox.mouse_enter = hover_destroy end
246 box.widgets = { iconbox, textbox }
248 -- insert the notification to the table
249 table.insert(notifications[position],notification)
252 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80