-
Notifications
You must be signed in to change notification settings - Fork 69
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
How to emit signals on an object #281
Comments
...but that is what you are already trying to do. Edit: Hm, dunno 🤷 |
Hey @psychon, thanks for responding. I've looked into this a bit more and it looks like there is a method defined in |
I guess (based on Line 165 in 9eaad42
Line 238 in 9eaad42
_access . Time to look for the category "signal", I guess... According to this comment, it should be called from core: Lines 154 to 155 in 9eaad42
Perhaps this? This is just the Lines 384 to 386 in 9eaad42
If I get this right, __index simply forwards to __access ...?
Hm... Lines 211 to 214 in 9eaad42
.signal_foo to explicitly access foo in the category signal .
I'll do some |
Okay, no edit, but a new post instead. New insights: One has to call lgi/lgi/override/GObject-Object.lua Lines 333 to 338 in 9eaad42
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): lgi/lgi/override/GObject-Closure.lua Lines 235 to 236 in 9eaad42
There is no
This error comes from here and I do not really understand what is going on: Line 393 in 9eaad42
|
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 |
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?
Nope, no 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, This function has a Due to this
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 local GObject = require("lgi").GObject
for _, id in ipairs(GObject.signal_list_ids("WpDefaultNodesApi")) do
print(GObject.signal_query(id).signal_name)
end as
Unfortunately, after creating the object, trying to call print(default_nodes._signal_on_get_default_node) still produces the |
How exactly are you creating the object? I didn't find any API documentation, but I also did not look at anything but Does the Edit: I just installed 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:
|
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)
Somehow I feel like the |
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 Now |
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):
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 |
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 If one enables the commented out line to provide a place to save the |
So based on some C source code that uses the lgi/lgi/override/GObject-Closure.lua Lines 215 to 219 in 9eaad42
Not sure exactly how to use this but this is how the code we were looking at before formats arguments for signal_emitv() lgi/lgi/override/GObject-Object.lua Lines 303 to 317 in 9eaad42
|
Yup, that code in A random guess would be that "return a value from a signal" is just always broken in lgi (and 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? |
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 The other thing is that using edit: 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 hack.lualocal object_manager = Wp.ObjectManager.new(core)
object_manager:add_interest_full(Wp.ObjectInterest.new_type(Wp.Node))
object_manager:request_object_features(Wp.GlobalProxy,
Wp.ProxyFeatures.PROXY_FEATURE_BOUND |
Wp.ProxyFeatures.PIPEWIRE_OBJECT_FEATURE_INFO |
Wp.ProxyFeatures.PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS
)
core:install_object_manager(object_manager)
local objects = {}
-- here the signal syntax works totally fine as those signals are present in the typelib, eh
function object_manager.on_object_added(_, object)
objects[object:get_properties():get('object.id')] = object
end
function object_manager.on_object_removed(_, object)
objects[object:get_properties():get('object.id')] = nil
end
--- snip: following code is when we have an activated mixer plugin instance
GObject.signal_connect_closure(mixer, 'changed', GObject.Closure(function(_, node)
local object = objects[tostring(math.floor(node))]
print('process changed their volume/mute:', object:get_properties():get('application.process.id'))
Gio.Async.start(function()
local mute = nil
-- the enum_params(_sync) does not work, no idea why
-- it says 'returns null when nothing was cached'
-- but we more or less repeat the mixer C code here 🤷
-- and C calls the sync variant and it works fine
local prop_iter = object:async_enum_params('Props')
-- so we get the first (and only) prop object and iterate over its 'fields'
-- in C code they iterate but we have no way of knowing
-- if the boolean in the prop object is 'mute' so we just grab
-- the first-first one (instead of the first 'mute' one like C does)
local iter = prop_iter:next().value:new_iterator()
local item = iter:next()
while item do
local b = item.value:get_boolean()
if b ~= nil then -- just the first boolean, as I've said
mute = b
break
end
item = iter:next()
end
-- to get something BY NAME they ONLY have a vararg getter which gets multiple things
-- guess it looks fine for the C caller, but we cannot use that sadly
print('mute', mute)
end)()
end), true) And since there is no tl;dr; of this entire issue - it consists of two parts:
Actually 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 |
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, 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>'
.The text was updated successfully, but these errors were encountered: