Блокируй сейчас
или по хоткеюLock now, or lock on a hotkey
Без флага --watch щит поднимается сразу. С --watch процесс ждёт в фоне: нажми хоткей где угодно, и щит поднимется с анимацией из config.yml.
Without the --watch flag the shield goes up right away. With --watch the process waits in the background: press the hotkey anywhere, and the shield goes up with the animation set in config.yml.
dndmode --watch
Фоновый процесс, а не демонBackground process, not a daemon
Процесс виден в ps, его описывает --status и останавливает --kill. Ни launchd, ни login item, ни иконки в строке меню. После перезагрузки его нет.
ps shows it, --status describes it and --kill stops it. No launchd job, no login item, no menu-bar icon: after a reboot it is gone.
Ничего не держит и не видитHolds nothing, sees nothing
В ожидании у процесса нет ни щита, ни tap-а, ни awake lock. Нажатий он не видит: хоткей матчит сама macOS через Carbon. Mac спит и ведёт себя как обычно.
While waiting, the process holds no shield, no tap and no awake lock. It sees no keypress: macOS itself matches the hotkey via Carbon. The Mac sleeps and behaves normally.
RegisterEventHotKeyCarbonНажми хоткей: щит тот же, что у dndmodePress the hotkey: the shield is the same as dndmode
При каждом нажатии хоткея процесс перечитывает секрет из конфига и строит обычную сессию. HID-замок тот же, и флаги те же: --style, --timer, --mute, --focus.
On every hotkey press the process re-reads the secret from the config and builds an ordinary session. The HID lock is the same, and so are --style, --timer, --mute, --focus.
kCGHIDEventTapruntime.jsonНабери unlock code: процесс снова ждётType the unlock code: it waits again
Сессия завершается, щит опускается, и процесс ждёт следующего нажатия. В любой момент --status покажет PID, аптайм и поднят ли щит, а --kill остановит процесс.
The session ends, the shield comes down, and the process waits for the next press. Ask --status for PID, uptime and whether the shield is up; --kill stops the process.
Стили щитаShield styles
Шесть стилей.
Четыре запирают одинаковоSix styles. Four of them lock identically
Анимированные стили меняют только картинку поверх того же непрозрачного чёрного щита: тот же уровень окна, тот же HID-замок. Анимация никогда не реагирует на ввод. Любой отклик выдал бы перехват.
The animated styles only change what is drawn over the same opaque black shield: the same window level, the same HID lock. The animation never reacts to input. Any response would give the interception away.
--style black
black
Непрозрачный чёрный щит, стиль по умолчанию. Сквозь него не просвечивает ничего.
An opaque black shield, the default style. Nothing bleeds through.
--style matrix
matrix
Зелёный цифровой дождь поверх того же чёрного щита. Только косметика: все гарантии блокировки те же.
Green digital rain over the same black shield. Cosmetic only: every blocking guarantee stays the same.
--style terminal
terminal
Поток псевдоисходников с подсветкой печатает сам себя за мигающим курсором. Язык на выбор: go, python, typescript, rust или ys.
A stream of syntax-highlighted pseudo-source types itself out behind a blinking caret. Your choice of language: go, python, typescript, rust or ys.
--style dvd
dvd
Логотип DVD VIDEO скачет по чёрному щиту и на каждом отскоке меняет цвет, как скринсейвер старого DVD-плеера.
The DVD VIDEO logo bounces around the black shield and changes colour on every edge hit, like the old DVD-player screensaver.
--style glass
glass
Матовое стекло вместо чёрного: сквозь щит просвечивает размытый рабочий стол. За этот вид ты отдаёшь гарантию «ничего не видно», но ввод по-прежнему заблокирован. Нужно разрешение Screen Recording.
Frosted glass instead of black: the blurred desktop shows through the shield. You trade the no-bleed-through guarantee for the look, but input stays fully blocked. Needs the Screen Recording permission.
--style none
none
Только awake: ни щита, ни блокировки ввода, ни Focus, ни мьюта. Tap не ставится, поэтому Accessibility не нужен. Выход по Ctrl-C.
Awake-only: no shield, no input blocking, no Focus, no mute. No tap is installed, so it needs no Accessibility; exit with Ctrl-C.
Анимации ограничены ~30 FPS. Все стили, кроме glass и none, запирают одинаково.Animations are capped at ~30 FPS. Every style except glass and none locks identically.
Unlock codeUnlock code
Фраза, набранная в мёртвую клавиатуру, а не хоткейA passphrase typed into a dead keyboard, not a hotkey
dndmode следит за хвостом всего набранного и завершает сессию, как только хвост совпадёт с кодом. Приглашения и индикатора нет, на неверный код dndmode не отвечает, сбросить попытку нельзя. Продолжай печатать, пока не совпадёт.
dndmode watches the tail of everything typed and ends the session once the tail equals the code. No prompt, no progress indicator and no "wrong code"; a half-typed attempt cannot be reset. Keep typing until it matches.
Пробел разделяет шаги, каждый шаг записывается как (<модификатор>+)*<клавиша>. Клавиши: буквы, цифры, space, return, tab, стрелки, пунктуация. Регистр не важен.
Spaces separate steps, and each step is written as (<modifier>+)*<key>. Keys: letters, digits, space, return, tab, arrows, punctuation. Case does not matter.
Физические позиции, а не символыPhysical positions, not characters
Шаги сравниваются по позиции физической клавиши, поэтому на US, русской и AZERTY код работает одинаково. Но s w o r d на русской раскладке набирается клавишами ы ц о р в.
Steps are compared by physical key position, so the code works the same on US, Russian and AZERTY layouts. But s w o r d on a Russian layout is typed with the keys ы ц о р в.
Лишний модификатор ломает шагAn extra modifier breaks the step
Шаг без модификаторов совпадает только с нажатием без них. Прижатый Cmd или Shift его ломает. Нужный модификатор впиши в шаг: cmd+s.
A step without modifiers matches only a press with none held. Resting on Cmd or Shift breaks it. Write the modifier into the step: cmd+s.
Зажатая клавиша даёт один шагA held key counts as one step
Автоповтор отбрасывается до сравнения с кодом. Настоящие двойные буквы не страдают: второе нажатие приходит раньше автоповтора.
Auto-repeat is dropped before matching. Genuine double letters are unaffected: a real second press lands before auto-repeat starts.
Сравнивается хвост набранного, поэтому каждое нажатие даёт новую попытку: блокировок и ограничений частоты нет. Расчёт исходит из алфавита ~36 символов и автомата, печатающего 100 нажатий/с.
Tail matching makes every keypress a fresh attempt: no lockout, no rate limiting. The table assumes an alphabet of ~36 characters and an automated typist at 100 keys/s.
Не ставь f1-f12 в unlock codeDo not put f1-f12 in the unlock code
При настройке macOS по умолчанию F-ряд приходит как системное медиа-событие, а не как нажатие клавиши: dndmode его блокирует, но никогда не записывает. Код с f1 разбирается, проверяется и стартует без единого предупреждения, а потом его не набрать: экран заперт, без клавиатуры и без Ctrl-C. Спасает только Fn на каждом F-шаге или заранее включённый «Use F1, F2, etc. keys as standard function keys». Иначе остаётся SSH с другой машины или жёсткое выключение.
With the macOS default, the F row arrives as a system media event, not a keypress: dndmode blocks it but never records it. A code with f1 parses, validates and starts without a single warning, and then cannot be typed: the screen stays locked, no keyboard, no Ctrl-C. The only way in is to hold Fn on every F step or to turn on "Use F1, F2, etc. keys as standard function keys" beforehand. Otherwise the way out is SSH from another machine or a hard power-off.
Смени код по умолчанию, прежде чем на него полагатьсяChange the default code before relying on it
Поставляемый Ctrl+Option+Cmd+X одинаков на всех машинах и заведомо слабый: код длиной в один шаг, и любой, кто его знает, опустит щит. С ним dndmode запускается, а --debug предупреждает о нём на каждом старте. Замени его фразой из шести шагов и больше. Впиши её в файл, а чтобы секрета в файле не было вообще, задай через dndmode --set-password.
The shipped Ctrl+Option+Cmd+X is identical on every machine and deliberately weak: a one-step code, and anyone who knows it lowers the shield. dndmode still starts with it, and --debug warns about it on every start. Replace it with a phrase of six steps or more. Edit the file, or run dndmode --set-password so the secret never sits in the file at all.
Код уходит из конфигаThe unlock code leaves the config file
dndmode --set-password
По умолчанию код лежит в config.yml открытым текстом. Файл 0600 в каталоге 0700 отсекает только другие учётные записи на этой машине. Секрет остаётся на виду: cat config.yml на демонстрации экрана, коллега за плечом, dotfiles-репозиторий, снимок Time Machine. Команда захватывает новую последовательность с реальных нажатий, просит повторить и заменяет строку парой соль + SHA-256. Сохранённая пара не выдаёт ни последовательность, ни её длину.
By default the code sits in config.yml in plain text. A 0600 file in a 0700 directory stops only other accounts on the machine. The secret stays in plain view: a cat config.yml on a shared screen, a colleague behind you, a dotfiles repository, a Time Machine snapshot. The command captures a new sequence from real keypresses, asks for it twice and replaces the line with a salt + SHA-256 pair. Neither stored value reveals the sequence or its length.
Солёный SHA-256, а не KDFA salted SHA-256, not a KDF
Хэш защищает от случайной засветки вроде взгляда, cat или синхронизированного бэкапа. От offline-перебора он не защищает: обладатель файла переберёт короткие последовательности. Таблица длин действует без изменений. И старые байты остаются на диске. Если код уже попал в бэкап открытым текстом, считай его раскрытым и выбери новый, а не хэшируй старый.
The hash guards against accidental exposure such as a glance, a cat or a synced backup. It does not resist an offline brute force: whoever holds the file can grind through short sequences. The length table applies unchanged. And the old bytes stay on disk. If the plaintext is already backed up somewhere, treat it as disclosed and pick a new code instead of hashing the old one.
С чем не путатьWhat this is not
Два плохих варианта. И третийTwo bad options. And a third
Запрёшь экран, и macOS может приостановить или придушить работу. Оставишь открытым: работа идёт, но кто угодно рядом нажмёт клавишу, кликнет диалог и прочитает, что на экране.
Lock the screen and macOS may suspend or throttle the job. Leave it open and the job keeps running, but anyone passing by can touch the keyboard, click a dialog, or read what is on screen.
| способapproach | работа идётjob runs | ввод запертinput locked | экран скрытscreen hidden | не уснётstays awake | тишинаsilence |
|---|---|---|---|---|---|
Ctrl+Cmd+Q
запертый экранlock screen
|
~ | ✓ | ✓ | ✗ | ✗ |
| оставить открытымleave it open ничего не делатьdo nothing | ✓ | ✗ | ✗ | ~ | ✗ |
caffeinate
только awakeawake only
|
✓ | ✗ | ✗ | ✓ | ✗ |
dndmode
щит, HID-замок, assertion, мьютshield, HID lock, assertion, mute
|
✓ | ✓ | ✓ | ✓ | ✓ |
dndmode --style none - это тонкая обёртка над caffeinate: ни щита, ни HID-замка. Да и сам dndmode остаётся софт-локом для дома, офиса и коворкинга. Защиты уровня железа не даёт.dndmode --style none is a thin caffeinate wrapper: no shield, no HID lock. And dndmode itself is a soft lock for home, office or a coworking desk, not hardware-grade protection.
Выбери один путь и держись егоPick one install path and stay on it
Все три пути собирают dndmode из исходников и подписывают бинарник ad-hoc локально. Подписи получаются разные, поэтому каждому бинарнику нужен свой набор разрешений.
All three paths build dndmode from source and sign the binary ad-hoc locally. The signatures differ, so each binary needs its own grants.
Homebrew
рекомендуетсяrecommendedПопадает в /opt/homebrew/bin. Каждый brew upgrade пересобирает бинарник с новым cdhash, и macOS переспрашивает оба разрешения.
Lands in /opt/homebrew/bin. Every brew upgrade rebuilds with a new cdhash, so macOS asks for both grants again.
Из исходниковFrom source
стабильные разрешенияstable grantsПопадает в /usr/local/bin. Идентификатор и cdhash стабильны, поэтому git pull && make install сохраняет разрешения.
Lands in /usr/local/bin. The identifier and cdhash stay stable, so git pull && make install keeps the grants.
go install
быстроquickПопадает в $(go env GOPATH)/bin. Нужен Go 1.26+. Каждая пересборка @latest меняет cdhash: TCC отзывает оба разрешения.
Lands in $(go env GOPATH)/bin. Needs Go 1.26+. Every @latest rebuild changes the cdhash, and TCC revokes both grants.
После обновления придётся выдать разрешения зановоAfter an update you have to grant permissions again
macOS привязывает выданные разрешения к подписи бинарника. dndmode подписан ad-hoc с идентификатором com.dsbasko.dndmode, а brew upgrade и go install …@latest пересобирают его с новым cdhash. Для TCC это новое приложение. Об этом же предупреждают caveats формулы Homebrew и страницы релизов. make install из клона держит подпись стабильной.
macOS ties each grant to the binary's signature. dndmode is ad-hoc signed as com.dsbasko.dndmode, and brew upgrade or go install …@latest rebuild it with a new cdhash. To TCC that is a new application. The Homebrew formula's caveats and the release pages say the same. make install from a clone keeps the signature stable.
Требования: macOS 14 Sonoma и новее, только Apple Silicon (arm64). Intel не поддерживается. К каждому релизу приложен архив dndmode_vX.Y.Z_darwin_arm64.tar.gz с контрольной суммой. Скачанный браузером бинарник получает com.apple.quarantine. Атрибут снимается командой xattr -d com.apple.quarantine dndmode.Requirements: macOS 14 Sonoma or newer, Apple Silicon (arm64) only. Intel is not supported. Every release ships a dndmode_vX.Y.Z_darwin_arm64.tar.gz archive with a checksum. A binary downloaded by a browser gets com.apple.quarantine. The attribute is cleared with xattr -d com.apple.quarantine dndmode.