awesome/lib/naughty.lua.in

255 lines
9.6 KiB
Lua

----------------------------------------------------------------------------
-- @author koniu <gkusnierz@gmail.com>
-- @copyright 2008 koniu
-- @release @AWESOME_VERSION@
----------------------------------------------------------------------------
-- Package environment
local pairs = pairs
local table = table
local wibox = wibox
local image = image
local hooks = require("awful.hooks")
local string = string
local widget = widget
local button = button
local capi = { screen = screen }
local bt = require("beautiful")
local screen = screen
--- Notification library
module("naughty")
--- Naughty configuration - a table containing common/default popup settings.
-- You can override some of these for individual popups using args to notify().
-- @name config
-- @field timeout Number of seconds after which popups disappear.
-- Set to 0 for no timeout. Default: 5
-- @field screen Screen on which the popups will appear number. Default: 1
-- @field position Corner of the workarea the popups will appear.
-- Valid values: 'top_right', 'top_left', 'bottom_right', 'bottom_left'.
-- Default: 'top_right'
-- @field padding Space between popups and edge of the workarea. Default: 4
-- @field height Height of a single line of text. Default: 16
-- @field width Width of a popup. Default: 300
-- @field spacing Spacing between popups. Default: 1
-- @field ontop Boolean forcing popups to display on top. Default: true
-- @field margin Space between popup edge and content. Default: 10
-- @field font Popup font. Default: beautiful.font or "Verdana 8"
-- @field icon Popup icon. Default: nil
-- @field icon_size Size of the icon in pixels. Default: 16
-- @field fg Foreground color. Default: beautiful.fg_focus or '#ffffff'
-- @field bg Background color. Default: beautiful.bg_focus or '#535d6c'
-- @field border_color Border color.
-- Default: beautiful.border_focus or '#535d6c'
-- @field border_width Border width. Default: 1
-- @field hover_timeout Delay in seconds after which hovered popup disappears.
-- Default: nil
-- @class table
config = {}
config.timeout = 5
config.screen = 1
config.position = "top_right"
config.padding = 4
config.height = 16
config.width = 300
config.spacing = 1
config.ontop = true
config.margin = 10
config.icon = nil
config.icon_size = 16
config.border_width = 1
config.hover_timeout = nil
--- Index of notifications. See config table for valid 'position' values.
-- Each element is a table consisting of:
-- @field box Wibox object containing the popup
-- @field height Popup height
-- @field width Popup width
-- @field die Function to be executed on timeout
-- @name notifications[position]
-- @class table
notifications = {}
for s = 1, screen.count() do
notifications[s] = {
top_left = {},
top_right = {},
bottom_left = {},
bottom_right = {},
}
end
--- Evaluate desired position of the notification by index - internal
-- @param idx Index of the notification
-- @param position top_right | top_left | bottom_right | bottom_left
-- @param height Popup height
-- @param width Popup width (optional)
-- @return Absolute position in {x, y} dictionary
local function get_offset(screen, position, idx, width, height)
local ws = capi.screen[screen].workarea
local v = {}
width = width or notifications[screen][position][idx].width or config.width
-- calculate x
if position:match("left") then
v.x = ws.x + config.padding
else
v.x = ws.x + ws.width - (width + config.border_width*2 + config.padding)
end
-- calculate existing popups' height
local existing = 0
for i = 1, idx-1, 1 do
existing = existing + notifications[screen][position][i].height + config.spacing + config.border_width*2
end
-- calculate y
if position:match("top") then
v.y = ws.y + config.padding + existing
else
v.y = ws.y + ws.height - (config.padding + config.border_width + height + existing)
end
-- if positioned outside workarea, destroy oldest popup and recalculate
if v.y + height > ws.y + ws.height or v.y < ws.y then
idx = idx - 1
destroy(notifications[screen][position][1])
v = get_offset(screen, position, idx, width, height)
end
return v
end
--- Re-arrange notifications according to their position and index - internal
-- @return None
local function arrange(screen)
for p,pos in pairs(notifications[screen]) do
for i,notification in pairs(notifications[screen][p]) do
local offset = get_offset(screen, p, i, notification.width, notification.height)
notification.box:geometry({ x = offset.x, y = offset.y, width = notification.width, height = notification.height })
notification.idx = i
end
end
end
--- Destroy notification by index
-- @param idx Index of the notification
-- @param position One of 4 keys in notification dictionary: top_right, top_left, bottom_right, bottom_left
-- @return True if the popup was successfully destroyed, nil otherwise
function destroy(notification)
if notification then
local scr = notification.box.screen
table.remove(notifications[notification.box.screen][notification.position], notification.idx)
hooks.timer.unregister(notification.die)
notification.box.screen = nil
arrange(scr)
return true
end
end
--- Create notification. args is a dictionary of optional arguments. For more information and defaults see respective fields in config table.
-- @param text Text of the notification
-- @param timeout Time in seconds after which popup expires
-- @param title Title of the notification
-- @param position Corner of the workarea the popups will appear
-- @param icon Path to icon
-- @param icon_size Desired icon size in px
-- @param fg Foreground color
-- @param bg Background color
-- @param screen Target screen for the notification
-- @param ontop Target screen for the notification
-- @param run Function to run on left click
-- @param width The popup width
-- @usage naughty.notify({ title = 'Achtung!', text = 'You\'re idling', timeout = 0 })
function notify(args)
-- gather variables together
local timeout = args.timeout or config.timeout
local icon = args.icon or config.icon
local icon_size = args.icon_size or config.icon_size
local text = args.text or ""
local screen = args.screen or config.screen
local ontop = args.ontop or config.ontop
local width = args.width or config.width
-- beautiful
local beautiful = bt.get()
local font = args.font or config.font or beautiful.font or "Verdana 8"
local fg = args.fg or config.fg or beautiful.fg_normal or '#ffffff'
local bg = args.bg or config.bg or beautiful.bg_normal or '#535d6c'
local border_color = config.border_color or beautiful.bg_focus or '#535d6c'
local notification = {}
notification.position = args.position or config.position
notification.idx = #notifications[screen][notification.position] + 1
local title = ""
if args.title then title = args.title .. "\n" end
-- hook destroy
local die = function () destroy(notification) end
hooks.timer.register(timeout, die)
notification.die = die
local run = args.run or die
local hover_destroy = function ()
if config.hover_timeout == 0 then die()
else hooks.timer.register(config.hover_timeout, die) end
end
-- create textbox
local textbox = widget({ type = "textbox", name = "text", align = "flex" })
textbox:buttons({ button({ }, 1, run),
button({ }, 3, die) })
textbox.text = string.format('<margin right="'..config.margin..'" left="'..config.margin..'"/><span font_desc="%s"><b>%s</b>%s</span>', font, title, text)
if config.hover_timeout then textbox.mouse_enter = hover_destroy end
-- create iconbox
local iconbox = nil
if icon then
iconbox = widget({ type = "imagebox", name = "icon", align = "left" })
iconbox:buttons({ button({ }, 1, run),
button({ }, 3, die) })
local img = image(icon)
if icon_size then
img = img:crop_and_scale(0,0,img.height,img.width,icon_size,icon_size)
iconbox.resize = false
end
iconbox.image = img
if config.hover_timeout then iconbox.mouse_enter = hover_destroy end
end
-- create container wibox
notification.box = wibox({ name = "not" .. notification.idx,
position = "floating",
fg = fg,
bg = bg,
border_color = config.border_color,
border_width = config.border_width })
-- position the wibox
local lines = 1; for i in string.gmatch(title..text, "\n") do lines = lines + 1 end
if iconbox and iconbox.image.height > lines * config.height then
notification.height = iconbox.image.height
else
notification.height = lines * config.height end
notification.width = width
local offset = get_offset(screen, notification.position, notification.idx, notification.width, notification.height)
notification.box:geometry({ width = notification.width,
height = notification.height,
x = offset.x,
y = offset.y })
notification.box.ontop = ontop
notification.box.screen = screen
-- populate widgets
notification.box.widgets = { iconbox, textbox }
-- insert the notification to the table
table.insert(notifications[screen][notification.position],notification)
end
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80