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

В Linux я часто пользуюсь утилитой df. Мне её очень не хватает в Windows, а искать аналоги лень. А уж тем более было лень запускать всякие там Explorer, Powershell и т.п. - это слишком нормально :-). Так что было сделано волевое решение сделать свою утилиту, на РНР 5, с бутстрапом и JQuery.

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

Т.е. результирующее приложение это и веб-сервер, который раздаёт статические файлы и выполняет скрипты (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-приводе будет вставлен диск, то при каждом обращении он будет раскручиваться... А ещё диски могут быть двухбуквенными, смонтированными в директорию, быть доступными только по GUID и т.д. и т.п. - все они будут пропущены.

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

JQuery

В качестве отображения использую пример прогрессбара на бутстрапе. Здесь вам и сам 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 - альтернативная реализация 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 + WebView.

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

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

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

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

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

Размер

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

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

Лицензии

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

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

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

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

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

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

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

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

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

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

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

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

Комментарии (48)


  1. OnlySlon
    17.07.2022 11:15
    +17

    Истинно говорю, настали последние времена.


    1. Alexufo
      17.07.2022 12:38

      Можно это подогнать под теорию заговора


  1. Sazonov
    17.07.2022 12:59
    +9

    Позанудствую :). В винде имена дисков могут быть и двухбуквенными.


    1. DeXPeriX Автор
      17.07.2022 13:01
      +3

      Проект имеет открытый исходный код - с удовольствием приму pull-request :-)
      Теоретически можно добавить перебор по двум буквам. Но как-то совсем всё это грустно. Плюс очень сомневаюсь, что кто-то реально захочет делать системные утилиты именно на РНР.


    1. GennPen
      17.07.2022 13:38
      +4

      Либо не иметь букву, тогда нужно к ним обращаться по GUID, например: \\?\Volume{4c9de12e-05bc-11ed-b939-0242ac120002}\


      1. HemulGM
        17.07.2022 19:13
        +8

        Пусть все гуиды перебирает)))


      1. DeXPeriX Автор
        18.07.2022 09:30

        Спасибо большое, добавил в статью.


    1. a-tk
      18.07.2022 09:25
      +1

      Эммм... Чо? Где об этом почитать можно?


  1. Revertis
    17.07.2022 16:50
    +9

    Но ведь в винде можно просто открыть "Мой Компьютер" и увидеть всю эту информацию.

    Размеры и загруженность дисков в Win10, даже сетевых.
    Размеры и загруженность дисков в Win10, даже сетевых.


    1. DeXPeriX Автор
      18.07.2022 09:25

      Спасибо большое, добавил в статью.


    1. DeXPeriX Автор
      18.07.2022 09:32
      +2

      Кстати, не подскажете как добавить Plex в список сетевых локаций? И зачем? У меня диски от NAS подмонтированы как обычные сетевые диски. Обычно видео если хочу скачать/закачать/изменить - то пользуюсь ими.


      1. Eugeeny
        18.07.2022 12:26
        +1

        Это отображаются обнаруженные через DNS-SD медиасервера


  1. savostin
    17.07.2022 19:22
    +2

    На всякий случай, не знаю как там в других ОС, в Windows ниже 11 версии, кажется, WebView не поставляется вместе с системой (это кстати, не тот же браузер, что стоит и запускается по-умолчанию), а отдельный компонент весом 175 Мб. Собственно, как тот же Qt, например. Ну как бы не совсем и миниатюрное получается... Но согласен, все идет к тому, что в недалеком будущем этот компонент уже будет установлен.


    1. DeXPeriX Автор
      18.07.2022 09:40

      Даже в Windows 10 WebView придётся доустановить отдельно. Но это разовая операция.


      1. savostin
        18.07.2022 10:38

        Интересно, как объяснить пользователю, что для моей "миниатюрной утилиты", которая показывает размер диска, нужно разово установить такую хрень. И нафига она ему нужна (мы-то знаем, что понадобится, но он-то нет.)


        1. DeXPeriX Автор
          18.07.2022 11:28

          По мне, ситуация похожа на .Net4 в Windows 7: приложения маленькие, но требуют жирного дотнет установленного в системе. Нужно приложение? Устанавливай дотнет. Не хочешь? Тогда не будет тебе нашего приложения.
          Чтобы было менее болезненно можно при запуске своего приложения как-то проверять, установлен ли в системе WebView. И если нет, то показывать сообщение с возможностью установить компонент прям оттуда. Или просто перекидывать на сайт.


          1. a-tk
            18.07.2022 11:29

            Только разница в том, что .NET4 предустановлен в Win7, в отличие от этого.

            И да, инсталлер .NET весит меньше :)


            1. DeXPeriX Автор
              18.07.2022 11:41

              Хм. Я точно помню что доустанавливал какие-то версии дотнета на семёрке. Как минимум, я как разработчик могу затребовать уствновленного .NET Framework 4.7.2, который слишком новый чтобы идти сразу с семёркой.

              Да, инсталлер весит меньше. Время идёт, программы жиреют. Если так важен именно размер и совместимость со старыми системами - то можно воспользоваться например тем же Nuklear упомянутым в начале статьи.


          1. savostin
            18.07.2022 13:28
            +1

            Чтобы было менее болезненно можно при запуске своего приложения как-то проверять, установлен ли в системе WebView.

            Есть такая "функция".


          1. dmitryvashkevich
            18.07.2022 22:07
            +1

            У меня был мини-проект на дотнете, калькулятор для фотокниг. В итоге сама прога весила пару мегабайт с учетом ресурсов, но..

            Программа должна была просто запускаться с флешки фотографов без установки всяких многомегабайтных зависимостей. И в этом кейсе дотнет не прокатил. У кого-то был win7, у кого-то xp. Заработала моя прога у пары клиентов только.

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


        1. KizhiFox
          18.07.2022 11:32
          +1

          Некоторые приложения же умеют вызывать системное диалоговое окно "для работы программы необходимо установить следующие компоненты Windows: бла-бла-бла скачать в Интернете"


          1. DeXPeriX Автор
            18.07.2022 11:44

            Абсолютно верно. А те, которые не умеют, могут вызвать какой-нибудь MessageBox.

            Исходники CivetWebView открыты, дописать логику можно. Вопрос стоит ли тратить на это время? Я пока так и не получил ответов на вопросы, заданные в конце статьи.


    1. Revertis
      18.07.2022 13:13

      Интересно то, что ALFIS, использующий WebView, в винде ничего не просит. Ни разу никто не обращался по этому поводу.


  1. Busla
    17.07.2022 19:24
    +3

    В Linux я часто пользуюсь утилитой df. Мне её очень не хватает в Windows, а искать аналоги лень.

    Get-Volume вас спасёт


    1. DeXPeriX Автор
      18.07.2022 09:42

      Спасибо большое, добавил в статью.


  1. savostin
    17.07.2022 19:30
    +1

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

    mg_printf(conn,
    		          "HTTP/1.1 200 OK\r\nContent-Type: "
    		          "text/html\r\nConnection: close\r\n\r\n");
    		mg_printf(conn, "<html><body>\r\n");
    		mg_printf(conn,
    		          "<h2>This is an example text from a C++ handler</h2>\r\n");
    		mg_printf(conn,
    		          "<p>To see a page from the A handler <a "
    		          "href=\"a\">click here</a></p>\r\n");

    Как-то слабо они форкнулись. Ожидал, что уж этот страшный сон в современном коде уже не увидеть.


  1. ciuafm
    17.07.2022 20:14
    +1

    Извините, не дочитал до конца. Но GUI под виндой дешевле всего в PowerShell. Он везде есть и умеет как минимум winForms. Да и с системой очень плотно связан, можно и список дисков получить без перебора и реестр прочитать...

    Кстати диски не обязательно к буквам привязаны, их ещё можно в папки монтировать.


    1. savostin
      17.07.2022 20:32

      Это если вы знаете и то, и другое...


    1. DeXPeriX Автор
      17.07.2022 20:35
      +3

      Но тогда это не попадёт под хаб "Ненормальное программирование"....


    1. SerafimArts
      18.07.2022 13:15

      PHP и так из коробки (начиная с 7.4+) умеет в WinForms и так же можно и список дисков получить и реестр прочитать (последнее сложнее, т.к. АПИ у него довольно помоечное).


  1. storoj
    17.07.2022 20:38
    +3

    подозреваю, что если обратиться к диску A: при существующем флоппи-драйве, то можно и подвиснуть


    1. a-tk
      18.07.2022 09:32
      +1

      С оптическим диском тоже может быть весело.


  1. raamid
    18.07.2022 01:27

    Остался всего один шаг до веб приложения. Почему бы его не сделать? Пусть остается портативный сервер и обычный браузер. Тем более, браузеры сейчас есть во всем, что имеет экран. А иногда даже и там, где нет экрана.


    1. DeXPeriX Автор
      18.07.2022 09:27

      Этот портативный сервер и есть CivetWeb. Изначально этим путём я и шёл.
      Только при запуске CivetWeb пользователю было не совсем ясно, уже можно играть или ещё что-то происходит. И при закрытия окна браузера пытались запустить новый экземляр CivetWeb.


      1. raamid
        18.07.2022 17:08

        А что если сервер будет сам открывать окно браузера, а при закрытии окна браузера сервер будет автоматически выключаться?


        1. DeXPeriX Автор
          19.07.2022 09:05

          Звучит хорошо. Но сложнореализуемо. В браузере часто открыто много вкладок, и о закрытии конкретной он систему не информирует, насколько мне известно.
          Ещё можно например пинговать сервер каждую секунду. И убивать сервер по таймауту если ничего не пришло. Тоже так себе решение - браузер вполне может отправить вкладку в сяпщий режим.


          1. a-tk
            19.07.2022 09:08

            Можно что угодно использовать с поддержкой соединения: long polling, web sockets и т.д.


            1. DeXPeriX Автор
              19.07.2022 09:38

              А long polling или web sockets и т.д. работают пока вкладка находится в Sleep mode?


              1. raamid
                19.07.2022 14:02

                Если вы имеете в виду, работает ли web sockets когда вкладка неактивна, то да, работает. Если же речь про Google Chrome sleeping tab option, то эта опция по умолчанию выключена. На мой взгляд, если пользователь достаточно продвинут, чтобы включить эту опцию, то со вкладками он точно разберется. А у остальных все должно будет просто работать. Но на 100% я не уверен, нужно пробовать.


  1. u007
    18.07.2022 07:16
    +1

    >CivetWeb (бывший Mongoose до смены лицензии) является стандартом де-факто

    Микроапач, размером в 5 мегабайт: https://github.com/splitbrain/dokuwiki-stickbuilder, с php7 - 8,5 мб


    1. DeXPeriX Автор
      18.07.2022 11:33

      Не совсем понял, что это такое. В конце получается портативное приложение, не зависящее ни от чего? Или нужно устанавливать что-то в систему? 8.5 мегабайт - там и PHP с расширениями, и Apache и т.д.? Насколько вся связка полноценная? За счёт чего такой малый размер? Оригинальный ЕХЕ-файл php8 в винде весит порядка 80 мегабайт...


      1. u007
        18.07.2022 13:23
        +1

        Портативное и достаточно полноценное. Даже сишный рантайм таскает с собой. Можно скачать и потестить: https://download.dokuwiki.org/ Поставьте галочку "Include Web-Server".

        За счёт чего малый размер - это к автору, он провёл отличную работу) Библиотека php здесь занимает от 3 до 8 Мб в зависимости от версии и способа сборки. Расширений оставлен пяток (GD2, sqlite), но можно накидать своих.

        Сборка могла бы быть ещё меньше, но некоторые библиотеки используют механизм cf-guard, и их нельзя сжимать через UPX.


        1. DeXPeriX Автор
          18.07.2022 13:31

          Спасибо огромное! Выглядит очень интересно.


  1. a-tk
    18.07.2022 09:31
    +1

    А что там с сетевыми дисками? С дисками, подключаемыми через subst? С монтированием в каталоги? С reparse points?


    1. DeXPeriX Автор
      18.07.2022 09:43

      Спасибо большое, добавил в статью.


  1. a-tk
    18.07.2022 11:32

    К слову про всякие флоппики, оптические и сетевые диски: информацию о них следовало бы собирать асинхронно, достраивая представление по мере готовности данных. Что на упомянутом ранее .NET-е является типовым паттерном построения интерфейса.


    1. DeXPeriX Автор
      18.07.2022 11:35

      Согласен, приложение не выдерживает критики. Но оно больше иллюстративный характер - что именно можно сделать в РНР :-)
      А по сути, ответил ещё тут:
      https://habr.com/ru/post/674192/#comment_24534444


      1. a-tk
        18.07.2022 11:39

        ... а получилось обсуждение того, что не стоит делать :)