local dbus = require("lua-dbus.init")
local function createinstance(meta, ...)
local instance = setmetatable({}, meta)
if instance.init then instance:init(...) end
return instance
end
local Interface = { new = createinstance }
Interface.__index = Interface
function Interface:init(opts)
opts = opts or {}
self.bus = opts.bus or 'session'
self.paths = {}
self.name = opts.name
if self.name then
dbus.raw.request_name(self.bus, self.name)
end
end
-- opts: interface, path='/', methods={}, signals={}, properties={}
function Interface:add(opts)
opts = opts or {}
if not opts.interface then return end
opts.path = opts.path or '/'
local path = self.paths[opts.path] or {}
self.paths[opts.path] = path
if not path.introspection then
path.introspection = true
self:_introspection(opts)
end
local iface = path[opts.interface] or {}
path[opts.interface] = iface
iface.methods = iface.methods or {}
for method, args in pairs(opts.methods or {}) do
iface.methods[method] = args
end
iface.signals = iface.signals or {}
for name, args in pairs(opts.signals or {}) do
iface.signals[name] = args
end
local hasproperties = false
iface.properties = iface.properties or {}
for name, property in pairs(opts.properties or {}) do
iface.properties[name] = property
hasproperties = true
end
if not path.properties then
path.properties = true
self:_properties(opts)
end
local iface = self
dbus.on({ bus = self.bus, interface = opts.interface,
handler = function (sig, ...)
if sig.type ~= 'method_call' then return end
local interface = iface.paths[opts.path][sig.interface]
if not interface then return end
local method = (interface.methods or {})[sig.member]
if method and method.callback then
local i, result, ret = 1, {}, {method.callback(sig, ...)}
if not method.result then
result = ret
else
-- prepare callback results with actual interface result types
for j=1,#method.result,2 do
table.insert(result, method.result[j]) -- type
table.insert(result, ret[i]) -- value
i = i + 1
end
end
return unpack(result)
end
end,
})
end
local function introspect(iface, path, header)
local xml = header ~= false and
[[]] or ""
if path == "/" then
xml = xml .. ""
xml = xml .. introspect(iface, nil, false)
for facepath, interfaces in pairs(iface.paths or {}) do
xml = xml .. string.format([[]], string.sub(facepath, 2))
end
xml = xml .. ""
return xml
end
local interfaces = iface.paths and iface.paths[path or "/"]
if interfaces then
if path then
xml = xml .. string.format([[]], path)
end
for name, interface in pairs(interfaces) do
if name ~= 'introspection' and name ~= 'properties' then
xml = xml .. string.format([[]], name)
for method, args in pairs(interface.methods) do
xml = xml .. string.format([[]], method)
for i = 1, #args, 2 do
xml = xml .. string.format(
[[]],
args[i+1], args[i], 'in')
end
if args.result then
for i = 1, #args.result, 2 do
xml = xml .. string.format(
[[]],
args.result[i+1], args.result[i], 'out')
end
end
xml = xml .. ""
end
for name, args in pairs(interface.signals) do
xml = xml .. string.format([[]], name)
for i = 1, #args, 2 do
xml = xml .. string.format(
[[]],
args[i+1], args[i])
end
xml = xml .. ""
end
for name, property in pairs(interface.properties) do
local access = 'read'
if property.read and property.write then
access = 'readwrite'
elseif property.write then
access = 'write'
end
xml = xml .. string.format(
[[]],
name, property.type, access)
end
xml = xml .. ""
end
end
if path then
xml = xml .. ""
end
end
return xml
end
Interface.introspect = introspect
function Interface:_introspection(opts)
local iface = self
self:add({
interface = 'org.freedesktop.DBus.Introspectable',
path = opts.path,
methods = {
Introspect = {
result = {'s', "data"},
callback = function (sig) return introspect(iface, sig.path) end,
},
},
})
end
function Interface:_properties(opts)
local iface = self
self:add({
interface = 'org.freedesktop.DBus.Properties',
path = opts.path,
methods = {
Get = { 's', "interface", 's', "name",
result = {'v', "value"},
callback = function (sig, interface, name)
local interfaces = iface.paths and iface.paths[sig.path or "/"]
local face = interfaces and interfaces[interface]
if face and face.properties then
local property = face.properties[name]
if property and property.read then
return property.read()
end
end
end,
},
GetAll = { 's', "interface",
result = {'a{sv}', "values"},
callback = function (sig, interface)
local interfaces = iface.paths and iface.paths[sig.path or "/"]
local face = interfaces and interfaces[interface]
local values = {}
if face and face.properties then
for name, property in pairs(face.properties) do
if property.read then
values[name] = property.read()
end
end
end
return values
end,
},
Set = { 's', "interface", 's', "name", 'v', "value",
callback = function (sig, interface, name, value)
local interfaces = iface.paths and iface.paths[sig.path or "/"]
local face = interfaces and interfaces[interface]
if face and face.properties then
local property = face.properties[name]
if property and property.write then
return property.write(value)
end
end
end,
},
},
signals = {
PropertiesChanged = {
's', "interface", 'a{sv}', "values", 'as', "keys"
},
},
})
end
function Interface:properties(keys, values, opts)
opts = opts or {}
dbus.raw.emit_signal(self.bus, opts.path,
'org.freedesktop.DBus.Properties', 'PropertiesChanged',
's', opts.interface, 'a{sv}', values, 'as', keys)
end
function Interface:property(name, value, opts)
local values = {}
values[name] = value
self:properties({name}, values, opts)
end
return Interface