---------------------------------------------------------------------------- -- @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('%s%s', 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