Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

Как транслировать одно окно Windows на планшет через браузер без лишнего софта

Как транслировать одно окно Windows на планшет через браузер без лишнего софта

Мне потребовалось организовать трансляцию интерфейса конкретной программы с рабочего ПК на планшет, причем исключительно в режиме просмотра. Готовых утилит и протоколов на рынке предостаточно, но большинство из них превращают мобильное устройство во второй монитор, требуют установки громоздких клиентских приложений или транслируют дисплей целиком. Я решил разработать собственную компактную утилиту WindowStream, которая решает эту узкую задачу за пару кликов.

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

Что представляет собой утилита WindowStream

Эта программа создана на языке Python специально для операционной системы Windows и функционирует прямо из системного трея. Пользователь просто открывает контекстное меню, выбирает целевое приложение, после чего запускается вещание по локальной сети в формате MJPEG поверх стандартного протокола HTTP. На самом планшете, смартфоне или гостевом компьютере не нужно устанавливать дополнительный софт — достаточно запустить любой современный браузер и ввести адрес вида http://192.168.1.20:8080/.

Реклама

В ходе работы я тестировал утилиту на Windows 11, транслируя картинку на Android-планшет, а также на смартфон, соединенный с ПК через точку доступа Wi-Fi. На стороне мобильного браузера интерфейс поддерживает жесты масштабирования (зум до восьми крат), панорамирование, двойной тап для сброса, полноэкранный режим, а также специальный индикатор статуса соединения (активный стрим, пауза или обрыв связи). При перезапуске сервера или временной потере сети страница автоматически восстанавливает подключение, а заголовок вкладки динамически подстраивается под имя транслируемого окна.

Архитектура и внутреннее устройство программы

Чтобы добиться высокой отзывчивости и минимальных задержек, я заложил в проект несколько проверенных механизмов захвата, оптимизации трафика и сетевой раздачи.

Механизмы захвата изображения

В коде предусмотрено два принципиально разных режима захвата. Первый называется screen: в этом случае через библиотеку mss копируется прямоугольная область экрана, где располагается целевое окно. Этот метод работает невероятно быстро и универсально, однако если поверх вещаемого приложения случайно окажется другое окно, оно неизбежно попадет в итоговый кадр.

Второй режим называется printwindow. Здесь задействуется стандартная функция Win32 API PrintWindow с флагами PW_CLIENTONLY и PW_RENDERFULLCONTENT. Приложение самостоятельно прорисовывает свой интерфейс в буфер, поэтому перекрывающие окна ему не страшны. Главный нюанс заключается в том, что некоторые современные программы с аппаратным ускорением рендеринга выдают в таком режиме сплошной черный экран. Именно поэтому я вынес переключатель режимов в настройки, установив printwindow в качестве стандартного значения по умолчанию.

Реклама

Захват всегда ведется строго по клиентской области целевого приложения, исключая системную рамку и заголовок, причем в нативных пикселях. Программа изначально объявлена как DPI-aware, иначе при масштабировании интерфейса Windows на 125 или 150 процентов координаты захвата начинают сильно смещаться.

Оптимизация трафика и кодирование

Передавать статичную картинку по сети бессмысленно, поэтому я добавил проверку изменений. Для каждого захваченного кадра вычисляется контрольная сумма CRC32 от сырого буфера памяти. Если текущая хэш-сумма совпадает с предыдущей, кадр просто отбрасывается. Для статических программ и интерфейсов это решение радикально снижает нагрузку как на центральный процессор, так и на локальную сеть.

Сжатие в формат JPEG выполняется с помощью библиотеки PyTurboJPEG, которая выступает быстрой оберткой над libjpeg-turbo. Если масштаб выставлен на единицу, сырой буфер BGRX от mss передается в кодировщик напрямую, минуя промежуточные преобразования через библиотеку Pillow. Если же libjpeg-turbo в системе не обнаружен, утилита выдает предупреждение и автоматически переключается на Pillow — работает чуть медленнее, но сохраняет полную работоспособность.

Сетевая раздача и веб-сервер

Я намеренно отказался от тяжелых веб-фреймворков в пользу стандартного класса ThreadingHTTPServer и потокового метода multipart/x-mixed-replace. Один фоновый поток постоянно занят захватом и кодированием, а готовый JPEG записывается в общий буфер вместе с уникальным порядковым номером и таймстампом. Доступ к буферу защищен примитивом синхронизации Condition.

Каждое новое клиентское подключение обслуживается в персональном потоке: он ожидает поступления нового номера кадра и сразу отправляет свежие данные зрителю. Если конкретный планшет начинает тормозить, его очередь кадров не раздувается, а старые данные просто пропускаются, не создавая проблем для остальных подключений.

Если содержимое окна не меняется в течение пяти секунд, сервер принудительно пересылает последний актуальный кадр повторно, но с тем же номером. Это позволяет поддерживать соединение активным и помогает браузеру четко разделять ситуации «картинка не обновилась» и «полностью пропала связь». Обычный тег <img src=”/stream”> успешно отображает этот поток, игнорируя лишние служебные HTTP-заголовки.

Клиентская часть в браузере

Потоковые данные на стороне страницы разбираются вручную через JavaScript-интерфейс fetch. Мне потребовался такой подход ради кастомных заголовков X-Seq и X-Ts, по которым вычисляется реальная кадровая частота и сетевая задержка. Для оценки лага клиент каждую секунду опрашивает служебный эндпоинт /status со временем ПК, после чего по серии быстрых запросов вычисляет примерное расхождение системных часов.

Реклама

С какими техническими трудностями я столкнулся

Разработка выявила несколько неочевидных нюансов, о которых разработчики обычно не пишут в открытой документации популярных библиотек.

Проблемы с динамическим треем

Библиотека pystray в среде Windows формирует системное меню всего один раз при старте. Из-за этого список активных окон быстро устаревал: приложения, запущенные после старта утилиты, просто не попадали в меню. Проблему удалось решить через переопределение класса pystray.Icon с перехватом события правого клика мыши (WM_RBUTTONUP) и принудительным вызовом метода update_menu() перед отрисовкой списка.

Особенности вывода справок из собранного EXE

Я собираю исполняемый файл через PyInstaller с ключами –onefile и –noconsole, чтобы приложение не раздражало пользователя черным окном терминала. Обратной стороной такого подхода стал полный отпуск потока stdout: ключи вроде –help или –list физически ничего не выводили в консоль. Простые попытки вызова AttachConsole(ATTACH_PARENT_PROCESS) не срабатывали, так как упаковщик запускает процесс через собственный безконсольный загрузчик.

Пришлось программно подниматься по дереву родительских процессов в поисках активной консоли. Кроме того, поскольку приложение считается оконным, приглашение командной строки возвращает управление мгновенно, поэтому для корректного чтения справки требуется запускать утилиту через конструкцию start /wait “” WindowStream.exe –help. Подключение к консоли задействуется исключительно для этих сервисных ключей, иначе закрытие терминала автоматически привело бы к завершению работы самого трей-приложения.

Надежный поиск окон и юнит-тестирование

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

Всю логику конфигураций, локализации и интерфейса я покрыл юнит-тестами, которые успешно выполняются даже на Linux благодаря подмене системных модулей pywin32, winreg, mss и pystray специальными заглушками. Такой подход оправдал себя незамедлительно: тесты выловили реальный баг, когда некорректное строковое значение в файле settings.json вызывало падение из-за конфликта аргументов в переводчике.

Инструкция по использованию программы

Самый быстрый способ начать работу — скачать готовый бинарник WindowStream.exe из раздела релизов репозитория. Наличие интерпретатора Python на ПК не требуется. Поскольку файл не имеет цифровой подписи, защитник Windows SmartScreen и сторонние антивирусы могут выдать предупреждение — это стандартная ситуация для автономных сборок PyInstaller. При первом старте брандмауэр запросит доступ: обязательно разрешайте подключения только для частных локальных сетей.

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

Для развертывания из исходного кода выполните стандартные команды:

  • git clone https://github.com/eugenyh/WindowStream.git
  • cd WindowStream
  • pip install -r requirements.txt
  • python window_stream.py

Рекомендуется отдельно установить пакет libjpeg-turbo для максимальной производительности кодирования. Все пользовательские параметры сохраняются в файле %APPDATA%\WindowStream\settings.json, причем аргументы командной строки всегда имеют приоритет, а ключ –save позволяет зафиксировать текущие настройки на диск. Реализована функция автозапуска вместе с Windows: если целевое приложение еще не запустилось к моменту входа пользователя в систему, утилита будет терпеливо опрашивать систему каждые пять секунд. Интерфейс переведен на русский и английский языки.

Главные ограничения утилиты

Честный обзор невозможен без упоминания слабых мест разработанного инструмента. В программе полностью отсутствуют механизмы шифрования и авторизации, используется незащищенный протокол HTTP. Любой участник вашей локальной сети, узнавший IP-адрес и порт, сможет просматривать транслируемое окно. По этой причине утилиту стоит использовать исключительно в доверенных домашних или офисных сетях, избегать стриминга конфиденциальных данных в публичных местах и никогда не пробрасывать порты наружу в интернет.

Существуют и технологические ограничения захвата: свернутое приложение не передает кадры, оставляя на планшете застывшую картинку. Участки окна, выходящие за физические границы экрана, операционной системой не перерисовываются. В режиме screen захватываются любые перекрывающие объекты, а в режиме printwindow некоторые программы выдают черный экран. Если целевой софт запущен с правами администратора, сама утилита WindowStream также потребует повышения привилегий. Звуковая дорожка и элементы дистанционного управления не поддерживаются — это чисто пассивный инструмент визуального контроля.

Разработанный проект решает конкретную локальную проблему, умещая всю необходимую сетевую и графическую механику в единственный легко читаемый файл. Программа распространяется под свободной лицензией MIT, поэтому вы можете смело дорабатывать ее под свои сценарии.

Реклама
01.