Merge branch 'notification_actions_master' of git://github.com/fbarthelery/awesome

This commit is contained in:
Uli Schlachter 2015-01-10 01:20:16 +01:00
commit 33196d39ec
3 changed files with 177 additions and 8 deletions

52
dbus.c
View File

@ -791,6 +791,57 @@ luaA_dbus_disconnect_signal(lua_State *L)
return 0; 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);
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 top = lua_gettop(L);
int nargs = top - 4;
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 = 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");
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[] = const struct luaL_Reg awesome_dbus_lib[] =
{ {
{ "request_name", luaA_dbus_request_name }, { "request_name", luaA_dbus_request_name },
@ -799,6 +850,7 @@ const struct luaL_Reg awesome_dbus_lib[] =
{ "remove_match", luaA_dbus_remove_match }, { "remove_match", luaA_dbus_remove_match },
{ "connect_signal", luaA_dbus_connect_signal }, { "connect_signal", luaA_dbus_connect_signal },
{ "disconnect_signal", luaA_dbus_disconnect_signal }, { "disconnect_signal", luaA_dbus_disconnect_signal },
{ "emit_signal", luaA_dbus_emit_signal },
{ "__index", luaA_default_index }, { "__index", luaA_default_index },
{ "__newindex", luaA_default_newindex }, { "__newindex", luaA_default_newindex },
{ NULL, NULL } { NULL, NULL }

View File

@ -95,6 +95,13 @@ local urgency = {
critical = "\2" critical = "\2"
} }
local notificationClosedReason = {
expired = 1,
dismissedByUser = 2,
dismissedByCommand = 3,
undefined = 4
}
--- DBUS notification to preset mapping --- DBUS notification to preset mapping
-- The first element is an object containing the filter -- The first element is an object containing the filter
-- If the rules in the filter matches the associated preset will be applied -- If the rules in the filter matches the associated preset will be applied
@ -251,6 +258,20 @@ local function getById(id)
end end
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. --- Create notification. args is a dictionary of (optional) arguments.
-- @param text Text of the notification. Default: '' -- @param text Text of the notification. Default: ''
-- @param title Title of the notification. Default: nil -- @param title Title of the notification. Default: nil
@ -279,6 +300,7 @@ end
-- @param callback function that will be called with all arguments -- @param callback function that will be called with all arguments
-- the notification will only be displayed if the function returns true -- the notification will only be displayed if the function returns true
-- note: this function is only relevant to notifications sent via dbus -- 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 }) -- @usage naughty.notify({ title = "Achtung!", text = "You're idling", timeout = 0 })
-- @return The notification object -- @return The notification object
function naughty.notify(args) function naughty.notify(args)
@ -304,6 +326,7 @@ function naughty.notify(args)
local margin = args.margin or preset.margin local margin = args.margin or preset.margin
local border_width = args.border_width or preset.border_width local border_width = args.border_width or preset.border_width
local position = args.position or preset.position local position = args.position or preset.position
local actions = args.actions
local escape_pattern = "[<>&]" local escape_pattern = "[<>&]"
local escape_subs = { ['<'] = "&lt;", ['>'] = "&gt;", ['&'] = "&amp;" } local escape_subs = { ['<'] = "&lt;", ['>'] = "&gt;", ['&'] = "&amp;" }
@ -340,10 +363,13 @@ function naughty.notify(args)
if title then title = title .. "\n" else title = "" end if title then title = title .. "\n" else title = "" end
-- hook destroy -- 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 if timeout > 0 then
local timer_die = timer { timeout = timeout } 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 if not suspended then
timer_die:start() timer_die:start()
end end
@ -351,21 +377,25 @@ function naughty.notify(args)
end end
notification.die = die notification.die = die
local has_default_action = false;
local run = function () local run = function ()
if has_default_action then
sendActionInvoked(notification.id, "default")
end
if args.run then if args.run then
args.run(notification) args.run(notification)
else else
die() die(notificationClosedReason.dismissedByUser)
end end
end end
local hover_destroy = function () local hover_destroy = function ()
if hover_timeout == 0 then if hover_timeout == 0 then
die() die(notificationClosedReason.expired)
else else
if notification.timer then notification.timer:stop() end if notification.timer then notification.timer:stop() end
notification.timer = timer { timeout = hover_timeout } 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() notification.timer:start()
end end
end end
@ -400,6 +430,56 @@ function naughty.notify(args)
end end
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(notificationClosedReason.dismissedByUser)
end),
button({ }, 3, function()
sendActionInvoked(notification.id, actionid)
die(notificationClosedReason.dismissedByUser)
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 -- create iconbox
local iconbox = nil local iconbox = nil
local iconmargin = nil local iconmargin = nil
@ -461,12 +541,18 @@ function naughty.notify(args)
end end
end end
height = height + actions_total_height
-- calculate the width -- calculate the width
if not width then if not width then
local w, h = textbox:fit(-1, -1) local w, h = textbox:fit(-1, -1)
width = w + (iconbox and icon_w + 2 * margin or 0) + 2 * margin width = w + (iconbox and icon_w + 2 * margin or 0) + 2 * margin
end end
if width < actions_max_width then
width = actions_max_width
end
-- crop to workarea size if too big -- crop to workarea size if too big
local workarea = capi.screen[screen].workarea local workarea = capi.screen[screen].workarea
if width > workarea.width - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0) then if width > workarea.width - 2 * (border_width or 0) - 2 * (naughty.config.padding or 0) then
@ -497,10 +583,17 @@ function naughty.notify(args)
layout:add(iconmargin) layout:add(iconmargin)
end end
layout:add(marginbox) 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 -- 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 -- insert the notification to the table
table.insert(naughty.notifications[screen][notification.position], notification) table.insert(naughty.notifications[screen][notification.position], notification)
@ -544,6 +637,7 @@ if capi.dbus then
end end
end end
local preset = args.preset or naughty.config.defaults local preset = args.preset or naughty.config.defaults
args.actions = actions
if not preset.callback or (type(preset.callback) == "function" and if not preset.callback or (type(preset.callback) == "function" and
preset.callback(data, appname, replaces_id, icon, title, text, actions, hints, expire)) then preset.callback(data, appname, replaces_id, icon, title, text, actions, hints, expire)) then
if icon ~= "" then if icon ~= "" then
@ -610,6 +704,7 @@ if capi.dbus then
elseif data.member == "CloseNotification" then elseif data.member == "CloseNotification" then
local obj = getById(appname) local obj = getById(appname)
if obj then if obj then
sendNotificationClosed(obj.id, notificationClosedReason.dismissedByCommand)
naughty.destroy(obj) naughty.destroy(obj)
end end
elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then elseif data.member == "GetServerInfo" or data.member == "GetServerInformation" then
@ -618,7 +713,7 @@ if capi.dbus then
elseif data.member == "GetCapabilities" then elseif data.member == "GetCapabilities" then
-- We actually do display the body of the message, we support <b>, <i> -- We actually do display the body of the message, we support <b>, <i>
-- and <u> in the body and we handle static (non-animated) icons. -- and <u> in the body and we handle static (non-animated) icons.
return "as", { "s", "body", "s", "body-markup", "s", "icon-static" } return "as", { "s", "body", "s", "body-markup", "s", "icon-static", "s", "actions" }
end end
end) end)
@ -663,6 +758,14 @@ if capi.dbus then
<arg name="return_vendor" type="s" direction="out"/> <arg name="return_vendor" type="s" direction="out"/>
<arg name="return_version" type="s" direction="out"/> <arg name="return_version" type="s" direction="out"/>
</method> </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> </interface>
</node>]=] </node>]=]
return "s", xml return "s", xml

View File

@ -41,3 +41,17 @@ module("dbus")
-- @param func The function to call. -- @param func The function to call.
-- @name disconnect_signal -- @name disconnect_signal
-- @class function -- @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
--