Pull to refresh

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

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




Что такое GTK Broadway




Про broadway рассказывали на хабре, аж в 2011 году. Однако, мало что поменялось с тех пор в области освещения данной опции.
UPD, спасибо awoland:
Изначально это технология и внутреннее кодовое имя релиза 6.3 X Window System 11 (X11R6.3):
«Broadway was the internal code name for The X Window System 11 Release 6.3 (X11R6.3) development effort. The X11R6.3 project was a ground-breaking initiative for enabling the use of X to create and access interactive applications on the World Wide Web. Any application linked to the Web using X11R6.3 can be located, accessed and executed with the same tools used for accessing static HTML documents today — web browsers.»

Основной идеей является написание одной единственной версии кода на базе обычного и уже привычного 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 и возможно кто-то сможет решить свою задачу боле простыми методами.
Tags:
Hubs:
+16
Comments 6
Comments Comments 6

Articles