Mac Automation Stack

Как я строю клавиатурные слои, чтобы ускорить работу

13 заметок · 19 мин

От “железа” к логике: как я строю клавиатурные слои, чтобы ускорить работу на Mac.: иллюстрация 1

Оглавление

  1. Клавиатурные слои2 мин
  2. Karabiner = набор JSON-правилфото1 мин
  3. 1) Caps Lock = Hyper1 мин
  4. 2) Слой на Space: tap = пробел, hold = F181 мин
  5. 3) backtick hold = F171 мин
  6. 4) RU-раскладка: точечные правила только для русского набора1 мин
  7. Hammerspoonфото2 мин
  8. Window Manager в Hammerspoon2 мин
  9. Медиа-контроль как сценарии в Hammerspoon3 мин
  10. Hammerspoon как локальный сервер и “пульт” управления1 мин
  11. Мониторы и стабильность рабочего места (Hammerspoon)1 мин
  12. MIDI-клавиатура и OBS как аппаратный контроллер (Hammerspoon)видео2 мин
  13. Тема клавиатурных слоев на этом этапе закрыта.фото1 мин

Клавиатурные слои

Самая базовая вещь, без которой мне уже сложно нормально ориентироваться в системе, это клавиатурные слои.

Я перебрал разные подходы, но в итоге остановился на связке:

Karabiner + Hammerspoon

Karabiner-Elements делает “железо ввода” предсказуемым:

он решает, какая физическая клавиша во что превращается (tap/hold, гипер-модификатор, виртуальные F-клавиши, правила по раскладкам).

Hammerspoon превращает эти сигналы в действия:

запуск и фокус приложений, управление окнами (перемещение, раскладки, перенос между мониторами, восстановление размеров), навигация по окнам, медиа-контроль по конкретным приложениям, системные штуки и быстрые команды, которые обычно спрятаны в меню. Плюс самое важное: это не просто бинды, а логика и контекст (проверки состояния, фокусировка нужного окна перед действием, задержки, фолбэки, сценарии). Так же у меня Hammerspoon ещё работает как локальный сервер: я поднимаю небольшой HTTP endpoint и могу триггерить свои команды удалённо (например, из Shortcuts с других устройств). Всё на Lua, без магии и без UI-лабиринтов.

Почему я выбрал именно Karabiner + Hammerspoon.

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

А здесь подход другой:

  • Karabiner даёт мне контроль на уровне ввода: я могу превращать любые физические клавиши в чистые сигналы и строить слои так, чтобы не ломать печать и не ловить конфликты.
  • Hammerspoon даёт мне контроль на уровне поведения: я описываю действия кодом, добавляю проверки, контекст, задержки, фолбэки, и получаю результат, который работает так, как нужно именно мне, а не “как это задумано в UI”.

В итоге это не “ещё одна программа с настройками”, а платформа, где я сам определяю правила. Именно за счёт этой гибкости я и выбрал такой подход.

К оглавлению

Karabiner = набор JSON-правил

Karabiner = набор JSON-правил: иллюстрация 1

Вся “магия” Karabiner живет в виде отдельных JSON-файлов, которые лежат по пути:

/User/.config/karabiner/assets/complex_modifications

Это папка для так называемых Complex Modifications:

каждый .json файл описывает одно правило или набор правил.

Как это работает в реальности

  1. Я добавляю новый .json в эту папку.

  2. Karabiner подхватывает его как “модификацию”, которую можно включить.

  3. Дальше: Karabiner → Complex Modifications → Add rule и я просто включаю нужное правило.

Я держу систему модульной:

  • один файл = одна идея
  • можно быстро включать/выключать, тестировать и откатываться
  • удобно переносить на другой Mac и хранить в git

Фактически Karabiner это “компилятор ввода”: я описываю правила в JSON, и он стабильно превращает физические клавиши в нужные сигналы.

Дальше покажу несколько своих модификаций Karabiner, которые я собрал под свой workflow и довёл до состояния “на автомате”.

К оглавлению

1) Caps Lock = Hyper

Caps Lock забран под Hyper-модификатор (cmd+ctrl+option+shift).

Это самое популярное решение и его умеют делать многие утилиты, но мне важно, чтобы все, что относится к вводу, жило в одном месте. Я хочу, чтобы именно Karabiner отвечал за “железо клавиатуры”. Поэтому Hyper я тоже задаю на этом уровне.

Задача Karabiner тут ровно одна: заменить Caps Lock на комбинацию, которая почти нигде не занята. Это превращает Caps в удобную “суперклавишу” под мизинцем и даёт большой чистый слой хоткеев.

Прикладываю JSON-файл с модификацией

Дальше покажу, как Space-hold превращается в слой команд (хоткеев).

К оглавлению

2) Слой на Space: tap = пробел, hold = F18

Это мой базовый слой и один из самых удачных с точки зрения эргономики.

Логика простая: пробел остаётся пробелом, а удержание пробела превращается в “служебную клавишу режима” (в моём случае это виртуальный F18). Дальше Hammerspoon слушает F18 и включает слой команд.

Почему именно Space: он находится в центре клавиатуры и всегда под большим пальцем. Это максимально естественная “кнопка режима”, потому что большой палец почти всегда свободен и его удобно держать в удержании, не мешая печати.

Ключевое преимущество такого подхода в том, что он не ломает текстовый ввод. Я печатаю как обычно: быстрый тап по пробелу всегда даёт пробел. А когда нужно управление, я просто зажимаю Space и получаю временный слой команд, который живёт ровно пока удержание активно.

Самый важный нюанс здесь это тайминг удержания. Я несколько раз перенастраивал этот слой и переписывал JSON, пока не нашёл комфортное значение to_if_held_down_threshold_milliseconds: чтобы режим не включался случайно при печати, но включался мгновенно, когда нужно.

JSON с этой модификацией прикрепляю.

Дальше разберу, как я строю иерархию команд через F17.

К оглавлению

3) backtick hold = F17

По той же логике у меня есть второй слой на backtick: тап печатает символ, а удержание отдаёт виртуальный F17. Этот слой я использую как “вход” в дополнительную иерархию команд через Leader Key.

Схема такая: удержание backtick включает слой, а дальше я могу проваливаться в подслои по смыслу.

Например, backtick (hold) + F у меня означает Finder. После этого ближайшие клавиши на клавиатуре становятся быстрыми шорткатами для открытия нужных мне папок в Finder. Это превращает навигацию по файловой структуре в пару нажатий, без поиска по боковой панели и без ручного кликанья.

Точно так же у меня сделан подслой для ChatGPT: backtick (hold) + G. В этом режиме ближайшие клавиши открывают нужный проект в ChatGPT. По сути, это “меню проектов под пальцами”, которое вызывается моментально и работает предсказуемо.

Такой второй слой удобен тем, что он не смешивает всё в одну плоскость: один слой у меня про повседневную навигацию, а backtick-слой становится входом в более глубокие, тематические подслои (папки, проекты, инструменты).

В следующем посте покажу, как я точечно правлю RU-пунктуацию в Karabiner, не ломая раскладку.

К оглавлению

4) RU-раскладка: точечные правила только для русского набора

У русскоязычных пользователей macOS есть типовая проблема: в стандартной RU-раскладке запятая и точка находятся на Shift+6 и Shift+7. При активном письме это неудобно и замедляет набор.

Самый распространённый обходной путь, который я видел, это перейти в системных настройках на раскладку Russian PC. Там пунктуация размещена практичнее. Но цена такого решения для меня слишком высокая: меняется поведение части символов, и в том числе ломается привычная предсказуемость со знаком вопроса между EN и RU.

Поэтому я оставил стандартную RU-раскладку и сделал точечное исправление через Karabiner. Правило включается только в русском input source и не трогает английскую раскладку:

  • Option + запятая печатает ,

  • Option + точка печатает .

В результате , и . в RU оказываются в тех же позициях, что и в EN, но с удержанием Option. Для моего набора это удобнее, чем переход на Russian PC: пунктуация становится доступной, а остальная логика русской раскладки остаётся неизменной.

Здесь я описываю только текущую версию моего подхода к настройкам Karabiner. Вариантов реализации и комбинаций правил можно придумать множество, и со временем мой сетап, скорее всего, будет расширяться и эволюционировать. В этом посте я фиксирую именно те решения, которые на данный момент лучше всего работают в моём ежедневном workflow.

В следующем посте расскажу, как эти сигналы из Karabiner связаны с Hammerspoon и как я превращаю их в конкретные действия.

В следующем посте покажу мой базовый каркас конфига Hammerspoon.

К оглавлению

Hammerspoon

Hammerspoon: иллюстрация 1

Hammerspoon это инструмент, который превращает macOS в настраиваемую среду под конкретный workflow. По сути, это лёгкая надстройка, которая слушает события клавиатуры и системы и выполняет действия, описанные кодом на Lua. Без лишнего UI и “мастеров настроек”: поведение задаётся напрямую, в виде правил и сценариев.

Главная ценность Hammerspoon в том, что он работает не как “ещё один ремаппер”, а как движок логики:

Запуск/фокус приложений, окна (позиции/раскладки/мониторы), навигация по окнам, медиа-контроль, системные действия, логика (проверки/задержки/фолбэки).

В моей практике это выглядит так:

  • Caps Lock (мой “суперклавишный” слой): Caps Lock + любая цифра/клавиша у меня привязаны к запуску или фокусу конкретных приложений. Это основной способ перемещаться по системе без Dock и поиска.

  • Space-layer: удержание Space включает отдельный режим, где собраны быстрые действия, в том числе медиа-контроль и запуск дополнительных приложений.

  • BackTick-layer: удержание backtick завязано на Leader Key: это вход в подслои, где, например, отдельные клавиши открывают нужные папки в Finder или конкретные проекты в ChatGPT. Я описывал об этом ранее тут.

Технически всё это описывается в конфигурации init.lua. Стандартный путь:

~/.hammerspoon/init.lua

Конфиг удобно хранить как код: версионировать, переносить на другие устройства, развивать поэтапно. В моём сетапе конфигурация синхронизируется через iCloud (через симлинк): система видит стандартный путь ~/.hammerspoon/, но фактически конфиг лежит в папке iCloud Drive, поэтому одинаковое поведение доступно на всех устройствах.

Базовый пример того, как выглядит действие в Hammerspoon: создаётся хоткей и привязывается к функции, которая делает нужное действие, например открывает приложение. В реальном конфиге это дополняется слоями, модальными режимами и сценариями, которые делают управление системой быстрым и предсказуемым.

Как выглядит запуск приложения в Hammerspoon

Базовая конструкция в конфиге выглядит так:

hs.hotkey.bind(hyper, "i", function()
hs.application.open("путь к приложению")
end)
  • hs.hotkey.bind(...) регистрирует сочетание клавиш

  • hyper это набор модификаторов (Hyper-слой)

  • “i” это клавиша-триггер внутри слоя

  • function() ... end это действие, которое выполняется при нажатии

  • hs.application.open(...) открывает приложение по точному пути

Альтернативный вариант для многих задач: hs.application.launchOrFocus("AppName").

В следующем посте расскажу про Window Manager в Hammerspoon.

К оглавлению

Window Manager в Hammerspoon

Оконный менеджмент в macOS часто сбоит на уровне деталей: фокус, анимации и внутренние состояния приложений. В моем сетапе с Hammerspoon окна управляются напрямую через API Hammerspoon (работа с объектом окна и его геометрией, а не через UI-состояния), поэтому результат получается более стабильным и предсказуемым.

Базовый интерфейс управления окнами у меня построен на слое Ctrl + Option + клавиша. Это быстрые, мышечно-запоминаемые команды:

  • Ctrl+Option+A прижимает окно к левому краю (левая половина)

  • Ctrl+Option+D прижимает к правому краю (правая половина)

  • Ctrl+Option+S ставит окно в центр (и т.д.)

Таких сочетаний много: под каждую “логичную” кнопку заведено своё действие для перемещения или изменения размеров окна. Для скорости работы на Mac это критично: окно перестаёт быть объектом, который “нужно руками расставлять”, и становится состоянием, которое вызывается одной командой.

1) Нулевая анимация

hs.window.animationDuration = 0

2) Два способа позиционирования

2.1) moveToUnit(…)

Надёжный тайлинг в долях экрана (0..1): левый/правый полукран, четверти и т.п. Устойчив к разным экранам и Dock/Menu Bar.

2.2) setFrame(…)

Когда нужна конкретная зона: колонка 25%, нижняя четверть, центрирование и т.д. Пример “правая колонка 25%” из конфига:

local win = hs.window.focusedWindow()
local screenFrame = win:screen():frame()
win:setFrame({
  x = screenFrame.x + screenFrame.w * 0.75,
  y = screenFrame.y,
  w = screenFrame.w * 0.25,
  h = screenFrame.h
})

Просто: берётся рабочая область экрана и окну задаётся прямоугольник, где ширина 25%, а x начинается с 75% (то есть край справа).

3) Toggle “разложить / вернуть”

Перед тайлингом сохраняется исходный frame в windowStates, затем можно одним действием вернуть окно “как было”. Это делает раскладки обратимыми.

4) Сетка окон приложения + возврат

Сценарий собирает окна текущего приложения, раскладывает сеткой по экрану, повторным запуском восстанавливает исходные позиции. Быстро наводит порядок и так же быстро возвращает обратно.

5) Перенос между мониторами

Перемещение на соседний экран по топологии: west/east/north/south. Это удобнее, чем “монитор №2”, потому что работает по реальному расположению экранов.

6) Микро-подстройка

Шаговые сдвиги (в конфиге шаг 100px) закрывают кейс “почти идеально, но нужно чуть подвинуть”.

Почему это надёжнее системных хоткеев и части готовых решений

Системные/некоторые сторонние команды часто завязаны на UI-состояние (фокус, анимации, “успел ли апп”). Здесь всё детерминированно: Hammerspoon получает окно, вычисляет целевую геометрию и применяет её напрямую (moveToUnit / setFrame / moveToScreen), при необходимости сохраняя состояние (windowStates). Плюс Hammerspoon в том, что я могу собирать собственные команды под себя и задавать в них любые параметры: точные размеры и координаты, привязку к экрану, шаги смещения, условия, сохранение/восстановление состояния и любые дополнительные характеристики, которые нужны именно моему workflow.

В следующем посте: мой подход к управлению медиа через Hammerspoon с задержками и fallback.

К оглавлению

Медиа-контроль как сценарии в Hammerspoon

Хочется иметь “внутренний цифровой микшер”, привязанный к одному устройству управления: чтобы быстро контролировать медиа и звук во всех открытых приложениях. Полного решения под это я пока не нашел: то, что пробовал, даёт задержку и иногда ведёт себя нестабильно.

Через Hammerspoon получилось приблизиться к нужному ощущению хотя бы на уровне практичного минимума: быстро и предсказуемо управлять воспроизведением в конкретных приложениях. Это не микшер в полном смысле, но уже радикально ускоряет работу с медиа.

Принцип

Не “универсальная кнопка паузы”, а сценарии под конкретные приложения:

активировать нужное приложение → дать фокусу “успокоиться” → отправить правильную команду → при необходимости иметь запасной путь.

1) Spotify: play/pause напрямую

Для Spotify используется AppleScript-команда playpause.

Плюс такого подхода: это не эмуляция клавиш, команда уходит прямо в Spotify и не зависит от того, какое окно активно.

2) YouTube / Twitch и похожие плееры: фокус → k

Для YouTube и Twitch сделан одинаковый паттерн:

  • активируется приложение

  • выход из Space-layer перед отправкой k, чтобы слой не перехватил клавишу

  • короткая задержка

  • отправляется k (стандартный play/pause в плеерах)

Важно: выход из слоя здесь не “косметика”, а инженерная деталь. Если оставить слой активным, k может интерпретироваться как команда слоя, а не как команда плеера.

Пример:

spaceLayer:bind({}, "t", function()
  local app = hs.application.find("Twitch")
  if not app then return hs.alert.show("Twitch не запущен") end
  app:activate()
  spaceLayer:exit()
  hs.timer.doAfter(0.05, function() hs.eventtap.keyStroke({}, "k") end)
end)

Разбор:

  • spaceLayer:bind({}, “t”, …) — внутри Space-layer клавиша T запускает сценарий.

  • hs.application.find(“Twitch”) — найти запущенный Twitch (иначе показать alert).

  • app:activate() — сфокусировать Twitch.

  • spaceLayer:exit() — выйти из слоя, чтобы k не перехватился хоткеями слоя.

  • hs.timer.doAfter(0.05, …) — небольшая задержка для переключения фокуса.

  • hs.eventtap.keyStroke({}, “k”) — отправить k (play/pause).

3) Safari: тот же принцип “поймать нужный плеер”

Отдельно заведены сценарии под Safari, с тем же смыслом:

  • сфокусировать браузер

  • небольшая пауза

  • отправить k в активную вкладку/плеер

Это удобно, когда медиа играет в разных местах: команда сначала приводит контекст в порядок, а потом уже нажимает “пауза”.

4) Apple Music и Podcasts: фокус → space + задержка

Для Music и Podcasts используется другой “язык управления”:

  • активируется приложение

  • небольшая задержка (чтобы фокус точно перешёл)

  • отправляется space как play/pause

Задержка тут важна: без неё часть команд уходит “в никуда” или в неправильный элемент интерфейса.

5) Apple Books: UI scripting + fallback (самый надёжный вариант)

Books сделан максимально устойчиво, потому что интерфейс и состояния там бывают капризные:

  • приложение активируется

  • через System Events пробуется нажатие Play/Pause в меню (учтены разные названия меню и RU/EN)

  • если меню не сработало, fallback: отправка space в приложение

Это по сути “двухконтурная система”: сначала самый надёжный путь (меню), затем резервный (клавиша).

В следующем посте покажу базовый код HTTP-сервера в Hammerspoon и структуру эндпоинтов.

К оглавлению

Hammerspoon как локальный сервер и “пульт” управления

Одна из самых практичных вещей в моём сетапе Hammerspoon: он работает не только как хоткеи, но и как локальный HTTP-сервер. Это превращает Mac в “хаб”, который можно дергать командами с других устройств.

Как это устроено

  • Поднимается HTTP server на фиксированном порту (в моём случае 9876).

  • Авторизация сделана через header token (X-Auth-Token) вместо Basic Auth, потому что так удобнее и стабильнее интегрироваться с Shortcuts.

  • Есть простой rate limit (минимальный интервал между вызовами), чтобы не спамить одинаковыми командами и не ловить гонки.

  • Есть health-check / watchdog: сервер регулярно проверяет /ping и, если что-то пошло не так, перезапускается. То есть он не “умирает тихо”.

Что даёт на практике

Получается очень прямой “пульт управления”, где каждый endpoint это действие:

  • медиа-команды по конкретным приложениям (например, Twitch/YouTube/Spotify/Music/Podcasts/Books)

  • системные команды, включая pmset displaysleepnow (быстро погасить дисплеи)

Ключевой момент: это не “универсальная кнопка”, а точечные сценарии, завязанные на конкретные приложения и нужную последовательность шагов (активация, задержка, команда и т.д.).

Архитектура

  • Mac Studio выступает как хаб: он выполняет действия локально и предсказуемо.

  • Shortcuts на iPhone или другом Mac выступают как клиенты: нажал кнопку → ушёл HTTP-запрос → на Mac выполнилась команда.

Это даёт ощущение “домашнего пульта”: управление медиа и системными действиями не привязано к тому, где сейчас руки, какое окно активно и на каком устройстве ты находишься.

Дальше разберу связку Hammerspoon + displayplacer: авто-раскладка экранов без ручной правки.

К оглавлению

Мониторы и стабильность рабочего места (Hammerspoon)

Многомониторный сетап в macOS часто ведёт себя хаотично: после переподключения дисплеев окна “переезжают”, Dock может оказаться не на том экране, а раскладка мониторов меняется непредсказуемо. Чтобы это не ломало рабочее место, часть логики вынесена в Hammerspoon.

1) displayplacer + отслеживание подключений

Раскладка экранов применяется не вручную, а через displayplacer: Hammerspoon проверяет наличие конкретного дисплея по persistent ID и понимает, какая конфигурация сейчас активна (например, подключён ли iPad/Sidecar).

2) Авто-применение лэйаутов через скрипты

Под разные сценарии подготовлены скрипты в ~/.layouts/… (условно “с iPad” и “без iPad”). При изменении набора экранов Hammerspoon ждёт короткую паузу, даёт macOS “успокоиться”, и затем применяет нужный лэйаут автоматически.

3) Антихаос после отключения Sidecar/iPad

Одна из самых неприятных ситуаций: отключаешь iPad, и часть окон улетает на “крайний” экран. В этой конфигурации Hammerspoon отслеживает момент отключения и аккуратно возвращает такие окна на primary display, чтобы не искать их по всей комнате.

В следующем посте покажу, как в Hammerspoon я поднимаю WebSocket-связь с OBS.

К оглавлению

MIDI-клавиатура и OBS как аппаратный контроллер (Hammerspoon)

У меня несколько MIDI-клавиатур, в следствии появилась простая идея: использовать одну из них, как физическую панель управления, где клавиши запускают команды. Hammerspoon здесь работает как “прошивка”: принимает MIDI-события, переводит их в действия и управляет OBS по WebSocket.

Пример:

В конфиге есть карта действий smkActions[…], и для ноты 48 назначено:

smkActions[48].label = "OBS: Scene #01"
smkActions[48].fn = function() obsSetSceneByIndex(1) end

Что это значит:

  • smkActions[48] — слот для конкретной MIDI-ноты (48).

  • .label — человекочитаемое имя (для логов/понимания).

  • .fn = function() … end — функция, которая выполняется при нажатии этой MIDI-клавиши.

  • obsSetSceneByIndex(1) — переключить OBS на сцену с индексом 1 (индекс берётся из списка сцен OBS).

Дальше в обработчике MIDI-событий есть логика: выполнять действия только в command mode (который включается sustain-педалью/CC64).

Ключевой фрагмент (суть):

if smkCommandMode and cmdType == "noteOn" then
  local note = mdTable.note
  local vel  = mdTable.velocity or 0
  if not note or vel <= 0 then return end

  if note < SMK_NOTE_MIN or note > SMK_NOTE_MAX then return end

  local act, layer = smkResolveAction(note)
  if not act then return end
  if not act.hotkey and not act.fn then return end

  -- дальше debounce и выполнение act.fn / act.hotkey
end
  • smkCommandMode — булевый флаг “режим команд включён”.

  • cmdType == "noteOn" — реагируем на нажатие клавиши (Note On).

  • mdTable.note — номер ноты (например, 48).

  • mdTable.velocity — сила нажатия; vel <= 0 отсекается (чтобы не триггерить “пустые” события).

  • SMK_NOTE_MIN/MAX — ограничение диапазона, чтобы обрабатывать только нужные 25 клавиш.

  • smkResolveAction(note) — достаёт действие для конкретной ноты (с учётом возможных слоёв/оверрайдов).

  • act.fn — если у ноты есть функция, она будет вызвана.

Дальше obsSetSceneByIndex(1) делает примерно такую цепочку:

  1. Получает список сцен OBS (через кеш с маленьким TTL, чтобы не дергать OBS на каждый клик).

  2. По индексу 1 берёт имя сцены.

  3. Отправляет в OBS команду SetCurrentProgramScene через WebSocket.

Итоговая логика одной клавиши

MIDI note 48 → (command mode ON) → находим smkActions[48] → вызываем obsSetSceneByIndex(1) → OBS переключает сцену на #1.

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

К оглавлению

Тема клавиатурных слоев на этом этапе закрыта.

Тема клавиатурных слоев на этом этапе закрыта.: иллюстрация 1

Важно понимать: здесь описана лишь часть того, что можно построить на связке Karabiner + Hammerspoon. Я показал решения, которые придумал под себя и которые реально использую каждый день: слои, сигналы, логика и сценарии.

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

Я буду регулярно возвращаться к этой теме и постепенно расширять и улучшать свой сетап, так что история со слоями точно не заканчивается, она будет только развиваться

К оглавлению