- PVSM.RU - https://www.pvsm.ru -

Миниатюрное десктопное GUI приложение на PHP — 2 МБ хватит для всех

Я часто заморачиваюсь на тему минимизации размера своих GUI приложений. Прошлая моя статья была про Nuklear [1]. Но сейчас захотелось более современных технологий. Чтоб HTML5, CSS3 и PHP. Чтоб приложение ни от чего не зависело, т.е. построено по принципу "всё включено". И чтоб конечный размер приложения не превысил 2 мегабайта. Получится ли?

В Linux я часто пользуюсь утилитой df [2]. Мне её очень не хватает в Windows, а искать аналоги лень. Так что было сделано волевое решение сделать свою, на РНР 5, с бутстрапом и JQuery.

Краткое решение моей задачи: CivetWeb [3] + WebView [4] + PH7 [5].

Т.е. результирующее приложение это и веб-сервер, который раздаёт статические файлы и выполняет скрипты (CivetWeb). И непосредственно браузер, который к этому веб-серверу подключается (WebView). PHP выполняется как СGI-BIN, через сторонний интерпретатор PH7.

Здесь заключена основная фишка связки - в качестве интерпретатора СGI-BIN может быть использован любой язык/компилятор/интерпретатор. Хоть Haxe используй, хоть Go, хоть на Powershell веб-страницы генерируй, хоть полноценный PHP возьми. Так же прямо в CivetWeb встроен интерпретатор Lua, который позволяет делать легковесные, но полноценные приложения.

Windows 11 версия приложения
Windows 11 версия приложения

Хватит лирики, перейдём к коду. PHP не самый лучший язык для создания системных приложений. Например, в нём нет функции для получения системных дисков. Но когда это нас останавливало? В Windows воспользуемся перебором всего алфавита и проверкой существования директории. В Linux же функция построчно читает файл /etc/fstab , разбивает каждую строку по пробелам чтоб получить столбцы, и проверяет на существование директории:

function fs_get_roots() {
    static $roots = null;
    if ($roots === null) {
        if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
            $driveLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
            for ($i = 0; $i < strlen($driveLetters); $i++) {
                $curDrive = $driveLetters[$i].':\';
                if (is_dir($curDrive)) {
                    $roots[] = $curDrive;
                }
            }
        } else {
            foreach (file('/etc/fstab') as $line) {
                if ($line[0] != '#') {
                    $line = str_replace('t', ' ', $line);
                    do $line = str_replace('  ', ' ', $line, $count); while($count);
                    $rows = explode(' ', $line);
                    if (count($rows) && is_dir($rows[1])) {
                        $roots[] = $rows[1];
                    }
                }
            }
        }      
    }
    return $roots;
}

Корни файловой системы кэшируются в статической переменной $roots. Т.е. если несколько раз вызвать эту функцию при генерации страницы, то последующий вызов отработает очень быстро, повторно диск сканироваться не будет.

Linux-версия
Linux-версия

С одной стороны, код получился не очень красивым. Прямой парсинг системных файлов используется в Linux для написания низкоуровневых утилит. Например, какая-нибудь из реализаций df вполне может читать /etc/fstab, такой код нормален для программ написанных на Си. Но не для высокоуровневых языков программирования. Угадывание имени диска по алфавиту - вообще злостный хак. Например, если в DVD-приводе будет вставлен диск, то при каждом обращении он будет раскручиваться...

С другой стороны, результат достигнут. PHP предназначен для генерирования веб-страниц. Но при желании даже на нём можно писать системное программное обеспечение.

JQuery

В качестве отображения использую пример прогрессбара на бутстрапе [6]. Здесь вам и сам Bootstrap, и HTML5, и ещё и JQuery зачем-то подключен.

Одним из желаний было чтобы в приложение была встроена система локализации. Чтоб русскоязычные пользователи видели приложение на русском, чехи - на чешском и т.д. Система локализации будет сделана на JQuery. Ну надо же его хоть для чего-нибудь использовать:

$(document).ready(function() {
    var userLang = (navigator.language || navigator.userLanguage)?.substring(0, 2)?.toLowerCase(); 
    $('[data-' + userLang + ']').each(function(element) {
        var localized = $(this).data(userLang);
        $(this).text(localized);
    });
});

В переменной userLang хранится 2 буквы языка браузера пользователя. Например, ru. Далее перебираем все теги у которых есть атрибут data-ru . Далее просто устанавливается текст текущего элемента, полученный из данных.

Пример HTML разметки для использования такой системы локализации:

<h2 data-ru="Системные диски" data-cz="Systémové disky">System Drives</h2>

Здесь сразу же виден основной недостаток такой системы локализации - код становится очень перегруженным, все пользователи всегда загружают данные для всех языков. Такая локализация подходит если в проекте нужно перевести порядка десятка коротких фраз и на малое количество языков. Т.е. годится только для примера.

PH7

PH7 [5] - альтернативная реализация PHP для встраиваемых систем. PH7 разрабатывалась чтоб оживить интерфейс в роутерах, где до этого часто HTML-коды встраивались прямо в прошивку, написанную на Си.

PH7 бесплатен для проектов с открытым исходным кодом. Основным преимуществом является крайне малый размер - у меня получилось скомпилировать ph7-cgi в приложение размером порядка 300 килобайт.

Недостатков у PH7 чуть больше чем достоинств. Основные:

  1. Устаревшая версия PHP. Версия 5.3 вышла в 2009 году...

  2. Нет поддержки регулярных выражений. Это автоматически отметает возможность использования многих библиотек (Smarty, Twig и т.д.)

  3. Не все функции работают кроссплатформенно.

  4. Не все функции работают.

Например, функции disk_total_space и disk_free_space всегда возвращают пустое значение в Linux, что видно на скриншоте. Я не стал делать альтернативную реализацию этих функций (например через разбор выхлопа df) т.к. это не является целью данной публикации. Да и в принципе проблематично вызывать сторонние утилиты, т.к. на месте функции exec стоит заглушка всегда возвращающая пустое значение.

В общем, PHP в данной публикации скорее элемент юмора, чем реальной пользы.

CivetWebView

Краткое решение уже было написано выше. Рассмотрим клиентско-серверную часть: CivetWeb [3] + WebView [4].

CivetWeb (бывший Mongoose до смены лицензии) является стандартом де-факто когда нужно что-то быстро раздать по HTTP на десктопе. Это не промышленное решение типа Nginx - моему локальному серверу не нужно раздавать миллионы файлов в секунду. И сотни злобных хакеров не будут пытаться его взломать. И очень большой вопрос, будет ли он вообще когда-либо пущен во внешнюю сеть.

WebView же - просто возможность встроить системный браузер в своё приложение. Для Windows это будет Edge, для Linux - GTK WebKit, для мака - Cocoa или WebKit. Т.е. какой-нибудь однозначно современный браузер, который хорошо поддерживает и HTML5, и CSS3, и JavaScript. При этом результирующее приложение не разрастётся на сотни мегабайт, т.к. непосредственно движок в полученное приложение встроен не будет.

Обе эти технологии для меня выглядят хорошо. Единственное, я так и не нашёл ни одного проекта, объединяющих их. Поэтому пришлось делать свой: CivetWebView - https://github.com/DeXP/CivetWebView [7]

По сути CivetWebView - это объединение кода двух примеров: CivetWeb и WebView. На текущий момент проект скорее в стадии прототипа - реализован лишь необходимый мне функционал. Многие вещи заданы просто как константы в коде.

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

Размер

Полное Win32 приложение занимает 1.53МБ. Самым объёмным компонентом является CivetWebView, который занимает почти мегабайт. Следующим идёт интерпретатор РНР размером 282 килобайта. Завершают тройку лидеров Bootstrap и JQuery, которые в сумме занимают 274 килобайта (без отладочных map-файлов). Сам код приложения (PHP + HTML + CSS) занимает порядка 5 килобайт.

Миниатюрное десктопное GUI приложение на PHP — 2 МБ хватит для всех - 3

Исходные коды и архивы с бинарными файлами можно скачать на гитхабе: https://github.com/DeXP/CivetWebView-PH7-Example [8]

Лицензии

Всё кроме PH7 лицензировано под "да делайте с этим что хотите, нам абсолютно всё равно". Т.е. если исключить из связки PH7, то можно делать коммерческие приложения. Кто-нибудь мне объяснит, зачем?)

Миниатюрное десктопное GUI приложение на PHP — 2 МБ хватит для всех - 4

Зачем всё это надо

Изначально у меня не стояла задача создания сферического GUI проекта в вакууме. Был проект онлайн интерпретатора визуальных новелл с консоли DS - VNDS-Online [9]. И мне сразу хотелось иметь возможность запускать эти игры не только на каком-нибудь сайте, но и локально на своём компьютере. Или на каком-нибудь другом устройстве.

Миниатюрное десктопное GUI приложение на PHP — 2 МБ хватит для всех - 5

vnds-online не может работать без серверной части. Как минимум, нужно иметь возможность получать список имеющихся игр. Этот функционал было не слишком сложно переписать на LUA [10].

Кроме того, хотелось дать возможность запускать игры простым двойным кликом по ЕХЕ-файлу. Чтоб даже самый далёкий от веб-разработки человек мог играть.

Проект в итоге получился компактным за счёт того, что большая часть кода подразумевает исполнение в браузере. Который уже установлен в систему, хорошо отлажен и оптимизирован. В общем, спасибо большое ВаЮрику [11] за замечательный движок.

Однако vnds-online тоже есть куда развиваться. Например, в движок явно нужно встроить возможность локализации. Ещё хотелось бы возможность адаптировать картинку под современные широкие экраны. Ну и отладка существующих багов разумеется.

Вопросы вместо выводов

Вместо вывода хочу задать сообществу два вопроса:

  1. Кому-нибудь нужен CivetWebView? Стоит ли его развивать? Вы бы им пользовались? Для чего? Есть ли какие-нибудь настоящие сценарии использования, где бы оно пригодилось? Или мир уже захватили Electron на пару с Node.js?

  2. Кому-нибудь нужен ещё один интерпретатор VNDS? Возможно под какие-нибудь странные платформы, но с современным браузером. Стоит ли вообще развивать vnds-online?

Полезные ссылки

Автор: Храбров Дмитрий

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/377066

Ссылки в тексте:

[1] Nuklear: https://habr.com/ru/post/338106/

[2] df: https://opensource.com/article/21/7/check-disk-space-linux-df

[3] CivetWeb: https://github.com/civetweb/civetweb

[4] WebView: https://github.com/webview/webview

[5] PH7: https://github.com/symisc/PH7

[6] пример прогрессбара на бутстрапе: https://bbbootstrap.com/snippets/linear-progress-bar-99672162

[7] https://github.com/DeXP/CivetWebView: https://github.com/DeXP/CivetWebView

[8] https://github.com/DeXP/CivetWebView-PH7-Example: https://github.com/DeXP/CivetWebView-PH7-Example

[9] VNDS-Online: https://github.com/VaYurik/vnds-online

[10] переписать на LUA: https://github.com/VaYurik/vnds-online/blob/master/lua/get_games_list.lua

[11] ВаЮрику: https://github.com/VaYurik

[12] Эта публикация на английском: https://dexp.in/articles/civetwebview/

[13] Источник: https://habr.com/ru/post/674192/?utm_source=habrahabr&utm_medium=rss&utm_campaign=674192