Merge pull request #2825 from Elv13/yet_more_notif_fixes
Support the notification spec v1.2
This commit is contained in:
commit
ed0918385c
|
@ -20,34 +20,15 @@ local hotkeys_popup = require("awful.hotkeys_popup")
|
||||||
require("awful.hotkeys_popup.keys")
|
require("awful.hotkeys_popup.keys")
|
||||||
|
|
||||||
-- {{{ Error handling
|
-- {{{ Error handling
|
||||||
-- @DOC_ERROR_HANDLING@
|
|
||||||
-- 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
|
naughty.connect_signal("request::display_error", function(message, startup)
|
||||||
naughty.notification {
|
naughty.notification {
|
||||||
preset = naughty.config.presets.critical,
|
urgency = "critical",
|
||||||
title = "Oops, there were errors during startup!",
|
title = "Oops, an error happened"..(startup and " during startup!" or "!"),
|
||||||
message = awesome.startup_errors
|
message = message
|
||||||
}
|
}
|
||||||
end
|
end)
|
||||||
|
|
||||||
-- Handle runtime errors after startup
|
|
||||||
do
|
|
||||||
local in_error = false
|
|
||||||
awesome.connect_signal("debug::error", function (err)
|
|
||||||
-- Make sure we don't go into an endless error loop
|
|
||||||
if in_error then return end
|
|
||||||
in_error = true
|
|
||||||
|
|
||||||
naughty.notification {
|
|
||||||
preset = naughty.config.presets.critical,
|
|
||||||
title = "Oops, an error happened!",
|
|
||||||
message = tostring(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
in_error = false
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- {{{ Variable definitions
|
-- {{{ Variable definitions
|
||||||
|
|
|
@ -134,6 +134,42 @@ function object:emit_signal(name, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function object._setup_class_signals(t)
|
||||||
|
local conns = {}
|
||||||
|
|
||||||
|
function t.connect_signal(name, func)
|
||||||
|
assert(name)
|
||||||
|
conns[name] = conns[name] or {}
|
||||||
|
table.insert(conns[name], func)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Emit a notification signal.
|
||||||
|
-- @tparam string name The signal name.
|
||||||
|
-- @param ... The signal callback arguments
|
||||||
|
function t.emit_signal(name, ...)
|
||||||
|
assert(name)
|
||||||
|
for _, func in pairs(conns[name] or {}) do
|
||||||
|
func(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Disconnect a signal from a source.
|
||||||
|
-- @tparam string name The name of the signal
|
||||||
|
-- @tparam function func The attached function
|
||||||
|
-- @treturn boolean If the disconnection was successful
|
||||||
|
function t.disconnect_signal(name, func)
|
||||||
|
for k, v in ipairs(conns[name] or {}) do
|
||||||
|
if v == func then
|
||||||
|
table.remove(conns[name], k)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return conns
|
||||||
|
end
|
||||||
|
|
||||||
local function get_miss(self, key)
|
local function get_miss(self, key)
|
||||||
local class = rawget(self, "_class")
|
local class = rawget(self, "_class")
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,11 @@ local xpcall = xpcall
|
||||||
|
|
||||||
local protected_call = {}
|
local protected_call = {}
|
||||||
|
|
||||||
local function error_handler(err)
|
function protected_call._error_handler(err)
|
||||||
gdebug.print_error(traceback("Error during a protected call: " .. tostring(err), 2))
|
gdebug.print_error(traceback("Error during a protected call: " .. tostring(err), 2))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function handle_result(success, ...)
|
function protected_call._handle_result(success, ...)
|
||||||
if success then
|
if success then
|
||||||
return ...
|
return ...
|
||||||
end
|
end
|
||||||
|
@ -27,13 +27,13 @@ if not select(2, xpcall(function(a) return a end, error, true)) then
|
||||||
-- Lua 5.1 doesn't support arguments in xpcall :-(
|
-- Lua 5.1 doesn't support arguments in xpcall :-(
|
||||||
do_pcall = function(func, ...)
|
do_pcall = function(func, ...)
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
return handle_result(xpcall(function()
|
return protected_call._handle_result(xpcall(function()
|
||||||
return func(unpack(args))
|
return func(unpack(args))
|
||||||
end, error_handler))
|
end, protected_call._error_handler))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
do_pcall = function(func, ...)
|
do_pcall = function(func, ...)
|
||||||
return handle_result(xpcall(func, error_handler, ...))
|
return protected_call._handle_result(xpcall(func, protected_call._error_handler, ...))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
--- A notification action.
|
--- A notification action.
|
||||||
--
|
--
|
||||||
-- A notification can have multiple actions to chose from. This module allows
|
-- A notification can have multiple actions to chose from. This module allows
|
||||||
-- to manage such actions.
|
-- to manage such actions. An action object can be shared by multiple
|
||||||
|
-- notifications.
|
||||||
--
|
--
|
||||||
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
|
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
|
||||||
-- @copyright 2019 Emmanuel Lepage Vallee
|
-- @copyright 2019 Emmanuel Lepage Vallee
|
||||||
|
@ -29,8 +30,8 @@ local action = {}
|
||||||
|
|
||||||
-- If the action is selected.
|
-- If the action is selected.
|
||||||
--
|
--
|
||||||
-- Only a single action can be selected per notification. It will be applied
|
-- Only a single action can be selected per notification. This is useful to
|
||||||
-- when `my_notification:apply()` is called.
|
-- implement keyboard navigation.
|
||||||
--
|
--
|
||||||
-- @property selected
|
-- @property selected
|
||||||
-- @param boolean
|
-- @param boolean
|
||||||
|
@ -50,10 +51,6 @@ local action = {}
|
||||||
-- @property icon_only
|
-- @property icon_only
|
||||||
-- @param[opt=false] boolean
|
-- @param[opt=false] boolean
|
||||||
|
|
||||||
--- The notification.
|
|
||||||
-- @property notification
|
|
||||||
-- @tparam naughty.notification notification
|
|
||||||
|
|
||||||
--- When a notification is invoked.
|
--- When a notification is invoked.
|
||||||
-- @signal invoked
|
-- @signal invoked
|
||||||
|
|
||||||
|
@ -64,10 +61,7 @@ end
|
||||||
function action:set_selected(value)
|
function action:set_selected(value)
|
||||||
self._private.selected = value
|
self._private.selected = value
|
||||||
self:emit_signal("property::selected", value)
|
self:emit_signal("property::selected", value)
|
||||||
|
self:emit_signal("_changed")
|
||||||
if self._private.notification then
|
|
||||||
self._private.notification:emit_signal("property::actions")
|
|
||||||
end
|
|
||||||
|
|
||||||
--TODO deselect other actions from the same notification
|
--TODO deselect other actions from the same notification
|
||||||
end
|
end
|
||||||
|
@ -79,15 +73,12 @@ end
|
||||||
function action:set_position(value)
|
function action:set_position(value)
|
||||||
self._private.position = value
|
self._private.position = value
|
||||||
self:emit_signal("property::position", value)
|
self:emit_signal("property::position", value)
|
||||||
|
self:emit_signal("_changed")
|
||||||
if self._private.notification then
|
|
||||||
self._private.notification:emit_signal("property::actions")
|
|
||||||
end
|
|
||||||
|
|
||||||
--TODO make sure the position is unique
|
--TODO make sure the position is unique
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, prop in ipairs { "name", "icon", "notification", "icon_only" } do
|
for _, prop in ipairs { "name", "icon", "icon_only" } do
|
||||||
action["get_"..prop] = function(self)
|
action["get_"..prop] = function(self)
|
||||||
return self._private[prop]
|
return self._private[prop]
|
||||||
end
|
end
|
||||||
|
@ -95,22 +86,18 @@ for _, prop in ipairs { "name", "icon", "notification", "icon_only" } do
|
||||||
action["set_"..prop] = function(self, value)
|
action["set_"..prop] = function(self, value)
|
||||||
self._private[prop] = value
|
self._private[prop] = value
|
||||||
self:emit_signal("property::"..prop, value)
|
self:emit_signal("property::"..prop, value)
|
||||||
|
self:emit_signal("_changed")
|
||||||
-- Make sure widgets with as an actionlist is updated.
|
|
||||||
if self._private.notification then
|
|
||||||
self._private.notification:emit_signal("property::actions")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local set_notif = action.set_notification
|
--TODO v4.5, remove this.
|
||||||
|
function action.set_notification()
|
||||||
function action.set_notification(self, value)
|
-- It didn't work because it prevented actions defined in the rules to be
|
||||||
local old = self._private.notification
|
-- in multiple notifications at once.
|
||||||
set_notif(self, value)
|
assert(
|
||||||
if old then
|
false,
|
||||||
old:emit_signal("property::actions")
|
"Setting a notification object was a bad idea and is now forbidden"
|
||||||
end
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Execute this action.
|
--- Execute this action.
|
||||||
|
@ -118,11 +105,12 @@ end
|
||||||
-- This only emits the `invoked` signal.
|
-- This only emits the `invoked` signal.
|
||||||
--
|
--
|
||||||
-- @method invoke
|
-- @method invoke
|
||||||
function action:invoke()
|
-- @tparam[opt={}] naughty.notification notif A notification object on which
|
||||||
assert(self._private.notification,
|
-- the action was invoked. If a notification is shared by many object (like
|
||||||
"Cannot invoke an action without a notification")
|
-- a "mute" or "snooze" action added to all notification), calling `:invoke()`
|
||||||
|
-- without adding the `notif` context will cause unexpected results.
|
||||||
self:emit_signal("invoked")
|
function action:invoke(notif)
|
||||||
|
self:emit_signal("invoked", notif)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function new(_, args)
|
local function new(_, args)
|
||||||
|
|
|
@ -48,6 +48,21 @@ ret.config.presets = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret.config._urgency = {
|
||||||
|
low = "\0",
|
||||||
|
normal = "\1",
|
||||||
|
critical = "\2"
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.config.mapping = {
|
||||||
|
{{urgency = ret.config._urgency.low }, ret.config.presets.low}, --compat
|
||||||
|
{{urgency = ret.config._urgency.normal }, ret.config.presets.normal}, --compat
|
||||||
|
{{urgency = ret.config._urgency.critical}, ret.config.presets.critical}, --compat
|
||||||
|
{{urgency = "low" }, ret.config.presets.low},
|
||||||
|
{{urgency = "normal" }, ret.config.presets.normal},
|
||||||
|
{{urgency = "critical"}, ret.config.presets.critical},
|
||||||
|
}
|
||||||
|
|
||||||
ret.config.defaults = {
|
ret.config.defaults = {
|
||||||
timeout = 5,
|
timeout = 5,
|
||||||
text = "",
|
text = "",
|
||||||
|
@ -55,7 +70,12 @@ ret.config.defaults = {
|
||||||
ontop = true,
|
ontop = true,
|
||||||
margin = dpi(5),
|
margin = dpi(5),
|
||||||
border_width = dpi(1),
|
border_width = dpi(1),
|
||||||
position = "top_right"
|
position = "top_right",
|
||||||
|
urgency = "normal",
|
||||||
|
message = "",
|
||||||
|
title = "",
|
||||||
|
app_name = "",
|
||||||
|
ignore = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.notification_closed_reason = {
|
ret.notification_closed_reason = {
|
||||||
|
@ -65,7 +85,7 @@ ret.notification_closed_reason = {
|
||||||
dismissedByUser = 2, --TODO v5 remove this undocumented legacy constant
|
dismissedByUser = 2, --TODO v5 remove this undocumented legacy constant
|
||||||
dismissed_by_user = 2,
|
dismissed_by_user = 2,
|
||||||
dismissedByCommand = 3, --TODO v5 remove this undocumented legacy constant
|
dismissedByCommand = 3, --TODO v5 remove this undocumented legacy constant
|
||||||
dismissed_by_vommand = 3,
|
dismissed_by_command = 3,
|
||||||
undefined = 4
|
undefined = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ local capi = { screen = screen }
|
||||||
local gdebug = require("gears.debug")
|
local gdebug = require("gears.debug")
|
||||||
local screen = require("awful.screen")
|
local screen = require("awful.screen")
|
||||||
local gtable = require("gears.table")
|
local gtable = require("gears.table")
|
||||||
|
local gobject = require("gears.object")
|
||||||
|
|
||||||
local naughty = {}
|
local naughty = {}
|
||||||
|
|
||||||
|
@ -134,10 +135,36 @@ gtable.crush(naughty, require("naughty.constants"))
|
||||||
-- @property auto_reset_timeout
|
-- @property auto_reset_timeout
|
||||||
-- @tparam[opt=true] boolean auto_reset_timeout
|
-- @tparam[opt=true] boolean auto_reset_timeout
|
||||||
|
|
||||||
|
--- Enable or disable naughty ability to claim to support animations.
|
||||||
|
--
|
||||||
|
-- When this is true, applications which query `naughty` feature support
|
||||||
|
-- will see that animations are supported. Note that there is *very little*
|
||||||
|
-- support for this and enabled it will cause bugs.
|
||||||
|
--
|
||||||
|
-- @property image_animations_enabled
|
||||||
|
-- @param[opt=false] boolean
|
||||||
|
|
||||||
|
--- Enable or disable the persistent notifications.
|
||||||
|
--
|
||||||
|
-- This is very annoying when using `naughty.layout.box` popups, but tolerable
|
||||||
|
-- when using `naughty.list.notifications`.
|
||||||
|
--
|
||||||
|
-- Note that enabling this **does nothing** in `naughty` itself. The timeouts
|
||||||
|
-- are still honored and notifications still destroyed. It is the user
|
||||||
|
-- responsibility to disable the dismiss timer. However, this tells the
|
||||||
|
-- applications that notification persistence is supported so they might
|
||||||
|
-- stop using systray icons for the sake of displaying or other changes like
|
||||||
|
-- that.
|
||||||
|
--
|
||||||
|
-- @property persistence_enabled
|
||||||
|
-- @param[opt=false] boolean
|
||||||
|
|
||||||
local properties = {
|
local properties = {
|
||||||
suspended = false,
|
suspended = false,
|
||||||
expiration_paused = false,
|
expiration_paused = false,
|
||||||
auto_reset_timeout= true,
|
auto_reset_timeout = true,
|
||||||
|
image_animations_enabled = false,
|
||||||
|
persistence_enabled = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
--TODO v5 Deprecate the public `naughty.notifications` (to make it private)
|
--TODO v5 Deprecate the public `naughty.notifications` (to make it private)
|
||||||
|
@ -197,7 +224,9 @@ local function update_index(n)
|
||||||
remove_from_index(n)
|
remove_from_index(n)
|
||||||
|
|
||||||
-- Add to the index again
|
-- Add to the index again
|
||||||
local s = get_screen(n.screen or n.preset.screen or screen.focused())
|
local s = get_screen(n.screen
|
||||||
|
or (n.preset and n.preset.screen)
|
||||||
|
or screen.focused())
|
||||||
naughty.notifications[s] = naughty.notifications[s] or {}
|
naughty.notifications[s] = naughty.notifications[s] or {}
|
||||||
table.insert(naughty.notifications[s][n.position], n)
|
table.insert(naughty.notifications[s][n.position], n)
|
||||||
end
|
end
|
||||||
|
@ -223,7 +252,7 @@ function naughty.suspend()
|
||||||
properties.suspended = true
|
properties.suspended = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local conns = {}
|
local conns = gobject._setup_class_signals(naughty)
|
||||||
|
|
||||||
--- Connect a global signal on the notification engine.
|
--- Connect a global signal on the notification engine.
|
||||||
--
|
--
|
||||||
|
@ -235,41 +264,21 @@ local conns = {}
|
||||||
--
|
--
|
||||||
-- @tparam string name The name of the signal
|
-- @tparam string name The name of the signal
|
||||||
-- @tparam function func The function to attach
|
-- @tparam function func The function to attach
|
||||||
|
-- @staticfct naughty.connect_signal
|
||||||
-- @usage naughty.connect_signal("added", function(notif)
|
-- @usage naughty.connect_signal("added", function(notif)
|
||||||
-- -- do something
|
-- -- do something
|
||||||
-- end)
|
-- end)
|
||||||
-- @staticfct naughty.connect_signal
|
|
||||||
function naughty.connect_signal(name, func)
|
|
||||||
assert(name)
|
|
||||||
conns[name] = conns[name] or {}
|
|
||||||
table.insert(conns[name], func)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Emit a notification signal.
|
--- Emit a notification signal.
|
||||||
-- @tparam string name The signal name.
|
-- @tparam string name The signal name.
|
||||||
-- @param ... The signal callback arguments
|
-- @param ... The signal callback arguments
|
||||||
-- @staticfct naughty.emit_signal
|
-- @staticfct naughty.emit_signal
|
||||||
function naughty.emit_signal(name, ...)
|
|
||||||
assert(name)
|
|
||||||
for _, func in pairs(conns[name] or {}) do
|
|
||||||
func(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Disconnect a signal from a source.
|
--- Disconnect a signal from a source.
|
||||||
-- @tparam string name The name of the signal
|
-- @tparam string name The name of the signal
|
||||||
-- @tparam function func The attached function
|
-- @tparam function func The attached function
|
||||||
-- @treturn boolean If the disconnection was successful
|
|
||||||
-- @staticfct naughty.disconnect_signal
|
-- @staticfct naughty.disconnect_signal
|
||||||
function naughty.disconnect_signal(name, func)
|
-- @treturn boolean If the disconnection was successful
|
||||||
for k, v in ipairs(conns[name] or {}) do
|
|
||||||
if v == func then
|
|
||||||
table.remove(conns[name], k)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function resume()
|
local function resume()
|
||||||
properties.suspended = false
|
properties.suspended = false
|
||||||
|
@ -392,6 +401,11 @@ function naughty.get_has_display_handler()
|
||||||
return conns["request::display"] and #conns["request::display"] > 0 or false
|
return conns["request::display"] and #conns["request::display"] > 0 or false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Presets are "deprecated" when notification rules are used.
|
||||||
|
function naughty.get__has_preset_handler()
|
||||||
|
return conns["request::preset"] and #conns["request::preset"] > 0 or false
|
||||||
|
end
|
||||||
|
|
||||||
--- Set new notification timeout.
|
--- Set new notification timeout.
|
||||||
--
|
--
|
||||||
-- This function is deprecated, use `notification:reset_timeout(new_timeout)`.
|
-- This function is deprecated, use `notification:reset_timeout(new_timeout)`.
|
||||||
|
@ -486,6 +500,12 @@ function naughty.set_expiration_paused(p)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Emitted when an error occurred and requires attention.
|
||||||
|
-- @signal request::display_error
|
||||||
|
-- @tparam string message The error message.
|
||||||
|
-- @tparam boolean startup If the error occurred during the initial loading of
|
||||||
|
-- rc.lua (and thus caused the fallback to kick in).
|
||||||
|
|
||||||
--- Emitted when a notification is created.
|
--- Emitted when a notification is created.
|
||||||
-- @signal added
|
-- @signal added
|
||||||
-- @tparam naughty.notification notification The notification object
|
-- @tparam naughty.notification notification The notification object
|
||||||
|
@ -514,13 +534,27 @@ end
|
||||||
-- including, but not limited to, all `naughty.notification` properties.
|
-- including, but not limited to, all `naughty.notification` properties.
|
||||||
-- @signal request::preset
|
-- @signal request::preset
|
||||||
|
|
||||||
|
--- Emitted when an action requires an icon it doesn't know.
|
||||||
|
--
|
||||||
|
-- The implementation should look in the icon theme for an action icon or
|
||||||
|
-- provide something natively.
|
||||||
|
--
|
||||||
|
-- If an icon is found, the handler must set the `icon` property on the `action`
|
||||||
|
-- object to a path or a `gears.surface`.
|
||||||
|
--
|
||||||
|
-- @signal request::icon
|
||||||
|
-- @tparam naughty.action action The action.
|
||||||
|
-- @tparam string icon_name The icon name.
|
||||||
|
|
||||||
-- Register a new notification object.
|
-- Register a new notification object.
|
||||||
local function register(notification, args)
|
local function register(notification, args)
|
||||||
-- Add the some more properties
|
-- Add the some more properties
|
||||||
rawset(notification, "get_suspended", get_suspended)
|
rawset(notification, "get_suspended", get_suspended)
|
||||||
|
|
||||||
--TODO v5 uncouple the notifications and the screen
|
--TODO v5 uncouple the notifications and the screen
|
||||||
local s = get_screen(args.screen or notification.preset.screen or screen.focused())
|
local s = get_screen(args.screen
|
||||||
|
or (notification.preset and notification.preset.screen)
|
||||||
|
or screen.focused())
|
||||||
|
|
||||||
-- insert the notification to the table
|
-- insert the notification to the table
|
||||||
table.insert(naughty._active, notification)
|
table.insert(naughty._active, notification)
|
||||||
|
@ -535,7 +569,7 @@ local function register(notification, args)
|
||||||
naughty.emit_signal("added", notification, args)
|
naughty.emit_signal("added", notification, args)
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(rawget(notification, "preset"))
|
assert(rawget(notification, "preset") or naughty._has_preset_handler)
|
||||||
|
|
||||||
naughty.emit_signal("property::active")
|
naughty.emit_signal("property::active")
|
||||||
|
|
||||||
|
@ -564,6 +598,8 @@ local function set_index_miss(_, key, value)
|
||||||
if not value then
|
if not value then
|
||||||
resume()
|
resume()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
naughty.emit_signal("property::"..key)
|
||||||
else
|
else
|
||||||
rawset(naughty, key, value)
|
rawset(naughty, key, value)
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,8 +12,8 @@ local pairs = pairs
|
||||||
local type = type
|
local type = type
|
||||||
local string = string
|
local string = string
|
||||||
local capi = { awesome = awesome }
|
local capi = { awesome = awesome }
|
||||||
local gtable = require("gears.table")
|
|
||||||
local gsurface = require("gears.surface")
|
local gsurface = require("gears.surface")
|
||||||
|
local gdebug = require("gears.debug")
|
||||||
local protected_call = require("gears.protected_call")
|
local protected_call = require("gears.protected_call")
|
||||||
local lgi = require("lgi")
|
local lgi = require("lgi")
|
||||||
local cairo, Gio, GLib, GObject = lgi.cairo, lgi.Gio, lgi.GLib, lgi.GObject
|
local cairo, Gio, GLib, GObject = lgi.cairo, lgi.Gio, lgi.GLib, lgi.GObject
|
||||||
|
@ -28,6 +28,10 @@ local cst = require("naughty.constants")
|
||||||
local nnotif = require("naughty.notification")
|
local nnotif = require("naughty.notification")
|
||||||
local naction = require("naughty.action")
|
local naction = require("naughty.action")
|
||||||
|
|
||||||
|
local capabilities = {
|
||||||
|
"body", "body-markup", "icon-static", "actions", "action-icons"
|
||||||
|
}
|
||||||
|
|
||||||
--- Notification library, dbus bindings
|
--- Notification library, dbus bindings
|
||||||
local dbus = { config = {} }
|
local dbus = { config = {} }
|
||||||
|
|
||||||
|
@ -51,11 +55,7 @@ local urgency = {
|
||||||
-- @tfield table 2 normal urgency
|
-- @tfield table 2 normal urgency
|
||||||
-- @tfield table 3 critical urgency
|
-- @tfield table 3 critical urgency
|
||||||
-- @table config.mapping
|
-- @table config.mapping
|
||||||
dbus.config.mapping = {
|
dbus.config.mapping = cst.mapping
|
||||||
{{urgency = urgency.low}, cst.config.presets.low},
|
|
||||||
{{urgency = urgency.normal}, cst.config.presets.normal},
|
|
||||||
{{urgency = urgency.critical}, cst.config.presets.critical}
|
|
||||||
}
|
|
||||||
|
|
||||||
local function sendActionInvoked(notificationId, action)
|
local function sendActionInvoked(notificationId, action)
|
||||||
if bus_connection then
|
if bus_connection then
|
||||||
|
@ -123,7 +123,7 @@ end
|
||||||
local notif_methods = {}
|
local notif_methods = {}
|
||||||
|
|
||||||
function notif_methods.Notify(sender, object_path, interface, method, parameters, invocation)
|
function notif_methods.Notify(sender, object_path, interface, method, parameters, invocation)
|
||||||
local appname, replaces_id, icon, title, text, actions, hints, expire =
|
local appname, replaces_id, app_icon, title, text, actions, hints, expire =
|
||||||
unpack(parameters.value)
|
unpack(parameters.value)
|
||||||
|
|
||||||
local args = {}
|
local args = {}
|
||||||
|
@ -141,17 +141,12 @@ function notif_methods.Notify(sender, object_path, interface, method, parameters
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if appname ~= "" then
|
if appname ~= "" then
|
||||||
args.appname = appname
|
args.appname = appname --TODO v6 Remove this.
|
||||||
end
|
args.app_name = appname
|
||||||
for _, obj in pairs(dbus.config.mapping) do
|
|
||||||
local filter, preset = obj[1], obj[2]
|
|
||||||
if (not filter.urgency or filter.urgency == hints.urgency) and
|
|
||||||
(not filter.category or filter.category == hints.category) and
|
|
||||||
(not filter.appname or filter.appname == appname) then
|
|
||||||
args.preset = gtable.join(args.preset, preset)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local preset = args.preset or cst.config.defaults
|
local preset = args.preset or cst.config.defaults
|
||||||
local notification
|
local notification
|
||||||
if actions then
|
if actions then
|
||||||
|
@ -167,14 +162,26 @@ function notif_methods.Notify(sender, object_path, interface, method, parameters
|
||||||
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
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
|
||||||
|
|
||||||
local a = naction {
|
local a = naction {
|
||||||
name = action_text,
|
name = action_text,
|
||||||
position = action_id,
|
id = action_id,
|
||||||
|
position = (i - 1)/2 + 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Right now `gears` doesn't have a great icon implementation
|
||||||
|
-- and `naughty` doesn't depend on `menubar`, so delegate the
|
||||||
|
-- icon "somewhere" using a request.
|
||||||
|
if hints["action-icons"] and action_id ~= "" then
|
||||||
|
naughty.emit_signal("request::icon", a, action_id)
|
||||||
|
end
|
||||||
|
|
||||||
a:connect_signal("invoked", function()
|
a:connect_signal("invoked", function()
|
||||||
sendActionInvoked(notification.id, action_id)
|
sendActionInvoked(notification.id, action_id)
|
||||||
|
|
||||||
|
if not notification.resident then
|
||||||
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
table.insert(args.actions, a)
|
table.insert(args.actions, a)
|
||||||
|
@ -189,22 +196,34 @@ function notif_methods.Notify(sender, object_path, interface, method, parameters
|
||||||
member = method, sender = sender, bus = "session"
|
member = method, sender = sender, bus = "session"
|
||||||
}
|
}
|
||||||
if not preset.callback or (type(preset.callback) == "function" and
|
if not preset.callback or (type(preset.callback) == "function" and
|
||||||
preset.callback(legacy_data, appname, replaces_id, icon, title, text, actions, hints, expire)) then
|
preset.callback(legacy_data, appname, replaces_id, app_icon, title, text, actions, hints, expire)) then
|
||||||
if icon ~= "" then
|
|
||||||
args.icon = icon
|
|
||||||
elseif hints.icon_data or hints.image_data then
|
if app_icon ~= "" then
|
||||||
|
args.app_icon = app_icon
|
||||||
|
end
|
||||||
|
|
||||||
|
if hints.icon_data or hints.image_data or hints["image-data"] then
|
||||||
-- Icon data is a bit complex and hence needs special care:
|
-- Icon data is a bit complex and hence needs special care:
|
||||||
-- .value breaks with the array of bytes (ay) that we get here.
|
-- .value breaks with the array of bytes (ay) that we get here.
|
||||||
-- So, bypass it and look up the needed value differently
|
-- So, bypass it and look up the needed value differently
|
||||||
local icon_data
|
local icon_condidates = {}
|
||||||
for k, v in parameters:get_child_value(7 - 1):pairs() do
|
for k, v in parameters:get_child_value(7 - 1):pairs() do
|
||||||
if k == "icon_data" then
|
if k == "image-data" then
|
||||||
icon_data = v
|
icon_condidates[1] = v -- not deprecated
|
||||||
elseif k == "image_data" and icon_data == nil then
|
break
|
||||||
icon_data = v
|
elseif k == "image_data" then -- deprecated
|
||||||
|
icon_condidates[2] = v
|
||||||
|
elseif k == "icon_data" then -- deprecated
|
||||||
|
icon_condidates[3] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The order is mandated by the spec.
|
||||||
|
local icon_data = icon_condidates[1]
|
||||||
|
or icon_condidates[2]
|
||||||
|
or icon_condidates[3]
|
||||||
|
|
||||||
-- icon_data is an array:
|
-- icon_data is an array:
|
||||||
-- 1 -> width
|
-- 1 -> width
|
||||||
-- 2 -> height
|
-- 2 -> height
|
||||||
|
@ -218,20 +237,87 @@ function notif_methods.Notify(sender, object_path, interface, method, parameters
|
||||||
-- GVariant.data to get that as an LGI byte buffer. That one can
|
-- GVariant.data to get that as an LGI byte buffer. That one can
|
||||||
-- then by converted to a string via its __tostring metamethod.
|
-- then by converted to a string via its __tostring metamethod.
|
||||||
local data = tostring(icon_data:get_child_value(7 - 1).data)
|
local data = tostring(icon_data:get_child_value(7 - 1).data)
|
||||||
args.icon = convert_icon(icon_data[1], icon_data[2], icon_data[3], icon_data[6], data)
|
args.image = convert_icon(icon_data[1], icon_data[2], icon_data[3], icon_data[6], data)
|
||||||
|
|
||||||
|
-- Convert all animation frames.
|
||||||
|
if naughty.image_animations_enabled then
|
||||||
|
args.images = {args.image}
|
||||||
|
|
||||||
|
if #icon_data > 7 then
|
||||||
|
for frame=8, #icon_data do
|
||||||
|
data = tostring(icon_data:get_child_value(frame-1).data)
|
||||||
|
|
||||||
|
table.insert(
|
||||||
|
args.images,
|
||||||
|
convert_icon(
|
||||||
|
icon_data[1],
|
||||||
|
icon_data[2],
|
||||||
|
icon_data[3],
|
||||||
|
icon_data[6],
|
||||||
|
data
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Alternate ways to set the icon. The specs recommends to allow both
|
||||||
|
-- the icon and image to co-exist since they serve different purpose.
|
||||||
|
-- However in case the icon isn't specified, use the image.
|
||||||
|
args.image = args.image
|
||||||
|
or hints["image-path"] -- not deprecated
|
||||||
|
or hints["image_path"] -- deprecated
|
||||||
|
|
||||||
|
if naughty.image_animations_enabled then
|
||||||
|
args.images = args.images or {}
|
||||||
|
end
|
||||||
|
|
||||||
if replaces_id and replaces_id ~= "" and replaces_id ~= 0 then
|
if replaces_id and replaces_id ~= "" and replaces_id ~= 0 then
|
||||||
args.replaces_id = replaces_id
|
args.replaces_id = replaces_id
|
||||||
end
|
end
|
||||||
if expire and expire > -1 then
|
if expire and expire > -1 then
|
||||||
args.timeout = expire / 1000
|
args.timeout = expire / 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
args.freedesktop_hints = hints
|
args.freedesktop_hints = hints
|
||||||
|
|
||||||
|
-- Not very pretty, but given the current format is documented in the
|
||||||
|
-- public API... well, whatever...
|
||||||
|
if hints and hints.urgency then
|
||||||
|
for name, key in pairs(urgency) do
|
||||||
|
local b = string.char(hints.urgency)
|
||||||
|
if key == b then
|
||||||
|
args.urgency = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
args.urgency = args.urgency or "normal"
|
||||||
|
|
||||||
-- Try to update existing objects when possible
|
-- Try to update existing objects when possible
|
||||||
notification = naughty.get_by_id(replaces_id)
|
notification = naughty.get_by_id(replaces_id)
|
||||||
|
|
||||||
if notification then
|
if notification then
|
||||||
|
if not notification._private._unique_sender then
|
||||||
|
-- If this happens, the notification is either trying to
|
||||||
|
-- highjack content created within AwesomeWM or it is garbage
|
||||||
|
-- to begin with.
|
||||||
|
gdebug.print_warning(
|
||||||
|
"A notification has been received, but tried to update "..
|
||||||
|
"the content of a notification it does not own."
|
||||||
|
)
|
||||||
|
elseif notification._private._unique_sender ~= sender then
|
||||||
|
-- Nothing says you cannot and some scripts may do it
|
||||||
|
-- accidentally, but this is rather unexpected.
|
||||||
|
gdebug.print_warning(
|
||||||
|
"Notification "..notification.title.." is being updated"..
|
||||||
|
"by a different DBus connection ("..sender.."), this is "..
|
||||||
|
"suspicious. The original connection was "..
|
||||||
|
notification._private._unique_sender
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
for k, v in pairs(args) do
|
for k, v in pairs(args) do
|
||||||
if k == "destroy" then k = "destroy_cb" end
|
if k == "destroy" then k = "destroy_cb" end
|
||||||
notification[k] = v
|
notification[k] = v
|
||||||
|
@ -240,6 +326,9 @@ function notif_methods.Notify(sender, object_path, interface, method, parameters
|
||||||
-- Even if no property changed, restart the timeout.
|
-- Even if no property changed, restart the timeout.
|
||||||
notification:reset_timeout()
|
notification:reset_timeout()
|
||||||
else
|
else
|
||||||
|
-- Only set the sender for new notifications.
|
||||||
|
args._unique_sender = sender
|
||||||
|
|
||||||
notification = nnotif(args)
|
notification = nnotif(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -261,16 +350,14 @@ end
|
||||||
function notif_methods.GetServerInformation(_, _, _, _, _, invocation)
|
function notif_methods.GetServerInformation(_, _, _, _, _, invocation)
|
||||||
-- name of notification app, name of vender, version, specification version
|
-- name of notification app, name of vender, version, specification version
|
||||||
invocation:return_value(GLib.Variant("(ssss)", {
|
invocation:return_value(GLib.Variant("(ssss)", {
|
||||||
"naughty", "awesome", capi.awesome.version, "1.0"
|
"naughty", "awesome", capi.awesome.version, "1.2"
|
||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
function notif_methods.GetCapabilities(_, _, _, _, _, invocation)
|
function notif_methods.GetCapabilities(_, _, _, _, _, invocation)
|
||||||
-- We actually do display the body of the message, we support <b>, <i>
|
-- We actually do display the body of the message, we support <b>, <i>
|
||||||
-- and <u> in the body and we handle static (non-animated) icons.
|
-- and <u> in the body and we handle static (non-animated) icons.
|
||||||
invocation:return_value(GLib.Variant("(as)", {
|
invocation:return_value(GLib.Variant("(as)", {capabilities}))
|
||||||
{ "body", "body-markup", "icon-static", "actions" }
|
|
||||||
}))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function method_call(_, sender, object_path, interface, method, parameters, invocation)
|
local function method_call(_, sender, object_path, interface, method, parameters, invocation)
|
||||||
|
@ -330,6 +417,101 @@ local function on_bus_acquire(conn, _)
|
||||||
GObject.Closure(method_call))
|
GObject.Closure(method_call))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local bus_proxy, pid_for_unique_name = nil, {}
|
||||||
|
|
||||||
|
Gio.DBusProxy.new_for_bus(
|
||||||
|
Gio.BusType.SESSION,
|
||||||
|
Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES,
|
||||||
|
nil,
|
||||||
|
"org.freedesktop.DBus",
|
||||||
|
"/org/freedesktop/DBus",
|
||||||
|
"org.freedesktop.DBus",
|
||||||
|
nil,
|
||||||
|
function(proxy)
|
||||||
|
bus_proxy = proxy
|
||||||
|
end,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
|
||||||
|
--- Get the clients associated with a notification.
|
||||||
|
--
|
||||||
|
-- Note that is based on the process PID. PIDs roll over, so don't use this
|
||||||
|
-- with very old notifications.
|
||||||
|
--
|
||||||
|
-- Also note that some multi-process application can use a different process
|
||||||
|
-- for the clients and the service used to send the notifications.
|
||||||
|
--
|
||||||
|
-- Since this is based on PIDs, it is impossible to know which client sent the
|
||||||
|
-- notification if the process has multiple clients (windows). Using the
|
||||||
|
-- `client.type` can be used to further filter this list into more probable
|
||||||
|
-- candidates (tooltips, menus and dialogs are unlikely to send notifications).
|
||||||
|
--
|
||||||
|
-- @tparam naughty.notification notif A notification object.
|
||||||
|
-- @treturn table A table with all associated clients.
|
||||||
|
function dbus.get_clients(notif)
|
||||||
|
-- First, the trivial case, but I never found an app that implements it.
|
||||||
|
-- It isn't standardized, but mentioned as possible.
|
||||||
|
local win_id = notif.freedesktop_hints and (notif.freedesktop_hints.window_ID
|
||||||
|
or notif.freedesktop_hints["window-id"]
|
||||||
|
or notif.freedesktop_hints.windowID
|
||||||
|
or notif.freedesktop_hints.windowid)
|
||||||
|
|
||||||
|
if win_id then
|
||||||
|
for _, c in ipairs(client.get()) do
|
||||||
|
if c.window_id == win_id then
|
||||||
|
return {win_id}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Less trivial, but mentioned in the spec. Note that this isn't
|
||||||
|
-- recommended by the spec, let alone mandatory. It is mentioned it can
|
||||||
|
-- exist. This wont work with Flatpak or Snaps.
|
||||||
|
local pid = notif.freedesktop_hints and (
|
||||||
|
notif.freedesktop_hints.PID or notif.freedesktop_hints.pid
|
||||||
|
)
|
||||||
|
|
||||||
|
if ((not bus_proxy) or not notif._private._unique_sender) and not pid then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if (not pid) and (not pid_for_unique_name[notif._private._unique_sender]) then
|
||||||
|
local owner = GLib.Variant("(s)", {notif._private._unique_sender})
|
||||||
|
|
||||||
|
-- It is sync, but this isn't done very often and since it is DBus
|
||||||
|
-- daemon itself, it is very responsive. Doing this using the async
|
||||||
|
-- variant would cause the clients to be unavailable in the notification
|
||||||
|
-- rules.
|
||||||
|
pid = bus_proxy:call_sync("GetConnectionUnixProcessID",
|
||||||
|
owner,
|
||||||
|
Gio.DBusCallFlags.NONE,
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
|
||||||
|
if (not pid) or (not pid.value) then return {} end
|
||||||
|
|
||||||
|
pid = pid.value and pid.value[1]
|
||||||
|
|
||||||
|
if not pid then return {} end
|
||||||
|
|
||||||
|
pid_for_unique_name[notif._private._unique_sender] = pid
|
||||||
|
end
|
||||||
|
|
||||||
|
pid = pid or pid_for_unique_name[notif._private._unique_sender]
|
||||||
|
|
||||||
|
if not pid then return {} end
|
||||||
|
|
||||||
|
local ret = {}
|
||||||
|
|
||||||
|
for _, c in ipairs(client.get()) do
|
||||||
|
if c.pid == pid then
|
||||||
|
table.insert(ret, c)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
local function on_name_acquired(conn, _)
|
local function on_name_acquired(conn, _)
|
||||||
bus_connection = conn
|
bus_connection = conn
|
||||||
end
|
end
|
||||||
|
@ -345,6 +527,35 @@ Gio.bus_own_name(Gio.BusType.SESSION, "org.freedesktop.Notifications",
|
||||||
-- For testing
|
-- For testing
|
||||||
dbus._notif_methods = notif_methods
|
dbus._notif_methods = notif_methods
|
||||||
|
|
||||||
|
local function remove_capability(cap)
|
||||||
|
for k, v in ipairs(capabilities) do
|
||||||
|
if v == cap then
|
||||||
|
table.remove(capabilities, k)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the capabilities.
|
||||||
|
naughty.connect_signal("property::persistence_enabled", function()
|
||||||
|
remove_capability("persistence")
|
||||||
|
|
||||||
|
if naughty.persistence_enabled then
|
||||||
|
table.insert(capabilities, "persistence")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
naughty.connect_signal("property::image_animations_enabled", function()
|
||||||
|
remove_capability("icon-multi")
|
||||||
|
remove_capability("icon-static")
|
||||||
|
|
||||||
|
table.insert(capabilities, naughty.persistence_enabled
|
||||||
|
and "icon-multi" or "icon-static"
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- For the tests.
|
||||||
|
dbus._capabilities = capabilities
|
||||||
|
|
||||||
return dbus
|
return dbus
|
||||||
|
|
||||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
local naughty = require("naughty.core")
|
local naughty = require("naughty.core")
|
||||||
|
local capi = {awesome = awesome}
|
||||||
if dbus then
|
if dbus then
|
||||||
naughty.dbus = require("naughty.dbus")
|
naughty.dbus = require("naughty.dbus")
|
||||||
end
|
end
|
||||||
|
@ -17,6 +18,31 @@ naughty.container = require("naughty.container")
|
||||||
naughty.action = require("naughty.action")
|
naughty.action = require("naughty.action")
|
||||||
naughty.notification = require("naughty.notification")
|
naughty.notification = require("naughty.notification")
|
||||||
|
|
||||||
|
-- Handle runtime errors during startup
|
||||||
|
if capi.awesome.startup_errors then
|
||||||
|
-- Wait until `rc.lua` is executed before creating the notifications.
|
||||||
|
-- Otherwise nothing is handling them (yet).
|
||||||
|
awesome.connect_signal("startup", function()
|
||||||
|
naughty.emit_signal(
|
||||||
|
"request::display_error", capi.awesome.startup_errors, true
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle runtime errors after startup
|
||||||
|
do
|
||||||
|
local in_error = false
|
||||||
|
capi.awesome.connect_signal("debug::error", function (err)
|
||||||
|
-- Make sure we don't go into an endless error loop
|
||||||
|
if in_error then return end
|
||||||
|
in_error = true
|
||||||
|
|
||||||
|
naughty.emit_signal("request::display_error", tostring(err), false)
|
||||||
|
|
||||||
|
in_error = false
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -17,6 +17,8 @@ local popup = require("awful.popup")
|
||||||
local awcommon = require("awful.widget.common")
|
local awcommon = require("awful.widget.common")
|
||||||
local placement = require("awful.placement")
|
local placement = require("awful.placement")
|
||||||
local abutton = require("awful.button")
|
local abutton = require("awful.button")
|
||||||
|
local gpcall = require("gears.protected_call")
|
||||||
|
local dpi = require("beautiful").xresources.apply_dpi
|
||||||
|
|
||||||
local default_widget = require("naughty.widget._default")
|
local default_widget = require("naughty.widget._default")
|
||||||
|
|
||||||
|
@ -135,10 +137,29 @@ end
|
||||||
-- @param widget
|
-- @param widget
|
||||||
|
|
||||||
local function generate_widget(args, n)
|
local function generate_widget(args, n)
|
||||||
local w = wibox.widget.base.make_widget_from_value(
|
local w = gpcall(wibox.widget.base.make_widget_from_value,
|
||||||
args.widget_template or default_widget
|
args.widget_template or (n and n.widget_template) or default_widget
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- This will happen if the user-provided widget_template is invalid and/or
|
||||||
|
-- got unexpected notifications.
|
||||||
|
if not w then
|
||||||
|
w = gpcall(wibox.widget.base.make_widget_from_value, default_widget)
|
||||||
|
|
||||||
|
-- In case this happens in an error message itself, make sure the
|
||||||
|
-- private error popup code knowns it and can revert to the fallback
|
||||||
|
-- popup.
|
||||||
|
if not w then
|
||||||
|
n._private.widget_template_failed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if w.set_width then
|
||||||
|
w:set_width(n.max_width or beautiful.notification_max_width or dpi(500))
|
||||||
|
end
|
||||||
|
|
||||||
-- Call `:set_notification` on all children
|
-- Call `:set_notification` on all children
|
||||||
awcommon._set_common_property(w, "notification", n or args.notification)
|
awcommon._set_common_property(w, "notification", n or args.notification)
|
||||||
|
|
||||||
|
@ -148,8 +169,7 @@ end
|
||||||
local function init(self, notification)
|
local function init(self, notification)
|
||||||
local args = self._private.args
|
local args = self._private.args
|
||||||
|
|
||||||
local preset = notification.preset
|
local preset = notification.preset or {}
|
||||||
assert(preset)
|
|
||||||
|
|
||||||
local position = args.position or notification.position or
|
local position = args.position or notification.position or
|
||||||
beautiful.notification_position or preset.position or "top_right"
|
beautiful.notification_position or preset.position or "top_right"
|
||||||
|
@ -225,7 +245,10 @@ local function new(args)
|
||||||
new_args = args and setmetatable(new_args, {__index = args}) or new_args
|
new_args = args and setmetatable(new_args, {__index = args}) or new_args
|
||||||
|
|
||||||
-- Generate the box before the popup is created to avoid the size changing
|
-- Generate the box before the popup is created to avoid the size changing
|
||||||
new_args.widget = generate_widget(new_args)
|
new_args.widget = generate_widget(new_args, new_args.notification)
|
||||||
|
|
||||||
|
-- It failed, request::fallback will be used, there is nothing left to do.
|
||||||
|
if not new_args.widget then return nil end
|
||||||
|
|
||||||
local ret = popup(new_args)
|
local ret = popup(new_args)
|
||||||
ret._private.args = new_args
|
ret._private.args = new_args
|
||||||
|
|
|
@ -297,10 +297,20 @@ end
|
||||||
|
|
||||||
naughty.connect_signal("destroyed", cleanup)
|
naughty.connect_signal("destroyed", cleanup)
|
||||||
|
|
||||||
|
-- Don't copy paste the list of fallback, it is hard to spot mistakes.
|
||||||
|
local function get_value(notification, args, preset, prop)
|
||||||
|
return notification[prop] -- set by the rules
|
||||||
|
or args[prop] -- magic and undocumented, but used by the legacy API
|
||||||
|
or preset[prop] --deprecated
|
||||||
|
or beautiful["notification_"..prop] -- from the theme
|
||||||
|
end
|
||||||
|
|
||||||
function naughty.default_notification_handler(notification, args)
|
function naughty.default_notification_handler(notification, args)
|
||||||
-- This is a fallback for users whose config doesn't have the newer
|
-- This is a fallback for users whose config doesn't have the newer
|
||||||
-- `request::display` section.
|
-- `request::display` section.
|
||||||
if naughty.has_display_handler then return end
|
if naughty.has_display_handler and not notification._private.widget_template_failed then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- If request::display is called more than once, simply make sure the wibox
|
-- If request::display is called more than once, simply make sure the wibox
|
||||||
-- is visible.
|
-- is visible.
|
||||||
|
@ -309,10 +319,15 @@ function naughty.default_notification_handler(notification, args)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local preset = notification.preset
|
local preset = notification.preset or {}
|
||||||
local text = args.message or args.text or preset.message or preset.text
|
|
||||||
local title = args.title or preset.title
|
local title = get_value(notification, args, preset, "title" )
|
||||||
local s = get_screen(args.screen or preset.screen or screen.focused())
|
local text = get_value(notification, args, preset, "message")
|
||||||
|
or args.text or preset.text
|
||||||
|
|
||||||
|
local s = get_screen(
|
||||||
|
get_value(notification, args, preset, "screen") or screen.focused()
|
||||||
|
)
|
||||||
|
|
||||||
if not s then
|
if not s then
|
||||||
local err = "naughty.notify: there is no screen available to display the following notification:"
|
local err = "naughty.notify: there is no screen available to display the following notification:"
|
||||||
|
@ -321,14 +336,14 @@ function naughty.default_notification_handler(notification, args)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local timeout = args.timeout or preset.timeout
|
local timeout = get_value(notification, args, preset, "timeout" )
|
||||||
local icon = args.icon or preset.icon
|
local icon = get_value(notification, args, preset, "icon" )
|
||||||
local icon_size = args.icon_size or preset.icon_size
|
local icon_size = get_value(notification, args, preset, "icon_size" )
|
||||||
or beautiful.notification_icon_size
|
local ontop = get_value(notification, args, preset, "ontop" )
|
||||||
local ontop = args.ontop or preset.ontop
|
local hover_timeout = get_value(notification, args, preset, "hover_timeout")
|
||||||
local hover_timeout = args.hover_timeout or preset.hover_timeout
|
local position = get_value(notification, args, preset, "position" )
|
||||||
local position = args.position or preset.position
|
|
||||||
local actions = args.actions
|
local actions = notification.actions or args.actions
|
||||||
local destroy_cb = args.destroy
|
local destroy_cb = args.destroy
|
||||||
|
|
||||||
notification.screen = s
|
notification.screen = s
|
||||||
|
@ -336,30 +351,26 @@ function naughty.default_notification_handler(notification, args)
|
||||||
notification.timeout = timeout
|
notification.timeout = timeout
|
||||||
|
|
||||||
-- beautiful
|
-- beautiful
|
||||||
local font = args.font or preset.font or beautiful.notification_font or
|
local font = get_value(notification, args, preset, "font" )
|
||||||
beautiful.font or capi.awesome.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 fg = get_value(notification, args, preset, "fg" )
|
||||||
local bg = args.bg or preset.bg or
|
or beautiful.fg_normal or '#ffffff'
|
||||||
beautiful.notification_bg or beautiful.bg_normal or '#535d6c'
|
|
||||||
local border_color = args.border_color or preset.border_color or
|
local bg = get_value(notification, args, preset, "bg" )
|
||||||
beautiful.notification_border_color or beautiful.bg_focus or '#535d6c'
|
or beautiful.bg_normal or '#535d6c'
|
||||||
local border_width = args.border_width or preset.border_width or
|
|
||||||
beautiful.notification_border_width
|
local border_color = get_value(notification, args, preset, "border_color")
|
||||||
local shape = args.shape or preset.shape or
|
or beautiful.bg_focus or '#535d6c'
|
||||||
beautiful.notification_shape
|
|
||||||
local width = args.width or preset.width or
|
local border_width = get_value(notification, args, preset, "border_width")
|
||||||
beautiful.notification_width
|
local shape = get_value(notification, args, preset, "shape" )
|
||||||
local height = args.height or preset.height or
|
local width = get_value(notification, args, preset, "width" )
|
||||||
beautiful.notification_height
|
local height = get_value(notification, args, preset, "height" )
|
||||||
local max_width = args.max_width or preset.max_width or
|
local max_width = get_value(notification, args, preset, "max_width" )
|
||||||
beautiful.notification_max_width
|
local max_height = get_value(notification, args, preset, "max_height" )
|
||||||
local max_height = args.max_height or preset.max_height or
|
local margin = get_value(notification, args, preset, "margin" )
|
||||||
beautiful.notification_max_height
|
local opacity = get_value(notification, args, preset, "opacity" )
|
||||||
local margin = args.margin or preset.margin or
|
|
||||||
beautiful.notification_margin
|
|
||||||
local opacity = args.opacity or preset.opacity or
|
|
||||||
beautiful.notification_opacity
|
|
||||||
|
|
||||||
notification.position = position
|
notification.position = position
|
||||||
|
|
||||||
|
@ -421,12 +432,10 @@ function naughty.default_notification_handler(notification, args)
|
||||||
|
|
||||||
actionmarginbox:buttons(gtable.join(
|
actionmarginbox:buttons(gtable.join(
|
||||||
button({ }, 1, function()
|
button({ }, 1, function()
|
||||||
action:invoke()
|
action:invoke(notification)
|
||||||
notification:destroy()
|
|
||||||
end),
|
end),
|
||||||
button({ }, 3, function()
|
button({ }, 3, function()
|
||||||
action:invoke()
|
action:invoke(notification)
|
||||||
notification:destroy()
|
|
||||||
end)
|
end)
|
||||||
))
|
))
|
||||||
actionslayout:add(actionmarginbox)
|
actionslayout:add(actionmarginbox)
|
||||||
|
|
|
@ -117,10 +117,6 @@ local module = {}
|
||||||
-- @tparam gears.surface|string action_bgimage_selected
|
-- @tparam gears.surface|string action_bgimage_selected
|
||||||
-- @see gears.surface
|
-- @see gears.surface
|
||||||
|
|
||||||
local default_buttons = gtable.join(
|
|
||||||
abutton({ }, 1, function(a) a:invoke() end)
|
|
||||||
)
|
|
||||||
|
|
||||||
local props = {"shape_border_color", "bg_image" , "fg",
|
local props = {"shape_border_color", "bg_image" , "fg",
|
||||||
"shape_border_width", "underline", "bg",
|
"shape_border_width", "underline", "bg",
|
||||||
"shape", "icon_size", }
|
"shape", "icon_size", }
|
||||||
|
@ -176,7 +172,7 @@ local function update(self)
|
||||||
|
|
||||||
awcommon.list_update(
|
awcommon.list_update(
|
||||||
self._private.layout,
|
self._private.layout,
|
||||||
default_buttons,
|
self._private.default_buttons,
|
||||||
function(o) return wb_label(o, self) end,
|
function(o) return wb_label(o, self) end,
|
||||||
self._private.data,
|
self._private.data,
|
||||||
self._private.notification.actions,
|
self._private.notification.actions,
|
||||||
|
@ -273,7 +269,7 @@ end
|
||||||
--- Create an action list.
|
--- Create an action list.
|
||||||
--
|
--
|
||||||
-- @tparam table args
|
-- @tparam table args
|
||||||
-- @tparam naughty.notification args.notification The notification/
|
-- @tparam naughty.notification args.notification The notification.
|
||||||
-- @tparam widget args.base_layout The action layout.
|
-- @tparam widget args.base_layout The action layout.
|
||||||
-- @tparam table args.style Override the beautiful values.
|
-- @tparam table args.style Override the beautiful values.
|
||||||
-- @tparam boolean args.style.underline_normal
|
-- @tparam boolean args.style.underline_normal
|
||||||
|
@ -312,6 +308,10 @@ local function new(_, args)
|
||||||
|
|
||||||
update_style(wdg)
|
update_style(wdg)
|
||||||
|
|
||||||
|
wdg._private.default_buttons = gtable.join(
|
||||||
|
abutton({ }, 1, function(a) a:invoke(args.notification) end)
|
||||||
|
)
|
||||||
|
|
||||||
return wdg
|
return wdg
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
local gobject = require("gears.object")
|
local gobject = require("gears.object")
|
||||||
local gtable = require("gears.table")
|
local gtable = require("gears.table")
|
||||||
|
local gsurface = require("gears.surface")
|
||||||
local timer = require("gears.timer")
|
local timer = require("gears.timer")
|
||||||
local cst = require("naughty.constants")
|
local cst = require("naughty.constants")
|
||||||
local naughty = require("naughty.core")
|
local naughty = require("naughty.core")
|
||||||
|
@ -74,8 +75,7 @@ local notification = {}
|
||||||
-- This is the equivalent to a PID as allows external applications to select
|
-- This is the equivalent to a PID as allows external applications to select
|
||||||
-- notifications.
|
-- notifications.
|
||||||
-- @property id
|
-- @property id
|
||||||
-- @param string
|
-- @param number
|
||||||
-- @see title
|
|
||||||
|
|
||||||
--- Text of the notification.
|
--- Text of the notification.
|
||||||
--
|
--
|
||||||
|
@ -96,6 +96,72 @@ local notification = {}
|
||||||
-- @property timeout
|
-- @property timeout
|
||||||
-- @param number
|
-- @param number
|
||||||
|
|
||||||
|
--- The notification urgency level.
|
||||||
|
--
|
||||||
|
-- The default urgency levels are:
|
||||||
|
--
|
||||||
|
-- * low
|
||||||
|
-- * normal
|
||||||
|
-- * critical
|
||||||
|
--
|
||||||
|
-- @property urgency
|
||||||
|
-- @param string
|
||||||
|
|
||||||
|
--- The notification category.
|
||||||
|
--
|
||||||
|
-- The category should be named using the `x-vendor.class.name` naming scheme or
|
||||||
|
-- use one of the default categories:
|
||||||
|
--
|
||||||
|
-- <table class='widget_list' border=1>
|
||||||
|
-- <tr style='font-weight: bold;'>
|
||||||
|
-- <th align='center'>Name</th>
|
||||||
|
-- <th align='center'>Description</th>
|
||||||
|
-- </tr>
|
||||||
|
-- <tr><td><b>device</b></td><td>A generic device-related notification that
|
||||||
|
-- doesn't fit into any other category.</td></tr>
|
||||||
|
-- <tr><td><b>device.added</b></td><td>A device, such as a USB device, was added to the system.</td></tr>
|
||||||
|
-- <tr><td><b>device.error</b></td><td>A device had some kind of error.</td></tr>
|
||||||
|
-- <tr><td><b>device.removed</b></td><td>A device, such as a USB device, was removed from the system.</td></tr>
|
||||||
|
-- <tr><td><b>email</b></td><td>A generic e-mail-related notification that doesn't fit into
|
||||||
|
-- any other category.</td></tr>
|
||||||
|
-- <tr><td><b>email.arrived</b></td><td>A new e-mail notification.</td></tr>
|
||||||
|
-- <tr><td><b>email.bounced</b></td><td>A notification stating that an e-mail has bounced.</td></tr>
|
||||||
|
-- <tr><td><b>im</b></td><td>A generic instant message-related notification that doesn't fit into
|
||||||
|
-- any other category.</td></tr>
|
||||||
|
-- <tr><td><b>im.error</b></td><td>An instant message error notification.</td></tr>
|
||||||
|
-- <tr><td><b>im.received</b></td><td>A received instant message notification.</td></tr>
|
||||||
|
-- <tr><td><b>network</b></td><td>A generic network notification that doesn't fit into any other
|
||||||
|
-- category.</td></tr>
|
||||||
|
-- <tr><td><b>network.connected</b></td><td>A network connection notification, such as successful
|
||||||
|
-- sign-on to a network service. <br />
|
||||||
|
-- This should not be confused with device.added for new network devices.</td></tr>
|
||||||
|
-- <tr><td><b>network.disconnected</b></td><td>A network disconnected notification. This should not
|
||||||
|
-- be confused with <br />
|
||||||
|
-- device.removed for disconnected network devices.</td></tr>
|
||||||
|
-- <tr><td><b>network.error</b></td><td>A network-related or connection-related error.</td></tr>
|
||||||
|
-- <tr><td><b>presence</b></td><td>A generic presence change notification that doesn't fit into any
|
||||||
|
-- other category, <br />
|
||||||
|
-- such as going away or idle.</td></tr>
|
||||||
|
-- <tr><td><b>presence.offline</b></td><td>An offline presence change notification.</td></tr>
|
||||||
|
-- <tr><td><b>presence.online</b></td><td>An online presence change notification.</td></tr>
|
||||||
|
-- <tr><td><b>transfer</b></td><td>A generic file transfer or download notification that doesn't
|
||||||
|
-- fit into any other category.</td></tr>
|
||||||
|
-- <tr><td><b>transfer.complete</b></td><td>A file transfer or download complete notification.</td></tr>
|
||||||
|
-- <tr><td><b>transfer.error</b></td><td>A file transfer or download error.</td></tr>
|
||||||
|
-- </table>
|
||||||
|
--
|
||||||
|
-- @property category
|
||||||
|
-- @tparam string|nil category
|
||||||
|
|
||||||
|
--- True if the notification should be kept when an action is pressed.
|
||||||
|
--
|
||||||
|
-- By default, invoking an action will destroy the notification. Some actions,
|
||||||
|
-- like the "Snooze" action of alarm clock, will cause the notification to
|
||||||
|
-- be updated with a date further in the future.
|
||||||
|
--
|
||||||
|
-- @property resident
|
||||||
|
-- @param[opt=false] boolean
|
||||||
|
|
||||||
--- Delay in seconds after which hovered popup disappears.
|
--- Delay in seconds after which hovered popup disappears.
|
||||||
-- @property hover_timeout
|
-- @property hover_timeout
|
||||||
-- @param number
|
-- @param number
|
||||||
|
@ -144,14 +210,59 @@ local notification = {}
|
||||||
-- @property font
|
-- @property font
|
||||||
-- @param string
|
-- @param string
|
||||||
|
|
||||||
--- Path to icon.
|
--- "All in one" way to access the default image or icon.
|
||||||
|
--
|
||||||
|
-- A notification can provide a combination of an icon, a static image, or if
|
||||||
|
-- enabled, a looping animation. Add to that the ability to import the icon
|
||||||
|
-- information from the client or from a `.desktop` file, there is multiple
|
||||||
|
-- conflicting sources of "icons".
|
||||||
|
--
|
||||||
|
-- On the other hand, the vast majority of notifications don't multiple or
|
||||||
|
-- ambiguous sources of icons. This property will pick the first of the
|
||||||
|
-- following.
|
||||||
|
--
|
||||||
|
-- * The `image`.
|
||||||
|
-- * The `app_icon`.
|
||||||
|
-- * The `icon` from a client with `normal` type.
|
||||||
|
-- * The `icon` of a client with `dialog` type.
|
||||||
|
--
|
||||||
-- @property icon
|
-- @property icon
|
||||||
-- @tparam string|surface icon
|
-- @tparam string|surface icon
|
||||||
|
-- @see app_icon
|
||||||
|
-- @see image
|
||||||
|
|
||||||
--- Desired icon size in px.
|
--- Desired icon size in px.
|
||||||
-- @property icon_size
|
-- @property icon_size
|
||||||
-- @param number
|
-- @param number
|
||||||
|
|
||||||
|
--- The icon provided in the `app_icon` field of the DBus notification.
|
||||||
|
--
|
||||||
|
-- This should always be either the URI (path) to an icon or a valid XDG
|
||||||
|
-- icon name to be fetched from the theme.
|
||||||
|
--
|
||||||
|
-- @property app_icon
|
||||||
|
-- @param string
|
||||||
|
|
||||||
|
--- The notification image.
|
||||||
|
--
|
||||||
|
-- This is usually provided as a `gears.surface` object. The image is used
|
||||||
|
-- instead of the `app_icon` by notification assets which are auto-generated
|
||||||
|
-- or stored elsewhere than the filesystem (databases, web, Android phones, etc).
|
||||||
|
--
|
||||||
|
-- @property image
|
||||||
|
-- @tparam string|surface image
|
||||||
|
|
||||||
|
--- The notification (animated) images.
|
||||||
|
--
|
||||||
|
-- Note that calling this without first setting
|
||||||
|
-- `naughty.image_animations_enabled` to true will throw an exception.
|
||||||
|
--
|
||||||
|
-- Also note that there is *zero* support for this anywhere else in `naughty`
|
||||||
|
-- and very, very few applications support this.
|
||||||
|
--
|
||||||
|
-- @property images
|
||||||
|
-- @tparam nil|table images
|
||||||
|
|
||||||
--- Foreground color.
|
--- Foreground color.
|
||||||
--
|
--
|
||||||
--@DOC_awful_notification_fg_EXAMPLE@
|
--@DOC_awful_notification_fg_EXAMPLE@
|
||||||
|
@ -285,11 +396,45 @@ local notification = {}
|
||||||
-- @property ignore_suspend If set to true this notification
|
-- @property ignore_suspend If set to true this notification
|
||||||
-- will be shown even if notifications are suspended via `naughty.suspend`.
|
-- will be shown even if notifications are suspended via `naughty.suspend`.
|
||||||
|
|
||||||
|
--- A list of clients associated with this notification.
|
||||||
|
--
|
||||||
|
-- When used with DBus notifications, this returns all clients sharing the PID
|
||||||
|
-- of the notification sender. Note that this is highly unreliable.
|
||||||
|
-- Applications that use a different process to send the notification or
|
||||||
|
-- applications (and scripts) calling the `notify-send` command wont have any
|
||||||
|
-- client.
|
||||||
|
--
|
||||||
|
-- @property clients
|
||||||
|
-- @param table
|
||||||
|
|
||||||
|
--- The maximum popup width.
|
||||||
|
--
|
||||||
|
-- Some notifications have overlong message, cap them to this width. Note that
|
||||||
|
-- this is ignored by `naughty.list.notifications` because it delegate this
|
||||||
|
-- decision to the layout.
|
||||||
|
--
|
||||||
|
-- @property[opt=500] max_width
|
||||||
|
-- @param number
|
||||||
|
|
||||||
|
--- The application name specified by the notification.
|
||||||
|
--
|
||||||
|
-- This can be anything. It is usually less relevant than the `clients`
|
||||||
|
-- property, but can sometime to specified for remote or headless notifications.
|
||||||
|
-- In these case, it helps to triage and detect the notification from the rules.
|
||||||
|
-- @property app_name
|
||||||
|
-- @param string
|
||||||
|
|
||||||
|
--- The widget template used to represent the notification.
|
||||||
|
--
|
||||||
|
-- Some notifications, such as chat messages or music applications are better
|
||||||
|
-- off with a specialized notification widget.
|
||||||
|
--
|
||||||
|
-- @property widget_template
|
||||||
|
-- @param table
|
||||||
|
|
||||||
--FIXME remove the screen attribute, let the handlers decide
|
--FIXME remove the screen attribute, let the handlers decide
|
||||||
-- document all handler extra properties
|
-- document all handler extra properties
|
||||||
|
|
||||||
--FIXME add methods such as persist
|
|
||||||
|
|
||||||
--- Destroy notification by notification object.
|
--- Destroy notification by notification object.
|
||||||
--
|
--
|
||||||
-- @method destroy
|
-- @method destroy
|
||||||
|
@ -319,7 +464,11 @@ end
|
||||||
function notification:reset_timeout(new_timeout)
|
function notification:reset_timeout(new_timeout)
|
||||||
if self.timer then self.timer:stop() end
|
if self.timer then self.timer:stop() end
|
||||||
|
|
||||||
|
-- Do not set `self.timeout` to `self.timeout` since that would create the
|
||||||
|
-- timer before the constructor ends.
|
||||||
|
if new_timeout and self.timer then
|
||||||
self.timeout = new_timeout or self.timeout
|
self.timeout = new_timeout or self.timeout
|
||||||
|
end
|
||||||
|
|
||||||
if self.timer and not self.timer.started then
|
if self.timer and not self.timer.started then
|
||||||
self.timer:start()
|
self.timer:start()
|
||||||
|
@ -333,6 +482,8 @@ function notification:set_id(new_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
function notification:set_timeout(timeout)
|
function notification:set_timeout(timeout)
|
||||||
|
timeout = timeout or 0
|
||||||
|
|
||||||
local die = function (reason)
|
local die = function (reason)
|
||||||
if reason == cst.notification_closed_reason.expired then
|
if reason == cst.notification_closed_reason.expired then
|
||||||
self.is_expired = true
|
self.is_expired = true
|
||||||
|
@ -399,7 +550,9 @@ local properties = {
|
||||||
"fg" , "bg" , "height" , "border_color" ,
|
"fg" , "bg" , "height" , "border_color" ,
|
||||||
"shape" , "opacity" , "margin" , "ignore_suspend",
|
"shape" , "opacity" , "margin" , "ignore_suspend",
|
||||||
"destroy" , "preset" , "callback", "actions" ,
|
"destroy" , "preset" , "callback", "actions" ,
|
||||||
"run" , "id" , "ignore" , "auto_reset_timeout"
|
"run" , "id" , "ignore" , "auto_reset_timeout",
|
||||||
|
"urgency" , "image" , "images" , "widget_template",
|
||||||
|
"max_width", "app_name",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, prop in ipairs(properties) do
|
for _, prop in ipairs(properties) do
|
||||||
|
@ -426,8 +579,107 @@ for _, prop in ipairs(properties) do
|
||||||
if reset then
|
if reset then
|
||||||
self:reset_timeout()
|
self:reset_timeout()
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return
|
end
|
||||||
|
|
||||||
|
local hints_default = {
|
||||||
|
urgency = "normal",
|
||||||
|
resident = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prop in ipairs { "category", "resident" } do
|
||||||
|
notification["get_"..prop] = notification["get_"..prop] or function(self)
|
||||||
|
return self._private[prop] or (
|
||||||
|
self._private.freedesktop_hints and self._private.freedesktop_hints[prop]
|
||||||
|
) or hints_default[prop]
|
||||||
|
end
|
||||||
|
|
||||||
|
notification["set_"..prop] = notification["set_"..prop] or function(self, value)
|
||||||
|
self._private[prop] = value
|
||||||
|
self:emit_signal("property::"..prop, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function notification.get_icon(self)
|
||||||
|
if self._private.icon then
|
||||||
|
return self._private.icon == "" and nil or self._private.icon
|
||||||
|
elseif self.image and self.image ~= "" then
|
||||||
|
return self.image
|
||||||
|
elseif self._private.app_icon and self._private.app_icon ~= "" then
|
||||||
|
return self._private.app_icon
|
||||||
|
end
|
||||||
|
|
||||||
|
local clients = notification.get_clients(self)
|
||||||
|
|
||||||
|
for _, c in ipairs(clients) do
|
||||||
|
if c.type == "normal" then
|
||||||
|
self._private.icon = gsurface(c.icon)
|
||||||
|
return self._private.icon
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, c in ipairs(clients) do
|
||||||
|
if c.type == "dialog" then
|
||||||
|
self._private.icon = gsurface(c.icon)
|
||||||
|
return self._private.icon
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function notification.get_clients(self)
|
||||||
|
-- Clients from the future don't send notification, it's useless to reload
|
||||||
|
-- the list over and over.
|
||||||
|
if self._private.clients then return self._private.clients end
|
||||||
|
|
||||||
|
if not self._private._unique_sender then return {} end
|
||||||
|
|
||||||
|
self._private.clients = require("naughty.dbus").get_clients(self)
|
||||||
|
|
||||||
|
return self._private.clients
|
||||||
|
end
|
||||||
|
|
||||||
|
function notification.set_actions(self, new_actions)
|
||||||
|
for _, a in ipairs(self._private.actions or {}) do
|
||||||
|
a:disconnect_signal("_changed", self._private.action_cb )
|
||||||
|
a:disconnect_signal("invoked" , self._private.invoked_cb)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clone so `append_actions` doesn't add unwanted actions to other
|
||||||
|
-- notifications.
|
||||||
|
self._private.actions = gtable.clone(new_actions, false)
|
||||||
|
|
||||||
|
for _, a in ipairs(self._private.actions or {}) do
|
||||||
|
a:connect_signal("_changed", self._private.action_cb )
|
||||||
|
a:connect_signal("invoked" , self._private.invoked_cb)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:emit_signal("property::actions", new_actions)
|
||||||
|
|
||||||
|
-- When a notification is updated over dbus or by setting a property,
|
||||||
|
-- it is usually convenient to reset the timeout.
|
||||||
|
local reset = ((not self.suspended)
|
||||||
|
and self.auto_reset_timeout ~= false
|
||||||
|
and naughty.auto_reset_timeout)
|
||||||
|
|
||||||
|
if reset then
|
||||||
|
self:reset_timeout()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add more actions to the notification.
|
||||||
|
-- @method append_actions
|
||||||
|
-- @tparam table new_actions
|
||||||
|
|
||||||
|
function notification:append_actions(new_actions)
|
||||||
|
self._private.actions = self._private.actions or {}
|
||||||
|
|
||||||
|
for _, a in ipairs(new_actions or {}) do
|
||||||
|
a:connect_signal("_changed", self._private.action_cb )
|
||||||
|
a:connect_signal("invoked" , self._private.invoked_cb)
|
||||||
|
table.insert(self._private.actions, a)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -476,6 +728,33 @@ local function convert_actions(actions)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The old API used monkey-patched variable presets.
|
||||||
|
--
|
||||||
|
-- Monkey-patched anything is always an issue and prevent module from safely
|
||||||
|
-- doing anything without stepping on each other foot. In the background,
|
||||||
|
-- presets were selected with a rule-like API anyway.
|
||||||
|
local function select_legacy_preset(n, args)
|
||||||
|
for _, obj in pairs(cst.config.mapping) do
|
||||||
|
local filter, preset = obj[1], obj[2]
|
||||||
|
if (not filter.urgency or filter.urgency == args.urgency) and
|
||||||
|
(not filter.category or filter.category == args.category) and
|
||||||
|
(not filter.appname or filter.appname == args.appname) then
|
||||||
|
args.preset = gtable.join(args.preset or {}, preset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
n._private[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- Create a notification.
|
--- Create a notification.
|
||||||
--
|
--
|
||||||
-- @tab args The argument table containing any of the arguments below.
|
-- @tab args The argument table containing any of the arguments below.
|
||||||
|
@ -557,60 +836,71 @@ local function create(args)
|
||||||
|
|
||||||
-- Avoid modifying the original table
|
-- Avoid modifying the original table
|
||||||
local private = {}
|
local private = {}
|
||||||
|
rawset(n, "_private", private)
|
||||||
|
|
||||||
-- gather variables together
|
-- Allow extensions to create override the preset with custom data
|
||||||
rawset(n, "preset", gtable.join(
|
if not naughty._has_preset_handler then
|
||||||
cst.config.defaults or {},
|
select_legacy_preset(n, args)
|
||||||
args.preset or cst.config.presets.normal or {},
|
end
|
||||||
rawget(n, "preset") or {}
|
|
||||||
))
|
|
||||||
|
|
||||||
if is_old_action then
|
if is_old_action then
|
||||||
convert_actions(args.actions)
|
convert_actions(args.actions)
|
||||||
end
|
end
|
||||||
|
|
||||||
for k, v in pairs(n.preset) do
|
|
||||||
private[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v in pairs(args) do
|
for k, v in pairs(args) do
|
||||||
private[k] = v
|
private[k] = v
|
||||||
end
|
end
|
||||||
|
|
||||||
-- notif.actions should not be nil to allow cheching if there is actions
|
|
||||||
-- using the shorthand `if #notif.actions > 0 then`
|
|
||||||
private.actions = private.actions or {}
|
|
||||||
|
|
||||||
-- Make sure the action are for this notification. Sharing actions with
|
|
||||||
-- multiple notification is not supported.
|
|
||||||
for _, a in ipairs(private.actions) do
|
|
||||||
a.notification = n
|
|
||||||
end
|
|
||||||
|
|
||||||
-- It's an automatic property
|
-- It's an automatic property
|
||||||
n.is_expired = false
|
n.is_expired = false
|
||||||
|
|
||||||
rawset(n, "_private", private)
|
|
||||||
|
|
||||||
gtable.crush(n, notification, true)
|
gtable.crush(n, notification, true)
|
||||||
|
|
||||||
n.id = n.id or notification._gen_next_id()
|
-- Always emit property::actions when any of the action change to allow
|
||||||
|
-- some widgets to be updated without over complicated built-in tracking
|
||||||
|
-- of all options.
|
||||||
|
function n._private.action_cb() n:emit_signal("property::actions") end
|
||||||
|
|
||||||
-- Allow extensions to create override the preset with custom data
|
-- Listen to action press and destroy non-resident notifications.
|
||||||
naughty.emit_signal("request::preset", n, args)
|
function n._private.invoked_cb(a, notif)
|
||||||
|
if (not notif) or notif == n then
|
||||||
|
n:emit_signal("invoked", a)
|
||||||
|
|
||||||
|
if not n.resident then
|
||||||
|
n:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- notif.actions should not be nil to allow checking if there is actions
|
||||||
|
-- using the shorthand `if #notif.actions > 0 then`
|
||||||
|
private.actions = {}
|
||||||
|
if args.actions then
|
||||||
|
notification.set_actions(n, args.actions)
|
||||||
|
end
|
||||||
|
|
||||||
|
n.id = n.id or notification._gen_next_id()
|
||||||
|
|
||||||
-- Register the notification before requesting a widget
|
-- Register the notification before requesting a widget
|
||||||
n:emit_signal("new", args)
|
n:emit_signal("new", args)
|
||||||
|
|
||||||
|
-- The rules are attached to this.
|
||||||
|
if naughty._has_preset_handler then
|
||||||
|
naughty.emit_signal("request::preset", n, args)
|
||||||
|
end
|
||||||
|
|
||||||
-- Let all listeners handle the actual visual aspects
|
-- Let all listeners handle the actual visual aspects
|
||||||
if (not n.ignore) and (not n.preset.ignore) then
|
if (not n.ignore) and ((not n.preset) or n.preset.ignore ~= true) then
|
||||||
naughty.emit_signal("request::display" , n, args)
|
naughty.emit_signal("request::display" , n, args)
|
||||||
naughty.emit_signal("request::fallback", n, args)
|
naughty.emit_signal("request::fallback", n, args)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Because otherwise the setter logic would not be executed
|
-- Because otherwise the setter logic would not be executed
|
||||||
if n._private.timeout then
|
if n._private.timeout then
|
||||||
n:set_timeout(n._private.timeout or n.preset.timeout)
|
n:set_timeout(n._private.timeout
|
||||||
|
or (n.preset and n.preset.timeout)
|
||||||
|
or cst.config.timeout
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return n
|
return n
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
local imagebox = require("wibox.widget.imagebox")
|
local imagebox = require("wibox.widget.imagebox")
|
||||||
local gtable = require("gears.table")
|
local gtable = require("gears.table")
|
||||||
local beautiful = require("beautiful")
|
local beautiful = require("beautiful")
|
||||||
|
local gsurface = require("gears.surface")
|
||||||
local dpi = require("beautiful.xresources").apply_dpi
|
local dpi = require("beautiful.xresources").apply_dpi
|
||||||
|
|
||||||
local icon = {}
|
local icon = {}
|
||||||
|
@ -85,7 +86,11 @@ function icon:set_notification(notif)
|
||||||
self._private.icon_changed_callback)
|
self._private.icon_changed_callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:set_image(notif.icon)
|
local icn = gsurface.load_silently(notif.icon)
|
||||||
|
|
||||||
|
if icn then
|
||||||
|
self:set_image(icn)
|
||||||
|
end
|
||||||
|
|
||||||
self._private.notification = notif
|
self._private.notification = notif
|
||||||
|
|
||||||
|
@ -141,7 +146,11 @@ local function new(args)
|
||||||
gtable.crush(tb, icon, true)
|
gtable.crush(tb, icon, true)
|
||||||
|
|
||||||
function tb._private.icon_changed_callback()
|
function tb._private.icon_changed_callback()
|
||||||
tb:set_image(tb._private.notification.icon)
|
local icn = gsurface.load_silently(tb._private.notification.icon)
|
||||||
|
|
||||||
|
if icn then
|
||||||
|
tb:set_image()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if args.notification then
|
if args.notification then
|
||||||
|
|
|
@ -250,7 +250,7 @@ for f in $tests; do
|
||||||
error="$(echo "$error" | grep -vE ".{19} W: awesome: (Can't read color .* from GTK)" || true)"
|
error="$(echo "$error" | grep -vE ".{19} W: awesome: (Can't read color .* from GTK)" || true)"
|
||||||
if [[ $fail_on_warning ]]; then
|
if [[ $fail_on_warning ]]; then
|
||||||
# Filter out ignored warnings.
|
# Filter out ignored warnings.
|
||||||
error="$(echo "$error" | grep -vE ".{19} W: awesome: (a_glib_poll|Cannot reliably detect EOF|beautiful: can't get colorscheme from xrdb|Can't read color .* from GTK+3 theme)" || true)"
|
error="$(echo "$error" | grep -vE ".{19} W: awesome: (a_glib_poll|Cannot reliably detect EOF|beautiful: can't get colorscheme from xrdb|Can't read color .* from GTK+3 theme|A notification|Notification)" || true)"
|
||||||
fi
|
fi
|
||||||
if [[ -n "$error" ]]; then
|
if [[ -n "$error" ]]; then
|
||||||
color_red
|
color_red
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
local spawn = require("awful.spawn")
|
local spawn = require("awful.spawn")
|
||||||
local naughty = require("naughty" )
|
local naughty = require("naughty" )
|
||||||
local gdebug = require("gears.debug")
|
local gdebug = require("gears.debug")
|
||||||
|
local gtable = require("gears.table")
|
||||||
local cairo = require("lgi" ).cairo
|
local cairo = require("lgi" ).cairo
|
||||||
local beautiful = require("beautiful")
|
local beautiful = require("beautiful")
|
||||||
local Gio = require("lgi" ).Gio
|
local Gio = require("lgi" ).Gio
|
||||||
local GLib = require("lgi" ).GLib
|
local GLib = require("lgi" ).GLib
|
||||||
|
local gpcall = require("gears.protected_call")
|
||||||
|
local dwidget = require("naughty.widget._default")
|
||||||
|
|
||||||
-- This module test deprecated APIs
|
-- This module test deprecated APIs
|
||||||
require("gears.debug").deprecate = function() end
|
require("gears.debug").deprecate = function() end
|
||||||
|
@ -555,6 +558,16 @@ table.insert(steps, function()
|
||||||
assert(n2.box.width +2*n2.box.border_width <= wa.width )
|
assert(n2.box.width +2*n2.box.border_width <= wa.width )
|
||||||
assert(n2.box.height+2*n2.box.border_width <= wa.height)
|
assert(n2.box.height+2*n2.box.border_width <= wa.height)
|
||||||
|
|
||||||
|
-- Check with client icons.
|
||||||
|
assert(not n1.icon)
|
||||||
|
|
||||||
|
n1._private.clients = {{icon= big_icon, type = "normal"}}
|
||||||
|
assert(n1.icon == big_icon)
|
||||||
|
assert(n1.box.width +2*n1.box.border_width <= wa.width )
|
||||||
|
assert(n1.box.height+2*n1.box.border_width <= wa.height)
|
||||||
|
assert(n2.box.width +2*n2.box.border_width <= wa.width )
|
||||||
|
assert(n2.box.height+2*n2.box.border_width <= wa.height)
|
||||||
|
|
||||||
n1:destroy()
|
n1:destroy()
|
||||||
n2:destroy()
|
n2:destroy()
|
||||||
|
|
||||||
|
@ -673,6 +686,9 @@ table.insert(steps, function()
|
||||||
assert(n.actions[2].name == "five" )
|
assert(n.actions[2].name == "five" )
|
||||||
assert(n.actions[3].name == "six" )
|
assert(n.actions[3].name == "six" )
|
||||||
|
|
||||||
|
n:destroy()
|
||||||
|
assert(#active == 0)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -719,6 +735,162 @@ table.insert(steps, function()
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- Test adding actions, resident mode and action sharing.
|
||||||
|
table.insert(steps, function()
|
||||||
|
local n1 = naughty.notification {
|
||||||
|
title = "foo",
|
||||||
|
message = "bar",
|
||||||
|
timeout = 25000,
|
||||||
|
resident = true,
|
||||||
|
actions = { naughty.action { name = "a1" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
local n2 = naughty.notification {
|
||||||
|
title = "foo",
|
||||||
|
message = "bar",
|
||||||
|
resident = true,
|
||||||
|
timeout = 25000,
|
||||||
|
actions = { naughty.action { name = "a2" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
local is_called = {}
|
||||||
|
|
||||||
|
n1:connect_signal("invoked", function() is_called[1] = true end)
|
||||||
|
n2:connect_signal("invoked", function() is_called[2] = true end)
|
||||||
|
|
||||||
|
n1.actions[1]:invoke(n1)
|
||||||
|
n2.actions[1]:invoke(n2)
|
||||||
|
|
||||||
|
assert(is_called[1])
|
||||||
|
assert(is_called[2])
|
||||||
|
assert(not n1._private.is_destroyed)
|
||||||
|
assert(not n2._private.is_destroyed)
|
||||||
|
|
||||||
|
local shared_a = naughty.action { name = "a3" }
|
||||||
|
|
||||||
|
n1:append_actions {shared_a}
|
||||||
|
n2:append_actions {shared_a}
|
||||||
|
|
||||||
|
n1:connect_signal("invoked", function() is_called[3] = true end)
|
||||||
|
n2:connect_signal("invoked", function() is_called[4] = true end)
|
||||||
|
|
||||||
|
assert(n1.actions[2] == shared_a)
|
||||||
|
assert(n2.actions[2] == shared_a)
|
||||||
|
|
||||||
|
shared_a:invoke(n1)
|
||||||
|
|
||||||
|
assert(is_called[3])
|
||||||
|
assert(not is_called[4])
|
||||||
|
assert(not n1._private.is_destroyed)
|
||||||
|
assert(not n2._private.is_destroyed)
|
||||||
|
|
||||||
|
n1.resident = false
|
||||||
|
n2.resident = false
|
||||||
|
|
||||||
|
shared_a:invoke(n1)
|
||||||
|
|
||||||
|
assert(n1._private.is_destroyed)
|
||||||
|
assert(not n2._private.is_destroyed)
|
||||||
|
|
||||||
|
shared_a:invoke(n2)
|
||||||
|
assert(n2._private.is_destroyed)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Test that exposing support for animations and persistence are exposed to DBus.
|
||||||
|
table.insert(steps, function()
|
||||||
|
assert(not naughty.persistence_enabled)
|
||||||
|
assert(not naughty.image_animations_enabled)
|
||||||
|
|
||||||
|
assert(gtable.hasitem(naughty.dbus._capabilities, "icon-static"))
|
||||||
|
assert(not gtable.hasitem(naughty.dbus._capabilities, "icon-multi"))
|
||||||
|
assert(not gtable.hasitem(naughty.dbus._capabilities, "persistence"))
|
||||||
|
|
||||||
|
naughty.persistence_enabled = true
|
||||||
|
naughty.image_animations_enabled = true
|
||||||
|
|
||||||
|
assert(naughty.persistence_enabled)
|
||||||
|
assert(naughty.image_animations_enabled)
|
||||||
|
|
||||||
|
assert(gtable.hasitem(naughty.dbus._capabilities, "icon-multi"))
|
||||||
|
assert(gtable.hasitem(naughty.dbus._capabilities, "persistence"))
|
||||||
|
assert(not gtable.hasitem(naughty.dbus._capabilities, "icon-static"))
|
||||||
|
|
||||||
|
naughty.persistence_enabled = false
|
||||||
|
naughty.image_animations_enabled = false
|
||||||
|
|
||||||
|
assert(not naughty.persistence_enabled)
|
||||||
|
assert(not naughty.image_animations_enabled)
|
||||||
|
|
||||||
|
assert( gtable.hasitem(naughty.dbus._capabilities, "icon-static"))
|
||||||
|
assert(not gtable.hasitem(naughty.dbus._capabilities, "icon-multi" ))
|
||||||
|
assert(not gtable.hasitem(naughty.dbus._capabilities, "persistence"))
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
local icon_requests = {}
|
||||||
|
|
||||||
|
-- Check if the action icon support is detected.
|
||||||
|
table.insert(steps, function()
|
||||||
|
assert(#active == 0)
|
||||||
|
|
||||||
|
naughty.connect_signal("request::icon", function(a, icon_name)
|
||||||
|
icon_requests[icon_name] = a
|
||||||
|
|
||||||
|
a.icon = icon_name == "list-add" and small_icon or big_icon
|
||||||
|
end)
|
||||||
|
|
||||||
|
local hints = {
|
||||||
|
["action-icons"] = GLib.Variant("b", true),
|
||||||
|
}
|
||||||
|
|
||||||
|
send_notify("Awesome test", 0, "", "title", "message body",
|
||||||
|
{ "list-add", "add", "list-remove", "remove" }, hints, 25000)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(steps, function()
|
||||||
|
if #active ~= 1 then return end
|
||||||
|
|
||||||
|
local n = active[1]
|
||||||
|
|
||||||
|
assert(n._private.freedesktop_hints)
|
||||||
|
assert(n._private.freedesktop_hints["action-icons"] == true)
|
||||||
|
|
||||||
|
assert(icon_requests["list-add" ] == n.actions[1])
|
||||||
|
assert(icon_requests["list-remove"] == n.actions[2])
|
||||||
|
|
||||||
|
assert(n.actions[1].icon == small_icon)
|
||||||
|
assert(n.actions[2].icon == big_icon )
|
||||||
|
|
||||||
|
assert(type(n.actions[1].position) == "number")
|
||||||
|
assert(type(n.actions[2].position) == "number")
|
||||||
|
assert(n.actions[1].position == 1)
|
||||||
|
assert(n.actions[2].position == 2)
|
||||||
|
|
||||||
|
n:destroy()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Test the error popup.
|
||||||
|
table.insert(steps, function()
|
||||||
|
local got = nil
|
||||||
|
|
||||||
|
naughty.connect_signal("request::display_error", function(err)
|
||||||
|
got = err
|
||||||
|
end)
|
||||||
|
|
||||||
|
awesome.emit_signal("debug::error", "foo")
|
||||||
|
|
||||||
|
assert(got == "foo")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
-- Now check if the old deprecated (but still supported) APIs don't have errors.
|
-- Now check if the old deprecated (but still supported) APIs don't have errors.
|
||||||
table.insert(steps, function()
|
table.insert(steps, function()
|
||||||
-- Tests are (by default) not allowed to call deprecated APIs
|
-- Tests are (by default) not allowed to call deprecated APIs
|
||||||
|
@ -830,6 +1002,82 @@ table.insert(steps, function()
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- Add a "new API" handler.
|
||||||
|
local current_template, had_error, handler_called = nil
|
||||||
|
|
||||||
|
table.insert(steps, function()
|
||||||
|
assert(naughty.has_display_handler == false)
|
||||||
|
|
||||||
|
naughty.connect_signal("request::display", function(n)
|
||||||
|
handler_called = true
|
||||||
|
|
||||||
|
naughty.layout.box {
|
||||||
|
notification = n,
|
||||||
|
widget_template = current_template
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Make sure the legacy popup is used when the new APIs fails.
|
||||||
|
table.insert(steps, function()
|
||||||
|
assert(naughty.has_display_handler == true)
|
||||||
|
|
||||||
|
function gpcall._error_handler()
|
||||||
|
had_error = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local n = naughty.notification {
|
||||||
|
title = nil,
|
||||||
|
message = nil,
|
||||||
|
timeout = 25000,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(handler_called)
|
||||||
|
assert(not had_error)
|
||||||
|
assert(not n._private.widget_template_failed)
|
||||||
|
assert(not n.box)
|
||||||
|
|
||||||
|
n:destroy()
|
||||||
|
handler_called = false
|
||||||
|
|
||||||
|
-- Try with a broken template.
|
||||||
|
current_template = {widget = function() assert(false) end}
|
||||||
|
|
||||||
|
n = naughty.notification {
|
||||||
|
title = "foo",
|
||||||
|
message = "bar",
|
||||||
|
timeout = 25000,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(handler_called)
|
||||||
|
assert(had_error)
|
||||||
|
assert(not n.box)
|
||||||
|
|
||||||
|
handler_called = false
|
||||||
|
had_error = false
|
||||||
|
|
||||||
|
-- Break the default template
|
||||||
|
assert(dwidget.widget)
|
||||||
|
dwidget.widget = nil
|
||||||
|
dwidget.layout = function() assert(false) end
|
||||||
|
table.remove(dwidget, 1)
|
||||||
|
|
||||||
|
n = naughty.notification {
|
||||||
|
title = "foo",
|
||||||
|
message = "bar",
|
||||||
|
timeout = 25000,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(handler_called)
|
||||||
|
assert(n._private.widget_template_failed)
|
||||||
|
assert(had_error)
|
||||||
|
assert(n.box)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
-- Test many screens.
|
-- Test many screens.
|
||||||
|
|
||||||
require("_runner").run_steps(steps)
|
require("_runner").run_steps(steps)
|
||||||
|
|
Loading…
Reference in New Issue