From e664a74710a4637b7abd97c410b532268d051a8c Mon Sep 17 00:00:00 2001 From: Da Risk Date: Wed, 31 Dec 2014 14:28:46 +0100 Subject: [PATCH 1/3] 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 --- dbus.c | 55 +++++++++++++++++++++ lib/naughty.lua.in | 119 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 166 insertions(+), 8 deletions(-) diff --git a/dbus.c b/dbus.c index 79d18372..2f5dba1e 100644 --- a/dbus.c +++ b/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 } diff --git a/lib/naughty.lua.in b/lib/naughty.lua.in index dd230ab9..befab40f 100644 --- a/lib/naughty.lua.in +++ b/lib/naughty.lua.in @@ -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('%s', 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 , -- and 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 + + + + + + + + ]=] 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 From 0ccda0eb9e6000b175205a82d65e47d257f71b0b Mon Sep 17 00:00:00 2001 From: Da Risk Date: Fri, 2 Jan 2015 20:07:22 +0100 Subject: [PATCH 2/3] Add luadoc for dbus.emit_signal() --- dbus.c | 9 +++------ luadoc/dbus.lua | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/dbus.c b/dbus.c index 2f5dba1e..a953a98d 100644 --- a/dbus.c +++ b/dbus.c @@ -812,16 +812,13 @@ luaA_dbus_emit_signal(lua_State *L) 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); + int top = lua_gettop(L); + int nargs = top - 4; if(nargs % 2 != 0) { @@ -830,7 +827,7 @@ luaA_dbus_emit_signal(lua_State *L) lua_pushboolean(L, 0); return 1; } - for(int i = 1; i < nargs; i += 2) { + for(int i = 5; i < top; i += 2) { if(!a_dbus_convert_value(L, i, &iter)) { luaA_warn(L, "your D-Bus signal emitting method has bad argument type"); diff --git a/luadoc/dbus.lua b/luadoc/dbus.lua index 98894a40..b5b7c3da 100644 --- a/luadoc/dbus.lua +++ b/luadoc/dbus.lua @@ -41,3 +41,17 @@ module("dbus") -- @param func The function to call. -- @name disconnect_signal -- @class function + +-- Emit a signal on the D-Bus. +-- @param bus A string indicating if we are using system or session bus. +-- @param path A string with the dbus path. +-- @param interface A string with the dbus interface. +-- @param method A string with the dbus method name. +-- @param type_1st_arg type of 1st argument +-- @param value_1st_arg value of 1st argument +-- @param type_2nd_arg type of 2nd argument +-- @param value_2nd_arg value of 2nd argument +-- ... etc +-- @name emit_signal +-- @class function +-- From 28a6468f37dc6d909e1f5d065c4a47f17d2a741a Mon Sep 17 00:00:00 2001 From: Da Risk Date: Fri, 2 Jan 2015 21:02:12 +0100 Subject: [PATCH 3/3] naughty: make sendNotificationClosed() and sendActionInvoked() local --- lib/naughty.lua.in | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/naughty.lua.in b/lib/naughty.lua.in index befab40f..3c2fb072 100644 --- a/lib/naughty.lua.in +++ b/lib/naughty.lua.in @@ -258,6 +258,20 @@ local function getById(id) end end +local function sendActionInvoked(notificationId, action) + res = capi.dbus.emit_signal("session", "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", "ActionInvoked", + "i", notificationId, + "s", action) +end + +local function sendNotificationClosed(notificationId, reason) + res = capi.dbus.emit_signal("session", "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", "NotificationClosed", + "i", notificationId, + "i", reason) +end + --- Create notification. args is a dictionary of (optional) arguments. -- @param text Text of the notification. Default: '' -- @param title Title of the notification. Default: nil @@ -449,11 +463,11 @@ function naughty.notify(args) actionmarginbox:buttons(util.table.join( button({ }, 1, function() sendActionInvoked(notification.id, actionid) - die(2) + die(notificationClosedReason.dismissedByUser) end), button({ }, 3, function() sendActionInvoked(notification.id, actionid) - die(2) + die(notificationClosedReason.dismissedByUser) end)) ) actionslayout:add(actionmarginbox) @@ -690,7 +704,7 @@ if capi.dbus then elseif data.member == "CloseNotification" then local obj = getById(appname) if obj then - sendNotificationClosed(obj.id, 3) + sendNotificationClosed(obj.id, notificationClosedReason.dismissedByCommand) naughty.destroy(obj) end elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then @@ -762,20 +776,6 @@ 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