Documenting GNOME Shell Extensions is an ongoing process; please be patient while we continue to improve it.

Although this page still contains useful information, there is a much newer tutorial at https://wiki.gnome.org/Projects/GnomeShell/Extensions/Writing

Step by step tutorial to create extensions

NOT COMPLETED AND REVISED YET!

Phrases or words in bigger need to be reviewed to ensure that are correct

To review

#toReview-4 #toReview-5 #toReview-6 #toReview-7

This tutorial is for gnome 3.4. The main differences between 3.4 and 3.6 are:

  • OpenSearch and PlacesDisplay were removed

  • The ViewSelector tab system was replaced with a simpler page API

  • The screensaver is in process and accessed as Main.screenShield
  • The session mode now changes at runtime (and this affect enablement of extensions)
  • Some modules were moved into js/ui/components/
  • St.IconType was removed, so you need to add ‘-symbolic’ to icon names

  • Notifications, status items and search providers deal with GIcons, not icon names
  • Message tray sources can override the right click menu
  • ConsoleKit/Systemd were unified as util.LoginManager

  • Input sources are stored in GSettings and affect IBus too
  • The volume status indicator was split in the menu and the actual indicator

So take them into account until I update this wiki to gnome 3.6

Index

From scratch

Setting up eclipse

Why use an IDE

It is very helpful to develop using an IDE with javascript support. It will help us with code folding, autocompletion, outline, etc.

The recommended IDE for this tutorial is Gnome Builder. It should be packaged for all distributions that also offer Gnome Desktop. It has syntax highlighting and code completion for Javascript.

Gnome also has an IDE called Anjuta. The problem is that Anjuta lack some features, like autocompletion, code folding, etc... so it is better to develop using either Eclipse or Gnome Builder until Anjuta gets to have this kind of features.

So, we start setting up eclipse to have javascript support.

Setting up eclipse

Install eclipse from your app store of your distribution. Open eclipse and select your workspace. Go to Help. Install new software. Select Work with "All available sites". Search for javascript. Install. That's it!

Problems

I haven't got eclipse in my distribution

Go to http://www.eclipse.org/downloads/ and download eclipse classic. I select work with "All available sites" but nothing is shown. Go to "Find more software by working with "All available software sites" preferences" and add your eclipse download site which depens on the eclipse version you have. For eclipse Juno it is http://download.eclipse.org/releases/juno.

Go to index

Using eclipse

When you create an extension, the extension is saved at ~/.local/shared/gnome-shell/extensions with a gnome-shell extension folder format, so you can't create a project here and edit the files expecting that each change you do reflects directly in the gnome-shell. So, if we use eclipse to develop extensions, the best way is to open directly the file you are editing, like ~/.local/shared/gnome-shell/extensions/myExtensionFolder/myExtension.js

To visualize the changes, you need to have enabled the extension. To enable the extension use gnome-tweak-tool.

After that, every time you make a change, you will have to save the file and restart the shell (Alt+F2 , write "r" without quotes, enter).

Go to index

Knowing javascript for gnome shell

Introducing javascript

JavaScript (sometimes abbreviated JS), which is a prototype-based scripting language, is dynamic, weakly typed and has first-class functions. It is a multi-paradigm language, supporting object-oriented, imperative, and functional programming styles.

Gnome-shell is written in javascript because it allows a quick prototyping. It means that it allows to change the code quickly, and do a lot of changes in a easy way. It is needed because gnome-shell is an application that is in a quick development, and it changes a lot in short time. The difference between javascript and other dynamic languages (python, ruby, php, etc...) is that javascript doesn't depend of a specific platform, so gnome-shell can run it with its own platform under it (Gjs).

Mozilla has an extended guide to programming in Javascript for the web. There will be some differences since we are not programming for the web, but most of it will be the same.

Quick characteristic things:

  • Classes, objects, variables, and functions are less or more the same.
  • To access some function or variable: object.function() / object.variable or object.function() / object['function']()
  • You can modify the code while running. It is called monkey-patching.
  • class.prototype allow inheritance or modifying the base class by directly rewriting its code.
  • Variables without scope. So, if you do "var x", x will be in all of your program. The only way to restrict the scope is "let x" what makes the variable x only usable in the scope in which it was defined.
  • If we want to use "this" outside of its scope we need the class Lang. It is used like this: functionOutsideOfScope(Lang.bind(this, this.function())). See https://live.gnome.org/GnomeShell/Gjs_StyleGuide ""this" in closures" to see one example.

Go to index

How extends functionality

You may want to wait until we get to the extensions section to do anything, as extensions are the easiest way to add functionality to GNOME. That section will walk you through the process of creating your first extension.

The main way to extend functionality is rewriting the code of the gnome shell in this way:

We have a class called Workspace (File: /usr/share/gnome-shell/js/ui/Workspace. The class is something like this:

   1 const Workspace = new Lang.Class({
   2     Name: 'Workspace',
   3 
   4     _init : function(metaWorkspace, monitorIndex) {
   5         // When dragging a window, we use this slot for reserve space.
   6         this._reservedSlot = null;
   7         this.metaWorkspace = metaWorkspace;
   8 etc...
   9 
  10 _lookupIndex: function (metaWindow) {
  11         for (let i = 0; i < this._windows.length; i++) {
  12             if (this._windows[i].metaWindow == metaWindow) {
  13                 return i;
  14             }
  15         }
  16         return -1;
  17     },
  18 })

And we want to modify the _lookupIndex to return always 1. So we do this in our extension:

   1 Workspace.prototype['_lookupIndex'] = function (metaWindow){ return 1; }

As we saw in the JavaScript tutorial, we can modify the base class by editing its "prototype". To access some function, object, variable, etc. we put the name of the function/variable/object/etc bracketed in a string format. In the right side of the equal sign, we write our function that we want to replace the original function with.

After enabling our extension and restarting the shell, if we execute the Workspace._lookupIndex function, it will always return 1.

Another way to extend functionality is to inject code, with this function:

   1 function injectToFunction(parent, name, func)
   2 {
   3         let origin = parent[name];
   4         parent[name] = function()
   5         {
   6                 let ret;
   7                 ret = origin.apply(this, arguments);
   8                 if (ret === undefined)
   9                         ret = func.apply(this, arguments);
  10                 return ret;
  11         }
  12         return origin;
  13 }
  14 
  15 function removeInjection(object, injection, name)
  16 {
  17         if (injection[name] === undefined)
  18                 delete object[name];
  19         else
  20                 object[name] = injection[name];
  21 }

And to inject some code in a function:

   1 //We store the injection, so we have to option to disable it later
   2 inections=[];
   3 injections['injectionOfLookupIndex']=injectToFunction(Workspace.prototype, '_lookupIndex',  function(){return 1;});

And to de-inject the code:

   1 removeInjection(Workspace.prototype, injections,  'injectionOfLookupIndex');

With this, we can add some code to the function without rewriting the whole function. Our code is written before the return statement of the original function and after all code of the original function

We'll see why we need to de-inject the code in "my first extensions" section.

Go to index

Knowing clutter

Introducing clutter

Clutter is a C programming API that allows you to create simple but visually appealing and engaging user interfaces. It offers a variety of objects (actors) which can be placed on a canvas (stage) and manipulated by the application or the user. It is therefore a "retained mode" graphics API. Unlike traditional 2D canvas APIs, Clutter allows these actors to move partly in the Z dimension. It is used by gnome-shell to implement the entire GUI.

There's three technologies that allow us to easily use clutter with javascript:

Clutter use GObjectIntrospection, a project that allows us to use JavaScript (as well as other languages), instead of using c. It works by transforming the c program API in general files(xml or custom files) allowing us to easily write a binding in another program language that can interpret it.

Next, we need a program that gets the general file result from GObjectIntrospection and transforms it into a specific programming language, like javascript. This program is specific for each programming language, and for javascript it is called Gjs.

Also, gnome-shell has his own clutter-based toolkit, called St, that gives you some useful actors (components in the GUI). St is also the way for gnome-shell to interact with CSS theming; so the form and color of every button, dialog etc. of gnome shell is defined in /usr/share/gnome-shell/theme, and the only class that manages this css is St.

So we have: Clutter in c -> GObjectIntrospection(result in a general file) ->

  • -> Gjs (interprets general file from GObjectIntrospection to javascript)

  • -> pyclutter (interprets general file from GObjectIntrospection to python)

  • -> clutter-perl (interprets general file from GObjectIntrospection to perl)

  • -> etc.

Important characteristic things in clutter are:

  • Stage: contains some actors (text, rectangles, etc). It's the window of the application. Also is an actor.
  • Actor: GUI object.
    • show() : you have to show each actor to be visible to the user.
    • hide() : hide the actor.

Go to index

Events

We need events for the GUI to react to mouse clicks and keyboard inputs. Also, we can use custom events inside our program, to communicate through events, functions in our program.

By default, each actor in clutter has these events:

  • button-press-event: Emitted when the user presses the mouse over the actor.
  • button-release-event: Emitted when the user releases the mouse over the actor.
  • motion-event: Emitted when the user moves the mouse over the actor.
  • enter-event: Emitted when the user moves the mouse in to the actor's area.
  • leave-event: Emitted when the user moves the mouse out of the actor's area.

The Signals section of the ClutterActor documentation lists all of the events emitted by an actor.

We can associate each event with one actor and a function that will be called when the event occurs, and will carry out what we want to happen when the event occurs (like move the actor when we click it, change its opacity when we hover it, etc.). This association is called "connect".

So, to connect one actor to one event: actor.connect(eventName, functionToCallWhenEventOccurs);

For example:

   1 //Create new actor
   2 actor=new Clutter.Actor();
   3 /*
   4  * Connect the actor to the enter-event and it will call the moveActor function when
   5  * we click the actor
   6 */
   7 actor.connect('enter-event', moveActor);
   8 
   9 /*
  10  * The enter event automatically passes some parameters to the function.
  11  * These parameters are the actor and the event.
  12  * http://docs.clutter-project.org/docs/clutter/stable/ClutterActor.html
  13  * in the document above, search for "The "leave-event" signal"
  14 */
  15 function  moveActor(actor, event)
  16 {
  17     let x=0;
  18     let y=2;
  19     //Set the new position of the actor.
  20     actor.set_position(x, y);
  21 }

Go to index

Some examples with raw clutter

Now we will see some examples with clutter, but gnome shell is not there, only clutter. Also, if you want all the code, you can get it from github. See all the code.

Initiate clutter and stage

   1 //Import the clutter class form the gi repository (the object introspection repository)
   2 const Clutter = imports.gi.Clutter;
   3 // Initialize clutter
   4 Clutter.init(null);
   5 /*
   6  * Create a stage. This function returns a new default stage, with its own
   7  * window. ClutterStage is derived from the ClutterActor object so many of that
   8  * object's functions are useful for the stage. For instance, call
   9  * Clutter.Stage.get_default().show() to make the stage visible.
  10  */
  11 let stage = new Clutter.Stage();
  12 
  13 // We connect the destroy event to quit from the mainloop when we close the
  14 // window.
  15 stage.connect("destroy", Clutter.main_quit);
  16 // Put some title
  17 stage.title = "Test";
  18 // Set a color to the stage to show that it is working
  19 stage.set_background_color(new Clutter.Color({
  20     red : 150,
  21     blue : 0,
  22     green : 0,
  23     alpha : 255
  24 }));
  25 // As we say, the stage is also an actor, so we show it to make visible
  26 stage.show();
  27 // Start a main loop so that the stage can animate its contents and respond to
  28 // user interaction.
  29 Clutter.main();

Save this code to a file "InitClutterAndStage.js" and to execute it we enter in a terminal:

gjs-console InitClutterAndStage.js

As you see, we are using gjs to execute the file, so you have to have Gjs installed (search for Gjs in your software management application).

Go to index

Draw something

Now we try to draw a rectangle in the stage:

   1 //Import the clutter class from the gi repository (the object introspection repository).
   2 const Clutter = imports.gi.Clutter;
   3 // Initialize clutter
   4 Clutter.init(null);
   5 /*
   6  * Create a stage. This function returns a new default stage, with its own
   7  * window. ClutterStage is derived from the ClutterActor object so many of that
   8  * object's functions are useful for the stage. For instance, call
   9  * Clutter.Stage.get_default().show() to make the stage visible.
  10  */
  11 let stage = new Clutter.Stage();
  12 
  13 // We connect the destroy event to quit from the main loop when we close the
  14 // window.
  15 stage.connect("destroy", Clutter.main_quit);
  16 // Set the title
  17 stage.title = "Test";
  18 // Set a color to the stage to show that it is working
  19 stage.set_background_color(new Clutter.Color({
  20     red : 10,
  21     blue : 10,
  22     green : 10,
  23     alpha : 255
  24 }));
  25 
  26 // Create a new actor
  27 let actorRectangle = new Clutter.Actor();
  28 // Make it like a rectangle
  29 actorRectangle.set_size(100, 100);
  30 actorRectangle.set_position(100, 100);
  31 /*
  32  * Colors are made in RGBA http://en.wikipedia.org/wiki/RGBA_color_space
  33  * Basically, Red, Green, Blue, Alpha (transparency). Each is a value between 0 and 255
  34  */
  35 actorRectangle.set_background_color(new Clutter.Color({
  36     red : 100,
  37     blue : 100,
  38     green : 100,
  39     alpha : 255
  40 }));
  41 
  42 stage.add_actor(actorRectangle);
  43 // As we say, the stage is also an actor, so we call show() to make it visible
  44 stage.show();
  45 // Start a main loop so that the stage can animate its contents and respond to
  46 // user interaction.
  47 Clutter.main();

Save this code to a file "DrawSomething.js" and to execute we enter in a terminal:

gjs-console DrawSomething.js

Go to index

Add some signals to make it interactive

Now, we do something funny. We will add a hover event to the rectangle actor, and each time we hover the actor, the actor will change the position randomly.

   1 //Import the clutter class from the gi repository(the object introspection repository)
   2 const Clutter = imports.gi.Clutter;
   3 
   4 // Initialize clutter
   5 Clutter.init(null);
   6 /*
   7  * Create a stage. This function returns a new default stage, with its own
   8  * window. ClutterStage is derived from the ClutterActor object so many of that
   9  * object's functions are useful for the stage. For instance, call
  10  * Clutter.Stage.get_default().show() to make the stage visible.
  11  */
  12 let stage = new Clutter.Stage();
  13 
  14 // We connect the destroy event to quit from the main loop when we close the
  15 // window.
  16 stage.connect("destroy", Clutter.main_quit);
  17 // Put some tittle
  18 stage.title = "Test";
  19 // Set a color to the stage to show that it is working
  20 stage.set_background_color(new Clutter.Color({
  21     red : 10,
  22     blue : 10,
  23     green : 10,
  24     alpha : 255
  25 }));
  26 
  27 // Create a new actor
  28 let
  29 actorRectangle = new Clutter.Actor();
  30 // Put his x, y size, position and background color
  31 actorRectangle.set_size(100, 100);
  32 actorRectangle.set_position(100, 100);
  33 actorRectangle.set_background_color(new Clutter.Color({
  34     red : 100,
  35     blue : 100,
  36     green : 100,
  37     alpha : 255
  38 }));
  39 
  40 // Sets actor as reactive. Reactive actors will receive events.
  41 actorRectangle.set_reactive(true);
  42 /*
  43  * Connect the actor to a event. When you hover it, function in the second
  44  * parameter will be called. So we are passing a reference to a function.
  45  */
  46 actorRectangle.connect('enter-event', changeRectanglePosition);
  47 
  48 stage.add_actor(actorRectangle);
  49 // As we say, the stage is also an actor, so we show it to make visible
  50 stage.show();
  51 // Start a main loop so that the stage can animate its contents and respond to
  52 // user interaction.
  53 Clutter.main();
  54 
  55 function changeRectanglePosition()
  56 {
  57     // We get the size of the stage (our window)
  58     let [sizex, sizey] = stage.get_size();
  59     /*
  60      * Math.random returns a float between 0 and 1, so we multiply by the size of
  61      * the stage and we acomplish a number between 0-sizeStage
  62      */
  63     let newx = Math.floor(Math.random() * sizex);
  64     let newy = Math.floor(Math.random() * sizey);
  65     /*
  66      * We can access that because it is a global variable. Also, remember that
  67      * with "let" the scope is the block and with "var" the scope is the whole
  68      * environment.
  69      */
  70     actorRectangle.set_position(newx, newy);
  71 }

Save this code to a file "ClutterEvents.js" and to execute it we enter in a terminal:

gjs-console ClutterEvents.js

Go to index

Animations

Now, we add an animation to the actor. This animation only will start if we hover over the actor. So we have to put the animation inside the function we call when we hover over the actor.

   1 //Import the clutter class from the gi repository(the object introspection repository)
   2 const
   3 Clutter = imports.gi.Clutter;
   4 
   5 // Initialize clutter
   6 Clutter.init(null);
   7 /*
   8  * Create a stage. This function returns a new default stage, with its own
   9  * window. ClutterStage is derived from the ClutterActor object, so many of these
  10  * object's functions are useful for the stage. For instance, call
  11  * Clutter.Stage.get_default().show() to make the stage visible.
  12  */
  13 let stage = new Clutter.Stage();
  14 
  15 // We connect the destroy event to quit from the mainloop when we close the
  16 // window.
  17 stage.connect("destroy", Clutter.main_quit);
  18 // Set a title
  19 stage.title = "Test";
  20 // Give the stage a color to show that it is working
  21 stage.set_background_color(new Clutter.Color({
  22     red : 10,
  23     blue : 10,
  24     green : 10,
  25     alpha : 255
  26 }));
  27 
  28 // Create a new actor
  29 let actorRectangle = new Clutter.Actor();
  30 // Set its x, y, size, position, and background color
  31 actorRectangle.set_size(100, 100);
  32 actorRectangle.set_position(100, 100);
  33 actorRectangle.set_background_color(new Clutter.Color({
  34     red : 100,
  35     blue : 100,
  36     green : 100,
  37     alpha : 255
  38 }));
  39 
  40 // Sets actor as reactive. Reactive actors will receive events.
  41 actorRectangle.set_reactive(true);
  42 /*
  43  * Connect the actor to a event. When you hover over it, the function in the
  44  * second parameter will be called. So we are passing a reference to a function.
  45  */
  46 actorRectangle.connect('enter-event', changeRectanglePosition);
  47 // Add the rectangle to the stage
  48 stage.add_actor(actorRectangle);
  49 
  50 // As we say, the stage is also an actor, so we show it to make visible
  51 stage.show();
  52 // Start a main loop so that the stage can animate its contents and respond to
  53 // user interaction.
  54 Clutter.main();
  55 
  56 function changeRectanglePosition()
  57 {
  58     // We get the size of the stage(our window)
  59     let [sizex, sizey] = stage.get_size();
  60     /*
  61      * Math.random return a float between 0 and 1, so we multiply by the size of
  62      * the stage and we acomplish a number between 0-sizeStage
  63      */
  64     let newx = Math.floor(Math.random() * sizex);
  65     let newy = Math.floor(Math.random() * sizey);
  66     /*
  67      * We can access that because is a global variable. Also, remember that
  68      * with "let" the scope is the block and with "var" the scope is the whole
  69      * environment.
  70      */
  71     actorRectangle.set_position(newx, newy);
  72 
  73     /*
  74      * WARNING: The next code is deprecated since clutter 1.6, but we don't have
  75      * an easy way to animate actors in raw clutter in stable version of
  76      * fedora 17, so we use deprecated code. The easy way in new code is:
  77      * actorRectangle.animate(Clutter.AnimationMode.EASE_OUT_ELASTIC, 500,
  78      * "scale-x", 0.5, "scale-y", 0.5, NULL); New code to animate actors:
  79      * http://docs.clutter-project.org/docs/clutter/stable/clutter-Implicit-Animations.html
  80      */
  81 
  82     /*
  83      * To animate an actor we need three things timeline: think about it like
  84      * play, pause and stop and bar time of your videos alpha: how is made the
  85      * transition, because we can made a transition bounding, quickly at first
  86      * and later slowly, etc. These are all the transitions:
  87      * http://docs.clutter-project.org/docs/clutter/stable/clutter-Implicit-Animations.html#ClutterAnimationMode
  88      * behaviour: what property of the actor we want to animate, scale, opacity,
  89      * etc. each property has his own class, behaviourScale, behaviourOpacity,
  90      * etc. all properties of behaviourScale:
  91      * http://docs.clutter-project.org/docs/clutter/stable/ClutterBehaviourScale.html
  92      */
  93     let timeline = new Clutter.Timeline({
  94         'duration' : 500
  95     });
  96     let alpha = new Clutter.Alpha({
  97         'timeline' : timeline,
  98         'mode' : Clutter.AnimationMode.EASE_OUT_ELASTIC
  99     });
 100     let behaviourScale = new Clutter.BehaviourScale({
 101         'alpha' : alpha,
 102         'x_scale_start' : 1.0,
 103         'y_scale_start' : 1.0,
 104         'x_scale_end' : 0.5,
 105         'y_scale_end' : 0.5
 106     });
 107     behaviourScale.apply(actorRectangle);
 108     timeline.start();
 109 }

Save this code to a file "ClutterAnimations.js" and to execute we enter in a terminal:

gjs-console ClutterAnimations.js

Game with all together

Now we will make a game (called "3 en raya" in Spanish). In this game we will have objects painted, events, and animations. We need a board, two players, and two types of tokens. It supposed that you already has read the previous headlands about clutter.

We need two classes, GameBoard, that define the program behaviour and data and Gameosx that define the GUI with clutter.

The code is documented with Natural docs. You can document the code and generate html documentation. Also, remember that documenting code is very important, and if you can create html documentation, it is very useful.

Class GameBoard:

   1 /*
   2     Code from http://townx.org/blog/elliot/introduction-sorts-javascript-desktop-application-development-gjs-and-clutter
   3     Commented and update to clutter 1.10 by Carlos Soriano
   4  */
   5 
   6 /*
   7     Define what is a square in the game
   8     We use this way to define a class,
   9     using the prototype.
  10     Visit <Ways to define a "class" in JS at http://www.phpied.com/3-ways-to-define-a-javascript-class>
  11  */
  12 
  13 /*
  14   Class: Square
  15 
  16   Define a square to play in the board This "class" use the method
  17   prototype to do define a "class" in JavaScript
  18 
  19   Parameters:
  20        x - position x of the square. For normal use, 0 to 2
  21        y - position y of the square. for normal use, 0 to 2
  22  */
  23 function Square(x, y, player) {
  24     this.player = player;
  25     this.x = x;
  26     this.y = y;
  27 };
  28 
  29 Square.prototype = {
  30         getPlayer : function () {
  31             return this.player;
  32         },
  33 
  34         setPlayer : function (player) {
  35             this.player = player;
  36         },
  37 
  38         getX : function () {
  39             return this.x;
  40         },
  41 
  42         getY : function () {
  43             return this.y;
  44         }
  45 };
  46 
  47 
  48 /*
  49   Class: Line
  50 
  51        Class to define the line drawn when some player won
  52  */
  53 function Line () {
  54     this.squares = [];
  55 };
  56 
  57 Line.prototype = {
  58         first : function () {
  59             return this.squares[0];
  60         },
  61 
  62         last : function () {
  63             return this.squares[this.squares.length - 1];
  64         },
  65 
  66         addSquare : function (square) {
  67             this.squares.push(square);
  68         },
  69 
  70         winsFor : function () {
  71             let current = null;
  72             let last = null;
  73 
  74             for (let i = 0; i < this.squares.length; i++) {
  75                 current = this.squares[i].getPlayer();
  76                 if (i > 0 && current != last) {
  77                     last = null;
  78                     break;
  79                 }
  80                 else {
  81                     last = current;
  82                 }
  83             }
  84             return last;
  85         },
  86 
  87         clear : function () {
  88             this.squares = [];
  89         }
  90 };
  91 
  92 /*
  93   Class: Board
  94 
  95         Define the board of the game
  96 
  97   Parameters:
  98 
  99         players - The count of the players
 100         sideSize - The quantity of squares can have the board in a side
 101  */
 102 
 103 function Board(players, sideSize) {
 104     this._init(players, sideSize);
 105 };
 106 
 107 Board.prototype = {
 108 
 109         _init : function(players, sideSize) {
 110             this.squares = [];
 111             this.sideSize = sideSize;
 112             this.winner = null;
 113             this.playerPos = 0;
 114             this.players = players;
 115 
 116             for (var i = 0; i < sideSize; i++) {
 117                 this.squares[i] = [];
 118                 for (var j = 0; j < sideSize; j++) {
 119                     this.squares[i][j] = new Square(i, j, null);
 120                 }
 121             }
 122         },
 123 
 124         switchPlayer : function() {
 125             this.playerPos = this.playerPos + 1;
 126             if (this.playerPos >= this.players.length) {
 127                 this.playerPos = 0;
 128             }
 129         },
 130 
 131         getNextPlayer : function() {
 132             return this.players[this.playerPos];
 133         },
 134 
 135         makeMove : function(x, y, player) {
 136             this.squares[x][y].setPlayer(player);
 137             let line = this.getWinningLine();
 138             return line;
 139         },
 140 
 141         canMove : function(x, y) {
 142             return !this.winner && this.squares[x][y].getPlayer() == null;
 143         },
 144 
 145         getWinningLine : function () {
 146             let line = new Line();
 147             let x = 0;
 148             let y = 0;
 149 
 150             // check columns
 151             while (this.winner == null && x < this.sideSize) {
 152                 line.clear();
 153                 for (y = 0; y < this.sideSize; y++) {
 154                     line.addSquare(this.squares[x][y]);
 155                 }
 156                 this.winner = line.winsFor();
 157                 x++;
 158             }
 159 
 160             // check rows
 161             y = 0;
 162             while (this.winner == null && y < this.sideSize) {
 163                 line.clear();
 164                 for (x = 0; x < this.sideSize; x++) {
 165                     line.addSquare(this.squares[x][y]);
 166                 }
 167                 this.winner = line.winsFor();
 168                 y++;
 169             }
 170 
 171             // check bottom right to top left diagonal
 172             if (this.winner == null) {
 173                 line.clear();
 174                 for (x = 0, y = 0; x < this.sideSize && y < this.sideSize; x++, y++) {
 175                     line.addSquare(this.squares[x][y]);
 176                 }
 177                 this.winner = line.winsFor();
 178             }
 179 
 180             // check bottom left to top right diagonal
 181             if (this.winner == null) {
 182                 line.clear();
 183                 for (x = 0, y = this.sideSize - 1; x < this.sideSize && y >= 0; x++, y--) {
 184                     line.addSquare(this.squares[x][y]);
 185                 }
 186                 this.winner = line.winsFor();
 187             }
 188 
 189             return line;
 190         }
 191 
 192 };

Save this code to a file "GameBoard.js"

Class Gameosx:

   1 /*
   2  * Code from http://townx.org/blog/elliot/introduction-sorts-javascript-desktop-application-development-gjs-and-clutter
   3  * Commented and update to clutter 1.10 by Carlos Soriano
   4  */
   5 
   6 /*
   7  * TODO:
   8  * - use a layout to put rectangles on the stage, rather than absolute positioning
   9  * - fix file searchPath so it works when run from anywhere
  10  */
  11 
  12 //X is always the first player
  13 //NB relies on a patched gjs with a 'print' function (used to show the winner)
  14 
  15 //import local file - NB this means the script only works from this directory at the moment
  16 imports.searchPath.push('.');
  17 const Ox = imports.GameBoard;
  18 //Import clutter from gi repository (GObjectintrospection)
  19 const Clutter = imports.gi.Clutter;
  20 //This class help you to enclosure the "this"
  21 const Lang = imports.lang;
  22 
  23 const OX_PLAYERS = ["X", "O"];
  24 const OX_SQUARE_SIZE_PX = 100;
  25 const OX_FONT_SIZE = OX_SQUARE_SIZE_PX * 0.95;
  26 const OX_LINE_WIDTH_PX = 10;
  27 const OX_SIDE_SIZE = 3;
  28 const OX_TEXT_COLOR = new Clutter.Color( {'red':255, 'blue':255, 'green':255, 'alpha':255} );
  29 const OX_STRIKE_COLOR = new Clutter.Color( {'red':255, 'blue':0, 'green':0, 'alpha':255} );
  30 
  31 /*
  32     Class: BoardView
  33 
  34         Class to define the view of the board, it means, to define the visual of our
  35         program. There is where Clutter have the action
  36 
  37     Parameters:
  38 
  39         players - number of players
  40         sideSize - number of squares to play
  41  */
  42 function BoardView(players, sideSize) {
  43     this._init(players, sideSize);
  44 };
  45 
  46 BoardView.prototype = {
  47         _init : function (players, sideSize) {
  48             this.board = new Ox.Board(players, sideSize);
  49             /*
  50              The board size will be the side size per the size of the square
  51              to play plus the lines between squares to separate squares to
  52              make the visual more clear
  53              */
  54             this.boardSizePx = (sideSize * OX_SQUARE_SIZE_PX) + (OX_LINE_WIDTH_PX * (sideSize + 1));
  55             // Initialize clutter
  56             Clutter.init (null);
  57             // Create new stage for our window and our actors
  58             this.stage = new Clutter.Stage();
  59             // We connect the destroy event to quit from the mainloop when we close the
  60             // window.
  61             this.stage.connect("destroy", Clutter.main_quit);
  62             //Set the title
  63             this.stage.title = "3 en raya";
  64             // The size of the visual board
  65             this.stage.set_size(this.boardSizePx, this.boardSizePx);
  66 
  67             let colorOfSquare = new Clutter.Color( {'red':50, 'blue':50, 'green':50, 'alpha':255} );
  68 
  69             /*
  70              We create a square actor for each place to play. Also, we connect
  71              each square actor to a mouse event for when we click in the
  72              square actor to play with this square. This event will cause that
  73              the actor will be painted as played, and check at the same time,
  74              the actor will check if someone win the game.
  75              */
  76             for (let i = 0; i < sideSize; i++)
  77             {
  78                 for (let j = 0; j < sideSize; j++)
  79                 {
  80                     let xpos = ((i + 1) * OX_LINE_WIDTH_PX) + (i * OX_SQUARE_SIZE_PX);
  81                     let ypos = ((j + 1) * OX_LINE_WIDTH_PX) + (j * OX_SQUARE_SIZE_PX);
  82 
  83                     let squareActor = new Clutter.Actor();
  84                     squareActor.set_size(OX_SQUARE_SIZE_PX, OX_SQUARE_SIZE_PX);
  85                     squareActor.set_background_color(colorOfSquare);
  86                     squareActor.set_position(xpos, ypos);
  87                     /*
  88                      We make the actor reactive, because we want to make the
  89                      square interactive and responsible of the mouse events.
  90                      */
  91                     squareActor.set_reactive(true);
  92 
  93                     let x = i;
  94                     let y = j;
  95                     /*
  96                      We connect the press event of the mouse to a function.
  97                      The function is defined inside. It is called "anonymous
  98                      function" because, as you see it hasn't got a name. It is
  99                      very useful to define functions quickly that is not
 100                      needed outside there.
 101 
 102                      Also you can see that we use Lang.bind(). As you can see
 103                      in this tutorial, in Knowing javascript->introducing
 104                      javascript, we need this because we will use the "this"
 105                      outside of his scope. This is a real example of this. If
 106                      you think a little, when a event is happens, the funtion
 107                      in the second parameter is called. BUT, we called a
 108                      function that inside it uses the "this"...and the "this"
 109                      at the moment of the calling is not THIS object, it is
 110                      the object that do the callback. So, we need to "close"
 111                      the this inside a virtual closure. We acomplish this
 112                      using Lang.bind.
 113 
 114                      In summary, we need Lang.bind when we will use the "this"
 115                      outside of his scope. A example is in callbacks. All
 116                      callbacks that we use with the "this" word inside we will
 117                      need the Lang.bind.
 118 
 119                      If you don't know what a callback is, see
 120                      http://en.wikipedia.org/wiki/Callback_(computer_programming)
 121 
 122                      Basically is a function as a parameter to another
 123                      function.
 124                      */
 125                     squareActor.connect('button-press-event',
 126                             Lang.bind(this, function(actor, event)
 127                                     {
 128                                 // Make sure that the we didn't play in this
 129                                 // square before
 130                                 if (this.board.canMove(x, y))
 131                                 {
 132                                     let nextPlayer = this.board.getNextPlayer();
 133 
 134                                     // Get the line if some player won
 135                                     let line = this.board.makeMove(x, y, nextPlayer);
 136 
 137                                     this.markMove(squareActor, nextPlayer);
 138                                     // If line returned is a valid win play
 139                                     if (line.winsFor())
 140                                     {
 141                                         this.strikeThrough(line);
 142                                     }
 143                                     // If nobody win, swicth player
 144                                     this.board.switchPlayer();
 145                                 }
 146                                     }));
 147                     // We add each square to the stage
 148                     this.stage.add_actor(squareActor);
 149                 }
 150             }
 151         },
 152 
 153         /*
 154              Function:
 155 
 156                  Draw the line when some player won.
 157 
 158              Parameters:
 159 
 160                  line - The line that we will draw.
 161 
 162              See Also:
 163 
 164                  <Line>
 165          */
 166         strikeThrough : function (line)
 167         {
 168             let first = line.first();
 169             let last = line.last();
 170 
 171             let height = 0;
 172             let width = 0;
 173             let x = 0;
 174             let y = 0;
 175             let rotate = 0;
 176             let straight_line_length = this.boardSizePx * 0.95;
 177 
 178             if (first.getX() == last.getX())
 179             {
 180                 // column
 181                 width = OX_LINE_WIDTH_PX  / 2;
 182                 height = straight_line_length;
 183                 x = ((first.getX() + 0.5) * OX_SQUARE_SIZE_PX) + ((first.getX() + 0.75) * OX_LINE_WIDTH_PX);
 184                 y = (this.boardSizePx - straight_line_length) / 2;
 185             }
 186             else if (first.getY() == last.getY())
 187             {
 188                 // row
 189                 width = straight_line_length;
 190                 height = OX_LINE_WIDTH_PX / 2;
 191                 x = (this.boardSizePx - straight_line_length) / 2;
 192                 y = ((first.getY() + 0.5) * OX_SQUARE_SIZE_PX) + ((first.getY() + 0.75) * OX_LINE_WIDTH_PX);
 193             }
 194             else
 195             {
 196                 // diagonal, length calculated aplying Pitagoras theorem
 197                 width = Math.sqrt(straight_line_length * straight_line_length * 2);
 198                 height = OX_LINE_WIDTH_PX / 2;
 199                 x = (this.boardSizePx - width) / 2;
 200                 y = (this.boardSizePx / 2) - (height / 2);
 201 
 202                 if (first.getX() == first.getY()) {
 203                     rotate = 45;
 204                 }
 205                 else {
 206                     rotate = -45;
 207                 }
 208             }
 209             // Create a new rectangle to draw a line
 210             let strike = new Clutter.Actor ();
 211             strike.set_background_color (OX_STRIKE_COLOR);
 212             strike.set_position (x, y);
 213             strike.set_size (width, height);
 214 
 215             /*
 216              If the line is in diagonal, we have to rotate the actor. To
 217              rotate it we have to indicate the axis to rotate and the center
 218              of the rotation. The axis will be z (trougth the screen), and the
 219              center of rotation will be the center of the square actor. The
 220              last 0 is the depth, that as you can imagine, it haven't got
 221              depth.
 222              http://docs.clutter-project.org/docs/clutter/stable/ClutterActor.html#clutter-actor-set-rotation
 223              */
 224             if (rotate != 0) {
 225                 strike.set_rotation (Clutter.RotateAxis.Z_AXIS, rotate, width / 2, height / 2, 0);
 226             }
 227             // add the line actor to the stage to sow it
 228             this.stage.add_actor(strike);
 229         },
 230         /*
 231             Function: markMove
 232 
 233                 This function allow you to show a move from one player. The function
 234                 will draw a "X" or a "O" depending of the player.
 235 
 236             Parameters:
 237                 clickedSquare - The square that is been clicked by the user.
 238                 player - wich player click the square.
 239          */
 240         markMove : function(clickedSquare, player)
 241         {
 242             //Put the letter associated with this player("X" or "O") in a text actor of clutter
 243             let letterToDraw = new Clutter.Text( {"text":player, "color":OX_TEXT_COLOR} );
 244             //Set the font, and size of the text
 245             letterToDraw.set_font_name("Sans Bold " + OX_FONT_SIZE + "px");
 246             //Get the position of the rectangle
 247             let [r_x, r_y] = clickedSquare.get_position();
 248             let offset_x = (clickedSquare.get_width() / 2) - (letterToDraw.get_width() / 2);
 249             let offset_y = (clickedSquare.get_height() / 2) - (letterToDraw.get_height() / 2);
 250 
 251             letterToDraw.set_position(r_x + offset_x, r_y + offset_y);
 252             letterToDraw.move_anchor_point_from_gravity (Clutter.Gravity.CENTER);
 253 
 254             this.stage.add_actor(letterToDraw);
 255             /*
 256              WARNING: The next code is deprecated. Instead of this we have to use a simple
 257              code. The code of the new version will be:
 258               actorRectangle.animate(Clutter.AnimationMode.EASE_OUT_ELASTIC, 500,
 259              "scale-x", 0.5, "scale-y", 0.5, NULL);
 260              */
 261 
 262             /*
 263              Create a new timeline as we see in the animation headland(its function it's like the time bar of youtube
 264               videos) with duration 500ms
 265              */
 266             let timeline = new Clutter.Timeline( {'duration':500} );
 267             /*
 268              Create a new function to control the timeline and the properties of the actor.
 269              This will use the ease out elastic type of animation, resulting in a animation that cause the letter
 270              seems an elastic thing
 271              */
 272             let alpha = new Clutter.Alpha ( {'timeline':timeline, 'mode':Clutter.AnimationMode.EASE_OUT_ELASTIC} );
 273             //Create a new behaviour to the actor. We want that the actor scale to 0.5 of its size.
 274             let behaviour = new Clutter.BehaviourScale( {'alpha':alpha, 'x_scale_start':1.0, 'y_scale_start':1.0,
 275                 'x_scale_end':0.5, 'y_scale_end':0.5} );
 276             //Apply the behaviour to the letter actor
 277             behaviour.apply (letterToDraw);
 278             //Start the animation
 279             timeline.start ();
 280         },
 281 
 282         show : function()
 283         {
 284             //Show the stage and his actors
 285             this.stage.show();
 286             //Start our program with the GUI we created
 287             Clutter.main();
 288         }
 289 };
 290 
 291 //Create our GUI
 292 let view = new BoardView(OX_PLAYERS, OX_SIDE_SIZE);
 293 //Start our GUI
 294 view.show();

Save this code to a file "Gameosx.js" and to execute the entire game we enter in a terminal:

gjs-console Gameosx.js

Tachan! You create a simple game with clutter and javascript. Enjoy!

Problems

I haven't got Gjs in my software management!

Install from source:

Go to index

Knowing gnome shell

Introducing GNOME Shell

GNOME Shell provides core interface functions like switching windows, launching applications or see your notifications. Gnome shell is made 50% in c 50% in javascript. You only have to touch the javascript code, because is the code that you can modify for your extension. Maybe, with the pass of the time, the percentage of c code will decrease. Gnome shell manage the workspace, notifications, windows(how is managed, not the rendering(which is managed by Mutter)), dash, top bar, and lacunh applications. It uses GPU acceleration. The rendering of the interface is managed by clutter, so gnome shell doesn't touch graphics. Also, gnome shell doesn't touch css, this is managed by St, we will see what St is. And, the last thing you have to know is that gnome shell, currently, is not using the animation framework of clutter(because when gnome shell was did the animation framework in clutter was not present), instead, clutter use Tweener. We'll see an example of tweener in My first extension section.

Gnome shell has these parts:

gnomeShellParts.png

gnomeShellOverviewParts.png

Also, when programming an extension, if you modify the code you have to restart gnome shell. To do this pres ALT+F2 and put "r" and enter

Sometimes gnome shell crash because your extension did something wrong. To return to normal, press CTRL+ALT+F2 and put

DISPLAY=:0 gnome-shell

and press enter.

Then press CTRL+ALT+F7 or CTRL+ALT+F8 and you will go to a new gnome-shell interface.

Note: if something crash you can go again to CTRL+ALT+F2 and see the error, and do the same to start a new gnome shell.

Also, maybe gnome shell doesn't crash but is unresponsive.

To fix it, press CTRL+ALT+F2 and then

ps -e | grep gnome-shell

with this list all the proccess that have gnome-shell in the name. Then, you see a number, called PID, and at the rigth gnome-shell. So, get this PID and

kill -9 "the PID that you get above"

With this you stop the procces you wrote with his PID.

An example:

carlos@carlos-pc-fedora ~$ ps -e | grep gnome-shell
 1174 ?        00:18:29 gnome-shell
 1276 ?        00:00:00 gnome-shell-cal
carlos@carlos-pc-fedora ~$ kill -9 1174

Go to index

Interaction with clutter

How does gnome shell interact with clutter? Basically gnome shell defines a lot of actors. As we saw above, each object in the GUI, like the "top bar", the "message tray", etc. are actors. The main function of Gnome shell is to define how the actors move, react to events, interact with each other, and so on.

Go to index

Gjs and how to use c API gnome libraries from javascript

Gjs is the library that allows you to use c libraries of gnome (in fact, almost all libraries of gnome are written in c) from javascript using GObject-introspection. GObject-introspection is a technique to allow developers to easily build bindings that allows you to use the c libraries from other computer languages like javascript, python, etc. You have not worry about it, except that when you need to use a function from a gnome library, the documentation is written for the C language. So you "don't know" exactly how your function is called if you want to use it from javascript.

But, there are some rules for mapping the c API to JavaScript, so reading the c API reference you can know how the function you want to use is called in javascript. In this page you have the rules that you have to follow to know how the function you want to use is called: https://live.gnome.org/Gjs/Mapping

So, for example, if we are doing some extension, and we want to create a button in the top bar of gnome-shell we go to the API reference of St in http://developer.gnome.org/st/stable/ and we look for a StBin, see http://developer.gnome.org/st/stable/StBin.html

First we see that the class(c struct) is called StBin, so, I guess that the class in javascript is called St.Bin, so for create a new StBin we do

   1 St.Bin()

But, we also can pass to the constructor, as wee saw before in Gjs/Mapping webpage, a properties map. Which properties can we pass to the constructor? All of the object hierarchy. So if we see into the StBin class we only see a few properties, but if we see at the object hierarchy headland http://developer.gnome.org/st/stable/StBin.html#StBin.object-hierarchy we also see that StBin is inhereted from StWidget. So we can use these properties also in the cosntructor. So for example we can do the next StBin call:

   1 button = new St.Bin({ style_class: 'panel-button',
   2                     reactive: true,
   3                     can_focus: true,
   4                     x_fill: true,
   5                     y_fill: false,
   6                     track_hover: true });

Here we are modifying properties from StBin and also from StWidget(because StBin is inhereted from StWidget). So for example, style_class property comes from StWidget class, and x_fill property comes from StBin class. The same happens to clutter API, etc. So now we know how to interpret c APIs to use into our javascript code.

Go to index

API

Apuf....this is very difficult, because of the lack of documentation...So the best way to learn is to see the source code (Yes, the best way...the only way!). To make this step easier, I made a diagram explaining which classes have what objects and I will explain you in overview each interesting component.

I recommend you take a ten minute coffee break... :)

After coffee, now see the diagram:

gnomeShellDiagram.bmp

To understand the diagram, think that this is a "contingency" diagram. In other words, it shows you the imports of each file. So, if Main imports Overview, it is because Main will use Overview.

It's a hierachical diagram, so we have levels 0-6. The level 0 is Main, wich is a file and also a class inside the file, so if we see the Main.js file we will see that it contains a class, called Main.

Also, Main imports Overview.js, Panel.js....etc. Each of these files contains some classes, but most of these files contains a class with the same name as the file. So we have Overview.js and inside we have Overview class and ShellInfo class.

In most cases, a class that imports a file will use the class from that file with the same name as the file imported. For example, Main imports Overview.js and Main uses and preserves an object from the class Overview (which is defined in the Overview.js file). So, we have Main.overview as an instance.

Normally, the attributes of a imported class are preserved as the name of the class in lower case, or, if the attribute is private, with an underscore. For example, we have Main.overview._dash.

In the diagram, the example above will be Main->Overview(file)->Overview(class) and the instance is preserved in Main as "overview" -> Dash(file) -> Dash(class) wich the instance is preserved in overview as _dash.

As you can see, most attributes of some class is the main class of a file (called equal to the file) are underscored or lower cased.

Now we'll see a brief explanation of each level, group, class, etc. I will put in bold the objects and libraries I consider most frequently used when we make an extension and in violet colour in the diagram. So I recommend you only pay attention to these now, and after doing some extension examples, you can go back and look at the other classes, wich are very interesting and define very useful things for the entire gnome platform.

Go to index

Class explanation

Updated: a better documentation for the API is here: http://mathematicalcoffee.blogspot.com.es/2012/09/gnome-shell-javascript-source.html . We are working together to embed the documentation here. Until this arrive, I recommend to see her post instead of the next class explanation.

The only reason you may have to see the next class explanation is that the diagram is correlated with it

  • MainUI
    • This is the main objects we will touch and rewritte to make our extension
    • Overview: This object have all we see normally when we are in the "Activities" headland. So, it has the dash, the workspaces, the window actors, the search bar, the display apps, etc.

      • Dash: This is the bar that you have at the left in overview.
      • WorkspaceView: This class manage all workspaces, it means, the workspaces placed at rigth and also the windows running. So if we want to change how the windows are shown in the overview(i.e. order the windows like KDE or Mac) we have to look into this.

      • ViewSelector: This is the selector for "Windows" and "Applications" tabs.

      • AppDisplay: This is the selector of all aps when you click "Applications" in the ViewSelector

    • MessageTray

      • This is the object at the bottom, where your notifications are showed
      • Notification: Is the instance of one notification (icon, text, banner, etc)
      • MessatreTray: The instance of the message tray

      • Source: The application source of one or more notifications. Source provides the icon, the access to the application assigned to the notification, etc.
      • SystemNotificationSource: Define a system source. It is inherit from Source.

      • SummaryItem: This is the item with the icon and text in the message tray representing one notification source.

      • FocusGrabber: Grab and control the focus for the message tray. We need this because the focus acts diferently depending of the siutation for the message tray.

      • !URLHighlighter: This highligh the urls in the messages when you are talking with someone.
    • Panel This is the top panel

      • Panel: It's the instance of the panel.
      • ActivitiesButton: Is the button of "Activities". Extend PanelMenu.Button. All the others icons in the panel are PanelMenu.Button

      • AnimatedIcon: Generate a animated icon (this is used for the "waves" when you reach the hot corner of the panel)

      • AppMenuButton: This class manages the "application menu" component that appears when you do rigth click over the appliction name in the panel.

      • PanelCorner: It's the hot corner that activate the overview.

      • TextShadower: Generate the shadow of the texts

    • CtrlAltTab: Implements the UI of the ctrl+alt+tab function.

    • Layout: This manages the shell "chrome"; the UI that's visible in the normal mode (ie, outside the Overview), that surrounds the main workspace content.
  • C libraries
    • All of these are libraries written in c. We doesn't rewritte these libraries to implement an extension. The main library we will probably use is St.
    • St: This is the unique library will be use for the GUI. It is made in c, using clutter. Manage the css theme of gnome shell and implements useful actors like buttons, text prompts, etc. See http://developer.gnome.org/st/stable/ to see the avaiable actors as buttons, text labels, etc.

    • Gio: This library allows you to acces to the filesystem easily. http://developer.gnome.org/gio/stable/ch01.html

    • GLib: It is a general-purpose utility library, which provides many useful data types, macros, type conversions, string utilities, file utilities and a mainloop abstraction. http://developer.gnome.org/glib/2.32/glib.html

    • Mainloop: Provide listen events. This is the GTK+ event listener. Gnome shell use this mainloop and the clutter one.
    • Shell: Implements some functions that gnome shell in javascript can't implements, like the screenshot or the desktop recorder.
    • Meta: It's the window manager. It is called Mutter. Is a window manager based on clutter.
    • GDK: An intermediate layer which isolates GTK+ from the details of the windowing system.
    • Clutter: What's this?? It's not familiar to me... :P Remember that it is write in c, but it use GObjectIntrpospection, so we can use clutter directly in javascript(or other programming languages that support GObjectIntrospection) as we saw in Knowing clutter

  • General utils
    • Some util classes that gnome shell use.
    • Magnifier: It is a accessibility tool, that allow to deficient users to see the desktop with zoom
    • TelephatyClient: Manage the chat, IRC, videocalls, audicalls, etc. in gnome shell interface. So, when some person talk us and we see the chat in the message tray, it is because telepathy client are doing his work.

    • LookingGlass: Tool for helo developers developing gnome shell and gnome shell extensions.

    • PlaceDisplay: Represents a place object, which is most normally a bookmark entry, a mount/volume, or a special place like the Home Folder, Computer, and Network.

    • Keyboard: Manage and define the keyboard events, etc. for gnome shell.
  • UIManagersAndHandlers
    • XdndHandler: Handle the drag and drop (DND) function of gnome shell.

    • WindowManager: Manage the windows behaviour in gnome shell, like wich window is in the top stack of showing, the dimm of one window, etc.

  • Prompts
    • Contains all the prompts you see in gnome shell, like when you try to install some applicaton (PolkitAutheticantionPromt) or when you connect some mass storage device to computer and a prompt telling you what to do is shown.

    • RunDialog: This is the dialog shown when you press Alt+F2.

    • NotificationDaemon: Manage all notifications. It is a class under TelephatyClient, etc. Telephaty client send him some data, and the NotificationDaemon filter it and acts accordingly.

    • EndSessionDialog: The prompt shown when you click on "power off" or "log out"

    • KeyringPrompt: It is the prompt shown when the system ask you about a password, like when you connect to a new wifi channel.

  • Util not touch
    • I called this group as "util not touch" because it agrouped some classes that, in principle, you won't change to do an extension. I said in princyple, because you can change whatever you want (If the extensions validators people are agree)

    • ShellDBus: This is an abstraction layer that allows other applications to interact with shell. For example, when you install or uninstall an extension by extensions.gnome.org, this class is called.

    • Environment: Initialize some variables for gnome shell environment, like Tweener.
    • NetworkAgent: Manage network (wifi, wire, bluethoot) interface.

    • StatusIconDispatcher: Dispatche icons for the status bar

    • Scripting: This module provides functionality for driving the shell user interface in an automated fashion. The primary current use case for this is automated performance testing, but it could be applied to other forms of automation, such as testing for correctness as well.
    • WindowAttentionHandler: Manage the "x is ready" dialog you got when you click to run some application and other things like this. It define when a window is requesting attention from the user.

    • ExtensionSystem: Manage the extensions

    • PolkitAuthenticationAgent: Manage the authentication for administrator actions, like when you install an application.

    • AutorunManager: Manage the autorun function, like when you insert some device in the computer and a dialog asking what to do shown.

    • AutomounManager: Manage the automount of devices, like when you insert a USB stick and it is mounted automatically.

Go to index

Looking glass

This is the tool for debug gnome shell and javascript code for gnome shell. To open it do Alt+F2 and type lg. Then a window overlapping all appears. Looking glass is divided into tabs: Evaluator, Windows, Memory and Extensions. The main view is "Evaluator". Here you can evaluate random code of javascript, accesing gnome shell objects and libraries. For example, write this in the evaluator and press enter:

   1 Main.notify("Hello world")

Then, you can see that you acces to the Main object and you call the "notify" function that generate a transient notification. So as you can see, you can evaluate random code and acces all the objects and libraries(clutter, St, Glib, etc) of gnome shell.

Also, in the Evaluator tab, you have a "picker". Do you see a "cuentagotas" at left corner of looking glass? Click into it. Then you can "pick" random object of the interface of gnome shell and know its name. This is useful if you are trying to know "where" is the object you are seeing in the class hierarchy of gnome shell and wich kind of object is.

In the Windows tab, you can see wich windows are currently active. Pay attention to wmclass. This name let you identify a application inside gnome.

The Extensions tab tell you wich extensions are installed and some other information about each extension. If some extension reach an error, the error will be displayed there (this is not always true, sometimes, the error is not displayed there and you have to see the ~.xsession-errors file to see if some error ocurred).

Go to index

My first extension

Finally! Now, we do our first simple extension, a simple hello world. For do that, we use a gnome tool for do extensions called "gnome-shell-extension-tool". Then, to start your first extension put this in a terminal:

gnome-shell-extension-tool --create-extension

Then you are asked about the name of your application. For this extension we put in "Hello world" and press enter.

Then you are asked about a description. Put something like: Show a hello world label pressing an icon in the system tray.

Then you are asked about an uuid. This is a globally-unique identifier for your extension. A good uuid name contains something related to your email. For example, if your email is first.second1925@server.com, I recommend something like theNameOfYourExtension@first.second1925-server.com

In that case, we write helloWorld@email.com and press enter.

Tada! The code of the main file is shown in gedit.

Before we look into the code, we'll active the extension to test it and see what happens. To active it, we have to go to gnome-tweak-tool (aka advanced settings) (if you didn't install it, install now from your software manager. Search for gnome-tweak-tool). Then in gnome-tweak-tool go to "Shell extensions" and turn on your extension (If is not shown, press alt+f2 and put 'r' without quotes and press enter to restart the shell and force to load all extensions). If this didn't work try using the extensions website: https://extensions.gnome.org/local/ and enable (turn on) your extension.

Now, do you see in the system tray a new icon with a gears shape? Click it and you can see how your extension tells you "Hello, world!".

Analyze this visually. You see an icon in the system tray. Also, when you click it, you see a label and this label has an animation (from opacity 100% to opacity 0%). Now, we'll see in the code how it is defined.

To see the code of your extension and edit it you have to know where is it. It is in "~/.local/share/gnome-shell/extensions/" and there are all extensions, including your new extension "helloWorld@email.com" folder. Go to your extension folder, and you'll see a extension.js, a metadata.json and a stylesheet.css

extension.js is your extension code in javascript. Don't change the name, each extension has to have an extension.js file to work.

metadata.json is the file that holds your context about your extension, like your extension uuid, name, description, etc. If you don't know what is JSon see http://www.json.org/ . Basically is a tool/language to preserve some data of our program between executions.

stylesheet.css is the file where the style of your UI objects are defined (for example the style of the "Hello, world!" label. You can change the font, the background color, the corner radius, etc.) It use CSS theming, if you don't know what CSS is see http://www.w3schools.com/css/ . CSS is commonly used to make webpages. It works together with HTML technology to create webpages, but now it is used for a bunch of different things. It is a standard to do things related with themes and visual appearance. This file is optional, because maybe your extension doesn't use UI object that needs custom styles.

Now you know wich files is needed for a extension, but you don't know how a extension is structured, so I will explain it briefly:

The extension has three mandatory functions. init(), enable() and disable().

Don’t do anything major in init()

init() is there to have anything that needs to happen at first-run, like binding text domains. Do not make any UI modifications or setup any callbacks or anything in init(). Your extension will break, and gnome-developers will reject it. Do any and all modifications in enable(). Undo all UI modifications in disable()

If you make any UI modifications in enable(), you need to undo them in disable(). So, if you add an icon to the top bar when your extension is enabled, you need to remove it. Simple. Failure to do so will be an immediate rejection. Remove all possible callback sources.

If you’re writing an extension, you must disconnect all signals and remove all Mainloop sources. Failure to do so will be an immediate rejection. So, if you connect to a signal using global.display.connect('window-created', onWindowCreated);, you need to disconnect it in this manner:

   1 let _windowCreatedId;
   2 
   3 function enable() {
   4     _windowCreatedId = global.display.connect('window-created', onWindowCreated);
   5 }
   6 
   7 function disable() {
   8     global.display.disconnect(_windowCreatedId);
   9 }

The same goes for any timeouts or intervals added with the Mainloop module. Use Mainloop.source_remove to make sure your source is removed.

Maybe you don't understand what the mainloop is, etc. but at least, you know that these are the rules, and we have to satisfy these rules. In some time, when we do some more complicated extensions and we use callbacks, signals, the mainloop, inject code, etc. we need to remember these rules.

See: http://blog.mecheye.net/2012/02/requirements-and-tips-for-getting-your-gnome-shell-extension-approved/

Once we know the rules, go to see into the code of the "hello world" extension.

Go to your favorite IDE (Anjuta, Eclipse or even gedit) and open the file extension.js. Then you can see the code of your "hello world" extension.

The code is not commented, so I commented carefully the code to understand it better for a beginner.

This is the code commented:

   1 /*In this example we will be click a button in the top bar,
   2   causing an event that create a text label (hello world), which with some
   3   animation, will be decreasing its opacity from 100% to 0%
   4  */
   5 
   6 
   7 /*Import St because is the library that allow you to create UI elements*/
   8 const St = imports.gi.St;
   9 /*
  10   Import Main because is the instance of the class that have all the UI elements
  11   and we have to add to the Main instance our UI elements
  12   */
  13 const Main = imports.ui.main;
  14 /*Import tweener to do the animations of the UI elements*/
  15 const Tweener = imports.ui.tweener;
  16 
  17 /*Global variables for use as button to click (button) and a text label.*/
  18 let text, button;
  19 
  20 /*
  21   Function to call when the label is opacity 0%, as the label remains as a
  22   UI element, but not visible, we have to delete it explicitily. So since
  23   the label reaches 0% of opacity we remove it from Main instance.
  24  */
  25 function _hideHello() {
  26     Main.uiGroup.remove_actor(text);
  27     text = null;
  28 }
  29 
  30 function _showHello() {
  31         /*if text not already present, we create a new UI element, using ST library, that allows us
  32           to create UI elements of gnome-shell.
  33           REFERENCE: http://developer.gnome.org/st/stable/
  34          */
  35     if (!text) {
  36         text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
  37         Main.uiGroup.add_actor(text);
  38     }
  39 
  40     text.opacity = 255;
  41 
  42     /*
  43       we have to choose the monitor we want to display the hello world label. Since in gnome-shell
  44       always has a primary monitor, we use it(the main monitor)
  45      */
  46     let monitor = Main.layoutManager.primaryMonitor;
  47 
  48     /*
  49      we change the position of the text to the center of the monitor.
  50      */
  51     text.set_position(Math.floor(monitor.width / 2 - text.width / 2),
  52                       Math.floor(monitor.height / 2 - text.height / 2));
  53 
  54     /*And using tweener for the animations, we indicate to tweener that we want
  55       to go to opacity 0%, in 2 seconds, with the type of transition easeOutQuad, and,
  56       when this animation has completed, we execute our function _hideHello.
  57       REFERENCE: http://hosted.zeh.com.br/tweener/docs/en-us/
  58      */
  59     Tweener.addTween(text,
  60                      { opacity: 0,
  61                        time: 2,
  62                        transition: 'easeOutQuad',
  63                        onComplete: _hideHello });
  64 }
  65 
  66 /*This is the init function, here we have to put our code to initialize our extension.
  67  we have to be careful with init(), enable() and disable() and do the right things here.
  68  REFERENCE: http://blog.mecheye.net/2012/02/requirements-and-tips-for-getting-your-gnome-shell-extension-approved/
  69  */
  70 function init() {
  71 }
  72 
  73 /*
  74   We have to write here our main extension code and the things that actually make works the extension(Add ui elements, signals, etc).
  75  */
  76 function enable() {
  77     /*
  78       We create a button for the top panel. We pass to the constructor a map of properties, properties from St.bin and its
  79       parent classes, like stWidget. So we declare this properties: a style class(from css theming of gnome shell), we made it reactive
  80       so the button respond for the mouse clicks, we made it that can focus, so marks the button as being able to receive keyboard focus
  81       via keyboard navigation, we made the button to fill the x space, and we don't want to fill the y space, so we set the values trues and false respectively
  82       and we want that the button be reactive to the hover of the mouse, so we set the value of the track_hover property to true.
  83     */
  84     button = new St.Bin({ style_class: 'panel-button',
  85                           reactive: true,
  86                           can_focus: true,
  87                           x_fill: true,
  88                           y_fill: false,
  89                           track_hover: true });
  90     /*
  91       We create an icon with the system-status-icon icon and give it the name "system-run"
  92      */
  93     let icon = new St.Icon({ icon_name: 'system-run',
  94                              style_class: 'system-status-icon' });
  95     /*
  96       we put as a child of the button the icon, so, in the structure of actors we have the icon inside the button that is a
  97       container.
  98      */
  99     button.set_child(icon);
 100     /*
 101       we connect the actor signal "button-press-event" from the button to the funcion _showHello. In this manner,
 102       when we press the button, this signal is emitted, and we captured it and execute the _showHello function.
 103       You can see all signals in the clutter reference(because we are using St that implements actors from clutter, and
 104       this signals comes from the actor class): http://developer.gnome.org/clutter/stable/ClutterActor.html#ClutterActor.signals
 105      */
 106     button.connect('button-press-event', _showHello);
 107 
 108         /*
 109           We add the button we created before to the rigth panel of the top panel (where the sound and wifi settings are)
 110          */
 111     Main.panel._rightBox.insert_child_at_index(button, 0);
 112 }
 113 
 114 /*We have to delete all conections and things from our extensions, to let the system how it is before our extension. So
 115  We have to unconnect the signals we connect, we have to delete all UI elements we created, etc.
 116  */
 117 function disable() {
 118         /*
 119          we remove the button from the right panel
 120          */
 121     Main.panel._rightBox.remove_child(button);
 122     button = null;
 123 }

Go to index

All code to download or modify

If you want all the code. images, and the wiki code posted in this wiki, you can donwload using git from here

https://github.com/csoriano89/StepByStepTutorial

Also, if you want to modify something, fix something or add some code to the wiki, I appreciated you if you use the git repository above and change the wiki after. If we do this in this manner we can have a repository with all the code to download and everything update.

Attic/GnomeShell/Extensions/StepByStepTutorial (last edited 2021-04-12 02:56:44 by AndyHolmes)