This site has been retired. For up to date information, see handbook.gnome.org or gitlab.gnome.org.


[Home] [TitleIndex] [WordIndex

GNOME's JavaScript

JavaScript (sometimes abbreviated JS), is a prototype-based scripting language. It is dynamic, weakly typed, has first-class functions and supports object-oriented.

Gnome-shell is written in JavaScript because it allows for quick prototyping and development cycles. The difference between javascript and other dynamic languages (python, ruby, php, etc...) is that javascript doesn't depend on a specific platform. Gnome-shell can than run it with its own platform (Gjs).

From Scratch

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.

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

For Beginners

Variables

   1 const x = 0; // a variable that cannot be reassigned
   2 var y =1 ; // a variable without a scope, you can use it anywhere in your code.
   3 let z= 2; // a variable that has single block scope, like in a for loop for example.
   4 

Variables can also be declared by using matching arrays or objects :

   1 let [ a, b, c ] = [ 1, 2, 3 ];
   2 let { a, b } = { a: "apple", b: "banana" };

If/else/elseif

   1 if (x>0) {
   2         print(“Positive number”)  ;
   3 } else if (x<0) {
   4         print(“Not a positive number”);
   5 } else {
   6         print(“Not a number”);
   7 };

For loops

   1 for (let i = 0; i < 10; i++) {
   2         print(i)
   3 };

Looping over arrays using for...of is much more convenient.

   1 let fruits = [ "apple", "orange", "banana" ];
   2 
   3 for (let fruit of fruits) {
   4         print(fruit);
   5 }

Functions

   1 function Foo(arg1, arg2) {do something};

Function parameters can accept default values;

   1 function add(x, y = 0) {
   2         return x + y;
   3 }

Imports

You import objects from the imports object.

   1 const Gio = imports.gi.Gio;
   2 const Panel = imports.ui.Panel;

If you want to include your own files, you have to add the current directory to imports' search path.

   1 imports.searchPath.unshift(".");
   2 const Awesome = imports.awesome; // import awesome.js from current directory
   3 const Brilliant = imports.lib.brilliant; // import lib/brilliant.js
   4 

Gjs Particularities

Class inheritance

I like the simplicity and readability of the Lang.Class function :

   1 const Lang = imports.lang
   2 
   3 const New_Class = new Lang.Class({
   4                 Name: 'new class',
   5                 Extends: Old_class,
   6 
   7                 _init: function() {do something};
   8 });

Using "this" in closures

"this" has a restricted scope, relative to how the closure is invoked, not to the value of "this" where the closure is created. It needs to be passed to the function or callback at invocation time. To do this, use the Lang.bind() helper function :

   1 const Lang = imports.lang;
   2 
   3 let closure = Lang.bind( this, this._Foo )

Extending existing functions

Rather than disabeling parts of the gnome-shell and re-writing the whole code to fit your needs, you can modify it directly.

Say we have the following class Foo :

   1 const Lang = imports.gi.lang
   2 
   3 const Foo = new Lang.Class({
   4         Name: 'Foo',
   5 
   6                 _init : function() {
   7                 this.variable  = 2
   8                 this._Bar()
   9         },
  10 
  11         _Bar : function (metaWindow) {
  12                 return this.variable ;
  13                 }
  14 })

Overwriting functions

If we want to re-write the _Bar function to always return 1, we edit the class' "prototype". Simply name the function, object or variable that you want to modify in the brackets and replace it's code :

   1 Foo.prototype['_Bar'] = function() { return 1; }

Adding to functions

Another way to extend functionality is to inject code, with these functions:

   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 }

To use them :

   1 // We store the injection, to have the option to disable it later
   2 inections=[];
   3 // Inject the code
   4 injections['injection1']=injectToFunction(Foo.prototype, '_Bar',  function(){return 1;});
   5 // To "de-inject" the code
   6 removeInjection(Foo.prototype, injections,  'injection1');

With this, we can add some code to the function without replacing the whole function. Our code is written after all code of the original function but before it's return statement.

GNOME's UI Elements

Unfortunately, I don't have the patience to go through all these files and describe in details what they do how. If you want something a bit more complete, but outdated, you can go to Mathematical Coffee's excelent blog post on the subject.

UI files in imports.ui

UI files in imports.ui.components

UI files in imports.ui.status

This contains the files for all the standard status indicators in the status area.


2024-10-23 11:37