invisible-ai: AI-оверлей, который в Linux правда можно спрятать от демонстрации экрана

Draft

Окно, которое не попадает в захват экрана, нужно не только для подсказок на собеседовании в духе Cluely: менеджер паролей, история буфера обмена — всё, что не должно оказаться на общем экране. В Linux это не невозможно, а зависит от композитора. Как это сделано в invisible-ai (Tauri v2 + Rust) под niri и почему в X11 так не выйдет никогда.

Карточка в стиле терминала: invisible-ai ask стримит ответ, рядом правило niri block-out-from screen-capture — оверлей видите вы, а Zoom видит стол под ним

Cluely даже не скрывает, зачем он нужен: это AI-оверлей, который подсказывает ответы на собеседовании и при этом «undetectable by design» — собеседник по ту сторону демонстрации экрана его не видит. Как ни относись к такому сценарию, он навёл меня на другой вопрос: почему в Linux вообще нет такого примитива?

Ведь окно, которое не попадает в захват экрана, — это не инструмент для списывания, а инструмент приватности, и нужен он чаще, чем кажется:

  • Менеджер паролей. Идёт созвон, нужен пароль, а окно Bitwarden не должно попасть на общий экран. Расширение в браузере тоже не спасает: один случайный клик по иконке с глазом — и пароль уже в трансляции.
  • История буфера обмена. Вставляешь токен, ключ, внутреннюю ссылку — из пикера, который, конечно же, открыт на экране, пока ты выбираешь.
  • Коды 2FA, внутренние дашборды, терминал с доступами к стейджингу в заголовке. Всего, что «на моём мониторе можно, в Zoom нельзя», больше, чем кажется, и демонстрация экрана показывает это всё разом.

На macOS это один вызов API (NSWindowSharingNone), на Windows — другой (WDA_EXCLUDEFROMCAPTURE). Под Linux Cluely не выходит вовсе. Открытые клоны, у которых есть сборки под Linux, честно об этом предупреждают: в README OpenCluely прямо написано, что на Linux setContentProtection молча ничего не делает и оверлей увидят все, кому вы показываете экран, а невидимость у Pluely держится на поведении композитора, которое никто не гарантирует.

Так что, надёжный невидимый для захвата оверлей в Linux невозможен? Нет. Но честный ответ зависит от композитора, и этот пост — про него. А заодно про приложение, которое я вокруг этого ответа построил: invisible-ai — AI-оверлей для Linux на Tauri v2 + Rust, который видит экран и слышит созвон. Я каждый день пользуюсь им на i3 и niri.

Стек
Rust, Tauri v2, WebKitGTK, GTK layer shell, PipeWire, whisper.cpp
Задача
Прозрачный для кликов AI-оверлей: скриншот + вопрос → потоковый ответ, живая расшифровка, скрытие от захвата экрана в niri
Исходники
Пока закрыты — напишите, если хотите посмотреть
Статус
Ранняя версия; у меня работает на i3 (X11) и niri (Wayland)
Лицензия
MIT

Чем Linux отличается (и почему не всё потеряно)

Аналога WDA_EXCLUDEFROMCAPTURE в Linux нет по архитектурной причине. В Windows и macOS захватом экрана владеет сама ОС, поэтому она и может дать окну флаг «не захватывать меня». В Linux то, кто захватывает экран, зависит от дисплейного сервера — и исключать окна в принципе умеет только один из двух:

  • X11: не сложно, а невозможно. Разрешений на захват там нет, а значит, и отказываться не от чего: любой клиент читает пиксели любого окна через XGetImage. Экран — одна общая картинка; как только оверлей в неё попал, все, кто захватывает экран (Zoom, OBS, Discord), видят одно и то же. Никакой трюк на уровне оконного менеджера этого не изменит.
  • Wayland: захватом владеет композитор, значит, он и может исключать. Некоторые уже умеют:
    • у niri есть block-out-from "screen-capture" в правилах окон и слоёв;
    • в KDE Plasma 6.6+ у KWin появилось действие «Hide from ScreenCast» для окна;
    • в GNOME/Mutter встроенного решения нет.

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

Что умеет invisible-ai

Полупрозрачная панель поверх рабочего стола, которой целиком управляешь с клавиатуры:

  • По умолчанию клики проходят сквозь неё. Прозрачная, всегда сверху, на всех рабочих столах, без значка в панели задач — работаешь прямо сквозь неё. interact делает её кликабельной и забирает фокус (фиолетовая рамка) — для настроек или вопроса текстом; при закрытии всё возвращается обратно.
  • ask делает скриншот монитора, на котором висит оверлей, отправляет его вместе с вопросом любому OpenAI-совместимому провайдеру (NanoGPT, OpenRouter, OpenCode Zen, Ollama — подойдёт любая модель с vision) и выводит ответ в панель по мере генерации: Markdown с подсветкой кода. Ключи API берутся из переменной окружения или shell-команды, в конфиге их нет, а сам конфиг перечитывается при каждом запросе.
  • listen включает живую расшифровку: локальный whisper.cpp распознаёт ваш микрофон («Me») и системный звук («Them»), которые pw-record пишет раздельно. На вопросы из расшифровки оверлей отвечает сам, с экраном и последними репликами в качестве контекста; строки с микрофона, которые просто повторяют собеседника, отбрасываются. Звук никуда не уходит с машины — наружу идут только запросы к модели.
  • Всё управление — через Unix-сокет. Запущенное приложение слушает $XDG_RUNTIME_DIR/invisible-ai.sock, а тот же бинарник с аргументом работает как клиент. Любая команда запускает приложение, если оно ещё не запущено, так что один хоткей в WM — это сразу и лаунчер, и пульт:
# i3
bindsym $mod+backslash exec invisible-ai toggle # показать / скрыть (и запустить)
bindsym $mod+Ctrl+backslash exec invisible-ai prompt # набрать вопрос
bindsym $mod+Mod1+backslash exec invisible-ai listen # голос вкл / выкл
bindsym $mod+Shift+backslash exec invisible-ai interact

В X11 всё это уже работает: прозрачность даёт picom, оконные трюки — GTK. Окно типа UTILITY в i3 становится плавающим; позиция и область ввода применяются в момент отображения, потому что всё, что запрошено раньше, чем i3 начал управлять окном, он игнорирует; focus_on_map выключен, поэтому появившийся оверлей никогда не крадёт фокус. Кроме того, invisible-ai не пускает панель в свои собственные скриншоты, чтобы модель не читала обратно свои прошлые ответы: у компактной панели её область закрашивается на картинке, а полноэкранная на ~150 мс уходит в нулевую прозрачность через picom, пока делается снимок. Но это работает только потому, что снимок делает само приложение. Для чужого захвата — Zoom, OBS — панель остаётся обычным окном. Опять же, это X11.

По-настоящему невидимый: niri

Самое интересное происходит в Wayland, в niri. Там оверлей — это layer-shell поверхность с пространством имён invisible-ai, а чтобы её спрятать, хватает одного правила в конфиге композитора, рядом с моими хоткеями:

layer-rule {
// invisible-ai overlay: hidden from screen sharing and screenshots.
match namespace="^invisible-ai$"
block-out-from "screen-capture"
}

И всё. Теперь niri вырезает этот слой из любого захвата — Zoom, OBS, Discord, grim, кто бы ни был на том конце. В том числе из скриншотов самого invisible-ai, которые оно делает через grim по протоколу wlr-screencopy: модель не видит панель даже в полноэкранном режиме, и ничего не нужно гасить или мигать.

Ну, почти. Стоковый niri рисует на месте вырезанной поверхности чёрный прямоугольник — так же, как KWin. Зрители видят непрозрачную плашку поверх рабочего стола, и для моих целей это хуже, чем ничего: она подсвечивает ровно то, что хотелось спрятать. macOS и Windows показывают то, что под окном, будто его нет. Мне хотелось так же, и для этого понадобился небольшой патч композитора — niri-invisible-layers.patch, который накладывается на сборку niri в моих dotfiles:

if target.should_block_out(self.rules.block_out_from) {
// …draw the solid-color block-out buffer…
rv.normal.push(elem.into());
// Draw nothing instead of a black rectangle, so screencasts and screenshots
// show whatever is underneath, as if the surface weren't there.
return rv;
}

Вместо того чтобы закрашивать поверхность чёрным, её просто не рисуют — и в захвате виден рабочий стол под ней, ровно как в macOS и Windows. Держать патч поверх своего композитора — осознанный компромисс: десяток строк поверх стокового niri, которые пересобирает моя конфигурация NixOS, в обмен на оверлей, которого на той стороне созвона действительно не видно.

Почему чёрный и что происходит в апстриме

Чёрная плашка — не случайность. Документация niri предлагает block-out-from для менеджеров паролей и мессенджеров, и там видимый прямоугольник, пожалуй, и есть смысл: вы видите на превью, что что-то скрыто. Поэтому мой патч хорош для моей машины, но отправлять его в апстрим в таком виде нельзя — он молча меняет поведение существующей опции для всех, кто на эту плашку полагается.

Я далеко не первый, кому нужно другое поведение. В Discussions niri (там живут запросы на фичи) за этот год та же просьба звучала трижды: #3596 (чёрная плашка подсказывает, что именно скрыто — «это уведомление», — и на собеседовании выглядит подозрительно), #4339 (не рисовать layer-shell поверхности, то есть ровно случай этого оверлея) и #4564 (окно AI-ассистента, «как WDA_EXCLUDEFROMCAPTURE»). Мейнтейнер не отказывает: он называет это «обсуждением о том, чтобы block-out-from умел не рисовать окна», хочет «сначала что-то простое» и посмотреть, как это устроено в других системах.

И код уже есть: PR #3685 добавляет отдельное правило hide-from для окон и слоёв, которое просто не включает их в захват, а block-out-from не трогает. Именно такой дизайн и может быть принят. PR открыт с марта, ревью у него не было, и теперь его нужно ребейзить, так что я описал свой случай и там, и в #4564 — вместе с ответом на «как это сделано в других системах»: и Windows, и macOS окно не зачерняют, а не включают в захват.

Оговорки, потому что «невидимость» — в кавычках

  • Это правило композитора, а не функция приложения. invisible-ai не может спрятать себя сам — правило слоя нужно добавить вручную. Без него оверлей виден, как любое другое окно.
  • В X11 это по-прежнему невозможно. На моих машинах с i3 это хорошая AI-панель, но не приватная. Если невидимость обязательна — нужен niri, а не X11.
  • Патч — моя личная ноша, пока что. Стоковый niri даёт всё, кроме «стол под окном вместо чёрной плашки», и этот патч мне придётся переносить с каждым обновлением niri, пока не смержат что-то вроде hide-from из #3685. Тогда это будет одно слово в правиле слоя и никакого патча.
  • Честно предупреждать, а не обещать. Следующий шаг — определять композитор и писать прямо в панели: «скрыт от захвата» или «виден: у вас X11/GNOME». Оверлей, который не может быть уверен, не должен делать вид, что уверен.

Что дальше

В планах: нативный провайдер Anthropic API; бэкенды для macOS (NSWindow.sharingType) и Windows (WDA_EXCLUDEFROMCAPTURE), чтобы на всех платформах была одна и та же гарантия; и функции помощника на созвонах — подсказки по живой расшифровке и саммари после звонка.

Если вам тоже не хватало такого примитива в Linux — честно говоря, ради менеджера паролей, — коротко: он есть, живёт он в композиторе, и сегодня это niri (или KDE 6.6+), а не X11. Сделайте оверлей layer-shell поверхностью, добавьте правило и не верьте приложениям, которые обещают то же самое везде.