Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example of how to implement *DBusInterfaceSkeleton* abstract class #275

Open
joseigor opened this issue Oct 19, 2021 · 10 comments
Open

Example of how to implement *DBusInterfaceSkeleton* abstract class #275

joseigor opened this issue Oct 19, 2021 · 10 comments

Comments

@joseigor
Copy link

Hi all,

I am working in a project using lgi where I need to implement a instance of the abstract class DBusInterfaceSkeleton. After many trails I still did not get any success so I was wondering if anyone could help me. I found some documentation of how to implement a sublcass using lgi.package but still not clear for me how I can get a instance of the class DBusInterfaceSkeleton.

Just to put in context I need a instance of this class in order to add a DBUS interface to a DBUS object using Gio.DBusObjectSkeleton.add_interface(...) method.

Best Regards,

@psychon
Copy link
Collaborator

psychon commented Oct 19, 2021

Do you happen to have some C example code for me to translate?

Last time I looked at this, I gave up since I failed to figure out how "all the pieces fit together".

(From the top of my head: I also don't know how the subclassing works exactly. But hopefully I can figure this out when I don't have to figure out Gio's dbus code at the same time.)

@joseigor
Copy link
Author

joseigor commented Oct 21, 2021

@psychon thanks for the quick replay. In this moment I am able to create a instance of DBusInterfaceSkeleton, see code below, but now by some reason I get a SEGMENTATION FAULT when I try to add an interface to a object. I tried to debug the reason of the error but without success. Last line of code below just cause the SEGMENTATION FAULT.

local lgi = require("lgi")
local Gio = lgi.Gio
local GObject = lgi.GObject
local GLib = lgi.GLib

local MyApp = lgi.package 'MyApp'
MyApp:class('MyDBusInterfaceSkeleton',  Gio.DBusInterfaceSkeleton)

-- Override get_info() method of Gio.DBusInterfaceSkeleton
function MyApp.MyDBusInterfaceSkeleton:do_get_info()
      return Gio.DBusInterfaceInfo{
        name = "org.test",
        methods = nil,
        signals = nil,
        properties = nil,
        annotations = nil
    }
end

-- create a instace of MyDBusInterfaceSkeleton which implements Gio.DBusInterfaceSkeleton
local interface_ske = MyApp.MyDBusInterfaceSkeleton()

local obj_man = Gio.DBusObjectManagerServer{object_path ="/object"}  
local obj_ske = Gio.DBusObjectSkeleton.new("/object/1")

obj_man:export(obj_ske)

-- Line below causes SEGMENTATION FAULT
obj_ske:add_interface(interface_ske)

@psychon
Copy link
Collaborator

psychon commented Oct 21, 2021

Sorry, but that does not segfault here :-(

@joseigor
Copy link
Author

joseigor commented Oct 21, 2021

It works on you side? Weird, let me check again... Thanks for the feedback

Cold you share how you tested it?

@psychon
Copy link
Collaborator

psychon commented Oct 21, 2021

$ cat > /tmp/t.lua && lua /tmp/t.lua 
local lgi = require("lgi")
local Gio = lgi.Gio
local GObject = lgi.GObject
local GLib = lgi.GLib

local MyApp = lgi.package 'MyApp'
MyApp:class('MyDBusInterfaceSkeleton',  Gio.DBusInterfaceSkeleton)

-- Override get_info() method of Gio.DBusInterfaceSkeleton
function MyApp.MyDBusInterfaceSkeleton:do_get_info()
      return Gio.DBusInterfaceInfo{
        name = "org.test",
        methods = nil,
        signals = nil,
        properties = nil,
        annotations = nil
    }
end

-- create a instace of MyDBusInterfaceSkeleton which implements Gio.DBusInterfaceSkeleton
local interface_ske = MyApp.MyDBusInterfaceSkeleton()

local obj_man = Gio.DBusObjectManagerServer{object_path ="/object"}  
local obj_ske = Gio.DBusObjectSkeleton.new("/object/1")

obj_man:export(obj_ske)

-- Line below causes SEGMENTATION FAULT
obj_ske:add_interface(interface_ske)

First line is what I entered in my shell. The rest is copy&paste from your post (with a final ctrl+d for "end of file). I tested this with all of lua5.1, 5.2, 5.3 and luajit.

@joseigor
Copy link
Author

joseigor commented Oct 21, 2021

Now I see, actually I narrowed down the case of segmentation fault, it happens when set the dbus-connection to object manager. I put all code I used to test below. And indeed the way you tested there is no segfault. If you try the code below just after we set the connection to the object-manager segfault happens in the line obj_ske:add_interface(interface_ske). Still not clear to me why.

local lgi = require("lgi")
local Gio = lgi.Gio
local GObject = lgi.GObject
local GLib = lgi.GLib


local MyApp = lgi.package 'MyApp'
MyApp:class('MyDBusInterfaceSkeleton',  Gio.DBusInterfaceSkeleton)

-- Override get_info() method of Gio.DBusInterfaceSkeleton
function MyApp.MyDBusInterfaceSkeleton:do_get_info()
      return Gio.DBusInterfaceInfo{
        name = "org.test",
        methods = nil,
        signals = nil,
        properties = nil,
        annotations = nil
    }
end

-- create a instace of MyDBusInterfaceSkeleton which implements Gio.DBusInterfaceSkeleton
local interface_ske = MyApp.MyDBusInterfaceSkeleton()

local obj_man = Gio.DBusObjectManagerServer{object_path ="/object"}  
local obj_ske = Gio.DBusObjectSkeleton.new("/object/1")

obj_man:export(obj_ske)


local function on_bus_acquire(conn, _, _)
  obj_man:set_connection(conn)
  -- Line below causes SEGMENTATION FAULT
  obj_ske:add_interface(interface_ske)
end

local function on_name_lost(conn, _, _)
    -- if con is nil, then no connection to the bus could be made
    if (conn) then
        print('Could not acquire the requested name.')
    else
        print('Connection to the bus cannot be established.')
    end
end

local mainloop

local function main()
  Gio.bus_own_name(
        Gio.BusType.SESSION,
        "com.org.luadbus1",
        Gio.BusNameOwnerFlags.NONE,
        GObject.Closure(on_bus_acquire), nil, GObject.Closure(on_name_lost)
    )
    
    mainloop = GLib.MainLoop.new()
    print("daemon started")

    mainloop:run()

    dbus_service.stop()
    print("daemon stopped")
end

main()

If we change the order of the calls, then segfault happens on obj_man:set_connection(conn).

local lgi = require("lgi")
local Gio = lgi.Gio
local GObject = lgi.GObject
local GLib = lgi.GLib


local MyApp = lgi.package 'MyApp'
MyApp:class('MyDBusInterfaceSkeleton',  Gio.DBusInterfaceSkeleton)

-- Override get_info() method of Gio.DBusInterfaceSkeleton
function MyApp.MyDBusInterfaceSkeleton:do_get_info()
      return Gio.DBusInterfaceInfo{
        name = "org.test",
        methods = nil,
        signals = nil,
        properties = nil,
        annotations = nil
    }
end

-- create a instace of MyDBusInterfaceSkeleton which implements Gio.DBusInterfaceSkeleton
local interface_ske = MyApp.MyDBusInterfaceSkeleton()

local obj_man = Gio.DBusObjectManagerServer{object_path ="/object"}  
local obj_ske = Gio.DBusObjectSkeleton.new("/object/1")

obj_man:export(obj_ske)


local function on_bus_acquire(conn, _, _)
    obj_ske:add_interface(interface_ske)
    
  -- Line below causes SEGMENTATION FAULT
  obj_man:set_connection(conn)
end

local function on_name_lost(conn, _, _)
    -- if con is nil, then no connection to the bus could be made
    if (conn) then
        print('Could not acquire the requested name.')
    else
        print('Connection to the bus cannot be established.')
    end
end

local mainloop

local function main()
  Gio.bus_own_name(
        Gio.BusType.SESSION,
        "com.org.luadbus1",
        Gio.BusNameOwnerFlags.NONE,
        GObject.Closure(on_bus_acquire), nil, GObject.Closure(on_name_lost)
    )
    
    mainloop = GLib.MainLoop.new()
    print("daemon started")

    mainloop:run()

    dbus_service.stop()
    print("daemon stopped")
end

main()

Below I put the code implementationo of GIO g_dbus_object_manager_server_set_connection

/**
 * g_dbus_object_manager_server_set_connection:
 * @manager: A #GDBusObjectManagerServer.
 * @connection: (nullable): A #GDBusConnection or %NULL.
 *
 * Exports all objects managed by @manager on @connection. If
 * @connection is %NULL, stops exporting objects.
 */
void
g_dbus_object_manager_server_set_connection (GDBusObjectManagerServer  *manager,
                                             GDBusConnection           *connection)
{
  g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager));
  g_return_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection));

  g_mutex_lock (&manager->priv->lock);

  if (manager->priv->connection == connection)
    {
      g_mutex_unlock (&manager->priv->lock);
      goto out;
    }

  if (manager->priv->connection != NULL)
    {
      unexport_all (manager, FALSE);
      g_object_unref (manager->priv->connection);
      manager->priv->connection = NULL;
    }

  manager->priv->connection = connection != NULL ? g_object_ref (connection) : NULL;
  if (manager->priv->connection != NULL)
    export_all (manager);

  g_mutex_unlock (&manager->priv->lock);

  g_object_notify (G_OBJECT (manager), "connection");
 out:
  ;
}

Once again, thank you so much for the support!

@psychon
Copy link
Collaborator

psychon commented Oct 22, 2021

The crash happens here:

GDBusInterfaceVTable *
g_dbus_interface_skeleton_get_vtable (GDBusInterfaceSkeleton *interface_)
{
  GDBusInterfaceVTable *ret;
  g_return_val_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface_), NULL);
  ret = G_DBUS_INTERFACE_SKELETON_GET_CLASS (interface_)->get_vtable (interface_);
  g_warn_if_fail (ret != NULL);
  return ret;
}

Specifically, it happens when calling the get_vtable function. This function pointer is NULL.

@psychon
Copy link
Collaborator

psychon commented Oct 22, 2021

Hm... I tried to add the following, but the function is not called (still same NULL pointer as before). Now I am back in "I do not know how this work and would like to have a working example in C that can be translated". Sorry.

function MyApp.MyDBusInterfaceSkeleton:do_get_vtable()
	print("You have to somehow implement this method")
end

@joseigor
Copy link
Author

Hi @psychon as per my investigation it seems the LGI lua bidding do not implement the get_vtable which is required, so it does not work if we try to override because it is not part of the DBusInterfaceSkeleton generated by lig this is the reason I guess it is NULL.

Regarding a work example please find below attached files from the official GIO repository.

https://drive.google.com/drive/folders/1B3iyrStz2rVw8alPOr9TMjkgHfC5Ylhw?usp=sharing

@psychon
Copy link
Collaborator

psychon commented Oct 24, 2021

Hrm. Sorry, I give up. That's 3.8k lines of code, 3.1k of which come from a code generator. Gio's DBus code is just not meant to be used from anything other than C.

Here is my state when I gave up:

local lgi = require("lgi")
local Gio = lgi.Gio
local GObject = lgi.GObject
local GLib = lgi.GLib

local MyApp = lgi.package 'MyApp'
MyApp:class('ExampleObjectSkeleton',  Gio.DBusObjectSkeleton)
MyApp:class('ExampleAnimalSkeleton',  Gio.DBusInterfaceSkeleton)

MyApp.ExampleAnimalSkeleton._property.mood = GObject.ParamSpecString("mood", "Mood", "Mood", nil, { "READWRITE" })
MyApp.ExampleObjectSkeleton._property.animal = GObject.ParamSpecObject("animal", "animal", "animal", MyApp.ExampleAnimalSkeleton, { "READWRITE" })

function MyApp.ExampleAnimalSkeleton._property_set:mood(value)
	self.priv.mood = value
end

function MyApp.ExampleAnimalSkeleton:set_mood(value)
	self.mood = value
end

local vtable = Gio.DBusInterfaceVTable{
	method_call = function(...) print("method call", ...) end,
	get_property = function(...) print("get property", ...) end,
	set_property = function(...) print("set property", ...) end
}
function MyApp.ExampleAnimalSkeleton:do_get_vtable()
	print("get vtable 1 - WHY THE HECK IS THIS NEVER CALLED?")
end

local info = Gio.DBusInterfaceInfo {
	name = "org.gtk.GDBus.Example.ObjectManager.Cat",
	-- Lots of NULLs follow
}
function MyApp.ExampleAnimalSkeleton:do_get_info()
	return info
end

function MyApp.ExampleObjectSkeleton._property_set:animal(value)
	self.priv.animal = value
	-- Not sure if I got this right... In fact, I am almost sure I got this wrong
	self:add_interface(value)
end

function MyApp.ExampleObjectSkeleton:set_animal(value)
	self.animal = value
end

local function example_object_skeleton_new(path)
	return MyApp.ExampleObjectSkeleton{["g-object-path"] = path}
end

local function example_animal_skeleton_new()
	return MyApp.ExampleAnimalSkeleton()
end


local function on_animal_poke(animal, invocation, make_sad, make_happy, ...)
	print("on_animal_poke", animal, invocation, make_sad, make_happy)

	if (make_sad and make_happy) or (not make_sad and not make_happy) then
		invocation:return_dbus_error("org.gtk.GDBus.Examples.ObjectManager.Error.Failed", "Exactly one of make_sad or make_happy must be TRUE")
		return true -- The C code uses a constant for this, but I did not find this anywhere
	end

	if make_sad then
		if animal:get_mood() == "Sad" then
			invocation:return_dbus_error("org.gtk.GDBus.Examples.ObjectManager.Error.SadAnimalIsSad", "Sad animal is already sad")
			return true
		end
		animal:set_mood("Sad")
		animal:complete_poke(animal, invocation)
	elseif make_happy then
		if animal:get_mood() == "Happy" then
			invocation:return_dbus_error("org.gtk.GDBus.Examples.ObjectManager.Error.HappyAnimalIsHappy", "Happy animal is already happy")
			return true
		end
		animal:set_mood("Happy")
		animal:complete_poke(animal, invocation)
	end
	assert(0)
end

local function on_bus_acquired(conn, name, data)
	print("Acquired a message bus connection")

	manager = Gio.DBusObjectManagerServer.new("/example/Animals")
	for n = 0, 10 do
		local object = example_object_skeleton_new(string.format("/example/Animals/%03d", n))
		local animal = example_animal_skeleton_new()
		animal:set_mood("Happy")
		object:set_animal(animal)

		--[[ No thanks, let's skip this for now
		if n % 2 == 1 then
			local cat = example_cat_skeleton_new()
			object:set_cat(cat)
		end
		--]]

		-- FIXME: How to I add the handle-poke signal?!?
		--animal.on_handle_poke = on_animal_poke

		manager:export(object)
	end
	manager:set_connection(conn)
end


local function on_name_acquired(conn, name, data)
	print("Acquired the name", con, name, data)
end

local function on_name_lost(conn, name, data)
	print("Lost the name", con, name, data)
end


-- main

local loop = GLib.MainLoop.new(nil, false)
id = Gio.bus_own_name(Gio.BusType.SESSION,
	"org.gtk.GDBus.Examples.ObjectManager",
	Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT + Gio.BusNameOwnerFlags.REPLACE,
	GObject.Closure(on_bus_acquired), GObject.Closure(on_name_lost), GObject.Closure(loop), nil)

loop:run()
Gio.bus_unown_name(id)

print("end of main")

Perhaps the following code helps you? It shows how to use Gio without using DBusInterfaceSkeleton (but of course you then lose its automatic introspection capabilities): https://github.com/awesomeWM/awesome/blob/ebc9b99ae2817ddd95b5b9b2238881fcd5c777e0/lib/naughty/dbus.lua#L429-L430

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants