Broadway — рендеринг интерфейса GTK3 в браузере (HTML5)

от автора

Иногда необходимо предоставить доступ к приложениям которые не всегда есть возможность установить локально, да и не всегда это нужно. Наверное, лучшим выходом тут был бы web интерфейс на JS/PHP и иже с ними. Но возможно есть другие, более простые в некоторых случаях пути? Особенно если приложение должно оставаться портативным, а ещё лучше не делать почти ничего дополнительно в коде для реализации такого функционала.
Такую возможность предоставляет Broadway — уже давно не новый, но остающийся в тени backend для GTK3, позволяющий привнести новые возможности туда, где казалось бы уже все давно протоптано.

Что такое GTK Broadway


Про broadway рассказывали на хабре, аж в 2011 году. Однако, мало что поменялось с тех пор в области освещения данной опции.
Основной идеей является написание одной единственной версии кода на базе обычного и уже привычного GTK3, который может одновременно и практически без изменений работать как классическое графическое приложение, а так же рендерить свой интерфейс посредством HTML5 и websockets в браузере. В версиях 3.8+ появилась возможность поставить пароль на подключение и возможность запуска множества приложений на одном сервере.

Какая версия GTK3?

Официально Broadway зарелизили вместе с GTK3, но только начиная с версии 3.8 данная подсистема избавилась от обидных ошибок. Я использую 3.10.7, так как в ней поменяли прицнип использования, исправили много ошибок и вынесли HTTP сервер как отдельное приложение. Поэтому рассказывать буду про 3.10, ибо все равно к нему всё придет.

Принцип работы


Вместе с GTK3 устанавливается HTTP сервер интегрированный с GTK3 (broadwayd). При запуске он создает сокет, к примеру /run/user/1000/broadway1.socket и ждет подключения приложения на GTK3 к этому сокету.
Можно указать иной порт (номер экрана), можно задать пароль на подключение ( >= GTK 3.8).

Зачем это нужно

Подобный режим работы не претендует на замену ставшим теперь стандартными интерфейсам на базе PHP/JS/Java и иже с ними. Но таким образом можно создать службу, например в виде виртуальной машины, которая будет предоставлять пользователям доступ к каким-либо вычислительным службам или утилитам без траты времени на разработку специального интерфейса, при этом обеспечив высокую производительность на стороне сервера. Я, к примеру, на нем делал консоль доступа к виртуальным машинам XenServer и сейчас реализую удаленный доступ к камерам своего телескопа.

Получение Broadway

На данный момент, насколько мне известно, ни один дистрибутив из тех, что я пробовал, не предоставляет пакет GTK3 + broadway в стабильных ветках. Debian 7 имеет такой пакет в experimental репозитории, но вроде и с ним не все гладко.
В Debian based системах можно добавить PPA собранный добрым человеком (Nicolas Delvaux)
Есть бэкпорт сделанный им же на базе 3.8.0

Оба варианта использовать надо с осторожностью и пониманием, ибо есть реальная возможность основательно поломать систему. Я же использую 3.10.7, тут уже только из исходников.

Краткая инструкция по сборке

Собираем, как описано в мануале к LFS не забывая про checkinstall вместо make install если у вас есть пакетный менеджер
К сожалению, там не описан важный нюанс — помимо сборки и установки самой библиотеки GTK3, необходимо вручную собрать и скопировать broadwayd куда-нибудь, доступное через $PATH, например в /usr/sbin

cd gtk+-3.10.7/gdk/broadway make clean make cp broadwayd /usr/sbin 

Запуск

Если запустить приложение так

GDK_BACKEND=broadway BROADWAY_DISPLAY=:0 ./gtk_app

,
то оно будет работать в фоне, как web сервер, а доступ к интерфейсу мы получаем, зайдя в браузере по соответствующему адресу и порту. В данном примере это будет localhost:8080 (Порт вычисляется как port = 8080 + (display — 1)). Веб сервер уже идет в поставке с GTK — broadwayd. При этом даже нет необходимости наличия работающего X сервера на хосте. Достаточно наличия нужных библиотек. Приложение будет отображаться в браузере практически так же, как и в стандартном режиме. Сравним:

Основные нюансы использования

Первое, что бросается в глаза — отсутствует имя окна, а так же заголовок страницы гласит «broadway 2.0», так же невозможно вручную изменить размер окна перетягиванием.

gtk_status_icon_set_visible(GTK_STATUS_ICON(tray_icon), TRUE); 

то это бы вызвало segfault. Следующий нюанс — все, что выполняется в браузере, делается относительно машины хоста. К примеру, выбрать файл на локальной браузеру машине и что-то с ним сделать на стороне приложения не выйдет. Или вызов libnotify приведет к появлению всплывающего окна на хосте, а не на машине с браузером.
С другой стороны, открываются другие плюсы вроде упрощенного доступа к ресурсам сервера, но необходимо уделять внимание безопасности, например через gtk_file_chooser_set_local_only, gtk_file_chooser_set_filter и настроить jail для пользователя отдельного и запускать веб версию под этим пользователем, иначе у пользователя все будет как на ладони, по крайней мере структура директорий.

Другая проблема в том, мышкой в браузере в таком приложении можно попасть в стандартное меню, а вот пальцем — не очень (если заходим с мобильного устройства). Кроме того, размеры и положение окна не везде одинаковы — это тоже надо будет учесть. И очень неприятное — GTK3 не поддерживает single click. Так что выбрать другую директорию мне так и не удалось, даже выключив double tap (Android 4.2.2/Firefox/Chrome). Зайти одновременно на одну и ту же сессию с разных машин/браузеров не выйдет, так как сокет один, предыдущая сессия будет автоматически закрыта

Практика

Рассмотрим, как можно учесть возможность использования Broadway в приложении.
Будем использовать Glade как конструктор интерфейса. Я использую две версии интерфейса в одном приложении: одна для обычного использования и одна — браузерная, которая старается учитывать нюансы работы в браузере и/или на мобильном устройстве.
GTK позволяет подгружать интерфейс из буфера памяти, чем и воспользуемся. В Makefile я добавляю преобразование из XML файла, в котором сохраняет интерфейс Glade, в массив uint8_t, который и будет подгружать приложение. Это позволяет хранить все в одном единственном исполняемом файле.

all:	 	xxd -i desktop.glade ../src/desktop.h; 	xxd -i web.glade ../src/web.h;	 	make -C ../src clean: 	make -C ../src clean 

Теперь у нас в заголовочном файле массив с интерфейсом. Основной цикл стандартен.

int main(int argc, char *argv[]) {     gtk_init(&argc, &argv);     GtkWidget *main_window = glade_init( );     gtk_widget_show(main_window);      gtk_main( ); } 

Заранее отвечу про return — собирается с std=c99
Далее нам нужно понять, когда использовать тот или иной вид интерфейса.
Здесь нам поможет то, что в случае если используется Broadway, то GDK экран называется «browser»

#ifdef __WIN32 G_MODULE_EXPORT #endif gboolean is_run_in_a_browser (void) {       GdkScreen *current_screen = gdk_screen_get_default();       char *screen_name= gdk_screen_make_display_name(current_screen);    gboolean is_browser = !strcmp(screen_name,"browser");     free(screen_name);    return is_browser; } 

ifdef тут нужен для адекватной работы под Win32, если понадобится.
В данной функции мы проверяем имя GDK окна, и если это браузер, то заодно убираем оформление (рамку) окна. Теперь подгрузим интерфейс в зависимости от того, через broadway запущено приложение или нет

#ifdef __WIN32 G_MODULE_EXPORT #endif GtkWidget *glade_init(void) {             GtkBuilder *builder = gtk_builder_new( );     GError *error = NULL;            gboolean web_run =  is_run_in_a_browser();     gtk_builder_add_from_string(builder, web_run ? (char *)web_glade : (char *)desktop_glade, -1, &error);     if (!error)     {         printf("Couldn't load builder buffer: %s", error->message);         g_error_free(error);         return NULL;     }         gtk_builder_connect_signals(builder, NULL );         GtkWidget *main_window = GTK_WIDGET (gtk_builder_get_object (builder, "mainwin"));             g_object_unref(builder);     return ( main_window ); } 

Запускаем приложение. Обнаруживаем, что установки расположения окна не работают. Сразу после запуска разрешение GDK screen 1024*768 — это рассмотрим позже. Кроме того, центровать окно придется руками, так как редко разрешение браузера совпадет с значением по умолчанию в broadway.
После того, как мы открыли приложение в браузере, нам необходимо выставить нужное разрешение экрана (чаще всего это будет растянуть на весь экран) и при этом совместить верхний левый угол с началом координат. Сделать это можно, например, так.

gtk_window_move(GTK_WINDOW(main_window),0,0);    GdkScreen *current_screen = gdk_screen_get_default(); int32_t w = gdk_screen_get_width(current_screen); int32_t h = gdk_screen_get_height(current_screen);         gtk_window_resize(GTK_WINDOW(main_window),w, h); 

Тогда тестовое приложение (VNC консоль) будет выглядеть вот так в Android/Firefox):

Если раньше (до 3.8) пользователю следовало выключать приложение самостоятельно, иначе если закрыть браузер или зайти с другого места, все что было видно — белый фон. То теперь broadway берет работу с сокетами и idle/disconnect на себя. Теперь надо разобраться с другими мелочами

Немного кастомизации broadwayd

К счастью, GTK3 это opensource, так что можно залезть поглубже и кое-что поменять.

Начнем с заголовка окна.

Текст по умолчанию, «broadway 2.0», вряд ли кого-то устроит, изменим это. HTML5 страница сервера состоит из шаблона и JS файла. Заголовок страницы прописан в стандартном хедере HTML:

<title>broadway 2.0</title> 

При сборке страница конвертируется perl скриптом в простой C массив, который хранится в gtk+-3.10.7/gdk/broadway/clienthtml.h — static const char client_html[].
Далее все просто, gtk+-3.10.7/gdk/broadway/broadway-server.c:

static void got_request (HttpRequest *request) ............... if (strcmp (escaped, "/client.html") == 0 || strcmp (escaped, "/") == 0)     send_data (request, "text/html", client_html, G_N_ELEMENTS(client_html) - 1); 

Изменим немного алгоритм работы, будем использовать хэдер как формат для sprintf. Поэтому gtk+-3.10.7/gdk/broadway/clienthtml.h придется немного поправить — добавить экранирование знаков процента, например как тут:

background-image: -moz-linear-gradient(#D1D2D2 0%%, #BABBBC 65%%, #D4D4D5 100%%);\n" 

вместо

background-image: -moz-linear-gradient(#D1D2D2 0%, #BABBBC 65%, #D4D4D5 100%);\n" 

И заменим текст заголовка

<title>broadway 2.0</title> 

на спецификатор

<title>%s</title> 

Изменим алгоритм работы самого сервера:

char *http_title = getenv("GTK_HTTP_TITLE");  if (NULL == http_title) {   http_title = "Default"; } size_t total_html_size = sizeof client_html + strlen(http_title) + 1; char *_client_html = malloc (total_html_size); snprintf(_client_html, total_html_size, client_html, http_title);   if (strcmp (escaped, "/client.html") == 0 || strcmp (escaped, "/") == 0)   send_data (request, "text/html", _client_html, strlen(_client_html) - 1); ........... g_free (_client_html); 

Можно было бы использовать asprintf, но тогда пришлось бы модернизировать Makefile, чего мне делать не хотелось.
Пересобираем broadwayd, зададим глобальную переменную и запускаем сервер

export GTK_HTTP_TITLE="PAGE TITLE" 

Можно изменить размер экрана по умолчанию, может быть актуально при использовании с мобильными устройствами.

static void gdk_broadway_screen_init (GdkBroadwayScreen *screen) {   screen->width = 1024;   screen->height = 768; } 

И изменить имя GDK экрана, таким образом можно, к примеру, создать несколько специфических версий broadwayd и идентифицировать их таким образом

static gchar * gdk_broadway_screen_make_display_name (GdkScreen *screen) {   return g_strdup ("browser"); }  static gchar * gdk_broadway_screen_get_monitor_plug_name (GdkScreen *screen, 					   gint       monitor_num) {   return g_strdup ("browser"); } 

Заключение

Broadway — действительно интересная фича GTK3, позволяющая быстро создавать интересные легкие сервисы для определенного круга задач. Но, к сожалению, практически не используемый. Данной статьей я хотел обратить внимание бОльшего количества людей на broadway и возможно кто-то сможет решить свою задачу боле простыми методами.

ссылка на оригинал статьи http://habrahabr.ru/post/212705/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *