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.
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).
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.
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:
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.
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.
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 }
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).
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
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
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:
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:
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
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.
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:
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.
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:
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.
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.
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).
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:
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.
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 }
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.