Язык программирования Vala и где он используется. Создаем простое приложение для прослушивания радио

929e8f83510a303567afa97cb172963e.jpg

Язык программирования Vala и где он используется. Создаем простое приложение для прослушивания радио

Для создания приложений с GUI под операционные системы GNU/Linux можно использовать практически любой язык программирования. Обычно раньше использовался язык C, но он не поддерживает объектно-ориентированное программирование и является довольно громоздким и многословным.

C++ более удобен в плане написания прикладных программ. Он, в отличие от C, уже имеет поддержку ООП и более лаконичен. 

На языках С и С++ можно писать не только под ОС Linux, но и под другие операционные системы. 

Для GTK было создано множество языковых привязок. Кроме уже названых C и C++ для написания GTK-приложений можно использовать C#, JavaScript, Python и Rust. Дополнительно существуют проекты, благодаря которым можно писать на Java или PHP.

Vala был специально создан для более простой и быстрой разработки приложений с использованием библиотеки GTK. Этот язык уникален тем, что код при выполнении программы транслируется в код на языке C, а уже потом преобразуется в машинный код. Благодаря этому скорость выполнения программы на Vala схожа со скоростью этой же программы на языке C.

Vala значительно упрощает создание приложений. Он многое вобрал в себя от таких известных языков, как Java и C#. В Vala доступны практически все современные техники программирования. На этом языке можно писать не только прикладной софт, но и системные программы.

Существуют неофициальные сборки компилятора для Windows, поддерживаемые сторонними разработчиками. Особой популярностью они не пользуются.

Примеры простых программ

Как же обойтись без helloworld-ов в статье об языке программирования? Я тоже не буду отходить от этой традиции. Вот простой пример программы без ООП:

void main()
{
  print("Hello, World\n");
}

А в этом примере уже применяются принципы ООП:

class Sample
{
    void run()
    {
        stdout.printf("Hello, World\n");
    }
 
    static int main(string[] args)
    {
        var sample = new Sample();
        sample.run();
        return 0;
    }
}

Ниже приводится пример простейшей программы с GUI:

using Gtk;
 
int main(string[] args)
{
    Gtk.init(ref args);
 
    var window = new Window();
    window.title = "Hello, World!";
    window.border_width = 10;
    window.window_position = WindowPosition.CENTER;
    window.set_default_size(350, 70);
    window.destroy.connect(Gtk.main_quit);
 
    var label = new Label("Hello, World!");
 
    window.add(label);
    window.show_all();
 
    Gtk.main();
    return 0;
}

Более подробную информацию об этом языке можно найти на сайте gitbook.io. Здесь помимо теории найдется и множество практических примеров. А на сайте valadoc.org находится официальная документация к языку. Описание всех классов, объектов и методов, а также примеры их использования — все это можно найти на указанном сайте. У языка с недавних пор имеется и свой вполне современный сайт.

Также существует такой проект, как Elementary, который развивает одноименную операционную систему и свой набор приложений, написанных на Vala. То есть этот язык, по сути, является официальным языком разработки для проекта. На этом сайте можно ознакомиться с полным списком приложений, специально созданных для Elementary.

На Vala уже создано множество полезных приложений. Например, Boxes — это программа для создания и управления виртуальными системами. Gitg — простой графический git-клиент. Dino — современная программа для обмена сообщениями. Monitor — простой системный монитор.

Для разработки рекомендуется использовать интегрированную среду Builder. Она является официальной IDE для создания приложений под окружение рабочего стола GNOME. Еще можно установить редактор Visual Studio Code со специальным расширением. Дополнительно я еще использую вот это расширение, которое позволяет запускать приложение сразу в песочнице.

Разработчики из GNOME Foundation рекомендуют распространять свои приложения в

виде самодостаточных пакетов flatpak и публиковать их на сервисе Flathub. Для этого в среде разработки Builder шаблоны проектов уже содержат манифест для создания flatpak. Разработчику лишь остается правильно его заполнить. Обычно это сводится к прописыванию разрешений и добавлению необходимых модулей.

Радио. Начало разработки

В этом посте я хотел бы привести примеры из собственного опыта создания программ на языке программирования Vala. Рассмотрим приложение Radio, репозиторий которого можно найти по этому адресу.

Писалось оно на основе другого моего приложения. Его репозиторий можно найти здесь.

Итак, передо мной стояла задача создать простое приложение для прослушивания онлайн-радиостанций. В приложение должен быть интегрирован поиск станций с сайта radio-browser.info. Из результатов поиска станции должны в один клик добавляться в список избранных. Также необходимо предусмотреть и добавление станций в ручном режиме, через простую форму, где пользователь может ввести название станции и адрес ее потока вещания. Приложение должно быть написано на GTK4 и libadwaita, то есть являться полноценным приложением для среды рабочего стола GNOME.

Понятно, что дополнительно нужно предусмотреть и возможность изменения данных станций, а также возможность их удаления.

Код для получения данных с сервера я взял из приложения стороннего разработчика. Называется оно Tuner, и, по сути, я собрался писать его упрощенный аналог. 

Из всех данных мне понадобятся только название станции и ее адрес потока. Я решил отказаться от использования фавиконов и прочих атрибутов. Вследствие этого код значительно сокращается по объему.

Пользовательский интерфейс можно создавать несколькими способами. Для визуального конструирования интерфейса существует приложение Cambalache. Еще есть возможность использовать Blueprint, который отличается непревзойденной лаконичностью и позволяет буквально в несколько строк описать весь UI. С документацией по этому проекту можно ознакомиться здесь.

Я все же выбрал писать интерфейс простым vala-кодом. Мне так привычнее. Все описание интерфейса находится в файле MainWindow.vala в construct. Сам UI приложения довольно простой и минималистичный. Все в духе философии GNOME!  

Главная страница приложения:

Текстовая метка с надписью «Добро пожаловать!» — это часть контейнера Gtk.Box с вертикальной ориентацией, состоящего из двух частей. В верхней части бокса находится уже упомянутая метка с приветствием. В ней отображаются названия станций. В нижней части контейнера располагается другая метка. В ней выводятся заголовки трансляций. Нижняя метка показывается, только если в ней имеется какое-то содержимое. В случае если метка пуста, она находится в скрытом состоянии.

Поиск станций

После запуска программы пользователь видит перед собой список из двухсот станций. Я просто не вижу смысла загружать больше. Этот лимит прописан в методе search:

public ArrayList search (string text) throws DataError {
        var resource = @"json/stations/search?limit=200&offset=0";
        if (text != null && text != "") {
            resource += @"&name=$(text)";
        }
        return get_stations (resource);
    }

Этот метод используется для формирования списка станций согласно поисковому запросу пользователя, который содержится в переменной text. Сам метод вызывается из метода show_stations:

var stations = new Gee.ArrayList();
          try{
          var client = new Client();
          if(search_box.is_visible()){
            stations = client.search(entry_search.get_text().down());
         }else{
            stations = client.search("");
        }

Далее объект stations разбирается на составляющие при помощи цикла foreach. Потом из этих составляющих добываются название станции и ее адрес потока, которые в свою очередь попадают в список:

foreach (var station in stations) {
               var row = new Adw.ActionRow () {
                title = station.name.replace("&", "and").strip(),
                subtitle = station.url.strip()
                };
           if(station.url != null && station.url != ""){
               list_box.append(row);
            }
          }

Поиск для избранных станций устроен несколько по другому. В отличие от функции поиска на первой странице приложения, где нужно ввести минимум три символа и нажать на клавишу ввода, здесь поиск срабатывает на каждое нажатие клавиш.

Что касается самой поисковой строки, то в зависимости от страницы приложения она имеет разный вид. Так для первой страницы справа от строки есть  кнопка, при нажатии на которую начинается процесс поиска:

На второй странице, само собой, этой кнопки нет. В принципе, эта кнопка не нужна, так как поиск можно начать, нажав на Enter, но я все-таки решил ее оставить.

Избранное

Для того чтобы добавить станцию в список избранных, пользователю достаточно нажать на соответствующий значок в хидербаре. Но не все так просто, как хотелось бы. Нужно учитывать, что у станции в списке может отсутствовать, например, название, и тогда надо отправить пользователя на специальную страницу для его ввода. Или же название может быть некорректным (к примеру, содержать недопустимые символы), тогда пользователь также должен быть отправлен на ту же страницу, но уже для правки названия. Если же у станции напрочь отсутствует URL потока, то тогда не остается ничего, кроме как вывести сообщение об ошибке. 

Страница с избранными станциями выглядит примерно так:

Как видно, хидербар здесь состоит из большего количества кнопок. Тут присутствуют функции для добавления станции по имени и URL, для удаления и редактирования станций и для показа/скрытия строки поиска. Кнопки, расположенные справа (пуск/стоп, запись, меню), отображаются на всех страницах.

Переключение страниц основано на компоненте Gtk.Stack. В него в виде дочерних элементов добавлены два списка станций и страница редактирования. Бокс с метками для вывода названия станций и заголовков трансляций в стек не входит. 

Директории

Задача с хранением избранных станций была решена довольно примитивным способом. Станции хранятся в отдельной директории в виде текстовых файлов, у которых имя файла — это название станции, а содержимое — ее URL.

Также в отдельной папке хранится и информация о последней прослушанной станции. В этой папке находятся два файла. В одном хранится название станции, а в другом — ее URL. 

Есть еще и третья папка, где хранятся записи станций. Записи сохраняются в формате mp3. Для удобства пользователя в меню предусмотрен пункт для открытия папки записей в файловом менеджере.

Все эти директории создаются при первом запуске приложения. Вот как это реализовано для папки избранных станций:

 favorite_stations_directory_path = Environment.get_user_data_dir()+"/favorite-stations";
   GLib.File favorite_stations_directory = GLib.File.new_for_path(favorite_stations_directory_path);
   if(!favorite_stations_directory.query_exists()){
     try{
        favorite_stations_directory.make_directory();
     }catch(Error e){
        stderr.printf ("Error: %s\n", e.message);
     }
   }

Все эти директории создаются в изолированной среде, и таким образом приложению не нужен доступ к файловой системе.

Настройки и потоки

Приложение имеет всего три настройки. Пользователь может настроить показ избранного после запуска. То есть список избранных станций будет показан сразу после запуска приложения. Есть возможность включить воспроизведение последней прослушанной станции при запуске. Последняя настройка позволяет отключить загрузку списка станций с сервера при запуске программы.

Про первые две настройки вроде все понятно — нужные, в принципе, вещи. А вот зачем понадобилась третья настройка? Дело в том, что в первых выпусках приложения поиск работал очень медленно. Все из-за того, что метод search попросту отсутствовал и поиск осуществлялся посредством метода get_stations. Позже, когда поиск был значительно улучшен, эту настройку решено было не убирать, так как поиск все равно продолжает работать при ее активном состоянии, а видеть первоначальный список станций нужно далеко не каждому пользователю.

В последних версиях программы поиск/показ станций происходит в отдельном потоке:

if(Thread.supported()){
                new Thread(null, show_stations).join();
            }else{
                 show_stations();
            }

Здесь в условии определяется поддержка создания потоков. Если таковая имеется, то метод show_stations будет запущен в новом потоке.

Заголовки трансляций и запись

Программа умеет показывать не только название станции, но и заголовки трансляций, например, название песен и исполнителей. Достигается это таким образом:

player.media_info_updated.connect ((obj) => {
            string? title = extract_title_from_stream (obj);
            if (title != null) {
                current_title.show();
                current_title.set_text(title);
                title_changed(title);
            }else{
                current_title.hide();
            }
        });

Благодаря тому, что к объекту player подсоединен сигнал media_info_updated, данные в текстовой метке current_title постоянно обновляются в соответствии с изменениями в потоке.

Способ записи вещания станций был взят из приложения Reco. Код был несколько упрощен. Были удалены ненужные форматы и добавлено определение даты и времени для составления имени mp3-файла.

Комбинации клавиш

Для удобства пользователя в приложение были добавлены различные комбинации клавиш. Эти комбинации позволяют осуществлять показ или скрытие строки поиска, а также инициировать начало поиска. Также с помощью, например, клавиши пробела можно запускать или останавливать воспроизведение. Для выхода из приложения тоже существует определенная комбинация. Вот как это было сделано:

var event_controller = new Gtk.EventControllerKey ();
        event_controller.key_pressed.connect ((keyval, keycode, state) => {
            if (Gdk.ModifierType.CONTROL_MASK in state && keyval == Gdk.Key.q) {
                app.quit();
            }

             if (Gdk.ModifierType.CONTROL_MASK in state && (keyval == Gdk.Key.f || keyval == Gdk.Key.s)) {
                 on_search_clicked();
            }

            return false;
        });
        event_controller.key_released.connect ((keyval, keycode, state) => {
              if (search_box.is_visible() && stack.visible_child == scroll && keyval == Gdk.Key.Return) {
                on_start_search_clicked();
            }

            if (!search_box.is_visible() && (stack.visible_child == scroll || stack.visible_child == favorite_scroll) && (keyval == Gdk.Key.space || keyval == Gdk.Key.Return)){
                if(current_state == PlayerState.PLAYING || current_state == PlayerState.BUFFERING){
                    on_stop_station();
                }else{
                    on_play_station();
                }
            }

            return;
        });
        ((Gtk.Widget)this).add_controller(event_controller);

Как видно, для клавиши ввода (Return) потребовалось подключать отдельный сигнал (key_released), так как на другой сигнал (key_pressed) не было никакой реакции. А все потому, что сигнал key_pressed не рассчитан на использование с клавишей Return.

Libadwaita

Немного поподробнее хотелось бы рассказать о libadwaita. В приложении из этой библиотеки используется много компонентов, например, тот же хидербар. Всплывающие сообщения также взяты из этой библиотеки:

private void set_toast (string str){
       var toast = new Adw.Toast(str);
       toast.set_timeout(3);
       overlay.add_toast(toast);
   }

Контейнер для списка станций это обычный Gtk.ListBox, но в него добавлены адвайтовские ActionRow, в которых определены свойства title и subtitle. 

Страница редактирования это Gtk.ListBox со встроенными в него дочерними элементами Adw.EntryRow. В эти EntryRow в качестве суффиксов добавлены кнопки для очистки полей:

Диалоговые окна принадлежат классу Adw.MessageDialog. Вообще, все окна в приложении из Адвайты. Вот пример метода alert, который используется для вывода простого окна сообщения:

private void alert (string heading, string body){
            var dialog_alert = new Adw.MessageDialog(this, heading, body);
            if (body != "") {
                dialog_alert.set_body(body);
            }
            dialog_alert.add_response("ok", _("_OK"));
            dialog_alert.set_response_appearance("ok", SUGGESTED);
            dialog_alert.response.connect((_) => { dialog_alert.close(); });
            dialog_alert.show();
        }

Переход на GNOME 44

Недавно вышла новая версия среды GNOME под номером 44. Естественно, я решил обновить все свои приложения, включая и Radio. При сборке пакета flatpak меня ждало сообщение об ошибке, гласящее, что библиотека libsoup-2.4 не обнаружена. Разработчики решили удалить эту версии из новой платформы. Я не захотел переходить на третью версию библиотеки, так как пришлось бы переписывать некоторую, хоть и небольшую, часть кода. Проще всего прописать модуль для старой версии библиотеки в манифесте программы. Так я и поступил:

 {
            "name": "libsoup-2.4",
            "buildsystem": "meson",
            "config-opts": [
                "-Dvapi=disabled",
                "-Dtests=false",
                "-Dtls_check=false",
                "-Dntlm=disabled",
                "-Dsysprof=enabled"
            ],
            "sources": [
                {
                    "type": "git",
                    "url": "https://gitlab.gnome.org/GNOME/libsoup.git",
                    "commit": "b0e2d85e36c5a9ee8d4caf16dbdc30302fcf470d"
                }
            ]
        }

После добавления модуля в манифест сборка пакета прошла успешно.

При добавлении модулей в манифест нужно указывать именно commit в разделе sources. Если вместо commit указать branch, то в вашей IDE проект, скорее всего, соберется, а вот сборочный бот в системе Flathub может выдать ошибку.

В целом приложение получилось минималистичным и простым в использовании, а благодаря новому фреймворку GTK4 и библиотеке libadwaita еще и довольно стильным и современным.

Vala оказался довольно удобным языком. На нем действительно можно вести быструю разработку самых разных прикладных программ. В среде Builder присутствует отличная поддержка этого языка, начиная от подсветки синтаксиса и заканчивая анализом кода и автодополнением. Шаблоны проектов содержат все необходимые файлы. Имеется встроенная документация. В общем, разработчики всего этого великолепия существенно облегчили жизнь другим разработчикам.

НЛО прилетело и оставило здесь промокод для читателей нашего блога: — 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS

© Habrahabr.ru