CNN TürkMudanya'da sağanak sel getirdi: Mahsur kalanlar kurtarıldıPunchPeter Obi demands better facilities for displaced persons in SokotoThe Jerusalem PostDebris found at Western embassy in Kyiv, mayor saysBollywood HungamaAnshuman Jha’s Lakadbaggha 2 - The Monkey Business becomes 1st Indian action franchise to have World Premiere at Razor Reel Flanders Film Fest, BelgiumDaily MaverickWatch – Cape Town Planning Tribunal agreed with officials almost every timeInquirerProsec hails SC dismissal of pleas vs impeach court voting thresholdBBC BusinessAfrica's richest man to launch Kenya oil refinery despite land protestsEgypt IndependentWhen is the longest total solar eclipse in Egypt?Wirtualna PolskaIncydent w samolocie Flydubai. Pilot wołał o pomocBBC NewsMore talks over Drumcree dispute as stalemate continuesNotJustOkWeekly round-up: Nigerian stars shining abroad in Europe's top leaguesThe South AfricanDay 18 of 24: Festive Quiz – Test your Elon Musk knowledge and win R250
The Daily Newsstand · Free, Always
Wednesday, September 30, 2026

Показываем одно окно Windows на планшете через обычный браузер

Translate

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

Готовых решений хватает: Spacedesk, Sunshine с Moonlight, VNC, демонстрация экрана в любом мессенджере для звонков. Часть из них превращает планшет во второй монитор, части нужен клиент на стороне устройства, часть показывает экран целиком, у некоторых свои условия лицензирования, которые стоит прочитать до того, как вы положитесь на инструмент. Мне хотелось чего-то проще: запустил на ПК, открыл адрес в браузере планшета, увидел окно. Получившуюся программу я выложил в открытый доступ. Она никого не заменяет, просто закрывает конкретную задачу, и если у вас такая же, возможно, вам пригодится.

Проект называется WindowStream: https://github.com/eugenyh/WindowStream

Что это

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

Написано на Python. Работал я на Windows 11, смотрел с Android-планшета, а также с телефона, подключённого к точке доступа Windows.

На странице просмотра есть зум пальцами (до 8 раз), перемещение картинки, двойное касание, полноэкранный режим и индикатор состояния: идёт трансляция, пауза или нет связи. Если сеть пропала или программу перезапустили, страница переподключается сама. Заголовок вкладки браузера совпадает с заголовком транслируемого окна.

Как это устроено внутри

Захват. Режимов два. В режиме screen через библиотеку mss копируется область окна прямо с экрана. Это быстро и работает почти всегда, но если поверх окна что-то лежит, оно попадёт в кадр. В режиме printwindow вызывается PrintWindow с флагами PW_CLIENTONLY и PW_RENDERFULLCONTENT: окно рисует себя само, поэтому перекрытие другими окнами не мешает. Минус в том, что некоторые приложения с аппаратной отрисовкой отдают таким способом чёрный кадр. Поэтому режим переключается в настройках, и по умолчанию стоит printwindow.

Снимается всегда клиентская область, без заголовка и рамки, в реальных пикселях (процесс объявлен DPI-aware, иначе при масштабе 125 или 150 процентов координаты уезжают).

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

JPEG кодируется через PyTurboJPEG (обёртка над libjpeg-turbo). При масштабе 1.0 буфер от mss в формате BGRX уходит в кодировщик напрямую, без промежуточного изображения Pillow. Если libjpeg-turbo не найден, программа сообщает об этом и работает через Pillow: медленнее, но работает.

Раздача. Никаких веб-фреймворков: стандартный ThreadingHTTPServer и multipart/x-mixed-replace. Один поток захватывает и кодирует, последний JPEG кладётся в общий буфер вместе с номером кадра и временем захвата. Буфер защищён Condition. Каждое подключение зрителя живёт в своём потоке, ждёт новый номер кадра и отправляет самый свежий. Медленный клиент просто пропускает кадры, очередь не копится, и остальные зрители его не ощущают.

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

Страница просмотра. Поток на странице разбирается вручную через fetch, потому что мне нужны заголовки X-Seq и X-Ts, из которых считаются частота кадров и задержка. Для задержки страница раз в секунду опрашивает /status, где лежит время ПК, и по измерению с наименьшим временем отклика из последних тридцати оценивает расхождение часов между ПК и планшетом. Это оценка, а не точное измерение, но для ощущения «сколько отстаёт» её хватает.

Мелочи, на которых я застрял

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

Меню в трее. На Windows pystray собирает меню один раз. Список открытых окон, который должен обновляться, поэтому устаревал: окна, открытые после запуска, в меню не попадали. Помогло наследование от pystray.Icon с перехватом правого клика (WM_RBUTTONUP) и вызовом update_menu() перед показом.

Вывод --help из собранного exe. Я собираю exe через PyInstaller с флагами --onefile --noconsole, чтобы не было чёрного окна. Побочный эффект: у такого приложения нет stdout, и --help или --list ничего не печатают. Простой AttachConsole(ATTACH_PARENT_PROCESS) консоль cmd не находил, потому что --onefile запускает приложение через загрузчик PyInstaller, у которого консоли нет. Пришлось подниматься по цепочке родительских процессов, пока консоль не найдётся. И ещё одна деталь: приглашение cmd возвращается раньше, чем появляется текст (приложение оконное), поэтому для ожидания нужно запускать start /wait "" WindowStream.exe --help. Подключаться к консоли стоит только для этих ключей, иначе закрытие окна консоли завершит трей-приложение.

Поиск окна после смены заголовка. Во многих программах в заголовке окна имя открытого файла. Открыли другой файл, и точное совпадение по заголовку не работает. Поэтому вместе с заголовком запоминается имя процесса, и если заголовок не нашёлся, берётся самое большое видимое окно этого процесса.

Тесты на Linux. Логика настроек, страницы и локализации покрыта юнит-тестами, а тесты запускаются даже без Windows: модули pywin32, winreg, mss и pystray подменяются заглушками. Это окупилось: тесты нашли настоящую ошибку. Некорректное значение в settings.json (например, "port": "abc") роняло загрузку настроек из-за конфликта имён в вызове T(key, key=...), где T это функция перевода строк. Исправил, сделав первый параметр только позиционным.

Как пользоваться

Проще всего взять готовый WindowStream.exe из раздела Releases в репозитории. Python не нужен. Файл не подписан, поэтому SmartScreen и антивирусы могут ругаться, это обычная история для сборок PyInstaller. При первом запуске Windows спросит про брандмауэр: разрешайте только частные сети.

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

Из исходников:

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

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

Ограничения, о которых надо знать

Без этого раздела статья была бы нечестной, поэтому пишу прямо.

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

Свёрнутое окно не снимается: на планшете остаётся последний кадр. Часть окна за краем экрана не перерисовывается самой Windows. В режиме screen в кадр попадают перекрывающие окна, в режиме printwindow часть приложений даёт чёрную картинку. Окна программ, запущенных от администратора, требуют запуска WindowStream тоже от администратора. Звука нет, управления нет, только Windows.

Вместо итога

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

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.