Skip to content

Repository files navigation

iPad Display — старый iPad как второй монитор для Windows и macOS

Превращает iPad в дополнительный экран компьютера: картинка с аппаратным H.264 до 60 fps, системный звук, управление курсором касаниями и жестами. Работает на устройствах, которым отказали Sidecar, Duet и Luna, — вплоть до iPad mini 1 / iPad 2 на iOS 9.3.5.

Панель хоста

  • Второй монитор, а не зеркало: хост создаёт виртуальный дисплей 1024×768, и окна, перетащенные на него, появляются на iPad.
  • Два пути на iPad: нативное приложение (H.264, звук, жесты, работа по кабелю) или просто Safari, без установки чего-либо.
  • Подключение само: iPad объявляет себя в сети, хост его находит; воткнутый кабель USB используется автоматически как более быстрый и стабильный канал.
  • Ничего лишнего после работы: на «Стоп» виртуальный монитор отцепляется, звуковое устройство исчезает из системы, окна возвращаются на основной экран.

Требования

Что Минимум Проверено на
Компьютер Windows 10/11 или macOS 12+, Node.js 18+ Windows 11 + RTX 3070 Ti, macOS 26.5 (M4 Pro)
iPad любой с Safari и WebSocket (iOS 6+) iPad mini 1 (A1432), iOS 9.3.5
Для нативного приложения джейлбрейк + OpenSSH на iPad EverPwnage 2.0.1 + iocaste, iOS 9.3.5
Для кабеля USB usbmuxd на компьютере «Apple Devices» из Microsoft Store / встроенный в macOS
Аппаратный кодек ffmpeg 9.0.1 (Windows), 8.1 (macOS)
Звук отдельным устройством VB-CABLE (Windows) или BlackHole (macOS) оба

Сеть: iPad и компьютер в одной сети Wi-Fi. Для кабеля сеть не нужна.


Готовые сборки

Каждый push в main собирает в GitHub Actions (workflow build) три артефакта, а тег v* публикует их на странице Releases:

Файл Что это
iPad Display-<версия>-setup-win-x64.exe установщик для Windows (NSIS); …-portable-win-x64.exe — тот же хост без установки, просто запускается
iPad Display-<версия>-mac-arm64.dmg / -mac-x64.dmg.zip) приложение для macOS на Apple Silicon / Intel
IPadDisplay-iPad-app.zip приложение для iPad (armv7, iOS 9); ставится по инструкции docs/IPAD-APP.md

Сборки не подписаны: Windows SmartScreen — «Подробнее → Выполнить в любом случае»; macOS — правый клик по приложению → «Открыть» (или xattr -d com.apple.quarantine "/Applications/iPad Display.app"). Хосту всё равно нужны ffmpeg (и на Windows — «Apple Devices» для кабеля, на macOS — BlackHole для звука): команды ниже. Собрать локально: npm install && npm run dist (на macOS перед этим npm run helpers:mac).

Установка из исходников

1. Хост на Windows

git clone https://github.com/manar-mk/ipad-display
cd ipad-display
npm install
winget install Gyan.FFmpeg                 # аппаратный H.264 через Media Foundation
winget install 9NP83LWLPZ9K --source msstore   # «Apple Devices»: даёт usbmuxd для кабеля

Дополнительно:

  • Виртуальный монитор — кнопка «Установить виртуальный монитор 1024×768» в первом блоке панели (драйвер Virtual Display Driver, MIT, лежит в driver/windows/). Windows спросит подтверждение администратора.
  • Звук отдельным устройством — поставьте VB-CABLE. В системе появится вывод «CABLE Input»: всё, что на него направлено, уходит на iPad.
  • Ярлык на рабочем столе создаётся командой:
node tools/make-shortcut.js

2. Хост на macOS

git clone https://github.com/manar-mk/ipad-display
cd ipad-display
npm install
xcode-select --install        # swiftc для помощников driver/macos (собираются сами)
brew install ffmpeg           # захват avfoundation + аппаратный H.264 VideoToolbox
brew install blackhole-2ch    # виртуальное аудиоустройство (спросит пароль администратора)

Разрешения в Системных настройках → Конфиденциальность и безопасность (панель подскажет, чего не хватает): Запись экрана (картинка), Универсальный доступ (касания как мышь), Микрофон (чтение BlackHole). Виртуальный монитор macOS-хост создаёт сам при запуске.

Две ловушки, на которых легко потерять час:

  • Запись экрана начинает действовать только после перезапуска приложения. Пока вы его не перезапустили, оно продолжает жаловаться на отсутствие разрешения, хотя переключатель уже включён.

  • Разрешения привязаны к подписи приложения. Пересобрали хост — и для системы это другое приложение, а включённый переключатель остаётся, но уже ничего не значит. Признак: приложение пишет accessibility trusted: false или Failed to get sources, хотя в настройках всё включено. Лечится сбросом записи начисто, после чего приложение запросит разрешение заново:

    tccutil reset ScreenCapture com.manar.ipaddisplay
    tccutil reset Accessibility com.manar.ipaddisplay

    Сборки из npm run dist подписываются локальной подписью с постоянным идентификатором, поэтому выдачу обычно не срывает. Запуск из исходников (npm start) наследует разрешения терминала — там этой проблемы нет.

3. iPad

Без установки — Safari: запустите хост и отсканируйте QR-код из блока «Без приложения». Картинка будет в JPEG, 10–15 fps, звук включается тапом по экрану.

Полноценно — нативное приложение (H.264 60 fps, звук, жесты, USB): см. отдельную инструкцию docs/IPAD-APP.md.


Запуск

  1. Откройте iPad Display на компьютере (ярлык на рабочем столе или npm start).
  2. Откройте iPad Display на iPad.
  3. Передача начинается сама: галочка «запускать захват при старте» включена по умолчанию, поэтому кнопка сразу показывает Стоп, а плашка в шапке — «iPad подключён по Wi-Fi» или «по USB». Кнопка «Старт» появляется, только если захват остановлен или галочка снята.
  4. Перетащите нужные окна на новый монитор (в Windows он виден в Параметры → Система → Дисплей, там же настраивается его положение относительно основного экрана; на macOS — Системные настройки → Мониторы).
  5. Стоп останавливает передачу и убирает звуковые устройства iPad из системы. Виртуальный монитор на Windows тоже отцепляется, а на macOS он живёт, пока открыт хост, и исчезает при выходе из него.

Кнопка «Как пользоваться» в шапке панели открывает ту же инструкцию и разбор частых проблем прямо в приложении.

Управление с iPad

Жест Действие
Один палец мышь: тап — клик, движение — перетаскивание
Два пальца прокрутка (вертикальная и горизонтальная)
Щипок масштаб (Ctrl+колесо, на macOS ⌘+колесо)
Долгое нажатие правая кнопка мыши
Тап тремя пальцами выбор компьютера, если их несколько

Звук

Системный звук идёт на iPad как PCM 22050 Гц моно.

На macOS кнопка «Сделать iPad звуковым устройством» создаёт два устройства вывода и сразу включает первое:

Устройство Где играет звук
iPad Display только на iPad — Mac молчит, как при подключении монитора с колонками
iPad Display + динамики одновременно на iPad и на динамиках Mac

Переключаются они в Системные настройки → Звук → Вывод, вернуть звук на Mac можно там же. Удалить оба: driver/macos/audiosetup --remove.

На Windows ту же роль играет VB-CABLE: вывод «CABLE Input». Чтобы отправить на iPad только одно приложение, назначьте ему это устройство (Параметры → Система → Звук → Микшер громкости; на macOS — в настройках самого приложения). Устройство кабеля существует только пока идёт передача.

Переключать системное устройство по умолчанию хост умеет (галочка «делать его устройством по умолчанию»), но утилиты вроде Nahimic и Realtek Audio Console возвращают своё через несколько секунд — поэтому по умолчанию галочка выключена.


Что делает хост по умолчанию

  • Выбирает виртуальный монитор (второй экран 4:3), на macOS сначала создаёт его.
  • Начинает захват сразу при запуске, включает звук и приём касаний.
  • Слушает UDP-маячки приложения iPad (порт 7802) и подключается сам; кабель имеет приоритет над Wi-Fi.
  • На «Стоп» и при закрытии окна убирает звуковые устройства кабеля; виртуальный монитор отцепляется на Windows, а на macOS исчезает при выходе из хоста.

Настройки: %APPDATA%\ipad-display\settings.json; на macOS — ~/Library/Application Support/iPad Display/ у собранного приложения и ~/Library/Application Support/ipad-display/ при запуске из исходников.


Диагностика

Проблема Что проверить
iPad не находится Приложение на iPad открыто; устройства в одной сети; включена галочка «подключаться автоматически» (плашка в шапке предупредит, если она снята)
Нет звука на iPad Приложению назначен вывод «CABLE Input»; галочка «звук на iPad»; громкость iPad; журнал node tools/ssh.js "cat /tmp/ipaddisplay.log"
Картинка дёргается Подключите кабель USB, уменьшите битрейт; по Wi-Fi помогает 5 ГГц
Нет виртуального монитора Кнопка установки в первом блоке панели; на macOS — сообщение об ошибке CGVirtualDisplay, запасной путь DeskPad
Приложение пропало с iPad После перезагрузки iPad джейлбрейк не активен: запустите EverPwnage → Jailbreak, затем «iPad Display»
iPad показывает другой компьютер На iPad тап тремя пальцами → выберите нужный хост или «Любой хост»
Звук есть на компьютере, но не на iPad Разберите тракт по участкам — см. «Почему нет звука» ниже
Запущено несколько копий хоста Порт 7800 занимает первая, остальные молча не обслуживают iPad. Проверка: lsof -i :7800 (Windows: netstat -ano | findstr 7800)
iPad принимает звук, но молчит Самопроверка на устройстве, см. «Почему нет звука» ниже
Чёрный экран на iPad Виртуальный монитор пуст — перетащите на него окно. Признак: 60 fps при ~0.1 Мбит/с

Полезные команды:

node tools/device-info.js                       # iPad по USB: модель, iOS, открытые порты
node tools/usbmux-list.js                       # usbmuxd и туннель к iPad
node tools/list-apps.js Any                     # какие приложения стоят на iPad
node tools/ssh.js "cat /tmp/ipaddisplay.log"    # журнал звука приложения (Wi-Fi: IPAD_SSH_HOST=<ip>)
node tools/test-client.js ws://127.0.0.1:7800/ 3 # кадры по WebSocket + тестовое касание
node tools/ssh.js "touch /tmp/ipaddisplay.selftest"  # тон 440 Гц на iPad; звучит, только пока хост шлёт звук (снять: rm -f)
node tools/ssh.js "killall mediaserverd"             # перезапуск звукового демона iPad

Команды с tools/ запускаются из папки проекта.

Почему нет звука

Звук проходит четыре участка, и каждый проверяется отдельно. Идите по порядку, первый несовпавший пункт и есть причина.

  1. Звук попадает в виртуальный кабель. Он попадает туда, только если приложение-источник выводит на устройство кабеля. На macOS это «iPad Display» (или «iPad Display + динамики»), на Windows — «CABLE Input». Если на macOS выбрано «iPad Display», а звук всё равно слышен из динамиков Mac, значит источник выводит мимо: проверьте выбор устройства в самом приложении.

  2. Хост читает кабель. Раз в пять секунд хост печатает строку вида [panel] audio: … peak=A/B via ffmpeg. Она идёт в консоль самого хоста, а не в его окно, поэтому запустите хост из терминала, чтобы её увидеть:

    "dist/mac-arm64/iPad Display.app/Contents/MacOS/iPad Display"   # собранное приложение
    npm start                                                        # из исходников

    A — уровень, который хост отправляет на iPad, B — уровень того же кабеля, снятый окном панели. Оба числа от 0 до 32767. Таблица ниже верна для via ffmpeg: только там A и B снимаются двумя независимыми захватами. При via renderer оба числа приходят из одного и того же захвата, и расхождения между ними быть не может.

    Показание Что это значит
    peak=0/0 в кабель ничего не поступает — вернитесь к пункту 1
    peak=0/большое захват хоста залип; на macOS он перезапустится сам примерно через 8 секунд, в консоли появится capturing silence (на Windows такого сторожа нет — перезапустите хост)
    оба большие звук уходит на iPad, причина дальше
  3. Звук доходит до приложения на iPad. node tools/ssh.js "cat /tmp/ipaddisplay.log": строка audio chunk #N пишется для первого пакета и дальше каждые 200-й, то есть примерно раз в 19 секунд, а счётчик идёт 1 → 200 → 400. Если он растёт, пакеты доходят. В поздних строках должно стоять started=1; в самой первой там закономерно started=0 — очередь ещё не запущена.

  4. iPad его воспроизводит. node tools/ssh.js "touch /tmp/ipaddisplay.selftest" — приложение проверяет этот файл только когда принимает звук от хоста (примерно раз в 2 секунды), поэтому хост должен быть подключён и передавать. Дальше приложение проиграет одну секунду тона 440 Гц, сгенерированного на самом устройстве, и запишет в журнал строку queue clock. Поле sampleTime должно расти примерно на частоту дискретизации каждую секунду: если оно стоит, воспроизведение не идёт. Тон повторяется, пока файл-флаг на месте, снимается командой node tools/ssh.js "rm -f /tmp/ipaddisplay.selftest".

    Если тона не слышно, перезапустите звуковой демон устройства и само приложение:

    node tools/ssh.js "killall mediaserverd"
    node tools/ssh.js "sblaunch com.manar.ipaddisplay"

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

┌──────────────── компьютер (Electron) ────────────────┐        ┌──────── iPad ────────┐
│ виртуальный монитор → ffmpeg (аппаратный H.264)      │ Wi-Fi  │ IPadDisplay.app      │
│                     → TCP 7801 ──────────────────────┼───────►│ AVSampleBufferLayer  │
│ виртуальный аудиокабель → ffmpeg → PCM 22050 ────────┼─ USB ─►│ AudioQueue           │
│ HTTP + WebSocket 7800 (JPEG для Safari) ◄────────────┼────────┤ касания, жесты       │
└──────────────────────────────────────────────────────┘        └──────────────────────┘

Протокол к приложению: [uint32 длина][тип][данные]. От хоста — H (avcC), V (кадр H.264), J (JPEG), F/A (формат и данные звука), N (имя хоста). От iPad — ack кадра, T (касание), S (прокрутка), Z (масштаб), R (правая кнопка), K (нужен ключевой кадр), P (кадр показан, по нему считается задержка), X (выбран другой хост).

Обнаружение: iPad каждые 2 с шлёт UDP «IPADDISPLAY 7801» (плюс «busy», если уже занят), хосты отвечают своим именем — из этого и строится список компьютеров на iPad.

Кодеки. H.264 — основной: Media Foundation (Windows) или VideoToolbox (macOS) кодируют аппаратно, iPad декодирует аппаратно; 60 fps при 1024×768, 2–8 Мбит/с, задержка сеть+декодер 5–9 мс. JPEG — для Safari (в iOS 9 нет Media Source Extensions) и как запасной путь, 10–15 fps.

Bluetooth не поддерживается намеренно: сторонним приложениям на iOS доступен только BLE (десятки КБ/с), а картинке нужно 1–2 МБ/с.


Структура

Файл Назначение
main.js Electron main: HTTP/WebSocket 7800, клиент к приложению (USB/TCP), обнаружение, касания, уборка сессии
panel.html, preload.js окно хоста: захват, JPEG, WebCodecs, настройки, инструкция
i18n.js тексты панели на двух языках
applaunch.js запуск приложения на iPad по SSH, когда оно не отвечает на порту
ffenc.js внешний аппаратный H.264 и захват звука через ffmpeg, разбор Annex B
usbmux.js клиент usbmuxd: список устройств и туннель к порту iPad без iproxy
client/index.html веб-клиент для Safari (ES5, совместим с iOS 9)
ios/ нативное приложение (Objective-C, iOS 9) + build.sh + tools/sblaunch.c
driver/windows/ Virtual Display Driver, install-vdd.ps1, session.ps1 (монитор и звук по сессии)
driver/macos/ vdisplay.swift (виртуальный монитор), mousehelper.swift (мышь и жесты), audiosetup.swift (устройства вывода звука), build.sh — хост собирает их swiftc сам, в собранном приложении они лежат готовыми
tools/ установка на iPad, SSH, диагностика, генератор иконок, эмуляторы
assets/ иконки приложения (node tools/make-icons.js рисует их без графических редакторов)
docs/IPAD-APP.md установка приложения на iPad
docs/macos-host-prompt.md задание для сессии, портирующей хост на macOS

Переменные окружения: IPAD_DISPLAY_PORT (порт HTTP, 7800), IPAD_DISPLAY_AUTOSTART=1, IPAD_DISPLAY_TCP=host:port или =usb, IPAD_DISPLAY_FFMPEG=<путь>, IPAD_SSH_HOST, IPAD_SSH_PASS.


Ограничения

  • iPad mini 1 не Retina: размер больше 1024×768 только увеличивает трафик.
  • Звук без сжатия (~350 кбит/с), задержка 0.2–0.5 с; HDCP-защищённый контент не захватывается.
  • Профиль джейлбрейка слетает при перезагрузке iPad — приложение нужно запускать после повторного джейлбрейка (см. docs/IPAD-APP.md).
  • Нативное приложение собирается только под armv7/iOS 9 (Xcode 14+ такое не умеет, поэтому сборка идёт в GitHub Actions на Linux-тулчейне theos).

About

Старый iPad как второй монитор для Windows и macOS: H.264 60 fps, звук, касания и жесты / An old iPad as a second monitor for Windows and macOS

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages