diff --git a/lib/naughty/action.lua b/lib/naughty/action.lua
new file mode 100644
index 00000000..a16a9570
--- /dev/null
+++ b/lib/naughty/action.lua
@@ -0,0 +1,127 @@
+---------------------------------------------------------------------------
+--- A notification action.
+--
+-- A notification can have multiple actions to chose from. This module allows
+-- to manage such actions.
+--
+-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
+-- @copyright 2019 Emmanuel Lepage Vallee
+-- @classmod naughty.action
+---------------------------------------------------------------------------
+local gtable = require("gears.table" )
+local gobject = require("gears.object")
+
+local action = {}
+
+--- Create a new action.
+-- @function naughty.action
+-- @tparam table args The arguments.
+-- @tparam string args.name The name.
+-- @tparam string args.position The position.
+-- @tparam string args.icon The icon.
+-- @tparam naughty.notification args.notification The notification object.
+-- @tparam boolean args.selected If this action is currently selected.
+-- @return A new action.
+
+-- The action name.
+-- @property name
+-- @tparam string name The name.
+
+-- If the action is selected.
+--
+-- Only a single action can be selected per notification. It will be applied
+-- when `my_notification:apply()` is called.
+--
+-- @property selected
+-- @param boolean
+
+--- The action position (index).
+-- @property position
+-- @param number
+
+--- The action icon.
+-- @property icon
+-- @param gears.surface
+
+--- The notification.
+-- @property notification
+-- @tparam naughty.notification notification
+
+--- When a notification is invoked.
+-- @signal invoked
+
+function action:get_selected()
+ return self._private.selected
+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
+
+ --TODO deselect other actions from the same notification
+end
+
+function action:get_position()
+ return self._private.position
+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
+
+ --TODO make sure the position is unique
+end
+
+for _, prop in ipairs { "name", "icon", "notification" } do
+ action["get_"..prop] = function(self)
+ return self._private[prop]
+ end
+
+ 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
+ end
+end
+
+--- Execute this action.
+function action:invoke()
+ assert(self._private.notification,
+ "Cannot invoke an action without a notification")
+
+ self:emit_signal("invoked")
+end
+
+local function new(_, args)
+ args = args or {}
+ local ret = gobject { enable_properties = true }
+
+ gtable.crush(ret, action, true)
+
+ local default = {
+ -- See "table 1" of the spec about the default name
+ name = args.name or "default",
+ selected = args.selected == true,
+ position = args.position,
+ icon = args.icon,
+ notification = args.notification,
+ }
+
+ rawset(ret, "_private", default)
+
+ return ret
+end
+
+return setmetatable(action, {__call = new})
diff --git a/lib/naughty/core.lua b/lib/naughty/core.lua
index ea382740..d7fee714 100644
--- a/lib/naughty/core.lua
+++ b/lib/naughty/core.lua
@@ -497,8 +497,7 @@ end
-- @tparam[opt] func args.callback Function that will be called with all arguments.
-- The notification will only be displayed if the function returns true.
-- Note: this function is only relevant to notifications sent via dbus.
--- @tparam[opt] table args.actions Mapping that maps a string to a callback when this
--- action is selected.
+-- @tparam[opt] table args.actions A list of `naughty.action`s.
-- @bool[opt=false] args.ignore_suspend If set to true this notification
-- will be shown even if notifications are suspended via `naughty.suspend`.
-- @usage naughty.notify({ title = "Achtung!", text = "You're idling", timeout = 0 })
diff --git a/lib/naughty/dbus.lua b/lib/naughty/dbus.lua
index 1d89a0d6..89ff9b94 100644
--- a/lib/naughty/dbus.lua
+++ b/lib/naughty/dbus.lua
@@ -27,6 +27,7 @@ local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility
local naughty = require("naughty.core")
local cst = require("naughty.constants")
local nnotif = require("naughty.notification")
+local naction = require("naughty.action")
--- Notification library, dbus bindings
local dbus = { config = {} }
@@ -158,10 +159,17 @@ capi.dbus.connect_signal("org.freedesktop.Notifications",
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
end
elseif action_id ~= nil and action_text ~= nil then
- args.actions[action_text] = function()
+ local a = naction {
+ name = action_text,
+ position = action_id,
+ }
+
+ a:connect_signal("invoked", function()
sendActionInvoked(notification.id, action_id)
notification:destroy(cst.notification_closed_reason.dismissed_by_user)
- end
+ end)
+
+ table.insert(args.actions, a)
end
end
end
diff --git a/lib/naughty/layout/legacy.lua b/lib/naughty/layout/legacy.lua
index 796a32b7..c46c96eb 100644
--- a/lib/naughty/layout/legacy.lua
+++ b/lib/naughty/layout/legacy.lua
@@ -404,23 +404,25 @@ function naughty.default_notification_handler(notification, args)
local actions_max_width = 0
local actions_total_height = 0
if actions then
- for action, callback in pairs(actions) do
+ for _, action in ipairs(actions) do
+ assert(type(action) == "table")
+ assert(action.name ~= nil)
local actiontextbox = wibox.widget.textbox()
local actionmarginbox = wibox.container.margin()
actionmarginbox:set_margins(margin)
actionmarginbox:set_widget(actiontextbox)
actiontextbox:set_valign("middle")
actiontextbox:set_font(font)
- actiontextbox:set_markup(string.format('☛ %s', action))
+ actiontextbox:set_markup(string.format('☛ %s', action.name))
-- calculate the height and width
local w, h = actiontextbox:get_preferred_size(s)
local action_height = h + 2 * margin
local action_width = w + 2 * margin
actionmarginbox:buttons(gtable.join(
- button({ }, 1, callback),
- button({ }, 3, callback)
- ))
+ button({ }, 1, function() action:trigger() end),
+ button({ }, 3, function() action:trigger() end)
+ ))
actionslayout:add(actionmarginbox)
actions_total_height = actions_total_height + action_height
diff --git a/lib/naughty/notification.lua b/lib/naughty/notification.lua
index f85cf621..402e4257 100644
--- a/lib/naughty/notification.lua
+++ b/lib/naughty/notification.lua
@@ -325,6 +325,43 @@ for _, prop in ipairs(properties) do
end
+--TODO v6: remove this
+local function convert_actions(actions)
+ gdebug.deprecate(
+ "The notification actions should now be of type `naughty.action`, "..
+ "not strings or callback functions",
+ {deprecated_in=5}
+ )
+
+ local naction = require("naughty.action")
+
+ -- Does not attempt to handle when there is a mix of strings and objects
+ for idx, name in pairs(actions) do
+ local cb = nil
+
+ if type(name) == "function" then
+ cb = name
+ end
+
+ if type(idx) == "string" then
+ name, idx = idx, nil
+ end
+
+ local a = naction {
+ position = idx,
+ name = name,
+ }
+
+ if cb then
+ a:connect_signal("invoked", cb)
+ end
+
+ -- Yes, it modifies `args`, this is legacy code, cloning the args
+ -- just for this isn't worth it.
+ actions[idx] = a
+ end
+end
+
--- Create a notification.
--
-- @tab args The argument table containing any of the arguments below.
@@ -364,8 +401,7 @@ end
-- @tparam[opt] func args.callback Function that will be called with all arguments.
-- The notification will only be displayed if the function returns true.
-- Note: this function is only relevant to notifications sent via dbus.
--- @tparam[opt] table args.actions Mapping that maps a string to a callback when this
--- action is selected.
+-- @tparam[opt] table args.actions A list of `naughty.action`s.
-- @bool[opt=false] args.ignore_suspend If set to true this notification
-- will be shown even if notifications are suspended via `naughty.suspend`.
-- @usage naughty.notify({ title = "Achtung!", text = "You're idling", timeout = 0 })
@@ -378,6 +414,16 @@ local function create(args)
if not args then return end
end
+ args = args or {}
+
+ -- Old actions usually have callbacks and names. But this isn't non
+ -- compliant with the spec. The spec has explicit ordering and optional
+ -- icons. The old format doesn't allow these metadata to be stored.
+ local is_old_action = args.actions and (
+ (args.actions[1] and type(args.actions[1]) == "string") or
+ (type(next(args.actions)) == "string")
+ )
+
local n = gobject {
enable_properties = true,
}
@@ -396,6 +442,10 @@ local function create(args)
rawget(n, "preset") or {}
))
+ if is_old_action then
+ convert_actions(args.actions)
+ end
+
for k, v in pairs(n.preset) do
private[k] = v
end