Иногда необходимо предоставить доступ к приложениям которые не всегда есть возможность установить локально, да и не всегда это нужно. Наверное, лучшим выходом тут был бы 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 и возможно кто-то сможет решить свою задачу боле простыми методами.
Автор: Pugnator