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")
|
||||
|
||||
-- {{{ Error handling
|
||||
-- @DOC_ERROR_HANDLING@
|
||||
-- Check if awesome encountered an error during startup and fell back to
|
||||
-- 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 {
|
||||
preset = naughty.config.presets.critical,
|
||||
title = "Oops, there were errors during startup!",
|
||||
message = awesome.startup_errors
|
||||
urgency = "critical",
|
||||
title = "Oops, an error happened"..(startup and " during startup!" or "!"),
|
||||
message = message
|
||||
}
|
||||
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
|
||||
end)
|
||||
-- }}}
|
||||
|
||||
-- {{{ Variable definitions
|
||||
|
|
|
@ -134,6 +134,42 @@ function object:emit_signal(name, ...)
|
|||
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 class = rawget(self, "_class")
|
||||
|
||||
|
|
|
@ -12,11 +12,11 @@ local xpcall = xpcall
|
|||
|
||||
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))
|
||||
end
|
||||
|
||||
local function handle_result(success, ...)
|
||||
function protected_call._handle_result(success, ...)
|
||||
if success then
|
||||
return ...
|
||||
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 :-(
|
||||
do_pcall = function(func, ...)
|
||||
local args = { ... }
|
||||
return handle_result(xpcall(function()
|
||||
return protected_call._handle_result(xpcall(function()
|
||||
return func(unpack(args))
|
||||
end, error_handler))
|
||||
end, protected_call._error_handler))
|
||||
end
|
||||
else
|
||||
do_pcall = function(func, ...)
|
||||
return handle_result(xpcall(func, error_handler, ...))
|
||||
return protected_call._handle_result(xpcall(func, protected_call._error_handler, ...))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
--- A notification action.
|
||||
--
|
||||
-- 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>
|
||||
-- @copyright 2019 Emmanuel Lepage Vallee
|
||||
|
@ -29,8 +30,8 @@ local action = {}
|
|||
|
||||
-- If the action is selected.
|
||||
--
|
||||
-- Only a single action can be selected per notification. It will be applied
|
||||
-- when `my_notification:apply()` is called.
|
||||
-- Only a single action can be selected per notification. This is useful to
|
||||
-- implement keyboard navigation.
|
||||
--
|
||||
-- @property selected
|
||||
-- @param boolean
|
||||
|
@ -50,10 +51,6 @@ local action = {}
|
|||
-- @property icon_only
|
||||
-- @param[opt=false] boolean
|
||||
|
||||
--- The notification.
|
||||
-- @property notification
|
||||
-- @tparam naughty.notification notification
|
||||
|
||||
--- When a notification is invoked.
|
||||
-- @signal invoked
|
||||
|
||||
|
@ -64,10 +61,7 @@ end
|
|||
function action:set_selected(value)
|
||||
self._private.selected = value
|
||||
self:emit_signal("property::selected", value)
|
||||
|
||||
if self._private.notification then
|
||||
self._private.notification:emit_signal("property::actions")
|
||||
end
|
||||
self:emit_signal("_changed")
|
||||
|
||||
--TODO deselect other actions from the same notification
|
||||
end
|
||||
|
@ -79,15 +73,12 @@ end
|
|||
function action:set_position(value)
|
||||
self._private.position = value
|
||||
self:emit_signal("property::position", value)
|
||||
|
||||
if self._private.notification then
|
||||
self._private.notification:emit_signal("property::actions")
|
||||
end
|
||||
self:emit_signal("_changed")
|
||||
|
||||
--TODO make sure the position is unique
|
||||
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)
|
||||
return self._private[prop]
|
||||
end
|
||||
|
@ -95,22 +86,18 @@ for _, prop in ipairs { "name", "icon", "notification", "icon_only" } do
|
|||
action["set_"..prop] = function(self, value)
|
||||
self._private[prop] = value
|
||||
self:emit_signal("property::"..prop, value)
|
||||
|
||||
-- Make sure widgets with as an actionlist is updated.
|
||||
if self._private.notification then
|
||||
self._private.notification:emit_signal("property::actions")
|
||||
end
|
||||
self:emit_signal("_changed")
|
||||
end
|
||||
end
|
||||
|
||||
local set_notif = action.set_notification
|
||||
|
||||
function action.set_notification(self, value)
|
||||
local old = self._private.notification
|
||||
set_notif(self, value)
|
||||
if old then
|
||||
old:emit_signal("property::actions")
|
||||
end
|
||||
--TODO v4.5, remove this.
|
||||
function action.set_notification()
|
||||
-- It didn't work because it prevented actions defined in the rules to be
|
||||
-- in multiple notifications at once.
|
||||
assert(
|
||||
false,
|
||||
"Setting a notification object was a bad idea and is now forbidden"
|
||||
)
|
||||
end
|
||||
|
||||
--- Execute this action.
|
||||
|
@ -118,11 +105,12 @@ end
|
|||
-- This only emits the `invoked` signal.
|
||||
--
|
||||
-- @method invoke
|
||||
function action:invoke()
|
||||
assert(self._private.notification,
|
||||
"Cannot invoke an action without a notification")
|
||||
|
||||
self:emit_signal("invoked")
|
||||
-- @tparam[opt={}] naughty.notification notif A notification object on which
|
||||
-- the action was invoked. If a notification is shared by many object (like
|
||||
-- a "mute" or "snooze" action added to all notification), calling `:invoke()`
|
||||
-- without adding the `notif` context will cause unexpected results.
|
||||
function action:invoke(notif)
|
||||
self:emit_signal("invoked", notif)
|
||||
end
|
||||
|
||||
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 = {
|
||||
timeout = 5,
|
||||
text = "",
|
||||
|
@ -55,7 +70,12 @@ ret.config.defaults = {
|
|||
ontop = true,
|
||||
margin = dpi(5),
|
||||
border_width = dpi(1),
|
||||
position = "top_right"
|
||||
position = "top_right",
|
||||
urgency = "normal",
|
||||
message = "",
|
||||
title = "",
|
||||
app_name = "",
|
||||
ignore = false,
|
||||
}
|
||||
|
||||
ret.notification_closed_reason = {
|
||||
|
@ -65,7 +85,7 @@ ret.notification_closed_reason = {
|
|||
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,
|
||||
dismissed_by_command = 3,
|
||||
undefined = 4
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,10 @@
|
|||
|
||||
-- Package environment
|
||||
local capi = { screen = screen }
|
||||
local gdebug = require("gears.debug")
|
||||
local screen = require("awful.screen")
|
||||
local gtable = require("gears.table")
|
||||
local gdebug = require("gears.debug")
|
||||
local screen = require("awful.screen")
|
||||
local gtable = require("gears.table")
|
||||
local gobject = require("gears.object")
|
||||
|
||||
local naughty = {}
|
||||
|
||||
|
@ -134,10 +135,36 @@ gtable.crush(naughty, require("naughty.constants"))
|
|||
-- @property 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 = {
|
||||
suspended = false,
|
||||
expiration_paused = false,
|
||||
auto_reset_timeout= true,
|
||||
suspended = false,
|
||||
expiration_paused = false,
|
||||
auto_reset_timeout = true,
|
||||
image_animations_enabled = false,
|
||||
persistence_enabled = false,
|
||||
}
|
||||
|
||||
--TODO v5 Deprecate the public `naughty.notifications` (to make it private)
|
||||
|
@ -197,7 +224,9 @@ local function update_index(n)
|
|||
remove_from_index(n)
|
||||
|
||||
-- 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 {}
|
||||
table.insert(naughty.notifications[s][n.position], n)
|
||||
end
|
||||
|
@ -223,7 +252,7 @@ function naughty.suspend()
|
|||
properties.suspended = true
|
||||
end
|
||||
|
||||
local conns = {}
|
||||
local conns = gobject._setup_class_signals(naughty)
|
||||
|
||||
--- Connect a global signal on the notification engine.
|
||||
--
|
||||
|
@ -235,41 +264,21 @@ local conns = {}
|
|||
--
|
||||
-- @tparam string name The name of the signal
|
||||
-- @tparam function func The function to attach
|
||||
-- @staticfct naughty.connect_signal
|
||||
-- @usage naughty.connect_signal("added", function(notif)
|
||||
-- -- do something
|
||||
-- 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.
|
||||
-- @tparam string name The signal name.
|
||||
-- @param ... The signal callback arguments
|
||||
-- @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.
|
||||
-- @tparam string name The name of the signal
|
||||
-- @tparam function func The attached function
|
||||
-- @treturn boolean If the disconnection was successful
|
||||
-- @staticfct naughty.disconnect_signal
|
||||
function naughty.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
|
||||
-- @treturn boolean If the disconnection was successful
|
||||
|
||||
local function resume()
|
||||
properties.suspended = false
|
||||
|
@ -392,6 +401,11 @@ function naughty.get_has_display_handler()
|
|||
return conns["request::display"] and #conns["request::display"] > 0 or false
|
||||
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.
|
||||
--
|
||||
-- This function is deprecated, use `notification:reset_timeout(new_timeout)`.
|
||||
|
@ -486,6 +500,12 @@ function naughty.set_expiration_paused(p)
|
|||
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.
|
||||
-- @signal added
|
||||
-- @tparam naughty.notification notification The notification object
|
||||
|
@ -514,13 +534,27 @@ end
|
|||
-- including, but not limited to, all `naughty.notification` properties.
|
||||
-- @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.
|
||||
local function register(notification, args)
|
||||
-- Add the some more properties
|
||||
rawset(notification, "get_suspended", get_suspended)
|
||||
|
||||
--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
|
||||
table.insert(naughty._active, notification)
|
||||
|
@ -535,7 +569,7 @@ local function register(notification, args)
|
|||
naughty.emit_signal("added", notification, args)
|
||||
end
|
||||
|
||||
assert(rawget(notification, "preset"))
|
||||
assert(rawget(notification, "preset") or naughty._has_preset_handler)
|
||||
|
||||
naughty.emit_signal("property::active")
|
||||
|
||||
|
@ -564,6 +598,8 @@ local function set_index_miss(_, key, value)
|
|||
if not value then
|
||||
resume()
|
||||
end
|
||||
|
||||
naughty.emit_signal("property::"..key)
|
||||
else
|
||||
rawset(naughty, key, value)
|
||||
end
|
||||
|
|
|
@ -12,8 +12,8 @@ local pairs = pairs
|
|||
local type = type
|
||||
local string = string
|
||||
local capi = { awesome = awesome }
|
||||
local gtable = require("gears.table")
|
||||
local gsurface = require("gears.surface")
|
||||
local gdebug = require("gears.debug")
|
||||
local protected_call = require("gears.protected_call")
|
||||
local lgi = require("lgi")
|
||||
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 naction = require("naughty.action")
|
||||
|
||||
local capabilities = {
|
||||
"body", "body-markup", "icon-static", "actions", "action-icons"
|
||||
}
|
||||
|
||||
--- Notification library, dbus bindings
|
||||
local dbus = { config = {} }
|
||||
|
||||
|
@ -37,8 +41,8 @@ local bus_connection
|
|||
-- DBUS Notification constants
|
||||
-- https://developer.gnome.org/notification-spec/#urgency-levels
|
||||
local urgency = {
|
||||
low = "\0",
|
||||
normal = "\1",
|
||||
low = "\0",
|
||||
normal = "\1",
|
||||
critical = "\2"
|
||||
}
|
||||
|
||||
|
@ -51,11 +55,7 @@ local urgency = {
|
|||
-- @tfield table 2 normal urgency
|
||||
-- @tfield table 3 critical urgency
|
||||
-- @table config.mapping
|
||||
dbus.config.mapping = {
|
||||
{{urgency = urgency.low}, cst.config.presets.low},
|
||||
{{urgency = urgency.normal}, cst.config.presets.normal},
|
||||
{{urgency = urgency.critical}, cst.config.presets.critical}
|
||||
}
|
||||
dbus.config.mapping = cst.mapping
|
||||
|
||||
local function sendActionInvoked(notificationId, action)
|
||||
if bus_connection then
|
||||
|
@ -123,7 +123,7 @@ end
|
|||
local notif_methods = {}
|
||||
|
||||
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)
|
||||
|
||||
local args = {}
|
||||
|
@ -141,17 +141,12 @@ function notif_methods.Notify(sender, object_path, interface, method, parameters
|
|||
return
|
||||
end
|
||||
end
|
||||
|
||||
if appname ~= "" then
|
||||
args.appname = appname
|
||||
end
|
||||
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
|
||||
args.appname = appname --TODO v6 Remove this.
|
||||
args.app_name = appname
|
||||
end
|
||||
|
||||
local preset = args.preset or cst.config.defaults
|
||||
local notification
|
||||
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)
|
||||
end
|
||||
elseif action_id ~= nil and action_text ~= nil then
|
||||
|
||||
local a = naction {
|
||||
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()
|
||||
sendActionInvoked(notification.id, action_id)
|
||||
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||
|
||||
if not notification.resident then
|
||||
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
|
||||
end
|
||||
end)
|
||||
|
||||
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"
|
||||
}
|
||||
if not preset.callback or (type(preset.callback) == "function" and
|
||||
preset.callback(legacy_data, appname, replaces_id, icon, title, text, actions, hints, expire)) then
|
||||
if icon ~= "" then
|
||||
args.icon = icon
|
||||
elseif hints.icon_data or hints.image_data then
|
||||
preset.callback(legacy_data, appname, replaces_id, app_icon, title, text, actions, hints, expire)) 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:
|
||||
-- .value breaks with the array of bytes (ay) that we get here.
|
||||
-- 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
|
||||
if k == "icon_data" then
|
||||
icon_data = v
|
||||
elseif k == "image_data" and icon_data == nil then
|
||||
icon_data = v
|
||||
if k == "image-data" then
|
||||
icon_condidates[1] = v -- not deprecated
|
||||
break
|
||||
elseif k == "image_data" then -- deprecated
|
||||
icon_condidates[2] = v
|
||||
elseif k == "icon_data" then -- deprecated
|
||||
icon_condidates[3] = v
|
||||
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:
|
||||
-- 1 -> width
|
||||
-- 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
|
||||
-- then by converted to a string via its __tostring metamethod.
|
||||
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
|
||||
|
||||
-- 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
|
||||
args.replaces_id = replaces_id
|
||||
end
|
||||
if expire and expire > -1 then
|
||||
args.timeout = expire / 1000
|
||||
end
|
||||
|
||||
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
|
||||
notification = naughty.get_by_id(replaces_id)
|
||||
|
||||
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
|
||||
if k == "destroy" then k = "destroy_cb" end
|
||||
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.
|
||||
notification:reset_timeout()
|
||||
else
|
||||
-- Only set the sender for new notifications.
|
||||
args._unique_sender = sender
|
||||
|
||||
notification = nnotif(args)
|
||||
end
|
||||
|
||||
|
@ -261,16 +350,14 @@ end
|
|||
function notif_methods.GetServerInformation(_, _, _, _, _, invocation)
|
||||
-- name of notification app, name of vender, version, specification version
|
||||
invocation:return_value(GLib.Variant("(ssss)", {
|
||||
"naughty", "awesome", capi.awesome.version, "1.0"
|
||||
"naughty", "awesome", capi.awesome.version, "1.2"
|
||||
}))
|
||||
end
|
||||
|
||||
function notif_methods.GetCapabilities(_, _, _, _, _, invocation)
|
||||
-- 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.
|
||||
invocation:return_value(GLib.Variant("(as)", {
|
||||
{ "body", "body-markup", "icon-static", "actions" }
|
||||
}))
|
||||
invocation:return_value(GLib.Variant("(as)", {capabilities}))
|
||||
end
|
||||
|
||||
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))
|
||||
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, _)
|
||||
bus_connection = conn
|
||||
end
|
||||
|
@ -345,6 +527,35 @@ Gio.bus_own_name(Gio.BusType.SESSION, "org.freedesktop.Notifications",
|
|||
-- For testing
|
||||
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
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
---------------------------------------------------------------------------
|
||||
|
||||
local naughty = require("naughty.core")
|
||||
local capi = {awesome = awesome}
|
||||
if dbus then
|
||||
naughty.dbus = require("naughty.dbus")
|
||||
end
|
||||
|
@ -17,6 +18,31 @@ naughty.container = require("naughty.container")
|
|||
naughty.action = require("naughty.action")
|
||||
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
|
||||
|
||||
-- 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 placement = require("awful.placement")
|
||||
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")
|
||||
|
||||
|
@ -135,10 +137,29 @@ end
|
|||
-- @param widget
|
||||
|
||||
local function generate_widget(args, n)
|
||||
local w = wibox.widget.base.make_widget_from_value(
|
||||
args.widget_template or default_widget
|
||||
local w = gpcall(wibox.widget.base.make_widget_from_value,
|
||||
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
|
||||
awcommon._set_common_property(w, "notification", n or args.notification)
|
||||
|
||||
|
@ -148,8 +169,7 @@ end
|
|||
local function init(self, notification)
|
||||
local args = self._private.args
|
||||
|
||||
local preset = notification.preset
|
||||
assert(preset)
|
||||
local preset = notification.preset or {}
|
||||
|
||||
local position = args.position or notification.position or
|
||||
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
|
||||
|
||||
-- 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)
|
||||
ret._private.args = new_args
|
||||
|
|
|
@ -297,10 +297,20 @@ end
|
|||
|
||||
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)
|
||||
-- This is a fallback for users whose config doesn't have the newer
|
||||
-- `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
|
||||
-- is visible.
|
||||
|
@ -309,10 +319,15 @@ function naughty.default_notification_handler(notification, args)
|
|||
return
|
||||
end
|
||||
|
||||
local preset = notification.preset
|
||||
local text = args.message or args.text or preset.message or preset.text
|
||||
local title = args.title or preset.title
|
||||
local s = get_screen(args.screen or preset.screen or screen.focused())
|
||||
local preset = notification.preset or {}
|
||||
|
||||
local title = get_value(notification, args, preset, "title" )
|
||||
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
|
||||
local err = "naughty.notify: there is no screen available to display the following notification:"
|
||||
|
@ -321,45 +336,41 @@ function naughty.default_notification_handler(notification, args)
|
|||
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
|
||||
local timeout = get_value(notification, args, preset, "timeout" )
|
||||
local icon = get_value(notification, args, preset, "icon" )
|
||||
local icon_size = get_value(notification, args, preset, "icon_size" )
|
||||
local ontop = get_value(notification, args, preset, "ontop" )
|
||||
local hover_timeout = get_value(notification, args, preset, "hover_timeout")
|
||||
local position = get_value(notification, args, preset, "position" )
|
||||
|
||||
local actions = notification.actions or 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
|
||||
local font = get_value(notification, args, preset, "font" )
|
||||
or beautiful.font or capi.awesome.font
|
||||
|
||||
local fg = get_value(notification, args, preset, "fg" )
|
||||
or beautiful.fg_normal or '#ffffff'
|
||||
|
||||
local bg = get_value(notification, args, preset, "bg" )
|
||||
or beautiful.bg_normal or '#535d6c'
|
||||
|
||||
local border_color = get_value(notification, args, preset, "border_color")
|
||||
or beautiful.bg_focus or '#535d6c'
|
||||
|
||||
local border_width = get_value(notification, args, preset, "border_width")
|
||||
local shape = get_value(notification, args, preset, "shape" )
|
||||
local width = get_value(notification, args, preset, "width" )
|
||||
local height = get_value(notification, args, preset, "height" )
|
||||
local max_width = get_value(notification, args, preset, "max_width" )
|
||||
local max_height = get_value(notification, args, preset, "max_height" )
|
||||
local margin = get_value(notification, args, preset, "margin" )
|
||||
local opacity = get_value(notification, args, preset, "opacity" )
|
||||
|
||||
notification.position = position
|
||||
|
||||
|
@ -421,12 +432,10 @@ function naughty.default_notification_handler(notification, args)
|
|||
|
||||
actionmarginbox:buttons(gtable.join(
|
||||
button({ }, 1, function()
|
||||
action:invoke()
|
||||
notification:destroy()
|
||||
action:invoke(notification)
|
||||
end),
|
||||
button({ }, 3, function()
|
||||
action:invoke()
|
||||
notification:destroy()
|
||||
action:invoke(notification)
|
||||
end)
|
||||
))
|
||||
actionslayout:add(actionmarginbox)
|
||||
|
|
|
@ -117,10 +117,6 @@ local module = {}
|
|||
-- @tparam gears.surface|string action_bgimage_selected
|
||||
-- @see gears.surface
|
||||
|
||||
local default_buttons = gtable.join(
|
||||
abutton({ }, 1, function(a) a:invoke() end)
|
||||
)
|
||||
|
||||
local props = {"shape_border_color", "bg_image" , "fg",
|
||||
"shape_border_width", "underline", "bg",
|
||||
"shape", "icon_size", }
|
||||
|
@ -176,7 +172,7 @@ local function update(self)
|
|||
|
||||
awcommon.list_update(
|
||||
self._private.layout,
|
||||
default_buttons,
|
||||
self._private.default_buttons,
|
||||
function(o) return wb_label(o, self) end,
|
||||
self._private.data,
|
||||
self._private.notification.actions,
|
||||
|
@ -273,7 +269,7 @@ end
|
|||
--- Create an action list.
|
||||
--
|
||||
-- @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 table args.style Override the beautiful values.
|
||||
-- @tparam boolean args.style.underline_normal
|
||||
|
@ -312,6 +308,10 @@ local function new(_, args)
|
|||
|
||||
update_style(wdg)
|
||||
|
||||
wdg._private.default_buttons = gtable.join(
|
||||
abutton({ }, 1, function(a) a:invoke(args.notification) end)
|
||||
)
|
||||
|
||||
return wdg
|
||||
end
|
||||
|
||||
|
|
|
@ -15,12 +15,13 @@
|
|||
-- @copyright 2017 Emmanuel Lepage Vallee
|
||||
-- @coreclassmod 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 gdebug = require("gears.debug")
|
||||
local gobject = require("gears.object")
|
||||
local gtable = require("gears.table")
|
||||
local gsurface = require("gears.surface")
|
||||
local timer = require("gears.timer")
|
||||
local cst = require("naughty.constants")
|
||||
local naughty = require("naughty.core")
|
||||
local gdebug = require("gears.debug")
|
||||
|
||||
local notification = {}
|
||||
|
||||
|
@ -74,8 +75,7 @@ local notification = {}
|
|||
-- This is the equivalent to a PID as allows external applications to select
|
||||
-- notifications.
|
||||
-- @property id
|
||||
-- @param string
|
||||
-- @see title
|
||||
-- @param number
|
||||
|
||||
--- Text of the notification.
|
||||
--
|
||||
|
@ -96,6 +96,72 @@ local notification = {}
|
|||
-- @property timeout
|
||||
-- @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.
|
||||
-- @property hover_timeout
|
||||
-- @param number
|
||||
|
@ -144,14 +210,59 @@ local notification = {}
|
|||
-- @property font
|
||||
-- @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
|
||||
-- @tparam string|surface icon
|
||||
-- @see app_icon
|
||||
-- @see image
|
||||
|
||||
--- Desired icon size in px.
|
||||
-- @property icon_size
|
||||
-- @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.
|
||||
--
|
||||
--@DOC_awful_notification_fg_EXAMPLE@
|
||||
|
@ -285,11 +396,45 @@ local notification = {}
|
|||
-- @property ignore_suspend If set to true this notification
|
||||
-- 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
|
||||
-- document all handler extra properties
|
||||
|
||||
--FIXME add methods such as persist
|
||||
|
||||
--- Destroy notification by notification object.
|
||||
--
|
||||
-- @method destroy
|
||||
|
@ -319,7 +464,11 @@ end
|
|||
function notification:reset_timeout(new_timeout)
|
||||
if self.timer then self.timer:stop() end
|
||||
|
||||
self.timeout = new_timeout or self.timeout
|
||||
-- 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
|
||||
end
|
||||
|
||||
if self.timer and not self.timer.started then
|
||||
self.timer:start()
|
||||
|
@ -333,6 +482,8 @@ function notification:set_id(new_id)
|
|||
end
|
||||
|
||||
function notification:set_timeout(timeout)
|
||||
timeout = timeout or 0
|
||||
|
||||
local die = function (reason)
|
||||
if reason == cst.notification_closed_reason.expired then
|
||||
self.is_expired = true
|
||||
|
@ -399,7 +550,9 @@ local properties = {
|
|||
"fg" , "bg" , "height" , "border_color" ,
|
||||
"shape" , "opacity" , "margin" , "ignore_suspend",
|
||||
"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
|
||||
|
@ -426,8 +579,107 @@ for _, prop in ipairs(properties) do
|
|||
if reset then
|
||||
self:reset_timeout()
|
||||
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
|
||||
|
@ -476,6 +728,33 @@ local function convert_actions(actions)
|
|||
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.
|
||||
--
|
||||
-- @tab args The argument table containing any of the arguments below.
|
||||
|
@ -557,60 +836,71 @@ local function create(args)
|
|||
|
||||
-- Avoid modifying the original table
|
||||
local private = {}
|
||||
rawset(n, "_private", 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 {}
|
||||
))
|
||||
-- Allow extensions to create override the preset with custom data
|
||||
if not naughty._has_preset_handler then
|
||||
select_legacy_preset(n, args)
|
||||
end
|
||||
|
||||
if is_old_action then
|
||||
convert_actions(args.actions)
|
||||
end
|
||||
|
||||
for k, v in pairs(n.preset) do
|
||||
private[k] = v
|
||||
end
|
||||
|
||||
for k, v in pairs(args) do
|
||||
private[k] = v
|
||||
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
|
||||
n.is_expired = false
|
||||
|
||||
rawset(n, "_private", private)
|
||||
|
||||
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
|
||||
naughty.emit_signal("request::preset", n, args)
|
||||
-- Listen to action press and destroy non-resident notifications.
|
||||
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
|
||||
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
|
||||
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::fallback", 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)
|
||||
n:set_timeout(n._private.timeout
|
||||
or (n.preset and n.preset.timeout)
|
||||
or cst.config.timeout
|
||||
)
|
||||
end
|
||||
|
||||
return n
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
local imagebox = require("wibox.widget.imagebox")
|
||||
local gtable = require("gears.table")
|
||||
local beautiful = require("beautiful")
|
||||
local gsurface = require("gears.surface")
|
||||
local dpi = require("beautiful.xresources").apply_dpi
|
||||
|
||||
local icon = {}
|
||||
|
@ -85,7 +86,11 @@ function icon:set_notification(notif)
|
|||
self._private.icon_changed_callback)
|
||||
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
|
||||
|
||||
|
@ -141,7 +146,11 @@ local function new(args)
|
|||
gtable.crush(tb, icon, true)
|
||||
|
||||
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
|
||||
|
||||
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)"
|
||||
if [[ $fail_on_warning ]]; then
|
||||
# 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
|
||||
if [[ -n "$error" ]]; then
|
||||
color_red
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
local spawn = require("awful.spawn")
|
||||
local naughty = require("naughty" )
|
||||
local gdebug = require("gears.debug")
|
||||
local gtable = require("gears.table")
|
||||
local cairo = require("lgi" ).cairo
|
||||
local beautiful = require("beautiful")
|
||||
local Gio = require("lgi" ).Gio
|
||||
local GLib = require("lgi" ).GLib
|
||||
local gpcall = require("gears.protected_call")
|
||||
local dwidget = require("naughty.widget._default")
|
||||
|
||||
-- This module test deprecated APIs
|
||||
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.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()
|
||||
n2:destroy()
|
||||
|
||||
|
@ -673,6 +686,9 @@ table.insert(steps, function()
|
|||
assert(n.actions[2].name == "five" )
|
||||
assert(n.actions[3].name == "six" )
|
||||
|
||||
n:destroy()
|
||||
assert(#active == 0)
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
|
@ -719,6 +735,162 @@ table.insert(steps, function()
|
|||
return true
|
||||
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.
|
||||
table.insert(steps, function()
|
||||
-- Tests are (by default) not allowed to call deprecated APIs
|
||||
|
@ -830,6 +1002,82 @@ table.insert(steps, function()
|
|||
return true
|
||||
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.
|
||||
|
||||
require("_runner").run_steps(steps)
|
||||
|
|
Loading…
Reference in New Issue