Crea tu propio indicador para Ubuntu

Este es uno de los capítulos del tutorial Crea tu propia extensión para GNOME Shell. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.

Una de las características mas comunes en cualquier entorno de escritorio con independencia del sistema operativo que utilices es el área de notificaciones. El área de notificación es la parte del panel superior de Ubuntu donde se sitúan accesos directos a programas y otros elementos. Algunos de esos elementos son los que nos permiten gestionar la conexión a internet, el menú de sonido y la batería. Es precisamente para el área de notificaciones donde puedes crear tu primera extensión para Ubuntu. Se trata de crear tu propio indicador para Ubuntu pero como extensión de GNOME Shell.

Los indicadores de aplicación son sencillas herramientas que permiten mostrar diferente información. Tradicionalmente, un indicador estaba relacionado con una aplicación, y te mostraba información de la misma. Sin embargo, con el paso del tiempo, se han convertido en una fuente de información en si misma. Es decir, un indicador para Ubuntu, nos ofrece información de lo mas variopinta. Nos puede informar sobre la situación meteorológica, el uso de la red, o cualquier otro aspecto que te puedas imaginar.

Así, crear tu propio indicador para Ubuntu, es algo relativamente sencillo y que puede mejorar la experiencia de usuario. Se trata de una forma de potenciar el escritorio, ayudando al usuario con información adicional. Y como he indicado anteriormente, esta información puede ser de lo mas variopinta.

Vamos allá… vamos a crear nuestro propio indicador para Ubuntu en GNOME Shell…

Crea tu propio indicador para Ubuntu

Crear nuestro propio indicador para Ubuntu

La estructura básica de una extensión de GNOME Shell

Una extensión para GNOME Shell está compuesta como mínimo de dos archivos. Como un indicador para Ubuntu no es mas que una extensión para GNOME Shell, nuestro propio indicador para Ubuntu, tan solo necesitará de dos archivos.

Aunque como mencioné en el primer capítulo de este tutorial, existe una herramienta que nos permite crear una extensión gnome-shell-extension-tool, vamos a hacerlo todo a mano.

Recordarte que las extensiones para GNOME Shell se implementan en GNOME JavaScript que está basado en SpiderMonkey.

SpiderMonkey

SpiderMonkey es el motor JavaScript de Mozilla implementado en C y C. Lo utilizan diferentes proyectos de Mozilla. Como te puedes imaginar, una de los proyectos que utilizan SpiderMonkey es Firefox.

La metainformación

El primero de los dos archivos mínimos indispensables para una extensión de GNOME Shell es metadata.json. Este archivo contiene la información mínima indispensable para el correcto funcionamiento de nuestra extensión. Además nos permite guardar información que podemos utilizar en los diferentes módulos que pueden constituir nuestra extensión de GNOME Shell.

Este archivo tiene formato json, tal y como puedes ver a continuación,

{
 "uuid": "power-commands@atareao.es",
 "name": "Power commands",
 "description": "Herramientas para gestionar Ubuntu",
 "shell-version": ["3.26", "3.28"],
 "url": "https://www.atareao.es",
 "version": 1
}

Algunos de estos parámetros se podrían obviar, como puede ser description o url, sin embargo son de utilidad para el usuario final, puesto que aportan información.

  • El uuid es un identificador único. Puede ser cualquiera, pero te debes asegurar que sea único. Normalmente se suele hacer como nombre-de-la-extensión@pagina.es. Donde pagina.es es la página web donde hay información de la extensión. Esto es un criterio ampliamente extensido, pero no es necesario que lo sigas.
  • El parámetro name se refiere al nombre de la extensión. Puede ser cualquiera.
  • A continuación, dentro de la lista de claves del archivo metadata.json, es description. Este parámetro es la descripción de nuestra extensión. Aquí podemos no solo explicar con mas detalle en que consiste nuestra extensión, sino que además podemos explicar como utilizarla.
  • La variable url se refiere a la página web de la extensión. Preferentemente deberías de indicar allí la página web donde explica el uso y funcionamiento de la extensión.
  • Por último, el parámetro shell-version indica en que versiones de GNOME Shell funciona nuestra extensión. Dado que JavaScript para GNOME Shell evoluciona de forma rápida, muchas extensiones han dejado de funcionar. Los desarrolladores de extensiones para GNOME Shell tienen un importante trabajo de mantenimiento si quieren que sus extensiones funcionen en las diferentes versiones de GNOME Shell. Con este parámetro debes indicar para que versiones es compatible tu extensión. Es posible obviar la comprobación de versión utilizando dconf-editor y modificando el parámetro /org/gnome/shell/disable-extension-version-validation. Esto también lo puedes modificar desde el terminal,
gsettings set org.gnome.shell disable-extension-version-validation "true"

La extensión

El segundo de los archivos que constituyen nuestra extensión para GNOME Shell es extension.js. Este archivo está implementado en JavaScript, y debe tener al menos tres funciones.

  • init. Es la función que se utiliza para inicializar la extensión.
  • enable. Esta función se ejecuta cuando se habilita la extensión.
  • disable. Por último, esta función se ejecuta al deshabilitar la extensión. Esta función se tiene que encargar de detener todo aquello que nuestra extensión haya puesto en marcha. Pero no solo esto, además tiene que restaurar el sistema al estado previo. Es decir, esta función es la encargada de dejar el sistema como si nuestro extensión nunca hubiera estado allí.
imports.gi.versions.St = "1.0";

const St = imports.gi.St;

const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const Main = imports.ui.main;

class PowerCommandsButton extends PanelMenu.Button{
    constructor(){
        super(St.Align.START);

        let box = new St.BoxLayout();

        let icon = new St.Icon({ icon_name: 'emblem-system',
                                 style_class: 'system-status-icon' });
        box.add(icon);
        this.actor.add_child(box);

        let item = new PopupMenu.PopupMenuItem('Salvapantallas');
        item.connect('activate',  ()=>{
            Util.spawn(['gnome-screensaver-command', '--activate']);
        });
        this.menu.addMenuItem(item);
    }
}

let powerCommandsButton;

function init(){
}

function enable(){
    powerCommandsButton = new PowerCommandsButton();
    Main.panel.addToStatusArea('PowerCommandsButton', powerCommandsButton, 0, 'right');
}

function disable() {
    powerCommandsButton.destroy();
}

El código en detalle…

Vamos a meternos en harina desgranando con detalle las diferentes partes del código. El primer paso que hacemos es importar aquellos módulos que necesitaremos para nuestra extensión. Aunque antes de ello nos vamos a asegurar de la librería que la librería que utilizaremos. Esto se corresponde con la primera línea,

imports.gi.versions.St = "1.0";

A continuación importamos las librerías necesarias,

const St = imports.gi.St;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;

Y posteriormente comenzamos con la declaración de la clase. Como ves, en poco mas de diez líneas de código hemos creado nuestro indicador de aplicación. Esta clase hereda de PanelMenu.Button, que es la define este tipo de objetos. Aquí hemos de destacar dos elementos. Por un lado la parte que se mostrará directamente en el panel superior, y a la que simplemente hemos añadido un icono St.Icon dentro de una caja St.BoxLayout. Aunque también podríamos ayer añadido un texto junto al icono St.Label.

ClutterActor

La documentación sobre St, la puedes extraer del manual de referencia de GNOME, aunque aquí está orientado para C. Sin embargo, es suficientemente clara para tus propósitos. De esta manera St.BoxLayout es un contenedor que nos permite colocar varios elementos en una línea. Existen otros tipos de contenedores como son St.Bin, que solo nos permite colocar un único elemento o St.ScrollView que nos permite colocar elementos que admitan desplazamiento. Todos estos elementos derivan de ClutterActor, que es el elemento básico. Es importante no perderle la pista y tener su documentación cerca, no solo para tener claras propiedades y métodos, sino también para conocer las señales bajo las que interactua.

PanelMenu.Button

Nuestra clase deriva de PanelMenu.Button. Y aquí es donde nos encontramos con el primer problema… la falta de documentación. Lo mas sencillo es recurrir al código fuente de GNOME Shell, y darle un vistazo para conocer los métodos y propiedades de cada una de las clases. En el caso de la clase que nos ocupa tenemos dos elementos fundamentales que son actor y menu.

El constructor para PanelMenu.Button es constructor(menuAlignment, nameText, dontCreateMenu). En nuestro caso, al heredar utilizamos super(St.Align.Start) para llamar al constructor de la clase padre, pero solo pasamos el parámetro de la alineación del menú, el resto de parámetros no se pasan.

Por último añadimos elementos al menú. En nuestro caso hemos añadido un único que es el que se encarga de lanzar la herramienta xkill. Para ejecutar la herramienta utilizamos la función spawn del módulo misc.utils. La documentación de este módulo la puedes encontrar también en el código fuente. Esta función lo que hace es ejecutar la orden que le pasemos, en segundo plano, gestionando los posibles errores que ocurran.

Para crear cada uno de los elementos del menú, tendremos que declararlos y añadirlos al menú, y por supuesto, en su caso, asignarles la acción que debe ejecutar al activarlo.

Iniciar, habilitar y deshabilitar la extensión

Ahora que ya hemos definido nuestro indicador solo nos queda definir las tres funciones que necesita una extensión para funcionar. Estas tres funcionesa lee comentao antrme, vamos a entrar ahora en detalle en cada una de ellas.

  • init. En el ejemplo que nos ocupa esta función no tiene contenido, puesto que nuestra extensión no necesita ninguna inicialización. Como veremos en capítulos posteriores del tutorial, aquí es donde debemos ubicar lo necesario para internacionalizar nuestra extensión.
  • enable. Aquí es donde hemos creado el indicador, y lo situamos en el panel superior. Esta función se ejecutará cuando habilitemos la extensión desde la aplicación Retoques.
  • disable. En esta función destruimos el indicador. La función se ejecutará cuando deshabilitemos la extensión desde la aplicación Retoques.

Conclusiones

Con esto ya tenemos realizada nuestra primera extensión. Como ves, es algo realmente sencillo de hacer, y en pocos pasos tienes tu propia extensión en funcionamiento.

Para que te resulte mas sencillo seguir este tutorial he creado un repositorio en GitHub para esta extensión Power Commands. He etiquetado el código de forma que cada versión se corresponde con un capítulo. Así la versión 1.0 se corresponde con el capítulo 1, la versión 2.0 se corresponde con el capítulo 2 y así sucesivamente… Espero que te sea de utilidad.

Sin embargo, vemos que nos faltan algunos elementos interesantes para poder distribuir nuestra extensión. Por un lado está la posibilidad de tener elementos que sean configurables por el usuario, es decir, que nuestra extensión sea personalizable por el usuario. Por otro lado, está la internacionalización de nuestra extensión, para que se puedan utilizar en otros idiomas. Estos dos aspectos los trataremos en posteriores tutoriales.

Ahora solo queda que te lances a la creación de tu propia extensión. Visto lo sencillo que es, no tienes excusas para no comenzar a hacerla en cuanto termines de leer este segundo capítulo del tutorial.


Más información,