Thursday 6 September 2012

Developing GNOME shell extensions: finding documentation part 2

Part 2 of finding documentation for developing gnome shell extensions; Part 1 is here.

You can't always find documentation online. For example, Meta (imports.gi.Meta) doesn't have documentation online. Or perhaps you just want more details on the specific GNOME Javascript implementation of the C functions. Or there's a function listed on the page that you can't seem to find on the GNOME Javascript side.

Then, I recommend you look at the corresponding .gir file for your library, for example Meta-3.0.gir.

A gir file defines the binding between the C functions and the Javascript functions. They're used to define bindings between languages. If you look inside a gir file, you'll see it just looks like an XML file.

In this post I'll cover a few things:

Getting information from the gir file

Looking for signals in a gir file

To quickly look for signals in a gir file, you can just grep for glib:signal like so:

grep 'glib:signal' Meta-3.0.gir
# will give me a list of signals in the Meta library.
# ... (lots of signals)
      <glib:signal name="workspace-changed" when="last">
      </glib:signal>
# ... (lots more signals)

In the example above I wanted to see what signals the Meta library provided and in particular was after the workspace-changed signal. You can then go into the gir file and see that the workspace-changed signal belongs to the Meta.Screen class, so if I want to monitor workspace changes I have to do:

global.screen.connect('workspace-changed', myFunction);

(This is an example of a useful signal that is not used anywhere in the gnome-shell javascript files, so I wouldn't have known about it otherwise). Note - many of the global.* objects (global.screen, global.display, ...) are Meta objects.

Methods not available on the javascript side

Often you'll find that a function that is documented and available on the C side, but you can't seem to find it on the javascript side.

For example, suppose I want to get the current Metacity theme. I see that there's a function meta_theme_get_current that will do what I want, but when I try Meta.Theme.get_current(), I found out that .get_current() is not a function!

Looking in the gir file:

<record name="Theme" c:type="MetaTheme" disguised="1">                      
.... (more functions in between) ...

  <function name="get_current"                                              
            c:identifier="meta_theme_get_current"                           
            introspectable="0">                                             
    <return-value>                                                          
      <type name="Theme" c:type="MetaTheme*"/>                              
    </return-value>                                                         
  </function>

See the introspectable="0"? that means you can't get it from the javascript side. Too bad (time to pester the mailing list/find another way around it).

Where to find gir files/Converting typelib files to gir

Great, so we now know about looking into gir files for more information. But where do we find these gir files?

Most of them live in /usr/share/gir-1.0. Often they will not appear there until you install the relevant dev or gir package (for example if you want Wnck-[version].gir on Ubuntu you need to install the gir[girversion]-wnck-[version] package (e.g. gir1.0-wnck-1.0), of the libwnck-devel package on Fedora).

Chances are you don't have many gir files in /usr/share/gir-1.0. Don't despair!

A gir file is really a human-readable version of a typelib file (a bit like what gschema.xml is to gschema.compiled). Have a look in /usr/lib/girepository-1.0. You will see a huge number of .typelib files. (Maybe /usr/lib64/girepository-1.0 for 64bit machines).

Everything in here can be imported in GJS through imports.gi.[name].

Some of them are not there (e.g. Meta, Shell, St) because they are considered "private". This means if you run gjs on the command line, you will not be able to find them. However, you can find their typelib files here:

  • /usr/lib/gnome-shell (Shell, St)
  • /usr/lib/mutter (Meta)

So, how do we convert typelib files to human-readable gir files?

Use the g-ir-* tools. These are a collection of tools for generating gir or typelib files from source and converting between the two. We will be using g-ir-generate.

If you do not have these tools installed already (I think from GNOME 3.4+ on they come packaged with gnome-shell), you can install them in the gobject-introspection package (Ubuntu), or on Fedora 16 they were in gobject-introspection-devel. Or you could just get the source from github.

To create a gir from a typelib (for example Meta):

g-ir-generate /usr/lib/mutter/Meta-3.0.typelib > Meta-3.0.gir

And voila! A gir file for Meta!

Sometimes g-ir-generate will complain about not being able to find various other typelibs, for example if you try to generate Shell-0.1.gir from the typelib:

[mathematicalcoffee ~]$ g-ir-generate /usr/lib/gnome-shell/Shell-0.1.typelib
** (g-ir-generate:9599): ERROR **: failed to load typelib: Typelib file for namespace 'St', version '1.0' not found
Trace/breakpoint trap (core dumped)

In this case it's trying to find St-1.0.typelib, which is one of those "private" ones in /usr/lib/gnome-shell, so you can include it using the --includedir argument (it also needs to find Meta-3.0.typelib in /usr/lib/mutter; I think by default just /usr/lib/girepository-1.0 is included):

[mathematicalcoffee ~]$ g-ir-generate --includedir=/usr/lib/gnome-shell \
    --includedir=/usr/lib/mutter /usr/lib/gnome-shell/Shell-0.1.typelib > Shell-0.1.gir

And that works.

Generating HTML documentation from gir files

It's easier to use some form of documentation than wading through the gir files all the time.

Here is how to generate HTML documentation from the gir files (you use g-ir-doc-tool):

mkdir mutter-docs
g-ir-doc-tool Meta-3.0.gir -o mutter-docs

Then turn the output (yelp files) into HTML (or you could just use the yelp files if you prefer):

yelp-build html mutter-docs

Then load index.html in a browser and you have some basic browsable documentation, complete with a list of signals (so if you click on the Meta.Screen page it has a list of signals you can connect to, although it is a bit sparse on explanation).

Notes:

  • If you can find the documentation online, use that instead. The generated documentation is often quite sparse/no explanations.
  • Sometimes g-ir-doc-tool complains about repository versions being unsupported. You can go into the gir file and modify the line <repository version="1.0" to <repository version="1.2" and it will then generate the documentation (not sure if anything is affected by that)
  • Sometimes g-ir-doc-tool complains about not being able to find the gir files of other libraries it depends on. You have to generate them and make sure they're in /usr/share/gir-1.0 (it doesn't seem to support an --includedir argument like g-ir-generate does).

Summary

When looking for documentation:

  • First look on developer.gnome.org
  • Otherwise generate some documentation from the gir file (/usr/share/gir-1.0, /usr/lib/mutter, /usr/lib/gnome-shell):

    g-ir-doc-tool /path/to/gir -o /path/to/output/directory
    yelp-build html /path/to/output/directory
    
  • If you can't find the gir file, find the typelib file and convert it to gir first (/usr/lib/girepository-1.0):

    g-ir-generate /path/to/typelib > /path/to/output/filename # use --includedir=/path/to/typelib/folder if need be
    

Hope that helps! Feedback welcome.

6 comments:

  1. I did :

    g-ir-generate /usr/lib64/gnome-shell/St-1.0.typelib and generated a gir file .

    How to use this file for imports now.
    The St import was priavte and hence gave error while running the HelloWorld.extension.js in Gnome

    Can you please tell to use the generated gir file for St import

    ReplyDelete
  2. I'm not sure I understand what you want. Do you mean you are trying to do

    const St = imports.gi.St;

    and it is not finding the library to import?

    St is a private library as you mention, and so the above will only work from a gnome-shell extension and not from any other process (even the prefs widget won't be able to find it).

    If you are running `gjs` on a javascript file manually you can tell it to include /usr/lib64/gnome-shell so that it will find the file, like

    gjs -I /usr/lib64/gnome-shell myFile.js

    Why don't you send me an email with some more details as to what you are trying to do.

    ReplyDelete
    Replies
    1. What id can i mail you ?

      Actually i have made a fedora 17 VM on my RHEL machine and trying to create a HelloWorld (to start with )extension on Gnome shell .

      This is what i did :
      gnome-shell-extension-tool -c

      Name should be a very short (ideally descriptive) string.
      Examples are: "Click To Focus", "Adblock", "Shell Window Shrinker".

      Name: "Hello World"

      Description is a single-sentence explanation of what your extension does.
      Examples are: "Make windows visible on click", "Block advertisement popups"
      "Animate windows shrinking on minimize"

      Description: Hello world Description

      Uuid is a globally-unique identifier for your extension.
      This should be in the format of an email address (foo.bar@extensions.example.com), but
      need not be an actual email address, though it's a good idea to base the uuid on your
      email address. For example, if your email address is janedoe@example.com, you might
      use an extension title clicktofocus@janedoe.example.com.
      Uuid [_Gnome_Program_@localhost.localdomain]: HelloWorld@localhost.localdomain
      Created extension in '/home/shveta/.local/share/gnome-shell/extensions/Gnome_Program_@localhost.localdomain'
      [shveta@localhost ~]$ cd .local/share/gnome-shell/extensions/
      [shveta@localhost extensions]$ gjs -I /usr/lib64/gnome-shell/St-1.0.typelib -c HelloWrold@localhost.localdomain/extension.js
      JS ERROR: !!! Exception was: SyntaxError: missing ; before statement
      JS ERROR: !!! lineNumber = '1'
      JS ERROR: !!! fileName = '""'
      JS ERROR: !!! stack = '""'
      JS ERROR: !!! message = '"missing ; before statement"'
      SyntaxError: missing ; before statement


      Initially i was getting St unable to import but now i included it (the way you suggested) in the command . Now i am getting this synatx error.

      The extension.js file looks like :
      [shveta@localhost extensions]$ cat HelloWorld@localhost.localdomain/extension.js

      const St = imports.gi.St;
      const Main = imports.ui.main;
      const Tweener = imports.ui.tweener;

      let text, button;

      function _hideHello() {
      Main.uiGroup.remove_actor(text);
      text = null;
      }

      function _showHello() {
      if (!text) {
      text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
      Main.uiGroup.add_actor(text);
      }

      text.opacity = 255;

      let monitor = Main.layoutManager.primaryMonitor;

      text.set_position(Math.floor(monitor.width / 2 - text.width / 2),
      Math.floor(monitor.height / 2 - text.height / 2));

      Tweener.addTween(text,
      { opacity: 0,
      time: 2,
      transition: 'easeOutQuad',
      onComplete: _hideHello });
      }

      function init() {
      button = new St.Bin({ style_class: 'panel-button',
      reactive: true,
      can_focus: true,
      x_fill: true,
      y_fill: false,
      track_hover: true });
      let icon = new St.Icon({ icon_name: 'system-run',
      icon_type: St.IconType.SYMBOLIC,
      style_class: 'system-status-icon' });

      button.set_child(icon);
      button.connect('button-press-event', _showHello);
      }

      function enable() {
      Main.panel._rightBox.insert_child_at_index(button, 0);
      }

      function disable() {
      Main.panel._rightBox.remove_child(button);
      }



      Since its a VM i am not able to run the extension using ALT+F2

      Please let me know if you can help.
      Thanks

      Delete
  3. Email me at mathematical.coffee@gmail.com

    GNOME shell extensions shouldn't be run from the command line. Do

    gnome-shell-extension-tool -e [your extension's UUID]

    to enable the extension, and then *restart* gnome-shell to have it run (if not with Alt+F2 'r', then do gnome-shell --replace in a terminal).

    ReplyDelete
  4. OK i tried that too but was not able to see the output on screen at all..
    Then installed gnome-tweak-tool to enable the extension and i could see an icon on the top right.

    then did gnome-shell --replace and clicked the icon ,
    I could see the output then..

    Thanks a lot for your help.

    ReplyDelete