gtkmm 4 progress

gtkmm 4 is alive

We (mostly Kjell Ahlstedt and myself) have been quietly working away on gtkmm 4 and an associated ABI-breaking version of glibmm. We’ve been tracking GTK+ 4 from git master, making sure that gtkmm builds against it, and making various API-breaking or ABI-breaking changes that had been left in the code as TODO comments waiting for a chance like this.

This includes simple ABI-breaking (but not API-breaking) stuff such as adding a base classes to widget esor changing the type sof a method parameters. Many other changes are about updating the code to be more modern C++, trying to do things the right way and avoiding duplication with new API in the C++ Standard library.

These changes pleases us as purists but, honestly, as an application developer it isn’t going to give you interesting new behaviour in your applications. If you too are enthusiastic about the C++ renaissance, porting to the new APIs might feel rewarding and correct, and you’ll have to do it someday anyway to get whatever new features arrive later, but I cannot claim there is a more compelling reason to do the work. You might get shiny new features via GTK+ 4, but so far it feels like a similar exercise in internal cleanup and removal of unloved API.

There are still people stuck on GTK+ 2, because most people only port code when they need to. I don’t think the transition to GTK+ 4 or gtkmm 4 will be any faster. Certainly not until the GTK+ 4 porting advice gets a lots better. Porting gtkmm and glom to GTK+ 4 has not been fun, particularly as the trend for API changes without explanation has only increased.

Anyway, here are some of the more significant changes so far in glibmm-2.54 (a horrible ABI name, but there are reasons) and gtkmm-4.0, both still thoroughly unstable and subject to API change:

(Lots of this is currently on in git master but will be in tarball releases soonish, when there are glib and gtk+ releases we can depend on.)

Deprecated API is gone

Anything that was deprecated in gtkmm 3 (or glibmm-2.4) is now completely removed from gtkmm 4 (or glibmm-2.54). You’ll want to build your application with gtkmm 3 with all deprecated API disabled before attempting to build against gtkmm 4.

In some cases this included deprecated base classes that we couldn’t let you optionally disable without breaking ABI, but now they are really really gone.

This is a perfect example of API changes that make us feel better but which are really not of much direct benefit to you as an application developer. It’s for your own good, really.

Glib::RefPtr is now std::shared_ptr

In gtkmm 3, Glib::RefPtr<> is a reference-counting smart pointer, mostly used to hide the manual GObject reference-counting that’s needed in C. It worked well, but C++11 introduced std::shared_ptr so we saw a chance to delete some code and  make our API even more “standard”. So, Glib::RefPtr is now just an alias for std::shared_ptr and we will probably change our APIs to use std::shared_ptr instead of Glib::RefPtr.

Glib::RefPtr is an intrusive smart pointer, meaning that it needs, and uses, a reference count in the underlying object rather than in the smartpointer itself. But std::shared_ptr is non-intrusive. So we now just take one reference when we instantiate the std::shared_ptr and release that one reference when the last std::shared_ptr is destroyed, via its Deleter callback. That means we always need to instantiate the std::shared_ptr via Glib::make_refptr_for_instance(), but we hide that inside glibmm/gtkmm code, and application developers would rarely need to do this anyway.

std::shared_ptr<> is a familiar type, so using it in our API should make it easier for the average person to reason about our API. And using it makes us part of the wider ongoing conversation in the C++ community about how to indicate and enforce ownership. For instance, we might start taking Thing* parameters instead of std::shared_ptr<Thing> parameters when we know that the called method will not need to share ownership. I mentioned this idea in an earlier post. However, we often cannot assume much about what the underlying C function really does.

This is a big change. Hopefully it will work.

Now uses libsigc++-3.0 instead of libsigc++-2.0

I rewrote libsigc++ for modern C++, using variadic templates and some other modern C++ techniques. It feels like libsigc++-2.0, but the code is much simpler. Compiler errors might be slightly less cryptic. This requires C++14.

Enums are inside related classes

Enums that were only used with a particular class are now inside that class. For instance, Gio::ApplicationFlags is now Gio::Application::Flags. This makes the API slightly clearer. This didn’t need C++11, but it did need an API break.

Enums are now C++11 enum classes

Enums are now declared as “enum class” (scoped enumerations) instead of “enum” (unscoped enumerations), so they cannot be implicitly converted to other types such as bool or int. That lets the compiler find some subtle programmer errors.

The enum values must now be prefixed by the enum name rather than having a prefix as part of the value name. For instance, we now use Gio::Application::Flags::HANDLES_OPEN instead of Gio::Application::FLAGS_HANDLES_OPEN (actually Gio::APPLICATION_FLAGS_HANDLES_OPEN before we put enums inside classes).

Gtk::TreeView and Gtk::TextView now have real const_iterators

This lets us make the API more const-correct, requiring less arbitrary const_casts<>s in application code.

Removed the old intermediate ListHandle/SListHandle/ArrayHandle/SArrayHandle types

Long ago, the gtkmm API used these intermediate types, wrapping glib types such as GList, GSList, and C arrays, so you didn’t need to choose between using a std::list, std::vector, or other standard container. Since gtkmm 3 we decided that this was more trouble than it was worth, and decided to just uses std::vector everywhere, but it’s only now that we’ve been able to remove them from the glibmm, pangomm, and atkmm APIs.

A possible change: Not using Glib::ustring

We are still considering whether to replace uses of Glib::ustring in our API with std::string, maybe just keeping Glib::ustring for when people really want to manipulate UTF-8 “characters”. I would much prefer standard C++ to have real UTF-8 support, for instance letting us step through and insert characters in a UTF-8 string, but that doesn’t look like it will happen in the foreseeable future.

Glib::ustring still wraps useful UTF-8 APIs in glib, in a std::string-like API, so we wouldn’t want to remove it.

Also, currently it’s useful to know that, for instance, a gtkmm method that returns a Glib::ustring is giving us a UTF-8 string (such as Gtk::FileChooser::get_current_name()), rather than something of unknown encoding (such as Gtk::FileChooser::get_filename()). We allow implicit conversions, for convenience, so we can’t use the compiler to check for awareness of these encoding differences, but having it in the method signature still feels nicer than having to read a method’s documentation.

12 thoughts on “gtkmm 4 progress

    1. I don’ t think the gtkmm4 port of Glom is worth your time right now:

      It only just works. I’ve had to disable its use of GimpRuler (I copied its code from the Gimp GTK+3 port) and gtksourceview because there are no GTK+4 versions of those. It also needs this avahi-ui port for GTK+4, which doesn’t look like it will be accepted anytime soon:

      I also need EggSpreadTable to be ported properly to the new GTK+4 width/height measure API:
      At the moment it has an endless loop in its measure vfunc implementation, which I hack out to get Glom running.

      So, it’s generally no fun.

      However, we are keeping gtkmm-documentation examples up to date and they seem to work:

  1. Thanks for your continuous work on Gtkmm :)
    I’ve just completed my first production application with Gtkmm3 and the remaing things are only polishing and some questions.

  2. Regarding the change to use shared_ptr and *not* using the reference counting mechanism of the underlying object: this is going to cause for everything in GStreamer that is based on GstMiniObject (i.e. not based on GObject). For those objects the underlying object’s reference count is used to decide at runtime if something is the sole owner of an object (refcount==1) or not, and to implement a copy-on-write mechanism based on that.

    I think generally it’s suboptimal if you have two layers of reference counting.

    1. Thaks for the reminder.

      I think, but I’m not sure, that it’s already causing problem in the existing gstreamermm anyway. It’s an odd, presumably unreliable, way to decide whether something is writeable, and I had hoped that was going to be improved sometime in gstreamer anyway.

      1. Well, it’s reliable if reference counting is used correctly. You increase the reference count (only) whenever there is a new (shared) owner of the object, not e.g. when the object is borrowed by a function for the scope of the function.

        I agree that it’s a bit suboptimal or tricky for bindings (if you don’t really have control over these aspects in the target language), but in the case of C++ and Rust this should be very well manageable (in case of the latter it works well, the standard library equivalent of shared_ptr even has the same copy-on-write mechanism as GStreamer). Also other libraries are doing things in similar ways, ffmpeg for example.

        1. If the gstreamermm developers wanted to, when they choose to target the new glibmm, they can have their own RefPtr, even if that’s a copy of the old RefPtr. For instance, cairomm has its own Cairo::RefPtr in the current stable API. They would then just return instances of that RefPtr instead of std::shared_ptr.

          But I don’t think the problem is solved in gstreamermm now anyway with the current RefPtr. This is just a very weird thing for gstreamer to do. But I haven’t paid much attention to the gstreamermm code for a long time and I can’t find a link to whatever discussions/bugs I last saw about this problem, so I could be completely wrong.

  3. I think the change to using shared_ptr is a big mistake.
    Replacing Glib container wrappers by std::vector sounds like a bad idea too.

  4. Thanks for your great work! I agree it’s a pity that utf8 manipulation is dropped in c++17’s STL. Despite this, do you know whether there are plans for libxml++ to use std::string instead of Glib::ustring? The implementation (I’m guessing) could use std::filesystem instead of Glib so that it would be possible to get rid of the Glib dependency altogether? While I love the whole gtkmm stack I’d like to use xml++ for low level “lightweight” cross platform stuff without sucking in the whole glibmm.

  5. > I agree it’s a pity that utf8 manipulation is dropped in c++17’s STL.

    I don’t think it has been dropped. It was never there, and I don’t think it was ever seriously proposed.

    > Despite this, do you know whether there are plans for libxml++ to use std::string instead of Glib::ustring?

    I am indeed considering this, though I still feel uneasy about presenting a single-byte-per-character API to what is always UTF-8 data, and I like how the current API makes it completely clear that you are getting UTF-8 regardless of the underlying XML document’s encoding. But the current, and likely future, state of C++, suggests that this is acceptable to people. It’s also nice that C++ move operations mean that constructing ustrings from strings will not be costly.

    > The implementation (I’m guessing) could use std::filesystem instead of Glib so that it would be possible to get rid of the Glib dependency altogether?

    libxml++ doesn’t use glib’s file or IO APIs. It just wraps libxml, which also doesn’t depend on glib.

    1. > I don’t think it has been dropped. It was never there, and I don’t think it was ever seriously proposed.

      I was referring to being dropped, it allowed to convert to std::u32string and access the unicode codepoints (an awfully inefficient way to do it).

      > I still feel uneasy about presenting a single-byte-per-character API to what is always UTF-8 data

      Maybe something like namespace xmlpp { using utf8string = std::string; }

      Anyway keep up the good work.

Leave a Reply

Your email address will not be published. Required fields are marked *