Open HumblePresent opened 2 years ago
...but that is what you are already trying to do.
Edit: Hm, dunno :shrug:
Hey @psychon, thanks for responding. I've looked into this a bit more and it looks like there is a method defined in lgi/override/GObject-Object.lua
called Object:_access_signal()
that will return a table with an emit()
method. This at least looks promising as far as being able to emit signals, but while this method is technically available through lgi it doesn't look like it's necessarily meant to be called directly. For one thing I'm not sure what all the arguments to this function are supposed to be as it is not documented. In particular the info
parameter seems to carry some description of the objects signals, but I'm not totally certain what needs to be in there. Do you have any idea if there is a more indirect or idiomatic way that this method is called?
I guess (based on git grep
) https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/component.lua#L165 and https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/component.lua#L238 do something with _access
. Time to look for the category "signal", I guess... According to this comment, it should be called from core: https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/component.lua#L154-L155
Perhaps this? This is just the __index
metamethod for objects. https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/object.c#L384-L386
If I get this right, __index
simply forwards to __access
...?
Hm... https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/component.lua#L211-L214 sounds like one should be able to do .signal_foo
to explicitly access foo
in the category
signal
.
I'll do some print
-debugging and see if one actually ends up in _access_signal
. I'll edit this comment with progress.
Okay, no edit, but a new post instead.
New insights: One has to call :emit
on the signal, but just calling it should have the same effect (which I only noticed now and not while investigating, whoops): https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/override/GObject-Object.lua#L333-L338
This code here seems wrong and causes the actual error we see (it tries to set up things for the signal's return value, a boolean): https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/override/GObject-Closure.lua#L235-L236
There is no type
property on Value
. It needs to be gtype
. But when changing this, I get an error when the signal is actually called:
(lua5.3:45690): Lgi-CRITICAL **: 09:49:23.301: attempt to steal record ownership from unowned rec
This error comes from here and I do not really understand what is going on: https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/record.c#L393
Thanks for doing some digging! So based on that match pattern when determining the category and name, one should be able to do something like obj._signal_name
in order to access the "name"
signal? I haven't had any luck doing that with the object I created this issue about but I might still be doing something wrong. Are you able to do something like require("gears.debug").dump(obj._signal_name)
without errors?
Oh, whoops, I totally forgot to post the test case that I came up with:
local dialog = require("lgi").Gtk.AboutDialog.new()
print(dialog._signal_on_activate_link)
dialog._signal_on_activate_link = function(...) print("signal was called", ...) return true end
print(dialog.on_activate_link.emit)
print("signal returns:", dialog.on_activate_link("foo"))
Which object/signal are you working with?
Are you able to do something like require("gears.debug").dump(obj._signal_name) without errors?
Nope, no gears
. ;-) However, this works fine:
local dialog = require("lgi").Gtk.AboutDialog.new()
for k, v in pairs(dialog._signal_on_activate_link) do
print(k, v)
end
Anyway, I do not really know what I am doing.
The following change turns the Lua error into a segfault (but at a later point) and the "attempt to steal" message from above:
diff --git a/lgi/override/GObject-Closure.lua b/lgi/override/GObject-Closure.lua
index 577f64f..dde56b0 100644
--- a/lgi/override/GObject-Closure.lua
+++ b/lgi/override/GObject-Closure.lua
@@ -231,9 +231,15 @@ function CallInfo:pre_call(...)
end
-- Prepare return value.
- local retval = Value()
- if self.ret then retval.type = self.ret.gtype end
- if self.phantom then retval.type = self.phantom.gtype end
+ local retvaltype
+ local retval
+ if self.ret then retvaltype = self.ret.gtype end
+ if self.phantom then retvaltype = self.phantom.gtype end
+ if retvaltype then
+ retval = Value(retvaltype)
+ else
+ retval = Value()
+ end
return retval, params, marshalling_params
end
I tried to work around that "attempt to steal" stuff, but yeah, this thing confuses me. To emit the signal, g_signal_emitv
is called: https://docs.gtk.org/gobject/func.signal_emitv.html
This function has a GValue*
as its last argument for the return value. This argument is marked (inout)
and I do not really understand the in
part: https://github.com/GNOME/glib/blob/a57c33fc1d657f8f9f788b9b042c6ded19bb3c59/gobject/gsignal.c#L3116
Due to this (inout)
, the parameter is also (transfer full)
:
Default Annotations: To avoid having the developers annotate everything the introspection framework is providing sane default annotation values for a couple of situations: [...] (inout) and (out) parameters: (transfer full) https://wiki.gnome.org/Projects/GObjectIntrospection/Annotations#Type_signature
I guess (but am not really sure), that something around this stuff is fishy. Hence my patch above. But apparently/obviously, I still got it wrong.
If only we had someone who understood this stuff... :-(
Haha sorry for the gears
reference, I just know you're an AwesomeWM guy :). I am trying to use the Wireplumber library to monitor audio sinks managed by Pipewire. There is a loadable plugin that facilitates querying for the current default node that requires emitting signals for use. The object is of type WpDefaultNodesApi
which inherits from Wp.Plugin. The actual plugin is undocumented but here is the source code. There are four primary signals that are used to interface with the plugin and they are all listed by
local GObject = require("lgi").GObject
for _, id in ipairs(GObject.signal_list_ids("WpDefaultNodesApi")) do
print(GObject.signal_query(id).signal_name)
end
as
get-default-node
get-default-configured-node-name
set-default-configured-node-name
changed
Unfortunately, after creating the object, trying to call
print(default_nodes._signal_on_get_default_node)
still produces the no `_signal_on_get_default_node`
error. There may be some GLib weirdness going on that lgi is not equipped to handle. I really appreciate your help on this though as the lgi internals are a bit harder to decipher.
Unfortunately, after creating the object,
How exactly are you creating the object? I didn't find any API documentation, but I also did not look at anything but module-default-nodes-api.c
.
Does the changed
signal work? It doesn't have a return type and it seems like the signal's return type is part of the problem here.
Edit: I just installed gir1.2-wp-0.4
. When I run the following:
local GObject = require("lgi").GObject
for _, id in ipairs(GObject.signal_list_ids("WpDefaultNodesApi")) do
print(GObject.signal_query(id).signal_name)
end
I only get:
(process:54335): GLib-GObject-CRITICAL **: 18:34:13.568: g_signal_list_ids: assertion 'G_TYPE_IS_INSTANTIATABLE (itype) || G_TYPE_IS_INTERFACE (itype)' failed
Okay, I do not understand wireplumber. I managed to get a segfault:
local lgi = require("lgi")
local Wp = lgi.Wp
Wp.init(0)
local core = Wp.Core.new(nil, nil)
[E] pw.context [pipewire.c:256 load_spa_handle()] load lib: plugin directory undefined, set SPA_PLUGIN_DIR
[E] pw.loop [loop.c:86 pw_loop_new()] 0x55f150b1a800: can't make support.system handle: No such file or directory
zsh: segmentation fault lua /tmp/wp.lua
Somehow I feel like the (nullable)
here are wrong: https://pipewire.pages.freedesktop.org/wireplumber/c_api/core_api.html#c.wp_core_new
Here is how I am creating the object
local Wp = require("lgi").Wp
Wp.init(Wp.InitFlags.ALL)
local core = Wp.Core()
core:connect()
core:load_component("libwireplumber-module-default-nodes-api", "module")
local default_nodes = Wp.Plugin.find(core, "default-nodes-api")
default_nodes:activate(Wp.Pluginfeatures.Enabled, nil, function(self, res)
if not self:activate_finish(res) then
print("FAILED to activate plugin: " .. self.name)
end
end)
Ah, the flags to init
are apparently the important difference.
Now :connect()
crashes after complaining about a missing client.config
file. :shrug:
Hmm, that sounds like something is not right with your pipewire installation maybe? I'm also running this code inside AwesomeWM's Lua runtime but that shouldn't matter I don't think.
I have no pipewire installation. ;-)
There is not even a pipewire daemon running and I think there should be. I only installed the GObject introspection files and libwireplumber in the hope that that would be enough to.... do something.
Ah that would explain it. From what I have read Wireplumber is a library/session manager that operates on top of the pipewire daemon so it might not work correctly without the daemon running.
I guess/think that the problem might be that there is no such signal in the GObject-Introspection data (10 is the depth to print; this data structure really is self-referential and recursive and thus infinite; e.g. the information about a method refers to the types of its arguments and from there one can get to the methods on that type and an endless loop is formed):
$ lua dump-typelib.lua 10 Wp | grep -4 default
is_pointer : false (boolean)
tag : guint32 (string)
is_basic : true (boolean)
deprecated : false (boolean)
log_writer_default : table: 0x556ee40ac900
type : function (string)
deprecated : false (boolean)
return_type : table: 0x556ee40accf0
type : type (string)
--
tag : interface (string)
is_basic : false (boolean)
deprecated : false (boolean)
flags : table: 0x556ee40b1a00
name : log_writer_default (string)
return_transfer : none (string)
namespace : Wp (string)
cats : table: 0x556ee40b20a0
args : table: 0x556ee40b2a20
However, the code really needs the list of signals in the GI data.
So, time to figure out how to emit a signal "the hard way"...
P.S.: Yes, I installed pipewire just for you
So, time to figure out how to emit a signal "the hard way"...
Argh. The answer really is "don't". Without information about the signals arguments and return type, everything is complicated. At least a bit.
local Wp = require("lgi").Wp
Wp.init(Wp.InitFlags.ALL)
local core = Wp.Core()
core:connect()
core:load_component("libwireplumber-module-default-nodes-api", "module")
local default_nodes = Wp.Plugin.find(core, "default-nodes-api")
print(default_nodes)
local GObject = require("lgi").GObject
local signal_id = GObject.signal_lookup("get-default-node", default_nodes._gtype)
print("Signal is", signal_id)
assert(signal_id ~= 0)
local Value = GObject.Value
local function emit_get_default_node(obj, arg)
local params = {
Value(obj._gtype, obj),
Value(GObject.Type.STRING, arg)
}
local retval = Value(GObject.Type.UINT, 0)
print("doing call")
--GObject.signal_emitv(params, signal_id, 0, retval)
foo = GObject.signal_emitv(params, signal_id, 0, nil)
print("done call")
foo:init(GObject.Type.UINT)
return retval.value
end
print(emit_get_default_node(default_nodes, "foo"))
print("after call")
collectgarbage("collect")
collectgarbage("collect")
collectgarbage("collect")
require("lgi").GLib.MainLoop():run()
The above does not crash. If one removes the start of a main loop at the end, everything crashes and burns when the object foo
is garbage collected. And I don't really understand why. g_signal_emitv
returns void
!?.
If one enables the commented out line to provide a place to save the retval
of the signal, things crash during signal emission.
So based on some C source code that uses the "default-nodes-api"
the "get-default-node"
signal takes one string argument that specifies a media class to query, something like "Audio/Sink"
. It returns a guint32
which is the ID of the default node. As far as g_signal_emitv()
returning void, it looks like a pointer to a GValue
is passed as a parameter to obtain any return value. It looks like the GObject.Closure.CallInfo
object is meant to help translate the C style arguments to lua.
https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/override/GObject-Closure.lua#L215-L219
Not sure exactly how to use this but this is how the code we were looking at before formats arguments for signal_emitv()
https://github.com/lgi-devs/lgi/blob/9eaad42eec50baa3e24f3420c35107d6d295577e/lgi/override/GObject-Object.lua#L303-L317
Yup, that code in pre_call
and emit_signal
is what I stared at when I wrote the above. I think the pre_call
just creates the array with GValue
instances that I created above.
A random guess would be that "return a value from a signal" is just always broken in lgi (and retval
in the code you linked to would be nil
in that case and possibly the whole issue disappears), but I am really not sure.
Do you happen to have some knowledge about e.g. the Gtk API? Is there some other signal returning a value? Possibly one where gobject-introspection information is available, so that one could "just" use LGI without much extra magic? I would expect that signal to cause the same kind of problems, but it would still feel like a confirmation...
Yeah I have wondered if there are other API's that have return values from signals that we could try to use. I'm not familiar with any off the top of my head but I'll let you know if I find any.
Hello, I was trying to do this exact thing (interfacing with wireplumber from awesome) and this thread was an interesting read - especially at the point you had the same code I had, I spent some time to achieve that before finding this :)
The only thing I can add is that I simply did this
local wtf = GObject.signal_emitv(params, signal_id, 0, retval)
getmetatable(wtf).__gc = nil
to avoid those free()
errors (not sure if we're leaking memory here tho), now the only hard roadblock is the attempt to steal record ownership from unowned rec
followed by a segfault.
The other thing is that using mixer
and default-nodes
plugins is a convenience and technically it should be possible to reimplement them in lua with lgi - without having to emit signals that return a value - although this promises to be a huge pita ¯\_(ツ)_/¯
edit: Well, the main thing you need to interface with it directly (instead of the cringe path of calling shell commands) for is the feedback whenever something else changes the volume so you can update the widget without doing polling - and this kind of works, yay:
Wp.Plugin.find(core, 'default-nodes-api'):activate(Wp.PluginFeatures.ENABLED, nil, function(self, res)
if not self:activate_finish(res) then
print("FAILED to activate plugin: " .. self.name)
end
GObject.signal_connect_closure(self, 'changed', GObject.Closure(function()
-- here we don't get anything besided the actual event of the default sink changing
print('default_nodes changed!')
end), true)
end)
Wp.Plugin.find(core, 'mixer-api'):activate(Wp.PluginFeatures.ENABLED, nil, function(self, res)
if not self:activate_finish(res) then
print("FAILED to activate plugin: " .. self.name)
end
GObject.signal_connect_closure(self, 'changed', GObject.Closure(function(...)
print('mixer changed!', ...) -- we even get the id of the node that changed here
end), true)
end)
Thanks for looking into this a bit more. I did consider reimplementing the mixer and default-nodes plugins in lua but it looked complicated based on their C source code and I worried that having to process all that info within the context of the Awesome main loop would slow things down or cause lag.
nah I actually tried and got stuck at one point since they really like varargs that are uncallable (it seems) with lgi
The only thing, I can kind of super-hacky extract is the value of mute by node-id that you get in mixer.changed
callback.
And since there is no wpctl get-mute
I am actually using this)
tl;dr; of this entire issue - it consists of two parts:
Wp
does not have plugin signals in the typelib (you can see that in the gir) so the nice lgi syntax for signal handling does not workinout
which lgi does not seem to handle properlyActually one more thing I also just tried is this
local retval = require 'lgi.core' .record.new(Value, nil, 1, true)
retval.gtype = GObject.Type.UINT
which makes a GValue that does not log the attempt to steal record ownership from unowned rec
thing - but it still just sigservs inside signal_emitv
¯\_(ツ)_/¯
Some possible, untested, paths forward
Keep in mind you can use C code modules in Awesome. The drawback if that now you get to compile part of your config, which makes it hard to redistribute. It also let you do real multi threading, so that's a win if there are worries that it will slow down Awesome too much. Also note that if it's done (partially) in C and is actually just a normal native Lua module, I would consider merging it in Awesome itself. Sound support has been a really frequent feature request over the years. Since PipeWire/WirePlumber finally seem like an actual long term solution, making it an optional dependency and give users what they want outweigh the portability and out-of-scope concerns IMHO.That would solve the issue of making configs using it hard to distribute.
According to this link, callable.c
would need to ffi_prep_cif_var
, but according to gobject introspection doc, it doesn't seem it's something that supported well. But GI newest release seems to have some improvements toward this. This also means it's too new for this scanner to be in most distros, then really, really too new to actually make those types usable. So that's a dead end.
Implementing the whole function using Raw LibFFI code is also an option, but that works only for luajit or with the 3rd party libffi module for Rio Lua.
I agree, implementing some of this functionality in C is the only real way forward right now.
Interestingly, WirePlumber actually has a Lua API based on GI that abstracts some of the lower level details of the library and has an easy to use interface for loading plugins and emitting signals. Unfortunately, it can only be used from sandboxed environment with special behavior and a subset of the standard libraries. Still, the C code that the Lua API is based on could be a good starting point for creating a standalone Lua module. I myself have not delved into the Lua library C API but could be interesting.
As far as redistribution could it become something like this library that uses LuaRocks for distribution?
Does LGI support emitting existing signals on an object? I have an object type that has two signals associated with it according to the result of
GObject.signal_list_ids()
, but I do not know how to emit those signals on an instance of the object. I have tried callingobj:on_<signal>()
but just get an error sayingno 'on_<signal>'
.