Skip to main content

How to Create a Tiny Gnome Panel Application

On every Gnome Shell desktop there is a main panel at the top of the screen. Usually, the panel shows the current time, the WiFi signal, the speaker volume, the power button, and the activity startup button. There can be many other things on the panel as individual user penchants. The panel will not be covered by anything, and will be there at all time except when there is a full-screen application running. The Gnome community has decided that the whole humanity needs the panel.

What little things of texts and icons on the panel are called Gnome Shell extensions. Each of them is a kind of a desktop application which always run through out a user session. They can be personalized.

We are going to create a simple and useless application that shows a running counter, something like the one that shows '1473' on the panel image above. It can be a starting point for a bigger and productive application. But before we begin let's get familiar with a collection of handy Gnome tools.

Gnome Command Dialog

On a Gnome Shell desktop, press Alt-F2. A dialog box will appear for us to enter a Gnome command. We can simply press Esc to exit the dialog, or to exit any Gnome dialog for that matter.

Gnome Looking Glass

Press Alt-F2 and key in lg (small L and small G) into the field and press Enter. The Gnome Looking Glass dialog will open. The Evaluator field at the bottom of the dialog will evaluate any Gnome Javascript (gjs) expression.

Click on Extensions at the top right corner of the dialog. It will show the list of existing extensions. We want our application to be in the list.

Restarting Gnome Shell Desktop

During our development we will need to restart the Gnome Shell desktop. Press Alt-F2 and enter r. The desktop will take a moment to restart. Don't worry. All our windows will still be there. We need to restart the desktop every time we make changes so that the desktop will reload our application.

Gnome Tweak Tool

We can tweak Gnome Shell according to our liking. But most importantly we need a tool to enable or disable our application. Press Alt-F2 and enter gnome-tweak-tool. The Gnome Tweak Tool dialog will open. On the left hand side menu, click Extensions. The list of extensions will appear. We can enable or disable any extension, or we can remove any of them.

We may need to install the Gnome Tweak Tool:

$ sudo apt install gnome-tweak-tool

Debugging

Before we start programming we need a method to debug. Gnome Shell keeps activity log including error messages. Messages related to our application will also appear in the log. Gnome Shell log can be monitored from a terminal. Execute the following command. It will print existing messages in the log and wait for new messages:

$ sudo journalctl /usr/bin/gnome-session -f

Our application can post messages into the log with the following command. We can also log debugging messages:

    global.log("Application message");

Generate a Hello, world! Extension

Gnome Shell has a tool to create a new extension. Execute the following command:

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

We will be asked to give the new extension a name, description and uuid. Let's assume the details are as follows:

  • name : Counter
  • description : A useless counter
  • uuid : counter@me.useless.com
Once the details are entered, a new extension will immediately be created, and the new extension.js file will open in a default editor.
const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

let text, button;

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

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

    text.opacity = 255;

    let monitor = Main.layoutManager.primaryMonitor;

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

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

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

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

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

function disable() {
    Main.panel._rightBox.remove_child(button);
}
More information on the code generation together with code annotation can be found at the Gnome wiki

The newly created extension can be found here:
~/.local/share/gnome-shell/extensions/
We can enable the new extension using the tweak tool. And restart the desktop once we have enabled it. A new gear icon will appear on the panel. Clicking on the icon will flash a Hello, world! at the center of the screen.

On starting up our extension, Gnome Shell will call the init() function. When we enable the extension, Gnome Shell will call the enable() function which in the code above will add a button into the panel. And when we disable the extension, Gnome Shell will call the disable() function which in the code above will remove the button from the panel.

Now we are ready to edit the extension.js file to add a useless counter.

Useless Counter

Let's show something a little bit extra. The Hello, world! extension has been fully generated for us, and we didn't even write a single line of code. Who said creating a Gnome panel application is hard?

We are going to add a timer. Unfortunately, a Gnome Shell extension is not an ordinary web-based Javascript application. We cannot use the ordinary Javascript timer. Gnome Javascript is only a wrapper to the Gnome C library. Gnome Javascript makes Gnome Shell extension development faster without having to go through the C build process.

We are going to use a Gnome timer. We are going to count up every 10 seconds and show it on the panel. The counter will start when the extension is enabled.

However, before we implement the timer we need a text label to replace the gear icon so that we can show the counter. Replace the following code in the init() function:
    let icon = new St.Icon({ icon_name: 'system-run-symbolic',
                             style_class: 'system-status-icon' });

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

with this one:
    label = new St.Label({text: "Counter"});    
    button.set_child(label);
    button.connect('button-press-event', _showHello);
and make label a global variable:
let label;
Side note: In case you want to make a button with an icon like in the original code above, and you want to know the names of all available icons like system-run-symbolic  then you need to install  gtk-3-examples package, and run gtk3-icon-browser:
$ sudo apt-get install gtk-3-examples
$ gtk3-icon-browser

A timer is a loop. However, Gnome Shell is event-driven which means a long loop in an application may hold up other applications. Hence, a loop is highly discouraged. A timer can be implemented using a Gnome timeout function which will wait for a duration of time and then execute a callback function. The trick is that we will call the timeout function inside the callback function where it will run almost recursively and behave like a timer. We will increment the counter and update the panel with the new value from within the callback function. We will name the callback function refresh(). The refresh() function will return immediately. The Gnome timeout function will call it again after 10 second. And the process repeats like a timer. Add the refresh() function to the code:
function refresh() {
    label.set_text(count.toString());
    ++count;	
    if (timeout != null)
        Mainloop.source_remove(timeout);
    timeout = Mainloop.timeout_add_seconds(10, refresh);
    return true;
}
and make timeout a global variable:
let timeout;
We need the object held by timeout later when we want to stop the timer. We must stop the timer when Gnome calls the disable() function.

The refresh() function uses Mainloop's timeout function. We need to import Mainloop into the code. Add the following line somewhere at the top of the code before any function or variable declaration:
const Mainloop = imports.mainloop;
Mainloop is referring to the Gnome main event-loop that manages all events in the Gnome environment. One of the event comes from a timeout source which can be created via the Mainloop. Both Mainloop and timeout run on separate threads.

Let's emphasize, this is not the ordinary Javascript. Web-based Javascript works with the browser's document object model (DOM). Gnome Javascript works with the Gnome environment.

The timer will be started by the enable() function. Add the following line to the end of the enable() function:
    refresh();
And we need to stop the timer when the disable() function is called. Add the following lines to the end of the disable() function:
    if (timeout != null)
        Mainloop.source_remove(timeout);
Restart the Gnome Shell desktop. A counter will replace the gear icon, and will count up every 10 seconds. That's it. We have implemented a useless counter application on the Gnome Shell desktop panel.

What's Next?

Let's get in-depth with the Gnome Shell desktop and its extensions through the official documentations in the wiki.

The panel is there because we want to be constantly notified with many things. Many little things can be positioned on the panel so that we don't have to switch contexts by opening a different window or a dialog box looking for them, or worst, searching the whole Internet for them. There are many little things waiting to be developed. Come on! We can implement something really useful.



Comments

Popular posts from this blog

Setting Up PyScripter for Quantum GIS

PyScripter is a general purpose Python Integrated Development Environment (IDE). Quantum GIS (QGIS) is a desktop GIS application that can be extended with Python plugins. Both are open source softwares. We intend to use PyScripter as an IDE to build QGIS Python plugin. We are using PyScripter 2.4.1.0 and QGIS 1.6.0 in Windows. PyScripter does not come with Python. On the other hand, QGIS is built in with Python. Thus, we will setup up PyScripter to use the build in Python in QGIS. We assume both PyScripter and QGIS are already installed. Preparing PyScripter batch file We assume that QGIS is installed in C:\OSGeo4W\ folder and PyScripter is installed in C:\Program Files\PyScripter\ . 1. Copy qgis.bat in C:\OSGeo4W\ bin to pyscripter.bat 2. Edit pyscripter.bat to remove the last line that read something like this start "Quantum GIS" /B "%OSGEO4W_ROOT%"\apps\qgis\bin\qgis.exe %* and replace it with this in one line Start "PyScripter" /B "C:\Progr

Sending Emails via SMTP

msmtp  requires a minimal setup for sending emails via SMTP compared to sendmail. Here is a configuration for you to send emails from a web host to an external SMTP server. Prior to doing that, you must check whether there is a clear communication channel between your web host and the SMTP server. You can use Telnet . Set up msmtp You are going to set msmtp as an MTA . Hence, you need to remove all other MTAs such as postfix and sendmail: $ sudo apt-get --purge autoremove postfix sendmail Install msmtp and related utilities: $ sudo apt-get install msmtp msmtp-mta mailutils Configure msmtp: $ sudo nano /etc/msmtprc # Set default values for all following accounts. defaults # Use the mail submission port 587 instead of the SMTP port 25. port 587 # Always use TLS. tls on # Set a list of trusted CAs for TLS. The default is to use system settings, but # you can select your own file. tls_trust_file /etc/ssl/certs/ca-certificates.crt # The SMTP server account mx host mail.mx.example

fatal: Couldn't find remote ref master

If you are using Github then  master is now known as main . Whatever you want to do with a master must now be referred to a main . If you search for this error message on the Internet then you will encounter with a lot of old discussions on how to set up your master properly which is probably not what you are looking for. The master  is your problem. Rename it to main . I wrote Git My Way about two years ago. Today I created another Github repository. I got this  "fatal: Couldn't find remote ref master"  error message when I wanted to sync the new repo for the first time with my notebook using the notes I wrote in the blog. All the discussions around the error message I found on the Internet were perplexing. Then I recalled that Github had renamed master to main  due to the master-slave connotation. We always have a master copy of a code, never a slave copy. Now suddenly a word context has been diminished for good. What is going to happen to the existing vast documen