← Volver a los artículos

Gnome: Desarrollo de aplicaciones. La aplicación

Artículo Desarrollo Gnome
Segundo artículo de la serie sobre desarrollo de apps en GNOME con Vala: repasa meson.build, config.vala.in, la UI, el ciclo de vida (startup, activate, open, shutdown) y los ficheros de integración con el escritorio (desktop, gschema, metainfo, D-Bus).
Gnome: Desarrollo de aplicaciones. Estructura de ficheros
Vamos a comenzar a construir el core de esta aplicación y al final de este artículo deberíamos poder compilar y ejecutar nuestra aplicación que nos debería mostrar una ventana con un mensaje de texto. 
No hay un orden específico para configurar los ficheros, si has usado Builder de Gnome le habrás dado un nombre al proyecto y un id y te habrá generado todos estos ficheros. Cada fichero tiene su función dentro de la estructura de la  aplicación.
Los ids de aplicaciones para Gnome llevan un formato de dominio invertido. Esto es para poder identificar de forma unívoca la app. En nuestro caso vamos a usar io.github.agustingv.example. (Si se tiene alojado el código en github y se quiere usar el dominio de github como parte del id de la aplicación hay que usar io en vez de com para que flatpak no lo rechace)

meson.build
Nuestro principal fichero de construcción en el vamos a definir como se va a compilar el proyecto, los argumentos de compilación y los subdirectorios que tiene que mirar. En cada subdirectorio que definamos debería existir otro fichero meson.build que defina las reglas, como las posibles dependencias, que necesita para realizar la construcción de la aplicación.

En este ejemplo solo accedemos a una propiedad definida en project que es meson.project_name() 

    project('example', 'vala', 'c',
      version: '1.0',
      meson_version: '>= 0.59.0',
      default_options: [
        'buildtype=debug',
        'warning_level=2',
        'default_library=static'
      ],
    )
     
    gnome = import('gnome')
    i18n = import('i18n')
     
    example_name = meson.project_name() 
     
    add_project_arguments(
      ['-DGETTEXT_PACKAGE="' + example_name + '"',
      '-w'],
      language: 'c',
    )
     
    subdir('data')
    if find_program('msgfmt', required: false).found()
      subdir('po')
    endif
    subdir('src')

src/config.vala.in

Como ya hemos comentado en un artículo anterior, Vala genera código C que luego se compila y para las constantes de configuración vala necesita generar las instrucciones de preprocesador de C. Hay dos formas de realizar esto. Creando un VAPI o creando un namespace en Vala e indicando en el meson.build la definición de las constantes. En este caso, para simplificar las cosas vamos a usar el namespace.

    namespace Config {
        public const string GETTEXT_PACKAGE = "@GETTEXT_PACKAGE@";
        public const string LOCALE_DIR      = "@LOCALE_DIR@";
    }

data/ui/main-window.ui

Los ficheros ui contienen el xml que define la interfaz de usuario, en los xml se hace referencia a los objetos definidos por Gtk o propios de la aplicación.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="ExampleMainWindow" parent="AdwApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child>
      <object class="GtkLabel" id="label">
        <property name="label" translatable="yes">Hello</property>
      </object>
    </child>
  </template>
</interface>

data/io.github.agustingv.example.gresource.xml

Este fichero registra los recursos de ui de la aplicación. En este caso solo tenemos data/ui/main-window.ui.

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/io/github/agustingv/example">
        <file preprocess="xml-stripblanks">ui/main-window.ui</file>
      </gresource>
    </gresources>

src/meson.build

Build de meson (ubicación de ficheros y dependencias) para el directorio src.
resources = gnome.compile_resources(
  'example-resources',
  '../data/io.github.agustingv.example.gresource.xml',
  source_dir: '../data',
  c_name: 'example',
)

config_vala = configure_file(
  input:  'config.vala.in',
  output: 'config.vala',
  configuration: {
    'GETTEXT_PACKAGE': meson.project_name(),
    'LOCALE_DIR': get_option('prefix') / get_option('localedir'),
  },
)

sources = files(
  'main.vala',
  'application.vala',
'main-window.vala' ) + [config_vala] dependencies = [ dependency('gtk4'), dependency('libadwaita-1', version: '>= 1.4'), dependency('gmodule-2.0'), meson.get_compiler('c').find_library('m', required: false), ] executable('example', sources + resources, dependencies: dependencies, vala_args: ['--target-glib=2.76', '--pkg=gmodule-2.0'], install: true, )


src/main.vala

Archivo principal que se encarga de iniciar la aplicación. Se puede usar cualquier nombre de fichero para esto ya que es el propio código el que se define como main (Recordemos que esto se ejecuta como código C y en C la función main es la que inicia el programa)

    int main (string[] args) {
        GLib.Intl.setlocale (LocaleCategory.ALL, "");
        GLib.Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALE_DIR);
        GLib.Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
        GLib.Intl.textdomain (Config.GETTEXT_PACKAGE);
     
        var app = new Example.Application ();
        return app.run (args);
    }

src/application.vala

Este va a ser nuestro archivo principal donde se define el comportamiento de nuestra aplicación y su ciclo de vida. 

CicloDescripción
startupSe ejecuta al arrancar la Adw.Application llama automáticamente a adw_init() en esta fase. Carga las hojas de estilos CSS personalizadas y define los atajos globales.
activateSe dispara cuando la aplicación es lanzada o se invoca una nueva instancia sin argumentos principales. Es el momento adecuado para crear la ventana principal, asignarle contenido y llamar al método para mostrarla.
openSe ejecuta si la aplicación se invoca desde la línea de comandos pasando archivos o parámetros para abrir directamente.
shutdownOcurre cuando la última ventana se cierra y la aplicación se dispone a salir del bucle principal. Momento para liberar recursos o guardar estados persistentes si no se hizo de forma automática.
namespace Example 
{

    public class Application : Adw.Application 
    {

        public Application () 
        {
            Object (
                application_id: "io.github.agustingv.example",
                flags: ApplicationFlags.DEFAULT_FLAGS
            );
        }

        construct 
        {

        }

        protected override void activate () 
        {
            var win = this.active_window;
            if (win == null) {
                win = new Example.MainWindow (this);
            }
            win.present ();
        }

    }
}


src/main-window.vala


Definición de la ventana principal
namespace Example {

    [GtkTemplate (ui = "/io/github/agustingv/example/ui/main-window.ui")]
    public class MainWindow : Adw.ApplicationWindow {

        public MainWindow (Gtk.Application app) {
            Object (application: app);
        }

        construct 
        {
        }

    }
}

data/io.github.agustingv.example.desktop.in

Definición de nombre de la aplicación y icono para el escritorio y dock
[Desktop Entry]
Name=Example
Exec=Example
Icon=io.github.agustingv.example
Terminal=false
Type=Application
Categories=Utility;
Keywords=GTK;
StartupNotify=true
DBusActivatable=true


data/io.github.agustingv.example.gschema.xml

Este es fichero que va a tener la estructura de la configuración de nuestra aplicación que se puede almacenar, modificar y recuperar. En un principio no vamos a necesitar ninguna configuración pero aquí se podría definir, por ejemplo, la geometría de la ventana. Las configuraciones de una aplicación instalada se suelen almacenar en .local/share. Depende si usas flatpak o no estará en una ruta u otra dentro de .local/share.
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="example">
    <schema id="io.github.agustingv.example" path="/io/github/agustingv/example/">
    </schema>
</schemalist>


data/io.github.agustingv.example.metainfo.xml.in

<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
  <id>io.github.agustingv.example</id>
  <metadata_license>CC0-1.0</metadata_license>
  <project_license>GPL-3.0-or-later</project_license>

  <name>Example</name>
  <summary>Keep the summary shorter, between 10 and 35 characters</summary>
  <description>
    <p>No description</p>
  </description>

  <developer id="tld.vendor">
    <name>Developer name</name>
  </developer>

  <!-- Required: Should be a link to the upstream homepage for the component -->
  <url type="homepage">https://example.org/</url>
  <!-- Recommended: It is highly recommended for open-source projects to display the source code repository -->
  <url type="vcs-browser">https://example.org/repository</url>
  <!-- Should point to the software's bug tracking system, for users to report new bugs -->
  <url type="bugtracker">https://example.org/issues</url>
  <!-- Should link a FAQ page for this software, to answer some of the most-asked questions in detail -->
  <!-- URLs of this type should point to a webpage where users can submit or modify translations of the upstream project -->
  <url type="translate">https://example.org/translate</url>
  <url type="faq">https://example.org/faq</url>
  <!-- Should provide a web link to an online user's reference, a software manual or help page -->
  <url type="help">https://example.org/help</url>
  <!-- URLs of this type should point to a webpage showing information on how to donate to the described software project -->
  <url type="donation">https://example.org/donate</url>
  <!-- This could for example be an HTTPS URL to an online form or a page describing how to contact the developer -->
  <url type="contact">https://example.org/contact</url>
  <!-- URLs of this type should point to a webpage showing information on how to contribute to the described software project -->
  <url type="contribute">https://example.org/contribute</url>

  <translation type="gettext">example</translation>
  <!-- All graphical applications having a desktop file must have this tag in the MetaInfo.
     If this is present, appstreamcli compose will pull icons, keywords and categories from the desktop file. -->
  <launchable type="desktop-id">io.github.agustingv.example.desktop</launchable>
  <!-- Use the OARS website (https://hughsie.github.io/oars/generate.html) to generate these and make sure to use oars-1.1 -->
  <content_rating type="oars-1.1" />

  <!-- Applications should set a brand color in both light and dark variants like so -->
  <branding>
    <color type="primary" scheme_preference="light">#ff00ff</color>
    <color type="primary" scheme_preference="dark">#993d3d</color>
  </branding>

  <screenshots>
    <screenshot type="default">
      <image>https://example.org/example1.png</image>
      <caption>A caption</caption>
    </screenshot>
    <screenshot>
      <image>https://example.org/example2.png</image>
      <caption>A caption</caption>
    </screenshot>
  </screenshots>

  <releases>
    <release version="1.0.1" date="2024-01-18">
      <url type="details">https://example.org/changelog.html#version_1.0.1</url>
      <description translate="no">
        <p>Release description</p>
        <ul>
          <li>List of changes</li>
          <li>List of changes</li>
        </ul>
      </description>
    </release>
  </releases>

</component>

data/io.github.agustingv.example.service.in

Definición del D-Bus para la comunicación de la aplicación. El D-bus se usa para comunicación entre aplicaciones, servicios y el entorno de escritorio. Por ejemplo; las notificaciones que muestran que te ha llegado un mail nuevo o que alguien te ha escrito un mensaje por slack se realizan a través del D-bus. 
[D-BUS Service]
Name=io.github.agustingv.example
Exec=@bindir@/example --gapplication-service


po/LINGUAS

En este fichero se definen los lenguajes de uso de nuestra aplicació.

es


po/POTFILES

Ficheros que llevan textos traducidos. Todos los ficheros que usen gettext para traducir un texto deben ir definidos aquí.

data/ui/main-window.ui


po/es.po

Fichero de traducciones.

# UI strings — main window
msgid "Helo, my first Gnome app"
msgstr "Hola, mi primera app para Gnome"


po/meson.build

Poco más que definir el uso de la internacionalización.

i18n.gettext(meson.project_name(),
  preset: 'glib',
)


Compilar y ejecutar la aplicación.

Generar los ficheros en C y los guiones de compilación para ninja:

meson setup build


Compilar el proyecto con ninja:

ninja -C ./build


Ejecutar el proyecto:

./build/src/example


[articulo slug="gnome-desarrollo-de-aplicaciones-menus-y-handlers"]