I'm new to gjs
and gtk
, so this may be very basic, but I did not find any other question regarding this.
While trying to understand the gjs bindings of gtk and gdk, I wanted to write a small script that would log the dimensions of the monitor where a window would open, so I came up with the following one:
import Gtk from 'gi://Gtk?version=4.0'
import Gdk from 'gi://Gdk?version=4.0'
// Gtk needs to be initialized to get a non-null default display, I think
Gtk.init()
const {Display, Surface} = Gdk
const display = Display.get_default()
const surface = Surface.new_toplevel(display)
const {geometry: {width, height}} = display.get_monitor_at_surface(surface)
console.log(`Width: ${width}`)
console.log(`Height: ${height}`)
surface.destroy()
While this script exits successfully (status code 0), it logs something like the following line:
(gjs:12345): Gjs-CRITICAL **: 00:00:00.000: Object 0x112233445566 of type GdkWaylandToplevel has been finalized while it was still owned by gjs, this is due to invalid memory management.
However, the following experiments left me confused as to how to appropriately modify this script:
- Removing the
surface.destroy()
line logs a warning sayinglosing last reference to undestroyed surface
which seems fair since that surface was not destroyed. - Appending a line with
console.log('appended')
logs that message, so destroying the surface did not throw any error (I tested this before ensuring that the status code of the original script was 0); however, I don't know if something else may trigger an error (like running the GLib main loop).
What would be the appropriate way to manage the memory in this script?
Edit:
While integrating the aquired dimensions to run a nested wayland session with the appropriate size, I realized that the critical message is logged when the script finished on its own but not when it is aborted via Ctrl+C. A minimal working example that does not log any irrelevant messages is:
import Gtk from 'gi://Gtk?version=4.0'
import Gdk from 'gi://Gdk?version=4.0'
import Gio from 'gi://Gio'
Gtk.init()
const {Display, Surface} = Gdk
const {Subprocess, SubprocessFlags: {SEARCH_PATH_FROM_ENVP}} = Gio
Gio._promisify(Subprocess.prototype, 'wait_async', 'wait_finish')
const display = Display.get_default()
const surface = Surface.new_toplevel(display)
const {geometry: {width, height}} = display.get_monitor_at_surface(surface)
surface.destroy()
await Subprocess.new(
['sleep', '5'],
SEARCH_PATH_FROM_ENVP
).wait_async(null)
As explained a bit earlier, if we wait 5 secends after running this script, the script finishes on its own and logs the critical message; however, if we press Ctrl+C before that, it aborts and that message is never logged.
This workaround relies on a bug that is already patched and will be released in GNOME 46; please, read the update.
Executing
surface.ref()
before destroying the surface got rid of the message.I still don't understand why, but as a suggestion from @andy.holmes, I opened an issue on the
gjs
project. I'll update this answer with the relevant feedback I get there.Updates:
The general rule of thumb is not to
destroy
objects unless there is a reason to do so documented in the official documentation since reference counting should take care of releasing resources in most cases. Moreover, if for some reason, thedestroy
method is used or expected to be used somewhere, all holders of this reference should listen for itsdestroy
signal and release such reference, which is an unneeded complication most of the time, as explained by @andy.holmes here.The behaviour described earlier was due to a bug addressed in issue 592 of gjs, where an unnecessary warning was issued if the
Gdk.Surface
lost all references without been destroyed. Further information regarding the bug can be found there.