naughty: Move the notification object into its own file.
Mostly for the documentation, but also as the new base upon which to build the modular notification GUI.
This commit is contained in:
parent
14eab7890f
commit
9df77e5c76
|
@ -24,9 +24,11 @@ require("awful.hotkeys_popup.keys")
|
||||||
-- Check if awesome encountered an error during startup and fell back to
|
-- Check if awesome encountered an error during startup and fell back to
|
||||||
-- another config (This code will only ever execute for the fallback config)
|
-- another config (This code will only ever execute for the fallback config)
|
||||||
if awesome.startup_errors then
|
if awesome.startup_errors then
|
||||||
naughty.notify({ preset = naughty.config.presets.critical,
|
naughty.notification {
|
||||||
|
preset = naughty.config.presets.critical,
|
||||||
title = "Oops, there were errors during startup!",
|
title = "Oops, there were errors during startup!",
|
||||||
text = awesome.startup_errors })
|
text = awesome.startup_errors
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle runtime errors after startup
|
-- Handle runtime errors after startup
|
||||||
|
@ -37,9 +39,12 @@ do
|
||||||
if in_error then return end
|
if in_error then return end
|
||||||
in_error = true
|
in_error = true
|
||||||
|
|
||||||
naughty.notify({ preset = naughty.config.presets.critical,
|
naughty.notification {
|
||||||
|
preset = naughty.config.presets.critical,
|
||||||
title = "Oops, an error happened!",
|
title = "Oops, an error happened!",
|
||||||
text = tostring(err) })
|
text = tostring(err)
|
||||||
|
}
|
||||||
|
|
||||||
in_error = false
|
in_error = false
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -121,9 +121,12 @@ file = {
|
||||||
'../lib/gears/init.lua',
|
'../lib/gears/init.lua',
|
||||||
'../lib/wibox/layout/init.lua',
|
'../lib/wibox/layout/init.lua',
|
||||||
'../lib/wibox/container/init.lua',
|
'../lib/wibox/container/init.lua',
|
||||||
|
'../lib/naughty/constants.lua',
|
||||||
|
'../lib/naughty/dbus.lua',
|
||||||
|
|
||||||
-- Ignore some parts of the widget library
|
-- Ignore some parts of the widget library
|
||||||
'../lib/awful/widget/init.lua',
|
'../lib/awful/widget/init.lua',
|
||||||
|
'../lib/naughty/layout/init.lua',
|
||||||
|
|
||||||
-- Deprecated classes for one years or more don't deserve entries
|
-- Deprecated classes for one years or more don't deserve entries
|
||||||
-- in the index
|
-- in the index
|
||||||
|
|
|
@ -132,17 +132,17 @@
|
||||||
--
|
--
|
||||||
-- awful.spawn.with_line_callback(noisy, {
|
-- awful.spawn.with_line_callback(noisy, {
|
||||||
-- stdout = function(line)
|
-- stdout = function(line)
|
||||||
-- naughty.notify { text = "LINE:"..line }
|
-- naughty.notification { text = "LINE:"..line }
|
||||||
-- end,
|
-- end,
|
||||||
-- stderr = function(line)
|
-- stderr = function(line)
|
||||||
-- naughty.notify { text = "ERR:"..line}
|
-- naughty.notification { text = "ERR:"..line}
|
||||||
-- end,
|
-- end,
|
||||||
-- })
|
-- })
|
||||||
--
|
--
|
||||||
-- If only the full output is needed, then `easy_async` is the right choice:
|
-- If only the full output is needed, then `easy_async` is the right choice:
|
||||||
--
|
--
|
||||||
-- awful.spawn.easy_async(noisy, function(stdout, stderr, reason, exit_code)
|
-- awful.spawn.easy_async(noisy, function(stdout, stderr, reason, exit_code)
|
||||||
-- naughty.notify { text = stdout }
|
-- naughty.notification { text = stdout }
|
||||||
-- end)
|
-- end)
|
||||||
--
|
--
|
||||||
-- **Default applications**:
|
-- **Default applications**:
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
--- This file hosts the shared constants used by the notification subsystem.
|
||||||
|
--
|
||||||
|
-- [[documented in core.lua]]
|
||||||
|
--
|
||||||
|
-- @author koniu <gkusnierz@gmail.com>
|
||||||
|
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
|
||||||
|
-- @copyright 2008 koniu
|
||||||
|
-- @copyright 2017 Emmanuel Lepage Vallee
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
local beautiful = require("beautiful")
|
||||||
|
local dpi = beautiful.xresources.apply_dpi
|
||||||
|
|
||||||
|
local ret = {}
|
||||||
|
|
||||||
|
ret.config = {
|
||||||
|
padding = dpi(4),
|
||||||
|
spacing = dpi(1),
|
||||||
|
icon_dirs = { "/usr/share/pixmaps/", },
|
||||||
|
icon_formats = { "png", "gif" },
|
||||||
|
notify_callback = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.config.presets = {
|
||||||
|
low = {
|
||||||
|
timeout = 5
|
||||||
|
},
|
||||||
|
normal = {},
|
||||||
|
critical = {
|
||||||
|
bg = "#ff0000",
|
||||||
|
fg = "#ffffff",
|
||||||
|
timeout = 0,
|
||||||
|
},
|
||||||
|
ok = {
|
||||||
|
bg = "#00bb00",
|
||||||
|
fg = "#ffffff",
|
||||||
|
timeout = 5,
|
||||||
|
},
|
||||||
|
info = {
|
||||||
|
bg = "#0000ff",
|
||||||
|
fg = "#ffffff",
|
||||||
|
timeout = 5,
|
||||||
|
},
|
||||||
|
warn = {
|
||||||
|
bg = "#ffaa00",
|
||||||
|
fg = "#000000",
|
||||||
|
timeout = 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.config.defaults = {
|
||||||
|
timeout = 5,
|
||||||
|
text = "",
|
||||||
|
screen = nil,
|
||||||
|
ontop = true,
|
||||||
|
margin = dpi(5),
|
||||||
|
border_width = dpi(1),
|
||||||
|
position = "top_right"
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.notification_closed_reason = {
|
||||||
|
silent = -1,
|
||||||
|
expired = 1,
|
||||||
|
dismissedByUser = 2, --TODO v5 remove this undocumented legacy constant
|
||||||
|
dismissed_by_user = 2,
|
||||||
|
dismissedByCommand = 3, --TODO v5 remove this undocumented legacy constant
|
||||||
|
dismissed_by_vommand = 3,
|
||||||
|
undefined = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Legacy --TODO v5 remove this alias
|
||||||
|
ret.notificationClosedReason = ret.notification_closed_reason
|
||||||
|
|
||||||
|
return ret
|
File diff suppressed because it is too large
Load Diff
|
@ -25,11 +25,14 @@ local tcat = table.concat
|
||||||
local tins = table.insert
|
local tins = table.insert
|
||||||
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
|
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
|
||||||
local naughty = require("naughty.core")
|
local naughty = require("naughty.core")
|
||||||
|
local cst = require("naughty.constants")
|
||||||
|
local nnotif = require("naughty.notification")
|
||||||
|
|
||||||
--- Notification library, dbus bindings
|
--- Notification library, dbus bindings
|
||||||
local dbus = { config = {} }
|
local dbus = { config = {} }
|
||||||
|
|
||||||
-- DBUS Notification constants
|
-- DBUS Notification constants
|
||||||
|
-- https://developer.gnome.org/notification-spec/#urgency-levels
|
||||||
local urgency = {
|
local urgency = {
|
||||||
low = "\0",
|
low = "\0",
|
||||||
normal = "\1",
|
normal = "\1",
|
||||||
|
@ -46,9 +49,9 @@ local urgency = {
|
||||||
-- @tfield table 3 critical urgency
|
-- @tfield table 3 critical urgency
|
||||||
-- @table config.mapping
|
-- @table config.mapping
|
||||||
dbus.config.mapping = {
|
dbus.config.mapping = {
|
||||||
{{urgency = urgency.low}, naughty.config.presets.low},
|
{{urgency = urgency.low}, cst.config.presets.low},
|
||||||
{{urgency = urgency.normal}, naughty.config.presets.normal},
|
{{urgency = urgency.normal}, cst.config.presets.normal},
|
||||||
{{urgency = urgency.critical}, naughty.config.presets.critical}
|
{{urgency = urgency.critical}, cst.config.presets.critical}
|
||||||
}
|
}
|
||||||
|
|
||||||
local function sendActionInvoked(notificationId, action)
|
local function sendActionInvoked(notificationId, action)
|
||||||
|
@ -140,7 +143,7 @@ capi.dbus.connect_signal("org.freedesktop.Notifications",
|
||||||
args.preset = gtable.join(args.preset, preset)
|
args.preset = gtable.join(args.preset, preset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local preset = args.preset or naughty.config.defaults
|
local preset = args.preset or cst.config.defaults
|
||||||
local notification
|
local notification
|
||||||
if actions then
|
if actions then
|
||||||
args.actions = {}
|
args.actions = {}
|
||||||
|
@ -152,12 +155,12 @@ capi.dbus.connect_signal("org.freedesktop.Notifications",
|
||||||
if action_id == "default" then
|
if action_id == "default" then
|
||||||
args.run = function()
|
args.run = function()
|
||||||
sendActionInvoked(notification.id, "default")
|
sendActionInvoked(notification.id, "default")
|
||||||
naughty.destroy(notification, naughty.notificationClosedReason.dismissedByUser)
|
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||||
end
|
end
|
||||||
elseif action_id ~= nil and action_text ~= nil then
|
elseif action_id ~= nil and action_text ~= nil then
|
||||||
args.actions[action_text] = function()
|
args.actions[action_text] = function()
|
||||||
sendActionInvoked(notification.id, action_id)
|
sendActionInvoked(notification.id, action_id)
|
||||||
naughty.destroy(notification, naughty.notificationClosedReason.dismissedByUser)
|
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -190,14 +193,14 @@ capi.dbus.connect_signal("org.freedesktop.Notifications",
|
||||||
args.timeout = expire / 1000
|
args.timeout = expire / 1000
|
||||||
end
|
end
|
||||||
args.freedesktop_hints = hints
|
args.freedesktop_hints = hints
|
||||||
notification = naughty.notify(args)
|
notification = nnotif(args)
|
||||||
return "u", notification.id
|
return "u", notification.id
|
||||||
end
|
end
|
||||||
return "u", "0"
|
return "u", "0"
|
||||||
elseif data.member == "CloseNotification" then
|
elseif data.member == "CloseNotification" then
|
||||||
local obj = naughty.getById(appname)
|
local obj = naughty.get_by_id(appname)
|
||||||
if obj then
|
if obj then
|
||||||
naughty.destroy(obj, naughty.notificationClosedReason.dismissedByCommand)
|
obj:destroy(cst.notification_closed_reason.dismissed_by_command)
|
||||||
end
|
end
|
||||||
elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then
|
elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then
|
||||||
-- name of notification app, name of vender, version, specification version
|
-- name of notification app, name of vender, version, specification version
|
||||||
|
|
|
@ -9,6 +9,9 @@ if dbus then
|
||||||
naughty.dbus = require("naughty.dbus")
|
naughty.dbus = require("naughty.dbus")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
naughty.layout = require("naughty.layout")
|
||||||
|
naughty.notification = require("naughty.notification")
|
||||||
|
|
||||||
return naughty
|
return naughty
|
||||||
|
|
||||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
|
||||||
|
-- @copyright 2017 Emmanuel Lepage Vallee
|
||||||
|
-- @module naughty.layout
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
return {
|
||||||
|
legacy = require("naughty.layout.legacy")
|
||||||
|
}
|
|
@ -0,0 +1,544 @@
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
--- A notification popup widget.
|
||||||
|
--
|
||||||
|
-- This is the legacy notification widget. It was the default until Awesome
|
||||||
|
-- v4.3 but is now being deprecated in favor of a more flexible widget.
|
||||||
|
--
|
||||||
|
-- The reason for this is/was that this widget is inflexible and mutate the
|
||||||
|
-- state of the notification object in a way that hinder other notification
|
||||||
|
-- widgets.
|
||||||
|
--
|
||||||
|
-- If no other notification widget is specified, Awesome fallback to this
|
||||||
|
-- widget.
|
||||||
|
--
|
||||||
|
--@DOC_naughty_actions_EXAMPLE@
|
||||||
|
--
|
||||||
|
-- @author koniu <gkusnierz@gmail.com>
|
||||||
|
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
|
||||||
|
-- @copyright 2008 koniu
|
||||||
|
-- @copyright 2017 Emmanuel Lepage Vallee
|
||||||
|
-- @classmod naughty.layout.legacy
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local capi = { screen = screen, awesome = awesome }
|
||||||
|
local naughty = require("naughty.core")
|
||||||
|
local screen = require("awful.screen")
|
||||||
|
local button = require("awful.button")
|
||||||
|
local beautiful = require("beautiful")
|
||||||
|
local surface = require("gears.surface")
|
||||||
|
local gtable = require("gears.table")
|
||||||
|
local wibox = require("wibox")
|
||||||
|
local gfs = require("gears.filesystem")
|
||||||
|
local timer = require("gears.timer")
|
||||||
|
local gmath = require("gears.math")
|
||||||
|
local cairo = require("lgi").cairo
|
||||||
|
local util = require("awful.util")
|
||||||
|
|
||||||
|
local function get_screen(s)
|
||||||
|
return s and capi.screen[s]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This is a copy of the table found in `naughty.core`. The reason the copy
|
||||||
|
-- exists is to make sure there is only unidirectional coupling between the
|
||||||
|
-- legacy widget (this class) and `naughty.core`. Exposing the "raw"
|
||||||
|
-- notification list is also a bad design and might cause indices and position
|
||||||
|
-- corruption. While it cannot be removed from the public API (yet), it can at
|
||||||
|
-- least be blacklisted internally.
|
||||||
|
local current_notifications = setmetatable({}, {__mode = "k"})
|
||||||
|
|
||||||
|
screen.connect_for_each_screen(function(s)
|
||||||
|
current_notifications[s] = {
|
||||||
|
top_left = {},
|
||||||
|
top_middle = {},
|
||||||
|
top_right = {},
|
||||||
|
bottom_left = {},
|
||||||
|
bottom_middle = {},
|
||||||
|
bottom_right = {},
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Counter for the notifications
|
||||||
|
-- Required for later access via DBUS
|
||||||
|
local counter = 1
|
||||||
|
|
||||||
|
--- 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 #current_notifications[s][position] + 1
|
||||||
|
width = width or current_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.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
|
||||||
|
local n = current_notifications[s][position][i]
|
||||||
|
|
||||||
|
-- `n` will not nil when there is too many notifications to fit in `s`
|
||||||
|
if n then
|
||||||
|
existing = existing + n.height + naughty.config.spacing
|
||||||
|
end
|
||||||
|
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 = current_notifications[s][position][i]
|
||||||
|
if n.timeout > 0 then
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Fallback to first one.
|
||||||
|
return current_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
|
||||||
|
local n = find_old_to_replace()
|
||||||
|
if n then
|
||||||
|
n:destroy(naughty.notification_closed_reason.too_many_on_screen)
|
||||||
|
end
|
||||||
|
v = get_offset(s, position, idx, width, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
|
||||||
|
local escape_pattern = "[<>&]"
|
||||||
|
local escape_subs = { ['<'] = "<", ['>'] = ">", ['&'] = "&" }
|
||||||
|
|
||||||
|
-- Cache the markup
|
||||||
|
local function set_escaped_text(self)
|
||||||
|
local text, title = self.text or "", self.title or ""
|
||||||
|
|
||||||
|
if title then title = title .. "\n" else title = "" end
|
||||||
|
|
||||||
|
local textbox = self.textbox
|
||||||
|
|
||||||
|
local function set_markup(pattern, replacements)
|
||||||
|
return textbox:set_markup_silently(string.format('<b>%s</b>%s', title, text:gsub(pattern, replacements)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_text()
|
||||||
|
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 set_markup("<br.->", "\n") then
|
||||||
|
-- That failed, escape everything which might cause an error from pango
|
||||||
|
if not set_markup(escape_pattern, escape_subs) then
|
||||||
|
-- Ok, just ignore all pango markup. If this fails, we got some invalid utf8
|
||||||
|
if not pcall(set_text) then
|
||||||
|
textbox:set_markup("<i><Invalid markup or UTF8, cannot display message></i>")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
naughty.connect_signal("property::text" ,set_escaped_text)
|
||||||
|
naughty.connect_signal("property::title",set_escaped_text)
|
||||||
|
|
||||||
|
|
||||||
|
--- Re-arrange notifications according to their position and index - internal
|
||||||
|
--
|
||||||
|
-- @return None
|
||||||
|
local function arrange(s)
|
||||||
|
-- {} in case the screen has been deleted
|
||||||
|
for p in pairs(current_notifications[s] or {}) do
|
||||||
|
for i,notification in pairs(current_notifications[s][p]) do
|
||||||
|
local offset = get_offset(s, p, i, notification.width, notification.height)
|
||||||
|
notification.box:geometry({ x = offset.x, y = offset.y })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function update_size(notification)
|
||||||
|
local n = notification
|
||||||
|
local s = n.size_info
|
||||||
|
local width = s.width
|
||||||
|
local height = s.height
|
||||||
|
local margin = s.margin
|
||||||
|
|
||||||
|
-- calculate the width
|
||||||
|
if not width then
|
||||||
|
local w, _ = n.textbox:get_preferred_size(n.screen)
|
||||||
|
width = w + (n.iconbox and s.icon_w + 2 * margin or 0) + 2 * margin
|
||||||
|
end
|
||||||
|
|
||||||
|
if width < s.actions_max_width then
|
||||||
|
width = s.actions_max_width
|
||||||
|
end
|
||||||
|
|
||||||
|
if s.max_width then
|
||||||
|
width = math.min(width, s.max_width)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- calculate the height
|
||||||
|
if not height then
|
||||||
|
local w = width - (n.iconbox and s.icon_w + 2 * margin or 0) - 2 * margin
|
||||||
|
local h = n.textbox:get_height_for_width(w, n.screen)
|
||||||
|
if n.iconbox and s.icon_h + 2 * margin > h + 2 * margin then
|
||||||
|
height = s.icon_h + 2 * margin
|
||||||
|
else
|
||||||
|
height = h + 2 * margin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
height = height + s.actions_total_height
|
||||||
|
|
||||||
|
if s.max_height then
|
||||||
|
height = math.min(height, s.max_height)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- crop to workarea size if too big
|
||||||
|
local workarea = n.screen.workarea
|
||||||
|
local border_width = s.border_width or 0
|
||||||
|
local padding = naughty.config.padding or 0
|
||||||
|
if width > workarea.width - 2*border_width - 2*padding then
|
||||||
|
width = workarea.width - 2*border_width - 2*padding
|
||||||
|
end
|
||||||
|
if height > workarea.height - 2*border_width - 2*padding then
|
||||||
|
height = workarea.height - 2*border_width - 2*padding
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set size in notification object
|
||||||
|
n.height = height + 2*border_width
|
||||||
|
n.width = width + 2*border_width
|
||||||
|
local offset = get_offset(n.screen, n.position, n.idx, n.width, n.height)
|
||||||
|
n.box:geometry({
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
x = offset.x,
|
||||||
|
y = offset.y,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- update positions of other notifications
|
||||||
|
arrange(n.screen)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function cleanup(self, _ --[[reason]], keep_visible)
|
||||||
|
-- It is not a legacy notification
|
||||||
|
if not self.box then return end
|
||||||
|
|
||||||
|
local scr = self.screen
|
||||||
|
|
||||||
|
assert(current_notifications[scr][self.position][self.idx] == self)
|
||||||
|
table.remove(current_notifications[scr][self.position], self.idx)
|
||||||
|
|
||||||
|
if (not keep_visible) or (not scr) then
|
||||||
|
self.box.visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
arrange(scr)
|
||||||
|
end
|
||||||
|
|
||||||
|
naughty.connect_signal("destroyed", cleanup)
|
||||||
|
|
||||||
|
--- The default notification GUI handler.
|
||||||
|
--
|
||||||
|
-- To disable this handler, use:
|
||||||
|
--
|
||||||
|
-- naughty.disconnect_signal(
|
||||||
|
-- "request::display", naughty.default_notification_handler
|
||||||
|
-- )
|
||||||
|
--
|
||||||
|
-- It looks like:
|
||||||
|
--
|
||||||
|
--@DOC_naughty_actions_EXAMPLE@
|
||||||
|
--
|
||||||
|
-- @tparam table notification The `naughty.notification` object.
|
||||||
|
-- @tparam table args Any arguments passed to the `naughty.notify` function,
|
||||||
|
-- including, but not limited to all `naughty.notification` properties.
|
||||||
|
-- @signalhandler naughty.default_notification_handler
|
||||||
|
function naughty.default_notification_handler(notification, args)
|
||||||
|
|
||||||
|
-- If request::display is called more than once, simply make sure the wibox
|
||||||
|
-- is visible.
|
||||||
|
if notification.box then
|
||||||
|
notification.box.visible = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local preset = notification.preset
|
||||||
|
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 timeout = args.timeout or preset.timeout
|
||||||
|
local icon = args.icon or preset.icon
|
||||||
|
local icon_size = args.icon_size or preset.icon_size
|
||||||
|
or beautiful.notification_icon_size
|
||||||
|
local ontop = args.ontop or preset.ontop
|
||||||
|
local hover_timeout = args.hover_timeout or preset.hover_timeout
|
||||||
|
local position = args.position or preset.position
|
||||||
|
local actions = args.actions
|
||||||
|
local destroy_cb = args.destroy
|
||||||
|
|
||||||
|
notification.screen = s
|
||||||
|
notification.destroy_cb = destroy_cb
|
||||||
|
notification.timeout = timeout
|
||||||
|
|
||||||
|
-- beautiful
|
||||||
|
local font = args.font or preset.font or beautiful.notification_font or
|
||||||
|
beautiful.font or capi.awesome.font
|
||||||
|
local fg = args.fg or preset.fg or
|
||||||
|
beautiful.notification_fg or beautiful.fg_normal or '#ffffff'
|
||||||
|
local bg = args.bg or preset.bg or
|
||||||
|
beautiful.notification_bg or beautiful.bg_normal or '#535d6c'
|
||||||
|
local border_color = args.border_color or preset.border_color or
|
||||||
|
beautiful.notification_border_color or beautiful.bg_focus or '#535d6c'
|
||||||
|
local border_width = args.border_width or preset.border_width or
|
||||||
|
beautiful.notification_border_width
|
||||||
|
local shape = args.shape or preset.shape or
|
||||||
|
beautiful.notification_shape
|
||||||
|
local width = args.width or preset.width or
|
||||||
|
beautiful.notification_width
|
||||||
|
local height = args.height or preset.height or
|
||||||
|
beautiful.notification_height
|
||||||
|
local max_width = args.max_width or preset.max_width or
|
||||||
|
beautiful.notification_max_width
|
||||||
|
local max_height = args.max_height or preset.max_height or
|
||||||
|
beautiful.notification_max_height
|
||||||
|
local margin = args.margin or preset.margin or
|
||||||
|
beautiful.notification_margin
|
||||||
|
local opacity = args.opacity or preset.opacity or
|
||||||
|
beautiful.notification_opacity
|
||||||
|
|
||||||
|
-- replace notification if needed
|
||||||
|
local reuse_box
|
||||||
|
if args.replaces_id then
|
||||||
|
local obj = naughty.get_by_id(args.replaces_id)
|
||||||
|
if obj then
|
||||||
|
-- destroy this and ...
|
||||||
|
naughty.destroy(obj, naughty.notification_closed_reason.silent, true)
|
||||||
|
reuse_box = obj.box
|
||||||
|
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
|
||||||
|
|
||||||
|
-- hook destroy
|
||||||
|
notification.timeout = timeout
|
||||||
|
local die = notification.die
|
||||||
|
|
||||||
|
local run = function ()
|
||||||
|
if args.run then
|
||||||
|
args.run(notification)
|
||||||
|
else
|
||||||
|
die(naughty.notification_closed_reason.dismissed_by_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local hover_destroy = function ()
|
||||||
|
if hover_timeout == 0 then
|
||||||
|
die(naughty.notification_closed_reason.expired)
|
||||||
|
else
|
||||||
|
if notification.timer then notification.timer:stop() end
|
||||||
|
notification.timer = timer { timeout = hover_timeout }
|
||||||
|
notification.timer:connect_signal("timeout", function() die(naughty.notification_closed_reason.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_escaped_text(notification)
|
||||||
|
|
||||||
|
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(gtable.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
|
||||||
|
|
||||||
|
local size_info = {
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
max_width = max_width,
|
||||||
|
max_height = max_height,
|
||||||
|
margin = margin,
|
||||||
|
border_width = border_width,
|
||||||
|
actions_max_width = actions_max_width,
|
||||||
|
actions_total_height = actions_total_height,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- create iconbox
|
||||||
|
local iconbox = nil
|
||||||
|
local iconmargin = nil
|
||||||
|
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)
|
||||||
|
-- urldecode URI path
|
||||||
|
icon = string.gsub(icon, "%%(%x%x)", function(x) return string.char(tonumber(x, 16)) end )
|
||||||
|
end
|
||||||
|
-- try to guess icon if the provided one is non-existent/readable
|
||||||
|
if type(icon) == "string" and not gfs.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 and (icon:get_height() > icon_size or icon:get_width() > icon_size) then
|
||||||
|
size_info.icon_scale_factor = icon_size / math.max(icon:get_height(),
|
||||||
|
icon:get_width())
|
||||||
|
|
||||||
|
size_info.icon_w = icon:get_width () * size_info.icon_scale_factor
|
||||||
|
size_info.icon_h = icon:get_height() * size_info.icon_scale_factor
|
||||||
|
|
||||||
|
local scaled =
|
||||||
|
cairo.ImageSurface(cairo.Format.ARGB32,
|
||||||
|
gmath.round(size_info.icon_w),
|
||||||
|
gmath.round(size_info.icon_h))
|
||||||
|
|
||||||
|
local cr = cairo.Context(scaled)
|
||||||
|
cr:scale(size_info.icon_scale_factor, size_info.icon_scale_factor)
|
||||||
|
cr:set_source_surface(icon, 0, 0)
|
||||||
|
cr:paint()
|
||||||
|
icon = scaled
|
||||||
|
else
|
||||||
|
size_info.icon_w = icon:get_width ()
|
||||||
|
size_info.icon_h = icon:get_height()
|
||||||
|
end
|
||||||
|
iconbox:set_resize(false)
|
||||||
|
iconbox:set_image(icon)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
notification.iconbox = iconbox
|
||||||
|
|
||||||
|
-- create container wibox
|
||||||
|
notification.box = wibox({ fg = fg,
|
||||||
|
bg = bg,
|
||||||
|
border_color = border_color,
|
||||||
|
border_width = border_width,
|
||||||
|
shape_border_color = shape and border_color,
|
||||||
|
shape_border_width = shape and border_width,
|
||||||
|
shape = shape,
|
||||||
|
type = "notification" })
|
||||||
|
|
||||||
|
if reuse_box then
|
||||||
|
notification.box = reuse_box
|
||||||
|
end
|
||||||
|
|
||||||
|
if hover_timeout then notification.box:connect_signal("mouse::enter", hover_destroy) end
|
||||||
|
|
||||||
|
notification.size_info = size_info
|
||||||
|
|
||||||
|
-- position the wibox
|
||||||
|
update_size(notification)
|
||||||
|
notification.box.ontop = ontop
|
||||||
|
notification.box.opacity = opacity
|
||||||
|
notification.box.visible = true
|
||||||
|
|
||||||
|
-- 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(gtable.join(button({}, 1, nil, run),
|
||||||
|
button({}, 3, nil, function()
|
||||||
|
die(naughty.notification_closed_reason.dismissed_by_user)
|
||||||
|
end)))
|
||||||
|
|
||||||
|
-- insert the notification to the table
|
||||||
|
table.insert(current_notifications[s][notification.position], notification)
|
||||||
|
|
||||||
|
if naughty.suspended and not args.ignore_suspend then
|
||||||
|
notification.box.visible = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
naughty.connect_signal("request::display", naughty.default_notification_handler)
|
|
@ -0,0 +1,416 @@
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
--- A notification object.
|
||||||
|
--
|
||||||
|
-- This class creates individual notification objects that can be manipulated
|
||||||
|
-- to extend the default behavior.
|
||||||
|
--
|
||||||
|
-- This class doesn't define the actual widget, but is rather intended as a data
|
||||||
|
-- object to hold the properties. All examples assume the default widgets, but
|
||||||
|
-- the whole implementation can be replaced.
|
||||||
|
--
|
||||||
|
--@DOC_naughty_actions_EXAMPLE@
|
||||||
|
--
|
||||||
|
-- @author Emmanuel Lepage Vallee
|
||||||
|
-- @copyright 2008 koniu
|
||||||
|
-- @copyright 2017 Emmanuel Lepage Vallee
|
||||||
|
-- @classmod naughty.notification
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
local gobject = require("gears.object")
|
||||||
|
local gtable = require("gears.table")
|
||||||
|
local timer = require("gears.timer")
|
||||||
|
local cst = require("naughty.constants")
|
||||||
|
local naughty = require("naughty.core")
|
||||||
|
|
||||||
|
local notification = {}
|
||||||
|
|
||||||
|
--- Notifications font.
|
||||||
|
-- @beautiful beautiful.notification_font
|
||||||
|
-- @tparam string|lgi.Pango.FontDescription notification_font
|
||||||
|
|
||||||
|
--- Notifications background color.
|
||||||
|
-- @beautiful beautiful.notification_bg
|
||||||
|
-- @tparam color notification_bg
|
||||||
|
|
||||||
|
--- Notifications foreground color.
|
||||||
|
-- @beautiful beautiful.notification_fg
|
||||||
|
-- @tparam color notification_fg
|
||||||
|
|
||||||
|
--- Notifications border width.
|
||||||
|
-- @beautiful beautiful.notification_border_width
|
||||||
|
-- @tparam int notification_border_width
|
||||||
|
|
||||||
|
--- Notifications border color.
|
||||||
|
-- @beautiful beautiful.notification_border_color
|
||||||
|
-- @tparam color notification_border_color
|
||||||
|
|
||||||
|
--- Notifications shape.
|
||||||
|
-- @beautiful beautiful.notification_shape
|
||||||
|
-- @tparam[opt] gears.shape notification_shape
|
||||||
|
-- @see gears.shape
|
||||||
|
|
||||||
|
--- Notifications opacity.
|
||||||
|
-- @beautiful beautiful.notification_opacity
|
||||||
|
-- @tparam[opt] int notification_opacity
|
||||||
|
|
||||||
|
--- Notifications margin.
|
||||||
|
-- @beautiful beautiful.notification_margin
|
||||||
|
-- @tparam int notification_margin
|
||||||
|
|
||||||
|
--- Notifications width.
|
||||||
|
-- @beautiful beautiful.notification_width
|
||||||
|
-- @tparam int notification_width
|
||||||
|
|
||||||
|
--- Notifications height.
|
||||||
|
-- @beautiful beautiful.notification_height
|
||||||
|
-- @tparam int notification_height
|
||||||
|
|
||||||
|
--- Unique identifier of the notification.
|
||||||
|
-- This is the equivalent to a PID as allows external applications to select
|
||||||
|
-- notifications.
|
||||||
|
-- @property text
|
||||||
|
-- @param string
|
||||||
|
-- @see title
|
||||||
|
|
||||||
|
--- Text of the notification.
|
||||||
|
-- @property text
|
||||||
|
-- @param string
|
||||||
|
-- @see title
|
||||||
|
|
||||||
|
--- Title of the notification.
|
||||||
|
--@DOC_naughty_helloworld_EXAMPLE@
|
||||||
|
-- @property title
|
||||||
|
-- @param string
|
||||||
|
|
||||||
|
--- Time in seconds after which popup expires.
|
||||||
|
-- Set 0 for no timeout.
|
||||||
|
-- @property timeout
|
||||||
|
-- @param number
|
||||||
|
|
||||||
|
--- Delay in seconds after which hovered popup disappears.
|
||||||
|
-- @property hover_timeout
|
||||||
|
-- @param number
|
||||||
|
|
||||||
|
--- Target screen for the notification.
|
||||||
|
-- @property screen
|
||||||
|
-- @param screen
|
||||||
|
|
||||||
|
--- Corner of the workarea displaying the popups.
|
||||||
|
--
|
||||||
|
-- The possible values are:
|
||||||
|
--
|
||||||
|
-- * *top_right*
|
||||||
|
-- * *top_left*
|
||||||
|
-- * *bottom_left*
|
||||||
|
-- * *bottom_right*
|
||||||
|
-- * *top_middle*
|
||||||
|
-- * *bottom_middle*
|
||||||
|
--
|
||||||
|
--@DOC_awful_notification_corner_EXAMPLE@
|
||||||
|
--
|
||||||
|
-- @property position
|
||||||
|
-- @param string
|
||||||
|
|
||||||
|
--- Boolean forcing popups to display on top.
|
||||||
|
-- @property ontop
|
||||||
|
-- @param boolean
|
||||||
|
|
||||||
|
--- Popup height.
|
||||||
|
-- @property height
|
||||||
|
-- @param number
|
||||||
|
|
||||||
|
--- Popup width.
|
||||||
|
-- @property width
|
||||||
|
-- @param number
|
||||||
|
|
||||||
|
--- Notification font.
|
||||||
|
--@DOC_naughty_colors_EXAMPLE@
|
||||||
|
-- @property font
|
||||||
|
-- @param string
|
||||||
|
|
||||||
|
--- Path to icon.
|
||||||
|
-- @property icon
|
||||||
|
-- @tparam string|surface icon
|
||||||
|
|
||||||
|
--- Desired icon size in px.
|
||||||
|
-- @property icon_size
|
||||||
|
-- @param number
|
||||||
|
|
||||||
|
--- Foreground color.
|
||||||
|
-- @property fg
|
||||||
|
-- @tparam string|color|pattern fg
|
||||||
|
-- @see title
|
||||||
|
-- @see gears.color
|
||||||
|
|
||||||
|
--- Background color.
|
||||||
|
-- @property bg
|
||||||
|
-- @tparam string|color|pattern bg
|
||||||
|
-- @see title
|
||||||
|
-- @see gears.color
|
||||||
|
|
||||||
|
--- Border width.
|
||||||
|
-- @property border_width
|
||||||
|
-- @param number
|
||||||
|
-- @see title
|
||||||
|
|
||||||
|
--- Border color.
|
||||||
|
-- @property border_color
|
||||||
|
-- @param string
|
||||||
|
-- @see title
|
||||||
|
-- @see gears.color
|
||||||
|
|
||||||
|
--- Widget shape.
|
||||||
|
--@DOC_naughty_shape_EXAMPLE@
|
||||||
|
-- @property shape
|
||||||
|
|
||||||
|
--- Widget opacity.
|
||||||
|
-- @property opacity
|
||||||
|
-- @param number From 0 to 1
|
||||||
|
|
||||||
|
--- Widget margin.
|
||||||
|
-- @property margin
|
||||||
|
-- @tparam number|table margin
|
||||||
|
-- @see shape
|
||||||
|
|
||||||
|
--- Function to run on left click.
|
||||||
|
-- @property run
|
||||||
|
-- @param function
|
||||||
|
|
||||||
|
--- Function to run when notification is destroyed.
|
||||||
|
-- @property destroy
|
||||||
|
-- @param function
|
||||||
|
|
||||||
|
--- Table with any of the above parameters.
|
||||||
|
-- args will override ones defined
|
||||||
|
-- in the preset.
|
||||||
|
-- @property preset
|
||||||
|
-- @param table
|
||||||
|
|
||||||
|
--- Replace the notification with the given ID.
|
||||||
|
-- @property replaces_id
|
||||||
|
-- @param number
|
||||||
|
|
||||||
|
--- 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.
|
||||||
|
-- @property callback
|
||||||
|
-- @param function
|
||||||
|
|
||||||
|
--- A table containing strings that represents actions to buttons.
|
||||||
|
--
|
||||||
|
-- The table key (a number) is used by DBus to set map the action.
|
||||||
|
--
|
||||||
|
-- @property actions
|
||||||
|
-- @param table
|
||||||
|
|
||||||
|
--- Ignore this notification, do not display.
|
||||||
|
--
|
||||||
|
-- Note that this property has to be set in a `preset` or in a `request::preset`
|
||||||
|
-- handler.
|
||||||
|
--
|
||||||
|
-- @property ignore
|
||||||
|
-- @param boolean
|
||||||
|
|
||||||
|
--- Tell if the notification is currently suspended (read only).
|
||||||
|
--
|
||||||
|
-- This is always equal to `naughty.suspended`
|
||||||
|
--@property suspended
|
||||||
|
--@param boolean
|
||||||
|
|
||||||
|
--- Emitted when the notification is destroyed.
|
||||||
|
-- @signal destroyed
|
||||||
|
|
||||||
|
-- . --FIXME needs a description
|
||||||
|
-- @property ignore_suspend If set to true this notification
|
||||||
|
-- will be shown even if notifications are suspended via `naughty.suspend`.
|
||||||
|
|
||||||
|
--FIXME remove the screen attribute, let the handlers decide
|
||||||
|
-- document all handler extra properties
|
||||||
|
|
||||||
|
--FIXME add methods such as persist
|
||||||
|
|
||||||
|
--- Destroy notification by notification object
|
||||||
|
--
|
||||||
|
-- @tparam string reason One of the reasons from `notification_closed_reason`
|
||||||
|
-- @tparam[opt=false] boolean keep_visible If true, keep the notification visible
|
||||||
|
-- @return True if the popup was successfully destroyed, nil otherwise
|
||||||
|
function notification:destroy(reason, keep_visible)
|
||||||
|
self:emit_signal("destroyed")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set new notification timeout.
|
||||||
|
-- @tparam number new_timeout Time in seconds after which notification disappears.
|
||||||
|
function notification:reset_timeout(new_timeout)
|
||||||
|
if self.timer then self.timer:stop() end
|
||||||
|
|
||||||
|
local timeout = new_timeout or self.timeout
|
||||||
|
self:set_timeout(self, timeout)
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
self.timer:start()
|
||||||
|
end
|
||||||
|
|
||||||
|
function notification:set_id(new_id)
|
||||||
|
assert(self._private.id == nil, "Notification identifier can only be set once")
|
||||||
|
self._private.id = new_id
|
||||||
|
self:emit_signal("property::id", new_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
function notification:set_timeout(timeout)
|
||||||
|
local die = function (reason)
|
||||||
|
self:destroy(reason)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.timer and self._private.timeout == timeout then return end
|
||||||
|
|
||||||
|
if timeout > 0 then
|
||||||
|
local timer_die = timer { timeout = timeout }
|
||||||
|
timer_die:connect_signal("timeout", function() die(cst.notification_closed_reason.expired) end)
|
||||||
|
if not self.suspended then --FIXME there's still a dependency loop to fix before it works
|
||||||
|
timer_die:start()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Prevent a memory leak and the accumulation of active timers
|
||||||
|
if self.timer and self.timer.started then
|
||||||
|
self.timer:stop()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.timer = timer_die
|
||||||
|
end
|
||||||
|
self.die = die
|
||||||
|
self._private.timeout = timeout
|
||||||
|
end
|
||||||
|
|
||||||
|
local properties = {
|
||||||
|
"text" , "title" , "timeout" , "hover_timeout" ,
|
||||||
|
"screen" , "position", "ontop" , "border_width" ,
|
||||||
|
"width" , "font" , "icon" , "icon_size" ,
|
||||||
|
"fg" , "bg" , "height" , "border_color" ,
|
||||||
|
"shape" , "opacity" , "margin" , "ignore_suspend",
|
||||||
|
"destroy" , "preset" , "callback", "replaces_id" ,
|
||||||
|
"actions" , "run" , "id" , "ignore" ,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prop in ipairs(properties) do
|
||||||
|
notification["get_"..prop] = notification["get_"..prop] or function(self)
|
||||||
|
-- It's possible this could be called from the `request::preset` handler.
|
||||||
|
-- `rawget()` is necessary to avoid a stack overflow.
|
||||||
|
local preset = rawget(self, "preset")
|
||||||
|
|
||||||
|
return self._private[prop]
|
||||||
|
or (preset and preset[prop])
|
||||||
|
or cst.config.defaults[prop]
|
||||||
|
end
|
||||||
|
|
||||||
|
notification["set_"..prop] = notification["set_"..prop] or function(self, value)
|
||||||
|
self._private[prop] = value
|
||||||
|
self:emit_signal("property::"..prop, value)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
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=`beautiful.notification_height` or auto] args.height Popup height.
|
||||||
|
-- @int[opt=`beautiful.notification_width` or auto] args.width Popup width.
|
||||||
|
-- @string[opt=`beautiful.notification_font` or `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.notification_fg` or `beautiful.fg_focus` or `'#ffffff'`] args.fg Foreground color.
|
||||||
|
-- @string[opt=`beautiful.notification_fg` or `beautiful.bg_focus` or `'#535d6c'`] args.bg Background color.
|
||||||
|
-- @int[opt=`beautiful.notification_border_width` or 1] args.border_width Border width.
|
||||||
|
-- @string[opt=`beautiful.notification_border_color` or
|
||||||
|
-- `beautiful.border_focus` or `'#535d6c'`] args.border_color Border color.
|
||||||
|
-- @tparam[opt=`beautiful.notification_shape`] gears.shape args.shape Widget shape.
|
||||||
|
-- @tparam[opt=`beautiful.notification_opacity`] gears.opacity args.opacity Widget opacity.
|
||||||
|
-- @tparam[opt=`beautiful.notification_margin`] gears.margin args.margin Widget margin.
|
||||||
|
-- @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.notification_closed_reason.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.
|
||||||
|
-- @bool[opt=false] args.ignore_suspend If set to true this notification
|
||||||
|
-- will be shown even if notifications are suspended via `naughty.suspend`.
|
||||||
|
-- @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.notification
|
||||||
|
local function create(args)
|
||||||
|
if cst.config.notify_callback then
|
||||||
|
args = cst.config.notify_callback(args)
|
||||||
|
if not args then return end
|
||||||
|
end
|
||||||
|
|
||||||
|
local n = gobject {
|
||||||
|
enable_properties = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(naughty.emit_signal)
|
||||||
|
-- Make sure all signals bubble up
|
||||||
|
n:_connect_everything(naughty.emit_signal)
|
||||||
|
|
||||||
|
-- Avoid modifying the original table
|
||||||
|
local private = {}
|
||||||
|
|
||||||
|
-- gather variables together
|
||||||
|
rawset(n, "preset", gtable.join(
|
||||||
|
cst.config.defaults or {},
|
||||||
|
args.preset or cst.config.presets.normal or {},
|
||||||
|
rawget(n, "preset") or {}
|
||||||
|
))
|
||||||
|
|
||||||
|
for k, v in pairs(n.preset) do
|
||||||
|
private[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(args) do
|
||||||
|
private[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
rawset(n, "_private", private)
|
||||||
|
|
||||||
|
gtable.crush(n, notification, true)
|
||||||
|
|
||||||
|
-- Allow extensions to create override the preset with custom data
|
||||||
|
naughty.emit_signal("request::preset", n, args)
|
||||||
|
|
||||||
|
-- Register the notification before requesting a widget
|
||||||
|
n:emit_signal("new", args)
|
||||||
|
|
||||||
|
-- Let all listeners handle the actual visual aspects
|
||||||
|
if (not n.ignore) and (not n.preset.ignore) then
|
||||||
|
naughty.emit_signal("request::display", n, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Because otherwise the setter logic would not be executed
|
||||||
|
if n._private.timeout then
|
||||||
|
n:set_timeout(n._private.timeout or n.preset.timeout)
|
||||||
|
end
|
||||||
|
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(notification, {__call = function(_, ...) return create(...) end})
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
local awful = { keygrabber = require("awful.keygrabber") } --DOC_HIDE
|
local awful = { keygrabber = require("awful.keygrabber") } --DOC_HIDE
|
||||||
|
|
||||||
local naughty = { notify = function() end } --DOC_HIDE
|
local naughty = { notification = function() end } --DOC_HIDE
|
||||||
|
|
||||||
local autostart_works = false --DOC_HIDE
|
local autostart_works = false --DOC_HIDE
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ awful.keygrabber {
|
||||||
stop_callback = function(_, _, _, sequence)
|
stop_callback = function(_, _, _, sequence)
|
||||||
autostart_works = true --DOC_HIDE
|
autostart_works = true --DOC_HIDE
|
||||||
assert(sequence == "abc") --DOC_HIDE
|
assert(sequence == "abc") --DOC_HIDE
|
||||||
naughty.notify{text="The keys were:"..sequence}
|
naughty.notification {text="The keys were:"..sequence}
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ local naughty = {} --DOC_HIDE
|
||||||
prompt = "<b>Run: </b>",
|
prompt = "<b>Run: </b>",
|
||||||
keypressed_callback = function(mod, key, cmd) --luacheck: no unused args
|
keypressed_callback = function(mod, key, cmd) --luacheck: no unused args
|
||||||
if key == "Shift_L" then
|
if key == "Shift_L" then
|
||||||
notif = naughty.notify { text = "Shift pressed" }
|
notif = naughty.notification { text = "Shift pressed" }
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
keyreleased_callback = function(mod, key, cmd) --luacheck: no unused args
|
keyreleased_callback = function(mod, key, cmd) --luacheck: no unused args
|
||||||
|
|
|
@ -18,7 +18,7 @@ local naughty = {} --DOC_HIDE
|
||||||
textbox = atextbox,
|
textbox = atextbox,
|
||||||
exe_callback = function(input)
|
exe_callback = function(input)
|
||||||
if not input or #input == 0 then return end
|
if not input or #input == 0 then return end
|
||||||
naughty.notify{ text = "The input was: "..input }
|
naughty.notification { text = "The input was: "..input }
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,7 +31,7 @@ local steps = {
|
||||||
fake_screen.selected_tag.layout = max
|
fake_screen.selected_tag.layout = max
|
||||||
|
|
||||||
-- Display a notification on the screen-to-be-removed
|
-- Display a notification on the screen-to-be-removed
|
||||||
naughty.notify{ text = "test", screen = fake_screen }
|
naughty.notification { text = "test", screen = fake_screen }
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue