У MacBook на процессорах M экран скруглён сверху, а снизу углы прямые. При этом macOS рисует окна, Dock и меню со скруглениями со всех сторон. В итоге в нижних углах экрана всё время видны маленькие треугольники обоев, особенно на тёмной теме и в развёрнутых окнах.
Видимо, я перфекционист, потому что, это бесит каждый раз, когда есть контраст между содержанием открытого окна и обоев. Поэтому я сделал (ну как я? - я и клод) Screen Corners: она рисует в нижних углах встроенного экрана две крошечные чёрные маски, и экран выглядит одинаково скруглённым со всех сторон.

Что умеет
Углы видны поверх всех окон, Dock и полноэкранных приложений, на всех рабочих столах.
Клики проходят сквозь маски.
Иконка в строке меню: слайдер радиуса, скругление верхних углов и внешних мониторов, автозапуск.
Почти не тратит CPU: четыре статичных окна, которые перерисовываются только при смене конфигурации экранов.
Бесплатно, MIT, без телеметрии и зависимостей.
Дальше — несколько деталей реализации, которые могут пригодиться, если будете делать что-то похожее.
Как это сделано
Окно-уголок
Каждый угол — это отдельное маленькое окно без рамки размером r × r. Внутри нарисована фигура «квадрат минус круг». Удобнее всего сделать её через even-odd заливку:
override func draw(_ dirtyRect: NSRect) { let r = bounds.width let center = NSPoint(x: r, y: r) // для левого нижнего угла let path = NSBezierPath(rect: bounds) path.appendOval(in: NSRect(x: center.x - r, y: center.y - r, width: 2 * r, height: 2 * r)) path.windingRule = .evenOdd // квадрат минус круг NSColor.black.setFill() path.fill()
}
Чтобы окно не мешало и было видно везде, ему нужен такой набор настроек:
w.isOpaque = false
w.backgroundColor = .clear
w.hasShadow = false
w.ignoresMouseEvents = true // клики проходят насквозь
w.level = .screenSaver // выше Dock и меню
w.collectionBehavior = [.canJoinAllSpaces, // на всех рабочих столах .stationary, // не уезжает в Mission Control .ignoresCycle, // не попадает в Cmd+` .fullScreenAuxiliary] // видно над полноэкранными приложениями
Встроенный экран определяется через CGDisplayIsBuiltin, а при подключении монитора, смене разрешения и выходе из сна окна пересоздаются по NSApplication.didChangeScreenParametersNotification и NSWorkspace.didWakeNotification.
Подвох со скриншотами
Чтобы чёрные уголки не портили скриншоты, я выставил окнам sharingType = .none. Это сработало даже слишком хорошо: когда я делал картинку «до/после» для README, оба скриншота получились байт в байт одинаковыми. Маски на экране есть, а на снимке их нет.
В итоге в приложении появилась скрытая настройка, которая разрешает углам попадать на скриншоты:
defaults write com.screencorners.app allowCapture -bool true
Swift 6 и MainActor
Первая версия была одним файлом с top-level кодом и упала на новом компиляторе:
error: call to main actor-isolated initializer 'init(cfg:)'
in a synchronous nonisolated context
Top-level код в main.swift не считается изолированным на главном акторе, а AppKit-классы — считаются. Самый чистый выход — явная точка входа:
@main
struct ScreenCornersApp { @MainActor static func main() { let app = NSApplication.shared let delegate = AppDelegate() app.delegate = delegate app.setActivationPolicy(.accessory) // без иконки в Dock withExtendedLifetime(delegate) { app.run() } }
}
Собирать такой файл нужно с флагом -parse-as-library, иначе компилятор снова решит, что это скрипт.
Автозапуск через LaunchAgent
Вместо SMAppService я выбрал обычный пользовательский LaunchAgent в ~/Library/LaunchAgents: его видно, его легко удалить руками, и он не зависит от подписи приложения. Полезный трюк — KeepAlive с условием:
<key>KeepAlive</key>
<dict> <key>SuccessfulExit</key> <false/>
</dict>
Если приложение упало, launchd его поднимет. Если пользователь нажал «Выйти» (код возврата 0), перезапуска не будет.
Установка без Gatekeeper
Без платного аккаунта разработчика Apple готовое .app при первом запуске упрётся в предупреждение Gatekeeper. Поэтому установщик скачивает исходники и собирает приложение прямо на вашем Mac через Xcode Command Line Tools. Локально собранное приложение предупреждений не вызывает, а код можно прочитать перед установкой: там несколько сотен строк Swift.
Попробовать
Инструкция по установке, настройки и удаление описаны в README:
https://github.com/PankovDefi/screen-corners
Нужны macOS 13 или новее и Xcode Command Line Tools. Если у вас другая модель MacBook, напишите в комментариях, какой радиус подошёл: по умолчанию на моем macBook pro M1pro стоит 15 pt, но у разных моделей значение может немного отличаться.
Буду рад звёздам, issue и pull request’ам.

Комментарии 0