-
Notifications
You must be signed in to change notification settings - Fork 71
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
Memory management for GObject signals #334
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
-- Demo modified from https://github.com/lgi-devs/lgi/issues/333 - the stated issues should be resolved. | ||
|
||
-- hack to require lgi from this directory before /usr/ or wherever windows installs it. | ||
local sep = package.path:match("[/\\]") | ||
package.path = ("./?.lua;./?/init.lua;"):gsub("/", sep) .. package.path | ||
|
||
local lgi, lgi_path = require("lgi") | ||
|
||
-- check the hack worked | ||
assert(lgi_path:match("%.[/\\]lgi.lua"), "Loaded wrong LGI!") | ||
|
||
local Gtk = lgi.require("Gtk", "3.0") | ||
|
||
local button_1 = Gtk.Button.new_with_label("I can be clicked multiple times, and now handlers are overwritten") | ||
|
||
---@diagnostic disable-next-line:duplicate-set-field | ||
button_1.on_clicked = function() | ||
print("Button 2 Click handler 1!") | ||
end | ||
---@diagnostic disable-next-line:duplicate-set-field | ||
button_1.on_clicked = function() | ||
print("Button 2 Click handler 2!") | ||
end | ||
|
||
-- This button demonstrates the difficulty in disconnecting a signal: | ||
local button_2 = Gtk.Button.new_with_label("I can be clicked once!") | ||
|
||
local handler_id | ||
-- using widget.<signal>:connect from https://github.com/lgi-devs/lgi/blob/master/docs/guide.md#341-connecting-signals | ||
handler_id = button_2.on_clicked:connect(function(widget) | ||
print("Button 1 Clicked!") | ||
widget:set_label("I can no longer be clicked!") | ||
|
||
-- Now I can disconnect nicely! | ||
button_2.on_clicked:disconnect(handler_id) | ||
end) | ||
|
||
button_2.on_notify["has-focus"]:connect(function (...) | ||
print("has-focus", ...) | ||
end) | ||
|
||
local window = Gtk.Window { | ||
title = "GitHub PR demo", | ||
default_width = 400, | ||
default_height = 300, | ||
on_destroy = Gtk.main_quit, | ||
child = Gtk.VBox { | ||
button_1, | ||
button_2 | ||
}, | ||
} | ||
|
||
window:present() | ||
window:show_all() | ||
|
||
Gtk.main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -259,7 +259,7 @@ local function marshal_property(obj, name, flags, gtype, marshaller, ...) | |
local mode = select('#', ...) > 0 and 'WRITABLE' or 'READABLE' | ||
if not flags[mode] then | ||
error(("%s: `%s' not %s"):format(core.object.query(obj, 'repo')._name, | ||
name, core.downcase(mode))) | ||
name, core.downcase(mode))) | ||
end | ||
local value = Value(gtype) | ||
if mode == 'WRITABLE' then | ||
|
@@ -292,6 +292,7 @@ end | |
local quark_from_string = repo.GLib.quark_from_string | ||
local signal_lookup = repo.GObject.signal_lookup | ||
local signal_connect_closure_by_id = repo.GObject.signal_connect_closure_by_id | ||
local signal_handler_disconnect = repo.GObject.signal_handler_disconnect | ||
local signal_emitv = repo.GObject.signal_emitv | ||
-- Connects signal to specified object instance. | ||
local function connect_signal(obj, gtype, name, closure, detail, after) | ||
|
@@ -319,9 +320,19 @@ end | |
-- Signal accessor. | ||
function Object:_access_signal(object, info, ...) | ||
local gtype = self._gtype | ||
|
||
-- faster, as every branch indexes info.name more than once | ||
local name = info.name | ||
|
||
if select('#', ...) > 0 then | ||
local existing_handler = self._signal_handler[name] | ||
|
||
if existing_handler then | ||
signal_handler_disconnect(object, existing_handler) | ||
end | ||
|
||
-- Assignment means 'connect signal without detail'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dunno what this comment refers to exactly, but whatever "connect signal with detail" is, does it provide a way to disconnect? Is that what the code below does / adds / implements? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That comment is original to the LGI codebase, not my fork. GObject signal documentation is a bit fuzzy with detailed signals, but one example I found from Stackoverflow is the notify signal. I realize now I misformatted my detailed notification string - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did some more research, looks like the detailed signal implementation LGI provides is less polished than the other signal handlers. This code, inserted into button_2.on_notify["has-focus"] = function ()
print("button 2 focus state changed")
end In my opinion, the pad metatable should allow |
||
connect_signal(object, gtype, info.name, Closure((...), info)) | ||
self._signal_handler[name] = connect_signal(object, gtype, name, Closure((...), info)) | ||
else | ||
-- Reading yields table with signal operations. | ||
local mt = {} | ||
|
@@ -330,6 +341,11 @@ function Object:_access_signal(object, info, ...) | |
return connect_signal(object, gtype, info.name, | ||
Closure(target, info), detail, after) | ||
end | ||
|
||
function pad:disconnect(handler_id) | ||
return signal_handler_disconnect(object, handler_id) | ||
end | ||
|
||
function pad:emit(...) | ||
return emit_signal(object, gtype, info, nil, ...) | ||
end | ||
|
@@ -345,8 +361,27 @@ function Object:_access_signal(object, info, ...) | |
return emit_signal(object, gtype, info, detail, ...) | ||
end | ||
function mt:__newindex(detail, target) | ||
connect_signal(object, gtype, info.name, Closure(target, info), | ||
detail) | ||
local name_with_detail = name .. "::" .. detail | ||
local existing_handler = self._signal_handler[name_with_detail] | ||
if existing_handler then | ||
signal_handler_disconnect(object, existing_handler) | ||
end | ||
|
||
self._signal_handler[name_with_detail] = connect_signal(object, gtype, name, Closure(target, info), | ||
detail) | ||
end | ||
|
||
function mt.__index(_, detail) | ||
local sub_pad = {} | ||
|
||
function sub_pad:connect(target, after) | ||
return connect_signal(object, gtype, name, | ||
Closure(target, info), detail, after) | ||
end | ||
|
||
sub_pad.disconnect = pad.disconnect | ||
|
||
return sub_pad | ||
end | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think I like that. There might very well be reasons to have more than one signal handler connected to the same signal. (And yes, I know that this is kinda weird that an assignment only adds but does not override, but that wasn't me 🤷.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but that's why signal handler pads are useful.
GObject.on_<signal>:connect()
allows us to connect multiple handlers, whereas my fork only allows a single handler at a time to be attached via the assignment operator. This, in my opinion is the best of both worlds.