Вы не можете скрыть окно от Zoom на macOS 15

Обновлено

На современных версиях macOS невозможно надёжно скрыть окно от записи экрана — neither from another application, nor even from your own. Это техническое объяснение удаления функции CGSSetWindowCaptureExcluded, игнорирования SLSSetWindowSharingState в межпроцессном режиме и того, почему NSWindow.sharingType = .none является мягким предложением для Zoom и QuickTime, а не жёстким обязательством.

Вы не можете скрыть окно от Zoom на macOS 15 1
Реклама · УДАЛИТЬ?

Краткий технический анализ разработки приложения «личного окна», которое не работает должным образом.


Идея

Как многие из нас, я работаю с открытым окном на рабочем столе в приложении Claude во время встреч. И, как многие, иногда делятся экраном в Zoom, и мне бы хотелось, чтобы такое окно не было видно всем участникам звонка. Не из-за каких-то чувствительных данных — просто потому, что это личное рабочее состояние.

Я знал, что приложения, такие как 1Password и Hand Mirror, могут скрывать свои окна от записи экрана. Я решил создать небольшую утилиту в меню-бара, которая позволит мне выбирать любое приложение — Claude, Notes или что угодно — и переключать его окна на невидимость при записи, при этом оставляя их полностью видимыми и интерактивными на своём экране.

Проект на выходных. Максимум два часа.

Он занял больше времени. И главный вывод такой: на современной macOS вы не можете это сделать. Не для другого приложения, и — как оказалось — не надёжно для собственного.

Это техническая история того, почему.


Попытка 1: скрыть окно другого приложения

Классический способ сделать окно macOS невидимым для записи экрана — это NSWindow.sharingType = .none. Но это флаг на уровне окна, который может установить только собственное процесс, относительно своих собственных окон. AppKit не позволяет вам проникнуть в список окон другого приложения.

Известный обход, используемый годами инструментами, такими как Hand Mirror и различные программы для бланкирования экрана, — это функция SkyLight:

OSStatus CGSSetWindowCaptureExcluded(CGSConnectionID cid, CGWindowID wid, bool excluded);

Вы перебираете окна через CGWindowListCopyWindowInfo, фильтруете по PID целевого приложения и вызываете эту функцию для каждого ID окна. Поскольку вызов идёт через WindowServer (а не через собственное приложение), он может исключить любое окно из записи.

Я реализовал это через dlsym на /System/Library/PrivateFrameworks/SkyLight.framework/..., создал приложение, запустил его на macOS 15.3.1 и получил:

[InvisibleApp] symbol not found: CGSSetWindowCaptureExcluded
[InvisibleApp] symbol not found: SLSSetWindowCaptureExcluded

Функция исчезла. Apple её удалила. Я искал переименованную версию (SLSSetWindowExcludedFromCapture, CGSSetWindowSharingState, все очевидные варианты). Большинство не существуют, но я нашёл два, которые работают:

  • SLSSetWindowSharingState(CGSConnectionID, CGWindowID, int sharingState) — базовый вызов, который NSWindow.sharingType = .none использует внутренне. Обмен состоянием 0 является NSWindowSharingNone.
  • SLSGetWindowOwner — даёт вам ID соединения владельца окна.

Таким образом, я перестроил мост и вызвал SLSSetWindowSharingState на целевом окне, попробовав как моё основное соединение, так и соединение владельца окна.

Сборка, запуск, включение, лог показывает, что возврат noErr, снимок — Claude всё ещё присутствует на снимке. Попробуйте Zoom — Claude всё ещё виден в поделке.

SLSSetWindowSharingState успешно работает при вызове через процесс, но WindowServer тихо игнорирует это. Только собственное приложение может изменять состояние своего окна на macOS 15.

Я также подтвердил, что очевидный выход из ситуации закрыт: DYLD_INSERT_LIBRARIES внедрение в процесс Claude заблокировано, поскольку Claude (как большинство современных приложений) использует упрочнённый запуск и не имеет disable-library-validation разрешения.

Таким образом, это решено: никакое стороннее приложение не может скрыть окно другого приложения от записи на macOS 15. Вывод соответствует тому, что говорит Apple: состояние записи на уровне окна — это привилегия собственного приложения, конец.


Попытка 2: скрыть своё собственное окно

Альтернативный план: переключить продукт. Вместо инструмента, который скрывает Claude, создать небольшое окно чата, которое будет выполнять ту же задачу, но в другом виде. Мое окно, моя sharingType = .none. Это хорошо известный механизм, используемый каждым инструментом, который скрывает запись экрана, и это всего лишь одна строка кода:

window.sharingType = .none

Подключить поток чата к API Anthropic Messages (или к локальному claude CLI для аутентификации подписки), установить sharingType = .none на окно чата и отправить его. Это несомненный, поддерживаемый, публичный API.

Создано. Записано значение типа передачи в режиме выполнения, чтобы убедиться, что macOS приняло его:

[InvisibleApp] window 151526 sharingType=0

При снимке экрана — окно чата исчезло правильно. ✅

Запись экрана с помощью QuickTime — окно чата появилось в записи. ❌

Поделка экрана в Zoom — окно чата видно для других участников. ❌

Это было неожиданно. Цель всей точка — вывести его из этих потоков. Поэтому я создал встроенную проверку в приложении, которая выполняет снимок экрана с помощью ScreenCaptureKit (современный, публичный API записи), используемый QuickTime под капотом. Я попробовал как одноразовую запись ( NSWindowSharingNone is to get it out of these flows. So I built a self-test inside the app that performs a screen capture using ScreenCaptureKit (the modern, public capture API), the same framework QuickTime uses under the hood. I tried both single-shot capture (SCScreenshotManager.captureImage) так и непрерывную запись (SCStream) — оба варианта создавали PNG с корректно исключённым окном чата.

Таким образом:

Метод записиСкрывает наше окно?
Cmd-Shift-3/4/5 (системный снимок)
Снимок приложения SCK одноразовый
Снимок приложения SCK непрерывный SCStream
QuickTime «Новая запись экрана»
Zoom «Поделить экран → ПК»

То же ОС, та же машина, то же окно, то же sharingType = .none. Единственное, что меняется — это тот, кто выполняет запись.


Привилегированные пути записи

Это часть, которую большинство инженеров не осознают: на macOS 15 запись экрана — это не один API; это как минимум два — публичный и приватный с дополнительными разрешениями.

Я извлёк разрешения из QuickTime Player:

codesign -d --entitlements - /System/Applications/QuickTime\ Player.app
com.apple.private.screencapturekit.noprompt    = true
com.apple.private.tcc.allow                     = [
    kTCCServiceMicrophone,
    kTCCServiceCamera,
    kTCCServiceScreenCapture,
]

Эти com.apple.private.* ключи могут быть предоставлены только Apple своим первичным бинарным файлам. Вам и мне не удастся их запросить. Неудовлетворительная подпись отклонит их. App Store, безусловно, тоже.

И то, что они предоставляют, помимо пропуска запроса на разрешение, — возможность записывать изображение дисплея до того, как WindowServer применяет фильтрацию по типу передачи. Окно присутствует в буфере, который выходит — Apple-инструменты просто видят всё.

Zoom использует аналогичный привилегированный путь. Это может быть драйвер ядра, расширение системы или приватный API, полученный как долгосрочное приложение для конференций — я не изучил достаточно глубоко, чтобы сказать, какое именно. Но поведение одинаково: стандартная публичная система исключения не применяется к нему.


Что это означает

Если вы — разработчик стороннего приложения и ваша цель — «это окно не должно появляться в записи экрана на любом Mac», вы не можете гарантировать это на macOS 15. Ваше окно скрыто от всех путей записи, которые вы можете написать сами. Оно скрыто от Google Meet (браузер → getDisplayMedia → SCK), Microsoft Teams (SCK), OBS (SCK), всех соответствующих сторонних инструментов записи. Но Apple-инструменты и приложения, одобрённые Apple, всё ещё могут видеть его.

Функция «защиты от записи экрана» в 1Password / Hand Mirror имеет ту же ограниченность. Люди просто не записывают свой 1Password с помощью QuickTime, поэтому утечка остаётся незамеченной.

Честное резюме, которое я бы дал Apple: выпустите публичное разрешение, эквивалентное com.apple.private.screencapturekit.noprompt, или объявите, что NSWindow.sharingType = .none является жёстким гарантийным условием против всех путей записи. На macOS 15.3.1 это не так — это жёсткая гарантия против большинства путей и мягкая рекомендация, что привилегированные инструменты могут обойти это. Состояние делает так, что пользователи считают окно невидимым во время записи, когда на самом деле оно видно — это хуже, чем полное отсутствие защиты.


Обходы, упорядоченные по степени вреда

Для моего случая — поддержка скрытия окна чата во время поделки экрана в Zoom — все они работают, но ни один из них не идеален:

  1. Поделите только одно окно в Zoom, а не весь экран. В диалоге «Поделить экран» есть вкладка «Окно». Zoom записывает только пиксели этого окна и ничего больше, поэтому мой чат — и всё остальное на экране — автоматически исчезает. Это самый надёжный способ и требует нулевого кода. Недостаток: зрителям не видно контекста экрана.
  2. Используйте Meet или Teams вместо Zoom. Оба соблюдают sharingType = .none. Это отлично, если вы контролируете инструмент встречи, и плохо, если вы не контролируете.
  3. Поместите чат на отдельный экран. Mission Control даёт вам несколько рабочих столов. Поделите Страну 1, оставьте чат на Стране 2. Свайп, чтобы взаимодействовать. Медленно, нарушает поток, но работает.
  4. Используйте второй девайс. Телефон или iPad рядом с ноутбуком невозможно скрыть никаким инструментом записи, по определению.

Я выбрал вариант 1 плюс уже созданный чат-приложение. Чат невидим для ~95% путей записи, и в случае поделки экрана в Zoom я просто не использую поделку экрана в Zoom.


Что я бы хотел видеть дальше

  • Публичное разрешение которое позволило бы неотмеченному приложению объявить: «это окно моё исключено из всех записи, включая приложения с привилегиями».
  • Честность в NSWindow.sharingType документации. Документация делает это так, как будто это жёсткая гарантия. Это не так — как минимум, в документации должно быть указано, что привилегированные инструменты Apple и приложения с приватными разрешениями могут записывать независимо. Сегодня это не указано.
  • Способ проверить какие приложения на системе имеют привилегированный доступ к записи — аналогично «Файлы и папки» в Настройках и безопасности. Сегодня нет поверхности для пользователей, чтобы увидеть, кто может обойти их приватность.

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

Хотите убрать рекламу? Откажитесь от рекламы сегодня

Установите наши расширения

Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска

в Расширение Chrome в Расширение края в Расширение Firefox в Расширение Opera

Табло результатов прибыло!

Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!

Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?

новости с техническими моментами

Примите участие

Помогите нам продолжать предоставлять ценные бесплатные инструменты

Купи мне кофе
Реклама · УДАЛИТЬ?