ESPNNHL Tonight: Previews, stats and betting intel for Thursday's dozen gamesThe Jerusalem PostIran's floating oil reserves, used for China sales, completely depleted, analysts say - reportPunchOsun APC rejects short notice for Tinubu campaign councilBollywood HungamaEXCLUSIVE: Ramayana's South distributors locked; Dil Raju bags Andhra-Telangana, Yash takes Karnataka, Think Studios gets Tamil Nadu, E4 Entertainment acquires KeralaDaily MaverickBusiness Reflection: Loaded for Bear: MTBPS shines light on lost revenue from missed mining opportunitiesRTP DesportoProva de Supertubos suspensaInquirerEl Niño response must draw on scientific, historical data – expertZDF heuteEntdecken Sie das ZDF-NachrichtenstudioAntara NewsGovt taps SEZ-integrated vocational training for industry alignmentWirtualna PolskaSikorski uderza w prezydenta. "Nie ma na to zgody"SCMP ChinaChina urges opening Bab al-Mandeb in first joint statement with regional nationESPN CricinfoSamson, Iyer and Dhir take India home in nervy chase against NZ
The Daily Newsstand · Free, Always
Thursday, October 22, 2026

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

Translate

Вырез вверху экрана 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 (первая версия):

  1. Управление музыкой: обложка, play/pause, перемотка.

  2. «Полка» для файлов: перетащил файл на чёлку, он там лежит, потом достал и перетащил куда нужно.

  3. История буфера обмена.

  4. быстрые заметки;

Скриншотов в этом списке ещё нет, эта вкладка, как и автозапуск, появилась на второй день. А вот основная идея — нет иконки, панель «вытекает» из выреза — была с самого начала и дожила до релиза без изменений.

Это не было «написала промпт — получила приложение». Работа шла по нормальному инженерному процессу, и все его следы лежат в репозитории. Плагин 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 сама.

Что мешало

Как исправили

Курсор, прижатый к верхнему краю экрана, не открывал панель

Стандартный CGRect.contains не считает верхнюю границу частью прямоугольника, понадобилась своя проверка

Панель открывалась случайно, когда курсор просто проходил рядом

Зону наведения сузили до самого выреза минус 3 поинта с каждой стороны; точное число подбирали в два захода

Промахи по маленьким иконкам и крестикам

Кнопки SwiftUI реагируют только на нарисованные пиксели, поэтому всем дали полную прямоугольную область клика

Spotlight открывал старую копию из папки сборки

Папку сборки назвали *.noindex, и Spotlight перестал её видеть

Автозапуск не должен спорить с выбором пользователя

Brow включает его сам только один раз, а если пользователь выключил, больше не трогает

Ограничения

У Brow есть честные ограничения, и лучше знать о них заранее. Работает он только на встроенном экране с вырезом, если крышка закрыта и подключён внешний монитор, панель скрыта. Музыка держится на приватном API, и будущие версии macOS могут его сломать — тогда вместо трека появится «Нет данных о воспроизведении», а всё остальное продолжит работать. Скриншоты пока перетаскиваются по одному.

И ещё одно, приложение не подписано платным сертификатом разработчика Apple, поэтому при первом запуске macOS его заблокирует. Разрешить запуск нужно один раз, как — ниже. Автообновления тоже нет, новые версии появляются на странице релизов.

Итог

Проблемы, с которой всё началось, больше нет, скопированное не теряется, скриншоты не засоряют рабочий стол, а всё нужное лежит в одном движении мыши — без ярлыков, кнопок и настроек. Работать правда стало приятнее. А ещё этот проект показал мне, что с Claude можно за два дня сделать настоящее нативное приложение с тестами и документацией — если точно знаешь, чего хочешь, и сама пользуешься тем, что получается.

Что дальше? Думаю над поддержкой внешнего монитора, хотя идея спорная, ведь у внешнего монитора нет чёлки, а вся прелесть Brow в том, что он живёт в вырезе. Если у вас есть мнение, как это должно выглядеть, расскажите.

Как попробовать

Brow работает на MacBook с вырезом (MacBook Pro 14" и 16" 2 021 года и новее, MacBook Air на M2 и новее) с macOS 14 Sonoma или новее. Xcode для установки не нужен:

  1. Скачайте Brow-<версия>.zip со страницы релизов и перетащите Brow.app в «Программы».

  2. При первом запуске macOS покажет предупреждение — нажмите «Готово», затем откройте «Системные настройки → Конфиденциальность и безопасность» и нажмите «Всё равно открыть».

  3. Наведите курсор на вырез. Готово.

Подробная инструкция и сборка из исходников — в README.

Ссылки

Если Brow пригодился, поставьте звезду на GitHub — это лучший способ сказать спасибо. А если что‑то не работает или хочется новую функцию, пишите мне в Telegram, @freedrobili — допилим.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.