Превращает 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).
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.jsgit 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) наследует разрешения терминала — там этой проблемы нет.
Без установки — Safari: запустите хост и отсканируйте QR-код из блока «Без приложения». Картинка будет в JPEG, 10–15 fps, звук включается тапом по экрану.
Полноценно — нативное приложение (H.264 60 fps, звук, жесты, USB): см. отдельную инструкцию docs/IPAD-APP.md.
- Откройте iPad Display на компьютере (ярлык на рабочем столе или
npm start). - Откройте iPad Display на iPad.
- Передача начинается сама: галочка «запускать захват при старте» включена по умолчанию, поэтому кнопка сразу показывает Стоп, а плашка в шапке — «iPad подключён по Wi-Fi» или «по USB». Кнопка «Старт» появляется, только если захват остановлен или галочка снята.
- Перетащите нужные окна на новый монитор (в Windows он виден в Параметры → Система → Дисплей, там же настраивается его положение относительно основного экрана; на macOS — Системные настройки → Мониторы).
- Стоп останавливает передачу и убирает звуковые устройства iPad из системы. Виртуальный монитор на Windows тоже отцепляется, а на macOS он живёт, пока открыт хост, и исчезает при выходе из него.
Кнопка «Как пользоваться» в шапке панели открывает ту же инструкцию и разбор частых проблем прямо в приложении.
| Жест | Действие |
|---|---|
| Один палец | мышь: тап — клик, движение — перетаскивание |
| Два пальца | прокрутка (вертикальная и горизонтальная) |
| Щипок | масштаб (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/ запускаются из папки проекта.
Звук проходит четыре участка, и каждый проверяется отдельно. Идите по порядку, первый несовпавший пункт и есть причина.
-
Звук попадает в виртуальный кабель. Он попадает туда, только если приложение-источник выводит на устройство кабеля. На macOS это «iPad Display» (или «iPad Display + динамики»), на Windows — «CABLE Input». Если на macOS выбрано «iPad Display», а звук всё равно слышен из динамиков Mac, значит источник выводит мимо: проверьте выбор устройства в самом приложении.
-
Хост читает кабель. Раз в пять секунд хост печатает строку вида
[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, причина дальше -
Звук доходит до приложения на iPad.
node tools/ssh.js "cat /tmp/ipaddisplay.log": строкаaudio chunk #Nпишется для первого пакета и дальше каждые 200-й, то есть примерно раз в 19 секунд, а счётчик идёт 1 → 200 → 400. Если он растёт, пакеты доходят. В поздних строках должно стоятьstarted=1; в самой первой там закономерноstarted=0— очередь ещё не запущена. -
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).
