summaryrefslogtreecommitdiff
path: root/awesome/lib/naughty/core.lua
diff options
context:
space:
mode:
Diffstat (limited to 'awesome/lib/naughty/core.lua')
-rw-r--r--awesome/lib/naughty/core.lua688
1 files changed, 0 insertions, 688 deletions
diff --git a/awesome/lib/naughty/core.lua b/awesome/lib/naughty/core.lua
deleted file mode 100644
index 764afe2..0000000
--- a/awesome/lib/naughty/core.lua
+++ /dev/null
@@ -1,688 +0,0 @@
-----------------------------------------------------------------------------
---- Notification library
---
--- @author koniu <gkusnierz@gmail.com>
--- @copyright 2008 koniu
--- @module naughty
-----------------------------------------------------------------------------
-
--- Package environment
-local pairs = pairs
-local table = table
-local type = type
-local string = string
-local pcall = pcall
-local capi = { screen = screen,
- awesome = awesome }
-local timer = require("gears.timer")
-local button = require("awful.button")
-local screen = require("awful.screen")
-local util = require("awful.util")
-local bt = require("beautiful")
-local wibox = require("wibox")
-local surface = require("gears.surface")
-local cairo = require("lgi").cairo
-local dpi = require("beautiful").xresources.apply_dpi
-
-local function get_screen(s)
- return s and capi.screen[s]
-end
-
-local naughty = {}
-
---[[--
-Naughty configuration - a table containing common popup settings.
-
-@table naughty.config
-@tfield[opt=apply_dpi(4)] int padding Space between popups and edge of the
- workarea.
-@tfield[opt=apply_dpi(1)] int spacing Spacing between popups.
-@tfield[opt={"/usr/share/pixmaps/"}] table icon_dirs List of directories
- that will be checked by `getIcon()`.
-@tfield[opt={ "png", "gif" }] table icon_formats List of formats that will be
- checked by `getIcon()`.
-@tfield[opt] function notify_callback Callback used to modify or reject
-notifications, e.g.
- naughty.config.notify_callback = function(args)
- args.text = 'prefix: ' .. args.text
- return args
- end
-
-@tfield table presets Notification presets. See `config.presets`.
-
-@tfield table defaults Default values for the params to `notify()`. These can
- optionally be overridden by specifying a preset. See `config.defaults`.
-
---]]
---
-naughty.config = {
- padding = dpi(4),
- spacing = dpi(1),
- icon_dirs = { "/usr/share/pixmaps/", },
- icon_formats = { "png", "gif" },
- notify_callback = nil,
-}
-
---- Notification presets for `naughty.notify`.
--- This holds presets for different purposes. A preset is a table of any
--- parameters for `notify()`, overriding the default values
--- (`naughty.config.defaults`).
---
--- You have to pass a reference of a preset in your `notify()` as the `preset`
--- argument.
---
--- The presets `"low"`, `"normal"` and `"critical"` are used for notifications
--- over DBUS.
---
--- @table config.presets
--- @tfield table low The preset for notifications with low urgency level.
--- @tfield[opt=5] int low.timeout
--- @tfield[opt=empty] table normal The default preset for every notification without a
--- preset that will also be used for normal urgency level.
--- @tfield table critical The preset for notifications with a critical urgency
--- level.
--- @tfield[opt="#ff0000"] string critical.bg
--- @tfield[opt="#ffffff"] string critical.fg
--- @tfield[opt=0] string critical.timeout
-naughty.config.presets = {
- low = {
- timeout = 5
- },
- normal = {},
- critical = {
- bg = "#ff0000",
- fg = "#ffffff",
- timeout = 0,
- }
-}
-
---- Defaults for `naughty.notify`.
---
--- @table config.defaults
--- @tfield[opt=5] int timeout
--- @tfield[opt=""] string text
--- @tfield[opt] int screen Defaults to `awful.screen.focused`.
--- @tfield[opt=true] boolean ontop
--- @tfield[opt=apply_dpi(5)] int margin
--- @tfield[opt=apply_dpi(1)] int border_width
--- @tfield[opt="top_right"] string position
-naughty.config.defaults = {
- timeout = 5,
- text = "",
- screen = nil,
- ontop = true,
- margin = dpi(5),
- border_width = dpi(1),
- position = "top_right"
-}
-
-naughty.notificationClosedReason = {
- silent = -1,
- expired = 1,
- dismissedByUser = 2,
- dismissedByCommand = 3,
- undefined = 4
-}
-
--- Counter for the notifications
--- Required for later access via DBUS
-local counter = 1
-
--- True if notifying is suspended
-local suspended = false
-
---- Index of notifications per screen and position.
--- 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
--- @field id Unique notification id based on a counter
--- @table notifications
-naughty.notifications = { suspended = { } }
-screen.connect_for_each_screen(function(s)
- naughty.notifications[s] = {
- top_left = {},
- top_middle = {},
- top_right = {},
- bottom_left = {},
- bottom_middle = {},
- bottom_right = {},
- }
-end)
-
-capi.screen.connect_signal("removed", function(scr)
- -- Destroy all notifications on this screen
- for _, list in pairs(naughty.notifications[scr]) do
- while #list > 0 do
- naughty.destroy(list[1])
- end
- end
- naughty.notifications[scr] = nil
-end)
-
---- Notification state
-function naughty.is_suspended()
- return suspended
-end
-
---- Suspend notifications
-function naughty.suspend()
- suspended = true
-end
-
---- Resume notifications
-function naughty.resume()
- suspended = false
- for _, v in pairs(naughty.notifications.suspended) do
- v.box.visible = true
- if v.timer then v.timer:start() end
- end
- naughty.notifications.suspended = { }
-end
-
---- Toggle notification state
-function naughty.toggle()
- if suspended then
- naughty.resume()
- else
- naughty.suspend()
- end
-end
-
---- Evaluate desired position of the notification by index - internal
---
--- @param s Screen to use
--- @param position top_right | top_left | bottom_right | bottom_left
--- | top_middle | bottom_middle
--- @param idx Index of the notification
--- @param[opt] width Popup width.
--- @param height Popup height
--- @return Absolute position and index in { x = X, y = Y, idx = I } table
-local function get_offset(s, position, idx, width, height)
- s = get_screen(s)
- local ws = s.workarea
- local v = {}
- idx = idx or #naughty.notifications[s][position] + 1
- width = width or naughty.notifications[s][position][idx].width
-
- -- calculate x
- if position:match("left") then
- v.x = ws.x + naughty.config.padding
- elseif position:match("middle") then
- v.x = (ws.width / 2) - (width / 2)
- else
- v.x = ws.x + ws.width - (width + naughty.config.padding)
- end
-
- -- calculate existing popups' height
- local existing = 0
- for i = 1, idx-1, 1 do
- existing = existing + naughty.notifications[s][position][i].height + naughty.config.spacing
- end
-
- -- calculate y
- if position:match("top") then
- v.y = ws.y + naughty.config.padding + existing
- else
- v.y = ws.y + ws.height - (naughty.config.padding + height + existing)
- end
-
- -- Find old notification to replace in case there is not enough room.
- -- This tries to skip permanent notifications (without a timeout),
- -- e.g. critical ones.
- local find_old_to_replace = function()
- for i = 1, idx-1 do
- local n = naughty.notifications[s][position][i]
- if n.timeout > 0 then
- return n
- end
- end
- -- Fallback to first one.
- return naughty.notifications[s][position][1]
- end
-
- -- if positioned outside workarea, destroy oldest popup and recalculate
- if v.y + height > ws.y + ws.height or v.y < ws.y then
- naughty.destroy(find_old_to_replace())
- idx = idx - 1
- v = get_offset(s, position, idx, width, height)
- end
- if not v.idx then v.idx = idx end
-
- return v
-end
-
---- Re-arrange notifications according to their position and index - internal
---
--- @return None
-local function arrange(s)
- for p in pairs(naughty.notifications[s]) do
- for i,notification in pairs(naughty.notifications[s][p]) do
- local offset = get_offset(s, p, i, notification.width, notification.height)
- notification.box:geometry({ x = offset.x, y = offset.y })
- notification.idx = offset.idx
- end
- end
-end
-
---- Destroy notification by notification object
---
--- @param notification Notification object to be destroyed
--- @param reason One of the reasons from notificationClosedReason
--- @return True if the popup was successfully destroyed, nil otherwise
-function naughty.destroy(notification, reason)
- if notification and notification.box.visible then
- if suspended then
- for k, v in pairs(naughty.notifications.suspended) do
- if v.box == notification.box then
- table.remove(naughty.notifications.suspended, k)
- break
- end
- end
- end
- local scr = notification.screen
- table.remove(naughty.notifications[scr][notification.position], notification.idx)
- if notification.timer then
- notification.timer:stop()
- end
- notification.box.visible = false
- arrange(scr)
- if notification.destroy_cb and reason ~= naughty.notificationClosedReason.silent then
- notification.destroy_cb(reason or naughty.notificationClosedReason.undefined)
- end
- return true
- end
-end
-
---- Get notification by ID
---
--- @param id ID of the notification
--- @return notification object if it was found, nil otherwise
-function naughty.getById(id)
- -- iterate the notifications to get the notfications with the correct ID
- for s in pairs(naughty.notifications) do
- for p in pairs(naughty.notifications[s]) do
- for _, notification in pairs(naughty.notifications[s][p]) do
- if notification.id == id then
- return notification
- end
- end
- end
- end
-end
-
---- Install expiration timer for notification object.
--- @tparam notification notification Notification object.
--- @tparam number timeout Time in seconds to be set as expiration timeout.
-local function set_timeout(notification, timeout)
- local die = function (reason)
- naughty.destroy(notification, reason)
- end
- if timeout > 0 then
- local timer_die = timer { timeout = timeout }
- timer_die:connect_signal("timeout", function() die(naughty.notificationClosedReason.expired) end)
- if not suspended then
- timer_die:start()
- end
- notification.timer = timer_die
- end
- notification.die = die
-end
-
---- Set new notification timeout.
--- @tparam notification notification Notification object, which timer is to be reset.
--- @tparam number new_timeout Time in seconds after which notification disappears.
--- @return None.
-function naughty.reset_timeout(notification, new_timeout)
- if notification.timer then notification.timer:stop() end
-
- local timeout = new_timeout or notification.timeout
- set_timeout(notification, timeout)
- notification.timeout = timeout
-
- notification.timer:start()
-end
-
---- Escape and set title and text for notification object.
--- @tparam notification notification Notification object.
--- @tparam string title Title of notification.
--- @tparam string text Main text of notification.
--- @return None.
-local function set_text(notification, title, text)
- local escape_pattern = "[<>&]"
- local escape_subs = { ['<'] = "&lt;", ['>'] = "&gt;", ['&'] = "&amp;" }
-
- local textbox = notification.textbox
-
- local function setMarkup(pattern, replacements)
- return textbox:set_markup_silently(string.format('<b>%s</b>%s', title, text:gsub(pattern, replacements)))
- end
- local function setText()
- textbox:set_text(string.format('%s %s', title, text))
- end
-
- -- Since the title cannot contain markup, it must be escaped first so that
- -- it is not interpreted by Pango later.
- title = title:gsub(escape_pattern, escape_subs)
- -- Try to set the text while only interpreting <br>.
- if not setMarkup("<br.->", "\n") then
- -- That failed, escape everything which might cause an error from pango
- if not setMarkup(escape_pattern, escape_subs) then
- -- Ok, just ignore all pango markup. If this fails, we got some invalid utf8
- if not pcall(setText) then
- textbox:set_markup("<i>&lt;Invalid markup or UTF8, cannot display message&gt;</i>")
- end
- end
- end
-end
-
---- Replace title and text of an existing notification.
--- @tparam notification notification Notification object, which contents are to be replaced.
--- @tparam string new_title New title of notification. If not specified, old title remains unchanged.
--- @tparam string new_text New text of notification. If not specified, old text remains unchanged.
--- @return None.
-function naughty.replace_text(notification, new_title, new_text)
- local title = new_title
-
- if title then title = title .. "\n" else title = "" end
-
- set_text(notification, title, new_text)
-end
-
---- Create a notification.
---
--- @tab args The argument table containing any of the arguments below.
--- @string[opt=""] args.text Text of the notification.
--- @string[opt] args.title Title of the notification.
--- @int[opt=5] args.timeout Time in seconds after which popup expires.
--- Set 0 for no timeout.
--- @int[opt] args.hover_timeout Delay in seconds after which hovered popup disappears.
--- @tparam[opt=focused] integer|screen args.screen Target screen for the notification.
--- @string[opt="top_right"] args.position Corner of the workarea displaying the popups.
--- Values: `"top_right"`, `"top_left"`, `"bottom_left"`,
--- `"bottom_right"`, `"top_middle"`, `"bottom_middle"`.
--- @bool[opt=true] args.ontop Boolean forcing popups to display on top.
--- @int[opt=auto] args.height Popup height.
--- @int[opt=auto] args.width Popup width.
--- @string[opt=beautiful.font or awesome.font] args.font Notification font.
--- @string[opt] args.icon Path to icon.
--- @int[opt] args.icon_size Desired icon size in px.
--- @string[opt=`beautiful.fg_focus` or `'#ffffff'`] args.fg Foreground color.
--- @string[opt=`beautiful.bg_focus` or `'#535d6c'`] args.bg Background color.
--- @int[opt=1] args.border_width Border width.
--- @string[opt=`beautiful.border_focus` or `'#535d6c'`] args.border_color Border color.
--- @tparam[opt] func args.run Function to run on left click. The notification
--- object will be passed to it as an argument.
--- You need to call e.g.
--- `notification.die(naughty.notificationClosedReason.dismissedByUser)` from
--- there to dismiss the notification yourself.
--- @tparam[opt] func args.destroy Function to run when notification is destroyed.
--- @tparam[opt] table args.preset Table with any of the above parameters.
--- Note: Any parameters specified directly in args will override ones defined
--- in the preset.
--- @tparam[opt] int args.replaces_id Replace the notification with the given ID.
--- @tparam[opt] func args.callback Function that will be called with all arguments.
--- The notification will only be displayed if the function returns true.
--- Note: this function is only relevant to notifications sent via dbus.
--- @tparam[opt] table args.actions Mapping that maps a string to a callback when this
--- action is selected.
--- @usage naughty.notify({ title = "Achtung!", text = "You're idling", timeout = 0 })
--- @treturn ?table The notification object, or nil in case a notification was
--- not displayed.
-function naughty.notify(args)
- if naughty.config.notify_callback then
- args = naughty.config.notify_callback(args)
- if not args then return end
- end
-
- -- gather variables together
- local preset = util.table.join(naughty.config.defaults or {},
- args.preset or naughty.config.presets.normal or {})
- local timeout = args.timeout or preset.timeout
- local icon = args.icon or preset.icon
- local icon_size = args.icon_size or preset.icon_size
- local text = args.text or preset.text
- local title = args.title or preset.title
- local s = get_screen(args.screen or preset.screen or screen.focused())
- if not s then
- local err = "naughty.notify: there is no screen available to display the following notification:"
- err = string.format("%s title='%s' text='%s'", err, tostring(title or ""), tostring(text or ""))
- require("gears.debug").print_warning(err)
- return
- end
- local ontop = args.ontop or preset.ontop
- local width = args.width or preset.width
- local height = args.height or preset.height
- local hover_timeout = args.hover_timeout or preset.hover_timeout
- local opacity = args.opacity or preset.opacity
- local margin = args.margin or preset.margin
- local border_width = args.border_width or preset.border_width
- local position = args.position or preset.position
- local actions = args.actions
- local destroy_cb = args.destroy
-
- -- beautiful
- local beautiful = bt.get()
- local font = args.font or preset.font or beautiful.font or capi.awesome.font
- local fg = args.fg or preset.fg or beautiful.fg_normal or '#ffffff'
- local bg = args.bg or preset.bg or beautiful.bg_normal or '#535d6c'
- local border_color = args.border_color or preset.border_color or beautiful.bg_focus or '#535d6c'
- local notification = { screen = s, destroy_cb = destroy_cb, timeout = timeout }
-
- -- replace notification if needed
- if args.replaces_id then
- local obj = naughty.getById(args.replaces_id)
- if obj then
- -- destroy this and ...
- naughty.destroy(obj, naughty.notificationClosedReason.silent)
- end
- -- ... may use its ID
- if args.replaces_id <= counter then
- notification.id = args.replaces_id
- else
- counter = counter + 1
- notification.id = counter
- end
- else
- -- get a brand new ID
- counter = counter + 1
- notification.id = counter
- end
-
- notification.position = position
-
- if title then title = title .. "\n" else title = "" end
-
- -- hook destroy
- set_timeout(notification, timeout)
- local die = notification.die
-
- local run = function ()
- if args.run then
- args.run(notification)
- else
- die(naughty.notificationClosedReason.dismissedByUser)
- end
- end
-
- local hover_destroy = function ()
- if hover_timeout == 0 then
- die(naughty.notificationClosedReason.expired)
- else
- if notification.timer then notification.timer:stop() end
- notification.timer = timer { timeout = hover_timeout }
- notification.timer:connect_signal("timeout", function() die(naughty.notificationClosedReason.expired) end)
- notification.timer:start()
- end
- end
-
- -- create textbox
- local textbox = wibox.widget.textbox()
- local marginbox = wibox.container.margin()
- marginbox:set_margins(margin)
- marginbox:set_widget(textbox)
- textbox:set_valign("middle")
- textbox:set_font(font)
-
- notification.textbox = textbox
-
- set_text(notification, title, text)
-
- local actionslayout = wibox.layout.fixed.vertical()
- local actions_max_width = 0
- local actions_total_height = 0
- if actions then
- for action, callback in pairs(actions) do
- local actiontextbox = wibox.widget.textbox()
- local actionmarginbox = wibox.container.margin()
- actionmarginbox:set_margins(margin)
- actionmarginbox:set_widget(actiontextbox)
- actiontextbox:set_valign("middle")
- actiontextbox:set_font(font)
- actiontextbox:set_markup(string.format('☛ <u>%s</u>', action))
- -- calculate the height and width
- local w, h = actiontextbox:get_preferred_size(s)
- local action_height = h + 2 * margin
- local action_width = w + 2 * margin
-
- actionmarginbox:buttons(util.table.join(
- button({ }, 1, callback),
- button({ }, 3, callback)
- ))
- actionslayout:add(actionmarginbox)
-
- actions_total_height = actions_total_height + action_height
- if actions_max_width < action_width then
- actions_max_width = action_width
- end
- end
- end
-
- -- create iconbox
- local iconbox = nil
- local iconmargin = nil
- local icon_w, icon_h = 0, 0
- if icon then
- -- Is this really an URI instead of a path?
- if type(icon) == "string" and string.sub(icon, 1, 7) == "file://" then
- icon = string.sub(icon, 8)
- end
- -- try to guess icon if the provided one is non-existent/readable
- if type(icon) == "string" and not util.file_readable(icon) then
- icon = util.geticonpath(icon, naughty.config.icon_formats, naughty.config.icon_dirs, icon_size) or icon
- end
-
- -- is the icon file readable?
- icon = surface.load_uncached(icon)
-
- -- if we have an icon, use it
- if icon then
- iconbox = wibox.widget.imagebox()
- iconmargin = wibox.container.margin(iconbox, margin, margin, margin, margin)
- if icon_size then
- local scaled = cairo.ImageSurface(cairo.Format.ARGB32, icon_size, icon_size)
- local cr = cairo.Context(scaled)
- cr:scale(icon_size / icon:get_height(), icon_size / icon:get_width())
- cr:set_source_surface(icon, 0, 0)
- cr:paint()
- icon = scaled
- end
- iconbox:set_resize(false)
- iconbox:set_image(icon)
- icon_w = icon:get_width()
- icon_h = icon:get_height()
- end
- end
-
- -- create container wibox
- notification.box = wibox({ fg = fg,
- bg = bg,
- border_color = border_color,
- border_width = border_width,
- type = "notification" })
-
- if hover_timeout then notification.box:connect_signal("mouse::enter", hover_destroy) end
-
- -- calculate the width
- if not width then
- local w, _ = textbox:get_preferred_size(s)
- width = w + (iconbox and icon_w + 2 * margin or 0) + 2 * margin
- end
-
- if width < actions_max_width then
- width = actions_max_width
- end
-
- -- calculate the height
- if not height then
- local w = width - (iconbox and icon_w + 2 * margin or 0) - 2 * margin
- local h = textbox:get_height_for_width(w, s)
- if iconbox and icon_h + 2 * margin > h + 2 * margin then
- height = icon_h + 2 * margin
- else
- height = h + 2 * margin
- end
- end
-
- height = height + actions_total_height
-
- -- crop to workarea size if too big
- local workarea = s.workarea
- if width > workarea.width - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0) then
- width = workarea.width - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0)
- end
- if height > workarea.height - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0) then
- height = workarea.height - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0)
- end
-
- -- set size in notification object
- notification.height = height + 2 * (border_width or 0)
- notification.width = width + 2 * (border_width or 0)
-
- -- position the wibox
- local offset = get_offset(s, notification.position, nil, notification.width, notification.height)
- notification.box.ontop = ontop
- notification.box:geometry({ width = width,
- height = height,
- x = offset.x,
- y = offset.y })
- notification.box.opacity = opacity
- notification.box.visible = true
- notification.idx = offset.idx
-
- -- populate widgets
- local layout = wibox.layout.fixed.horizontal()
- if iconmargin then
- layout:add(iconmargin)
- end
- layout:add(marginbox)
-
- local completelayout = wibox.layout.fixed.vertical()
- completelayout:add(layout)
- completelayout:add(actionslayout)
- notification.box:set_widget(completelayout)
-
- -- Setup the mouse events
- layout:buttons(util.table.join(button({ }, 1, run),
- button({ }, 3, function()
- die(naughty.notificationClosedReason.dismissedByUser)
- end)))
-
- -- insert the notification to the table
- table.insert(naughty.notifications[s][notification.position], notification)
-
- if suspended then
- notification.box.visible = false
- table.insert(naughty.notifications.suspended, notification)
- end
-
- -- return the notification
- return notification
-end
-
-return naughty
-
--- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80