Как я превратила чёлку MacBook в панель с буфером

Вырез вверху экрана MacBook занимает место и ничего не делает. На iPhone такая же чёлка, точнее островок давно стал Dynamic Island, а на Mac так и осталась чёрным прямоугольником. Я решила это исправить и вместе с Claude Code сделала Brow. Наводишь курсор на вырез, и из него плавно выезжает панель, тут история буфера обмена, последние скриншоты, полка для файлов, музыка и быстрая заметка. Уводишь курсор, и она прячется обратно.
Всё началось с потерянных логов
Я веб‑разработчик, и буфер обмена у меня работает без перерыва, тут и строки кода, и куски документации, и логи. Типичная ситуация для меня, сижу на сервере по SSH, копирую логи, чтобы отправить их дальше, выхожу из сессии — и по дороге успеваю скопировать что‑то ещё. Логи пропали, а сессии уже нет. И так с любым текстом, скопировала нужное, потом ещё пару раз что‑то попутное, и предыдущее уже не вернуть.
Со скриншотами та же история. Засорять рабочий стол файлами «Снимок экрана …png» не хочется, поэтому я давно делаю их сразу в буфер. Но тогда скриншот живёт ровно до следующего ⌘C. А скриншоты я отправляю постоянно, как в чат коллегам, так и в ИИшку, чтобы разобраться, что пошло не так.
Менеджеры буфера обмена, конечно, существуют. Но те, что я видела, нужно было установить, настроить — и в итоге они хранили только текст. Картинок и файлов там не было. Так что проще сделать самой.
Что умеет Brow
В покое Brow не видно: чёрная панель ровно по размеру выреза сливается с ним.

Стоит навести курсор, и вырез растягивается вниз в панель 480×200 поинтов. Сверху всегда плеер с видосом или музыкой, под ней вкладки.
Буфер. Это то, ради чего всё затевалось, и вкладка, которой я пользуюсь чаще всего. Здесь лежат последние 50 скопированных текстов с поиском. Клик по записи снова кладёт её в буфер, «×» убирает из истории. Те самые логи с сервера теперь никуда не денутся. Пароли из менеджеров паролей сюда не попадают =)

Скриншоты. Делаю снимок через ⌘⇧5 с вариантом «Сохранить в буфер обмена», и он появляется на отдельной вкладке. Хранятся последние пять. Клик снова копирует картинку, а миниатюру можно просто перетащить в чат, письмо или Finder — она придёт туда обычным PNG‑файлом. Рабочий стол при этом остаётся чистым.

Полка и музыка. Файл или папку можно перетащить на вырез, а панель сама откроется на полке, и файл ляжет туда до поры до времени... Когда он понадобится, его можно вытащить в любое место. На полке хранится ссылка, а не копия, так что место на диске не тратится. Музыка показывает, что играет сейчас, в любом плеере, включая видео в браузере.

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

Чтобы выглядело как фича самого Mac
Для меня было важно, чтобы Brow не выглядел как ещё одна утилита. У него нет иконки ни в Dock, ни в строке меню, приложение живёт только в вырезе. Настроек тоже нет — единственное меню открывается правым кликом по панели, там автозапуск и выход.
Панель выезжает с пружинной анимацией, как Dynamic Island на iPhone, и закрывается с небольшой задержкой, чтобы не дёргаться от случайного движения мыши. В итоге получилось именно то ощущение, которого я хотела, будто это не моя программа, а функция, которая всегда была в macOS.
Почему с Claude Code и как мы работали
Сейчас я пишу в основном на PHP, а в разное время работала и с Kotlin, и со Swift. Но тут была задача, которую хотелось решить прямо сейчас, не тратя время на то, чтобы заново погружаться в AppKit. Поэтому код писал Claude, а я решала, что именно делаем и как это должно выглядеть и ощущаться. И справился он отлично.
Вот как выглядел мой первый запрос, как есть:
Разработай прилжение для упрощего использвоания macbook У меня macbook pro 14 на m1 и на экране есть Челка которая ника кне используется, давай это исправим, создадим приложение которое будет выполнять следуюий функции:
Название: Brow («бровь»): отсылка к форме выреза, звучит как нормальное название продукта.
Это приложение без иконки в Dock (agent app). Оно висит прозрачным окном поверх выреза. Когда курсор попадает в зону чёлки, окно плавно «вытекает» вниз: панель расширяется из выреза, как Dynamic Island на iPhone. Внутри находятся виджеты.
MVP (первая версия):
Управление музыкой: обложка, play/pause, перемотка.
«Полка» для файлов: перетащил файл на чёлку, он там лежит, потом достал и перетащил куда нужно.
История буфера обмена.
быстрые заметки;
Скриншотов в этом списке ещё нет, эта вкладка, как и автозапуск, появилась на второй день. А вот основная идея — нет иконки, панель «вытекает» из выреза — была с самого начала и дожила до релиза без изменений.
Это не было «написала промпт — получила приложение». Работа шла по нормальному инженерному процессу, и все его следы лежат в репозитории. Плагин Superpowers для Claude Code сначала расспрашивает о задумке, потом пишет спецификацию, затем план из маленьких задач, и только после этого берётся за код — сначала тест, потом реализация. Каждая фича шла отдельной веткой и пулл‑реквестом.
Что | Сколько |
|---|---|
От спецификации до релиза | 2 дня, 14–15 октября 2 026 |
Коммитов | 42 |
Пулл‑реквестов | 9 |
Автотестов | 58 |
Спецификация и план | 118 и 722 строки |
Спецификация оказалась самым полезным артефактом. В ней поведение описано вплоть до чисел: размер панели, задержка перед сворачиванием, что делать с паролями в буфере. Когда что‑то менялось, правка попадала туда с датой, так что историю решений видно целиком. Спецификация и план открыты, их можно посмотреть в репозитории.
Были и вещи, которых я сама просто не знала. Самый яркий пример — музыка. Как достать из системы то, что сейчас играет, предложил Claude, и до этого способа я бы сама не додумалась. О нём чуть ниже, он того стоит.
Как это устроено
Brow написан на Swift и SwiftUI и собирается через Swift Package Manager без проекта Xcode. Вся логика вынесена в отдельный модуль BrowCore без AppKit, поэтому её можно тестировать без экрана и мыши. Приложение состоит из трёх потоков: событие системы → логика → экран или диск.

Когда панель открывается и когда закрывается
У macOS нет готового API «где вырез», его приходится вычислять. Высоту даёт NSScreen.safeAreaInsets, а ширину — разница между шириной экрана и свободными полосами меню слева и справа (auxiliaryTopLeftArea и auxiliaryTopRightArea). На MacBook Pro 14" вырез получается 185×32 поинта.
Дальше всё держится на двух прямоугольниках. Открывает панель только сам вырез, суженный на 3 поинта с каждой стороны, и без полосы снизу. А держит открытой вся раскрытая панель. Когда курсор уходит за её пределы, панель не закрывается сразу, а переходит в состояние «сворачивается» с дедлайном через 0,3 секунды. Если курсор вернулся раньше, сворачивание отменяется.
Время в эту логику передаётся параметром, а не берётся из системных часов. Благодаря этому тесты ничего не ждут, можно просто подставить момент через 0,29 секунды и убедиться, что панель ещё открыта.
func testLeavingCollapsesAfterDelay() {
m.mouseMoved(to: CGPoint(x: 756, y: 970), now: t0)
m.mouseMoved(to: CGPoint(x: 100, y: 100), now: t0)
XCTAssertEqual(m.state, .collapsing(deadline: t0 + 0.3))
m.tick(now: t0 + 0.29)
XCTAssertTrue(m.isExpanded)
m.tick(now: t0 + 0.3)
// ...
}Из 15 тестов на эту логику большая часть — это, по сути, UX‑правила, записанные кодом, что курсор чуть ниже выреза панель не открывает, курсор у самого края выреза не открывает, строка меню рядом не открывает. Координаты вроде y: 970 — это верх экрана, а в AppKit ось Y идёт снизу вверх.
Буфер обмена без паролей
macOS не сообщает приложениям, что буфер изменился. Зато у NSPasteboard есть счётчик changeCount, который растёт при каждой записи, и Brow раз в полсекунды сравнивает его с запомненным. Когда я кликаю по записи в истории, Brow сам кладёт её в буфер и сразу запоминает новое значение счётчика, чтобы не записать собственную копию ещё раз.
Хранить пароли в истории буфера нельзя. К счастью, менеджеры паролей давно договорились помечать такие данные служебными типами (соглашение nspasteboard.org). Brow видит пометку и не сохраняет запись:
public static let ignoredTypes: Set<String> = ["org.nspasteboard.ConcealedType", "org.nspasteboard.TransientType"]История хранит 50 последних записей. Повторно скопированный текст не дублируется, а поднимается наверх. Всё лежит локально в ~/Library/Application Support/Brow/, в сеть ничего не уходит.
Скриншот или картинка из браузера
На вкладку скриншотов должны попадать только снимки экрана, а не каждая скопированная картинка. Различить их помогает набор типов в буфере. ⌘⇧5 кладёт ровно один элемент с единственным типом public.png, а браузер вместе с картинкой кладёт HTML, адрес источника и TIFF. Проверка занимает одну строку:
public static func isScreenshot(itemTypes: [[String]]) -> Bool {
itemTypes.count == 1 && Set(itemTypes[0]) == ["public.png"]
}Хранятся пять последних снимков в виде PNG‑файлов, шестой вытесняет самый старый. При перетаскивании файл получает привычное имя «Снимок экрана <дата> в <время>.png».
Музыка через системный perl
Информацию о том, что сейчас играет, в macOS хранит приватный фреймворк MediaRemote. С macOS 15.4 Apple закрыла его для сторонних приложений, но системный /usr/bin/perl подписан самой Apple и доступ сохранил. Поэтому Brow запускает крошечный perl‑скрипт, который загружает нашу библиотеку на Objective‑C:
my $h = DynaLoader::dl_load_file($lib, 0) or die DynaLoader::dl_error();
my $sym = DynaLoader::dl_find_symbol($h, "brow_$mode") or die DynaLoader::dl_error();
DynaLoader::dl_install_xsub("main::run", $sym);
run();Библиотека подписывается на уведомления MediaRemote и на каждую смену трека пишет строку JSON в stdout. Brow читает её и обновляет панель. Если помощник упал, он перезапускается с нарастающей паузой. А когда завершается сам Brow, помощник видит конец stdin и выходит сам, не оставляя висящих процессов.
Мелочи, которые нашлись только в использовании
Самые полезные правки появились не из спецификации, а из того, что я сразу стала пользоваться Brow сама.
Что мешало | Как исправили |
|---|---|
Курсор, прижатый к верхнему краю экрана, не открывал панель | Стандартный |
Панель открывалась случайно, когда курсор просто проходил рядом | Зону наведения сузили до самого выреза минус 3 поинта с каждой стороны; точное число подбирали в два захода |
Промахи по маленьким иконкам и крестикам | Кнопки SwiftUI реагируют только на нарисованные пиксели, поэтому всем дали полную прямоугольную область клика |
Spotlight открывал старую копию из папки сборки | Папку сборки назвали |
Автозапуск не должен спорить с выбором пользователя | Brow включает его сам только один раз, а если пользователь выключил, больше не трогает |
Ограничения
У Brow есть честные ограничения, и лучше знать о них заранее. Работает он только на встроенном экране с вырезом, если крышка закрыта и подключён внешний монитор, панель скрыта. Музыка держится на приватном API, и будущие версии macOS могут его сломать — тогда вместо трека появится «Нет данных о воспроизведении», а всё остальное продолжит работать. Скриншоты пока перетаскиваются по одному.
И ещё одно, приложение не подписано платным сертификатом разработчика Apple, поэтому при первом запуске macOS его заблокирует. Разрешить запуск нужно один раз, как — ниже. Автообновления тоже нет, новые версии появляются на странице релизов.
Итог
Проблемы, с которой всё началось, больше нет, скопированное не теряется, скриншоты не засоряют рабочий стол, а всё нужное лежит в одном движении мыши — без ярлыков, кнопок и настроек. Работать правда стало приятнее. А ещё этот проект показал мне, что с Claude можно за два дня сделать настоящее нативное приложение с тестами и документацией — если точно знаешь, чего хочешь, и сама пользуешься тем, что получается.
Что дальше? Думаю над поддержкой внешнего монитора, хотя идея спорная, ведь у внешнего монитора нет чёлки, а вся прелесть Brow в том, что он живёт в вырезе. Если у вас есть мнение, как это должно выглядеть, расскажите.
Как попробовать
Brow работает на MacBook с вырезом (MacBook Pro 14" и 16" 2 021 года и новее, MacBook Air на M2 и новее) с macOS 14 Sonoma или новее. Xcode для установки не нужен:
Скачайте
Brow-<версия>.zipсо страницы релизов и перетащитеBrow.appв «Программы».При первом запуске macOS покажет предупреждение — нажмите «Готово», затем откройте «Системные настройки → Конфиденциальность и безопасность» и нажмите «Всё равно открыть».
Наведите курсор на вырез. Готово.
Подробная инструкция и сборка из исходников — в README.
Ссылки
Brow на GitHub — исходники, инструкция, вопросы и идеи в issues
Спецификация и план, с которых начиналась работа
Claude Code и плагин Superpowers
Если Brow пригодился, поставьте звезду на GitHub — это лучший способ сказать спасибо. А если что‑то не работает или хочется новую функцию, пишите мне в Telegram, @freedrobili — допилим.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.