Enabling Python support in Libpeas

Libpeas has been for a long time one of the most used libraries to implement plugins in GNOME applications. These are applications such as Gedit, GNOME Videos, GNOME Builder and others. Libpeas’ README states:

Adding support for libpeas-enabled plugins in your own application is a matter
of minutes.

However this has not been the case in Python because there is a problem. Although, currently it is possible to write Python-based plugins like many applications currently do like Gedit that has its Python Console, it is not possible to implement plugin systems for applications. The source of the problem resides in the Libpeas’ function peas_extension_set_newv, that receives an array of GParameter structures. But GParameter is not instropectible! Due to this problem, there was a large discussion in bugzilla. After some discussions, Emmanuele Bassi say that GParameter should be deprecated and adding a new function with the following prototype:

  gpointer g_object_newv2 (GType gtype,
                           guint n_properties,
                           const char *names[],
                           const GValue values[]);

Some months ago, I decided to implement Emmanuele Bassi’s suggestion and finally the patches were merged in master branch, and the function mentioned about was added but with the name g_object_new_with_properties. After that, the same idea could be implemented in libpeas. I have written a new function called peas_extension_set_new_with_properties and peas_engine_create_with_properties. The patch was proposed before but Garret Regier told me to do some checking and tests. So the new patch is just pending of review and it means that it will be possible to do the following:

extension_set = Peas.ExtensionSet.new_with_properties(engine, Peas.Activatable, ["object"], [a_gobject])

I have tried this function and it works. I have a very simple example that shows how to use Libpeas in Python with this patch. I think that this function will be really useful to all the GNOME community. I think that the function internally should use g_object_new_with_properties but for the while after talking with Garrett Regier (mantainer of Libpeas), peas_extension_set_with_properties will use GParameter internally for the while.

You have a very simple example of how to implement a Plugin System using Libpeas in Python in my github repository. I hope you can soon have this functionality in master.

 

The GInterface problem

According Libpeas’ README:

One of the most frustrating limitations of the Gedit plugins engine was that it
only allows extending a single class, called GeditPlugin. With libpeas, this
limitation vanishes, and the application writer is now able to provide a set of
GInterfaces the plugin writer will be able to implement as his plugin requires.

The problem is that although PyGObject allows to import interfaces from libraries written in C like Peas.Activatable, it is not possible to define new interfaces. So we will be limited to use only Peas.Activatable and thus probably extending the application by accessing directly to the GApplication.

I have been investigating the problem in PyGObject but I was told to stop, probably because it may take too much time. I have been reading PyGObject’s source code and I have an idea of how to solve this problem. I think that the solution is to add a metaclass to GObject.GInterface and as soon as a new interface is going to be defined, a new GType and a new GInterfaceInfo should be registered. However GObject.GInterface is actually written in C. I didn’t have idea to be honest how to do that in C, but I knew I could get to a solution by investingating. I was investigating and I knew that the solution was to add a metaclass but I couldn’t find too much information about that. So I asked in Python IRC channel. According Ned Batchelder (nedbat),  I was doing “something very very esoteric”, but after some discussions I had an idea. I could finally add a metaclass to GObject.GInterface, so I think I am in the right way, but I know it will take time. So I will not give too much importance to it as I was told by my mentor and Pitivi mantainers. So Pitivi will have only extension sets implementing just Peas.Activatable for the while.

The case of GNOME Builder

I think that a clear example of the use of GInterface to add extension points is GNOME Builder. Gedit is also a good example, but they not have many extension points as GNOME Builder do. I have been reading the source code of GNOME Builder. They define multiple interfaces:

[cfoch@localhost gnome-builder]$ find -name *-addin.c  | head -10
./libide/buildconfig/ide-buildconfig-pipeline-addin.c
./libide/editor/ide-editor-workbench-addin.c
./libide/editor/ide-editor-view-addin.c
./libide/editor/ide-editor-layout-stack-addin.c
./libide/buildsystem/ide-build-pipeline-addin.c
./libide/workbench/ide-workbench-addin.c
./libide/workbench/ide-layout-stack-addin.c
./libide/preferences/ide-preferences-addin.c
./libide/buildui/ide-build-workbench-addin.c
./libide/genesis/ide-genesis-addin.c

For example, you can see that IdeWorkbenchAddin defines the following vfuncs:

struct _IdeWorkbenchAddinInterface
{
  GTypeInterface parent;

  gchar    *(*get_id)          (IdeWorkbenchAddin      *self);
  void      (*load)            (IdeWorkbenchAddin      *self,
                                IdeWorkbench           *workbench);
  void      (*unload)          (IdeWorkbenchAddin      *self,
                                IdeWorkbench           *workbench);
  gboolean  (*can_open)        (IdeWorkbenchAddin      *self,
                                IdeUri                 *uri,
                                const gchar            *content_type,
                                gint                   *priority);
  void      (*open_async)      (IdeWorkbenchAddin      *self,
                                IdeUri                 *uri,
                                const gchar            *content_type,
                                IdeWorkbenchOpenFlags   flags,
                                GCancellable           *cancellable,
                                GAsyncReadyCallback     callback,
                                gpointer                user_data);
  gboolean  (*open_finish)     (IdeWorkbenchAddin      *self,
                                GAsyncResult           *result,
                                GError                **error);
  void      (*perspective_set) (IdeWorkbenchAddin      *self,
                                IdePerspective         *perspective);
};

Having an interface like this one avoids to expose everything by accessing directly from the main application. In GNOME Builder, in libide/workbench/ide-workbench.c a extension set is created so all extensions implementing this interface can do what they are ordered to do in this file by calling the methods of the IdeWorkbenchAddinInterface:

  self->addins = peas_extension_set_new (peas_engine_get_default (),
                                         IDE_TYPE_WORKBENCH_ADDIN,
                                         NULL);

For example, to set the perspective. Different plugins may have different ways to set the perspective.

  if (self->addins != NULL)
    peas_extension_set_foreach (self->addins,
                                ide_workbench_notify_perspective_set,
                                perspective);

And the plugins that implement these interfaces doesn’t need to know of other types (like the application). They just care about the perspective (and other objects that can be passed as arguments to its virtual functions).

static void
ide_workbench_notify_perspective_set (PeasExtensionSet *set,
                                      PeasPluginInfo   *plugin_info,
                                      PeasExtension    *exten,
                                      gpointer          user_data)
{
  IdeWorkbenchAddin *addin = (IdeWorkbenchAddin *)exten;
  IdePerspective *perspective = user_data;

  g_assert (PEAS_IS_EXTENSION_SET (set));
  g_assert (plugin_info != NULL);
  g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
  g_assert (IDE_IS_PERSPECTIVE (perspective));

  ide_workbench_addin_perspective_set (addin, perspective);
}

 

My story until GSoC 2017

It’s almost 9 years I am using GNU/Linux as my default Operating System. The first time I used GNU/Linux was in 2008, when I wanted to crack the password of the computers of my school in order to install and play the games I wanted. So my first contact with GNU/Linux was through OPHCrack. I was absolutely impressed when I could get the password of the computers of my school in less than ten minutes. I was 14 and I think I wanted to be a hacker. Goals changed through time, though.

To be honest, I even didn’t know that I was using GNU/Linux. But I started to investigate more and more about it. So I discovered there were many Linux distributions… and I decided to install Ubuntu. I decided to remove Windows, because my goal was to get adapted to Ubuntu as soon as possible. The first version of Ubuntu I tried was Ubuntu 8.04 (Hardy Heron). I had some difficulties, one of the ones I remember the most is that my WiFi card was not recognized. But I could overcome those problems. I noted two things very attractive in the desktop and it was that I could customize my environment as I wanted and that I could add some funny effects to the desktop: Compiz Fusion. Also, in that year I assisted to an event called the “Free Software Freedom Day” which was organized by the community Ubuntu Peru. In that conference my interest about Linux increased, because I knew that whoever could contribute to free software. There was a guy called Diego Escalante who was contributing to the GNOME Foundation in Epiphany and other guy called Nicolás Valcarcel who was a MOTU in Ubuntu. I think that my interest for contributing to Free Software started to grow there.

I started to contribute helping to new users in forums. In 2009, a guy from Uruguay I met in Internet called Diego Romero (who works in Mercadolibre Inc. now) and I created an on-line community called ubuntu-sud.com where we usually post news about Free Software and give some talks through IRC about different topics we has just learn. We were novices but we could help to beginners at least. Sometimes we invited folks that were contributing to relevant free software projects like OpenOffice.org. We got financed by a hosting company to host the website because one of the founders of that company liked our website. It was a good part of my life.

Logo of the old ubuntu-sud.com

Diego and I started to get busy because each of us had to prepare to study in the University and ubuntu-sud.com was shut down. I was in the academy but when I got some time I attended events about FOSS. In the university I got joined to the community Linux IDES and I talked to with a guy called Marco Villegas who I already knew in an event in another university. He told me that he had participated in the Google Summer of Code (aka GSoC) in Drupal and that motivated me to participate, too. By that time, I liked to contribute with wallpapers and skydomes for Compiz Fusion in GNOME-LOOK.ORG. I always wanted to contribute to GNOME, so I started to put more effort to investigate how to develop applications with PyGTK.

In 2013, I applied for a GSoC in Pitivi, but I wasn’t accepted. In 2014, I applied for the GSoC to develop a new feature to allow Pitivi to support Image Sequences which is useful when film makers wish to create stop-motion movies. Although this feature was not merged, probably, due to many discussions about the underlying GStreamer plugin, I tried to persist even after the GSoC to satisfy the  suggestions and petitions of the other GStreamer folks. My last patch was ready but nobody tried it. Luckily, Aaron Boxer has shown his interest on this plugin. After that, as I said I have kept contributing to GNOME, mostly in Pitivi and lately in GObject introducing a new function g_object_new_with_properties that is introduced to deprecate GParameter. This function should be useful in Libpeas, for example. The problem with GParameter is that it wasn’t introspectible. So you could do in Python something like:

from gi.repository import GObject
obj = GObject.Object.new(ObjClass, ["prop1", "prop2", "prop3"], [gvalue1, gvalue2, gvalue3])

 

This year, 2017, I have been accepted for the second time to the Google Summer of Code 2017. My project is to implement a plugin system in Pitivi and a set of 3 plugins. My mentor this year will be Alexandru Balut. If everything goes as I expect, I think that Pitivi could have the Plugin Manager merged in master before the middle of June. In an other post I will detail what I have done so far. The first stage of the project consists basically in adding a patch to Libpeas and create a plugin manager dialog in the Pitvi preferences dialog. The second stage will be to add a Developer Console plugin based on the Gedit’s Python Console and the other stages will be to create a plugin to add markers to the Pitivi Timeline each ten seconds and a plugin to generate a movie from images automatically. In case time remains, I will be developing as much plugins as possible.

 

How to stay logged in on IRC forever?

You may have different reasons why you want to stay (always) connected to an IRC channel. Maybe you did a question and you are waiting for a response but you have other things to do and you want to shut your notebook down. Or maybe you are the maintainer of a program and you would like to see all the questions your users did during the last 24 hours.

Firstly, you will need a cloud service with Linux. Then you will have to install and set up ‘weechat’ and ‘tmux’.

Installing Weechat
Weechat is a text-based IRC client easy-to-use.

$ sudo apt-get install weechat

Installing tmux
tmux is a terminal multiplexer that allows you to have multiple terminal sessions to be accessed from a single screen. You can switch between these terminals.

$ sudo apt-get install weechat

After installing tmux and weechat, start a new tmux session. We will call to this session ircsession.

$ tmux new -s ircsession

Then you will open weechat.

$ weechat


In weechat, you can connect to the IRC server and channel you want. For example, let’s connect to #gstreamer in irc.freenode.net!

/set irc.server.freenode.nicks "your_nickname"
/connect freenode
/join #gstreamer

If you got an error about SASL, do this:

/set irc.server.freenode.sasl_username "your_nickname"
/set irc.server.freenode.sasl_password "xxxxxxx"

That’s it. You can close the terminal window when you want. if you want to see your IRC tmux session again later, type:

tmux attach -t ircsession

TIPS

  • “pg up” to scroll up.
  • “pg dn” to scroll down.
  • “F11” to scroll nicklist up.
  • “F12” to scroll nicklist down.

For more information, you can check the Weechat quick start guide.

Inside gobject_new

Sometimes when you do not have nothing to do, you can go to Netflix to watch your favorite movie, or you may read GObject source code. I have been reading how g_object_new works and I have decided to explain about it what I have understood so far.

First, we are going to remember the prototype and how we create a GObject.

This is the prototype of g_object_new:

gpointer
g_object_new (GType	   object_type,
	      const gchar *first_property_name,
	      ...)

So in order to create a GObject of the type G_TYPE_TEST we can just do something like:

GObject *my_object = g_object_new (G_TYPE_TEST, "dummy1", 100.0, "dummy2", 200, NULL);

This line creates a Detroit Red Wings jersey mens gobject setting the properties values of the property called “dummy1” to 100 and the value of the property called “dummy2” to 200. What does happen here?

When you defined the G_TYPE_TEST object, you had to define its properties. To do so you must have called g_object_class_install_property function.

  g_object_class_install_property (gobject_class,
				   PROP_DUMMY1,
				   g_param_spec_int ("dummy1",
						     NULL, 
						     NULL,
						     0, G_MAXINT, 0,
						     G_PARAM_CONSTRUCT | G_PARAM_READWRITE));

GObject has a “pool” of “properties” that are “ready to use”. What this piece of code do is to tell GObject that a property called “dummy1” that belongs to the class of G_TYPE_TEST should be added to the “pool”.

When you call g_object_new, it will set the values property per property. First it will lookup in the “pool” if the property “dummy1” exists for the class of G_TYPE_TEST. Then the following may happen:

  1. If the property does not exist (was not installed), a critical error should be shown:

    “%s: object class ‘%s’ has no property named ‘%s’”

  2. If the property is not writable (it has not the type G_PARAM_WRITE or G_PARAM_READWRITE set), then a critical error will be shown

    “%s: property ‘%s’ of object class ‘%s’ is not writable”

  3. If the property has been specified twice

    “%s: property ‘%s’ for type ‘%s’ cannot be set twice”

If whichever of the cases described above occur, the object will be created, but the rest of the properties will not be analyzed. However, if none of the above cases present, as this example is the case, g_object_new will internally create a GObjectConstructorParam which is just a structure:

struct _GObjectConstructParam
{
  GParamSpec *pspec;
  GValue     *value;
};

After creating this structure by setting the “pspec” to the parameter specification that was taken from the pool and setting tthe value to a new created GValue containing the value of 100 (in the example), this GObjectConstructParam will be put in an array. g_object_new has an array for each property (or parameter). g_object_new uses by default an array of size 16 which means that GObject sees more likely that GObject classes doesn’t have more than 16 properties, but if there cheap jerseys are more, it will increase the size of this array.

After populating the array for all the properties passed to g_object_new, properties are not set yet. When properties will start to be set (throughout an internal function called object_set_property), transformations between values will be set. So for example, you can pass a float value, but the property is defined as an integer, actually.

An internal function called g_object_new_internal will be called. First, this internal function will create an instance (using g_type_create_instance). Then only the construct properties will be set, in this case “dummy1” because it was defined with G_PARAM_CONSTRUCT. After construct properties have been set, the other properties will be set. Setting a property as was mentioned, involves a transformation between value types. But not only that, GObject keeps a notification queue ‘notify_queue’, so whenever a bunch of properties are set, this queue is locked in order to add the properties to this queue using the function g_object_notify_queue_add. After all the properties has been set, the queue is unlocked and the object is returned.