Add support for notification actions
The desktop notification specification says that a notification can have different actions. These actions allow the user to interact with the client application and should be displayed by the notification server. * Add function to emit a DBus signal * Notifications : emit NotificationClosed signal when closing notification * Notifications: use constant for notification closed reasons * notifications: Implement notifications actions This is just a basic implementation to display the actions send with the notifications. The actions should be displayed differently * Notifications: add support for default action
This commit is contained in:
parent
30b313f77a
commit
e664a74710
55
dbus.c
55
dbus.c
|
@ -791,6 +791,60 @@ luaA_dbus_disconnect_signal(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/** Emit a signal on the D-Bus.
|
||||
* \param L The Lua VM state.
|
||||
* \return The number of elements pushed on stack.
|
||||
* \luastack
|
||||
* \lparam A string indicating if we are using system or session bus.
|
||||
* \lparam A string with the dbus path.
|
||||
* \lparam A string with the dbus interface.
|
||||
* \lparam A string with the dbus method name.
|
||||
* \lparam type of 1st arg
|
||||
* \lparam 1st arg value
|
||||
* \lparam type of 2nd arg
|
||||
* \lparam 2nd arg value
|
||||
* ... etc
|
||||
*/
|
||||
static int
|
||||
luaA_dbus_emit_signal(lua_State *L)
|
||||
{
|
||||
const char *bus_name = luaL_checkstring(L, 1);
|
||||
const char *path = luaL_checkstring(L, 2);
|
||||
const char *itface = luaL_checkstring(L, 3);
|
||||
const char *name = luaL_checkstring(L, 4);
|
||||
lua_remove(L, 1);
|
||||
lua_remove(L, 1);
|
||||
lua_remove(L, 1);
|
||||
lua_remove(L, 1);
|
||||
DBusConnection *dbus_connection = a_dbus_bus_getbyname(bus_name);
|
||||
DBusMessage* msg = dbus_message_new_signal(path, itface, name);
|
||||
|
||||
DBusMessageIter iter;
|
||||
dbus_message_iter_init_append(msg, &iter);
|
||||
int nargs = lua_gettop(L);
|
||||
|
||||
if(nargs % 2 != 0)
|
||||
{
|
||||
luaA_warn(L, "your D-Bus signal emiting method has wrong number of arguments");
|
||||
dbus_message_unref(msg);
|
||||
lua_pushboolean(L, 0);
|
||||
return 1;
|
||||
}
|
||||
for(int i = 1; i < nargs; i += 2) {
|
||||
if(!a_dbus_convert_value(L, i, &iter))
|
||||
{
|
||||
luaA_warn(L, "your D-Bus signal emitting method has bad argument type");
|
||||
dbus_message_unref(msg);
|
||||
lua_pushboolean(L, 0);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
dbus_connection_send(dbus_connection, msg, NULL);
|
||||
dbus_message_unref(msg);
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct luaL_Reg awesome_dbus_lib[] =
|
||||
{
|
||||
{ "request_name", luaA_dbus_request_name },
|
||||
|
@ -799,6 +853,7 @@ const struct luaL_Reg awesome_dbus_lib[] =
|
|||
{ "remove_match", luaA_dbus_remove_match },
|
||||
{ "connect_signal", luaA_dbus_connect_signal },
|
||||
{ "disconnect_signal", luaA_dbus_disconnect_signal },
|
||||
{ "emit_signal", luaA_dbus_emit_signal },
|
||||
{ "__index", luaA_default_index },
|
||||
{ "__newindex", luaA_default_newindex },
|
||||
{ NULL, NULL }
|
||||
|
|
|
@ -95,6 +95,13 @@ local urgency = {
|
|||
critical = "\2"
|
||||
}
|
||||
|
||||
local notificationClosedReason = {
|
||||
expired = 1,
|
||||
dismissedByUser = 2,
|
||||
dismissedByCommand = 3,
|
||||
undefined = 4
|
||||
}
|
||||
|
||||
--- DBUS notification to preset mapping
|
||||
-- The first element is an object containing the filter
|
||||
-- If the rules in the filter matches the associated preset will be applied
|
||||
|
@ -279,6 +286,7 @@ end
|
|||
-- @param 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
|
||||
-- @param actions array of pair of String:String for each action (id:localized text)
|
||||
-- @usage naughty.notify({ title = "Achtung!", text = "You're idling", timeout = 0 })
|
||||
-- @return The notification object
|
||||
function naughty.notify(args)
|
||||
|
@ -304,6 +312,7 @@ function naughty.notify(args)
|
|||
local margin = args.margin or preset.margin
|
||||
local border_width = args.border_width or preset.border_width
|
||||
local position = args.position or preset.position
|
||||
local actions = args.actions
|
||||
local escape_pattern = "[<>&]"
|
||||
local escape_subs = { ['<'] = "<", ['>'] = ">", ['&'] = "&" }
|
||||
|
||||
|
@ -340,10 +349,13 @@ function naughty.notify(args)
|
|||
if title then title = title .. "\n" else title = "" end
|
||||
|
||||
-- hook destroy
|
||||
local die = function () naughty.destroy(notification) end
|
||||
local die = function (reason)
|
||||
naughty.destroy(notification)
|
||||
sendNotificationClosed(notification.id, reason)
|
||||
end
|
||||
if timeout > 0 then
|
||||
local timer_die = timer { timeout = timeout }
|
||||
timer_die:connect_signal("timeout", die)
|
||||
timer_die:connect_signal("timeout", function() die(notificationClosedReason.expired) end)
|
||||
if not suspended then
|
||||
timer_die:start()
|
||||
end
|
||||
|
@ -351,21 +363,25 @@ function naughty.notify(args)
|
|||
end
|
||||
notification.die = die
|
||||
|
||||
local has_default_action = false;
|
||||
local run = function ()
|
||||
if has_default_action then
|
||||
sendActionInvoked(notification.id, "default")
|
||||
end
|
||||
if args.run then
|
||||
args.run(notification)
|
||||
else
|
||||
die()
|
||||
die(notificationClosedReason.dismissedByUser)
|
||||
end
|
||||
end
|
||||
|
||||
local hover_destroy = function ()
|
||||
if hover_timeout == 0 then
|
||||
die()
|
||||
die(notificationClosedReason.expired)
|
||||
else
|
||||
if notification.timer then notification.timer:stop() end
|
||||
notification.timer = timer { timeout = hover_timeout }
|
||||
notification.timer:connect_signal("timeout", die)
|
||||
notification.timer:connect_signal("timeout", function() die(notificationClosedReason.expired) end)
|
||||
notification.timer:start()
|
||||
end
|
||||
end
|
||||
|
@ -400,6 +416,56 @@ function naughty.notify(args)
|
|||
end
|
||||
end
|
||||
|
||||
local actionslayout = wibox.layout.fixed.vertical()
|
||||
local actions_max_width = 0
|
||||
local actions_total_height = 0
|
||||
if actions then
|
||||
local action
|
||||
local actionid
|
||||
-- create actions box
|
||||
for i , v in ipairs(actions) do
|
||||
if i % 2 == 1 then
|
||||
actionid = v
|
||||
if actionid == "default" then
|
||||
has_default_action = true;
|
||||
end
|
||||
elseif actionid ~= nil and actionid ~= "default" then
|
||||
action = v
|
||||
local actiontextbox = wibox.widget.textbox()
|
||||
local actionmarginbox = wibox.layout.margin()
|
||||
actionmarginbox:set_margins(margin)
|
||||
actionmarginbox:set_widget(actiontextbox)
|
||||
actiontextbox:set_valign("middle")
|
||||
actiontextbox:set_font(font)
|
||||
actiontextbox:set_markup(string.format('<b>%s</b>', action))
|
||||
-- calculate the height
|
||||
local w, h = actiontextbox:fit(-1, -1)
|
||||
local height = h + 2 * margin
|
||||
|
||||
-- calculate the width
|
||||
w, h = actiontextbox:fit(-1, -1)
|
||||
local width = w + 2 * margin
|
||||
|
||||
actionmarginbox:buttons(util.table.join(
|
||||
button({ }, 1, function()
|
||||
sendActionInvoked(notification.id, actionid)
|
||||
die(2)
|
||||
end),
|
||||
button({ }, 3, function()
|
||||
sendActionInvoked(notification.id, actionid)
|
||||
die(2)
|
||||
end))
|
||||
)
|
||||
actionslayout:add(actionmarginbox)
|
||||
|
||||
actions_total_height = actions_total_height + height
|
||||
if actions_max_width < width then
|
||||
actions_max_width = width
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- create iconbox
|
||||
local iconbox = nil
|
||||
local iconmargin = nil
|
||||
|
@ -461,12 +527,18 @@ function naughty.notify(args)
|
|||
end
|
||||
end
|
||||
|
||||
height = height + actions_total_height
|
||||
|
||||
-- calculate the width
|
||||
if not width then
|
||||
local w, h = textbox:fit(-1, -1)
|
||||
width = w + (iconbox and icon_w + 2 * margin or 0) + 2 * margin
|
||||
end
|
||||
|
||||
if width < actions_max_width then
|
||||
width = actions_max_width
|
||||
end
|
||||
|
||||
-- crop to workarea size if too big
|
||||
local workarea = capi.screen[screen].workarea
|
||||
if width > workarea.width - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0) then
|
||||
|
@ -497,10 +569,17 @@ function naughty.notify(args)
|
|||
layout:add(iconmargin)
|
||||
end
|
||||
layout:add(marginbox)
|
||||
notification.box:set_widget(layout)
|
||||
|
||||
local completelayout = wibox.layout.fixed.vertical()
|
||||
completelayout:add(layout)
|
||||
completelayout:add(actionslayout)
|
||||
notification.box:set_widget(completelayout)
|
||||
|
||||
-- Setup the mouse events
|
||||
layout:buttons(util.table.join(button({ }, 1, run), button({ }, 3, die)))
|
||||
layout:buttons(util.table.join(button({ }, 1, run),
|
||||
button({ }, 3, function()
|
||||
die(notificationClosedReason.dismissedByUser)
|
||||
end)))
|
||||
|
||||
-- insert the notification to the table
|
||||
table.insert(naughty.notifications[screen][notification.position], notification)
|
||||
|
@ -544,6 +623,7 @@ if capi.dbus then
|
|||
end
|
||||
end
|
||||
local preset = args.preset or naughty.config.defaults
|
||||
args.actions = actions
|
||||
if not preset.callback or (type(preset.callback) == "function" and
|
||||
preset.callback(data, appname, replaces_id, icon, title, text, actions, hints, expire)) then
|
||||
if icon ~= "" then
|
||||
|
@ -610,6 +690,7 @@ if capi.dbus then
|
|||
elseif data.member == "CloseNotification" then
|
||||
local obj = getById(appname)
|
||||
if obj then
|
||||
sendNotificationClosed(obj.id, 3)
|
||||
naughty.destroy(obj)
|
||||
end
|
||||
elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then
|
||||
|
@ -618,7 +699,7 @@ if capi.dbus then
|
|||
elseif data.member == "GetCapabilities" then
|
||||
-- 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.
|
||||
return "as", { "s", "body", "s", "body-markup", "s", "icon-static" }
|
||||
return "as", { "s", "body", "s", "body-markup", "s", "icon-static", "s", "actions" }
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -663,6 +744,14 @@ if capi.dbus then
|
|||
<arg name="return_vendor" type="s" direction="out"/>
|
||||
<arg name="return_version" type="s" direction="out"/>
|
||||
</method>
|
||||
<signal name="NotificationClosed">
|
||||
<arg name="id" type="u" direction="out"/>
|
||||
<arg name="reason" type="u" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ActionInvoked">
|
||||
<arg name="id" type="u" direction="out"/>
|
||||
<arg name="action_key" type="s" direction="out"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>]=]
|
||||
return "s", xml
|
||||
|
@ -673,6 +762,20 @@ if capi.dbus then
|
|||
capi.dbus.request_name("session", "org.freedesktop.Notifications")
|
||||
end
|
||||
|
||||
function sendActionInvoked(notificationId, action)
|
||||
res = capi.dbus.emit_signal("session", "/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications", "ActionInvoked",
|
||||
"i", notificationId,
|
||||
"s", action)
|
||||
end
|
||||
|
||||
function sendNotificationClosed(notificationId, reason)
|
||||
res = capi.dbus.emit_signal("session", "/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications", "NotificationClosed",
|
||||
"i", notificationId,
|
||||
"i", reason)
|
||||
end
|
||||
|
||||
return naughty
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||
|
|
Loading…
Reference in New Issue