Мне нужно было, чтобы работа не останавливалась, когда ноутбук лежит в рюкзаке. Причин закрывать крышку хватает и кроме этого: длинная сборка, тесты, выгрузка данных, скачивание модели — всё это хочется запустить и уйти, а не сидеть рядом часами. Но у macOS на этот счёт своё мнение: закрыл крышку — ушёл в сон.
Готового решения я не нашёл. Точнее, нашёл, но каждое не подходило. В итоге написал свою утилиту, clamshell, и выложил её в открытый доступ.
Почему привычные средства не работают
Сначала я, как все, попробовал очевидное.
caffeinate. Первая команда, которую ищут в таких случаях. Она не даёт системе уснуть по простою, но перед закрытием крышки бессильна: это отдельный триггер, который она не перехватывает. Проверил на своей машине — не помогает.
Приложения из меню-бара. KeepingYouAwake — популярное и честное. Но в его же README написано прямо: с закрытой крышкой не работает. И это не лень автора, а ограничение системы.
Amphetamine и подобные. Работают, но с оговорками: на батарее нужно отдельное расширение, а мне хотелось решение, которое я понимаю до последней строки и могу починить сам.
Дело в том, что на Apple Silicon закрытие крышки — это аппаратный сигнал. Система не спрашивает приложения, спать ей или нет, — она просто засыпает. Единственный выключатель, который это отменяет, — pmset disablesleep:
sudo pmset disablesleep 1 # сон запрещён
sudo pmset disablesleep 0 # всё как было
Работает это ровно так, как выглядит: нужен root, никакого «на память» — забудешь вернуть 0, ноутбук так и будет бодрствовать в рюкзаке, пока не сядет батарея.
Что я хотел получить
Голый pmset решает задачу, но пользоваться им неудобно. Я собрал требования:
- одна команда включается, одна выключается;
- таймер — «работай час и верни всё назад», чтобы не полагаться на память;
- порог батареи — «работай, но не разряжай батарею ниже двадцати процентов»;
- приложение в меню-баре, чтобы не держать терминал;
- ничего лишнего: никакого root-доступа сверх необходимого.
Последний пункт был для меня важнее всего. Давать скрипту бессрочный sudo — плохая идея, а держать в голове «не забудь выключить» — не решение.
Как это устроено
Вся привилегированная часть сжата до одного действия. Есть крошечный helper, который умеет только on и off:
case "${1:-}" in
on) /usr/bin/pmset disablesleep 1 ;;
off) /usr/bin/pmset disablesleep 0 ;;
*) echo "usage: clamshell-helper {on|off}" >&2; exit 2 ;;
esac
Именно на него выписано правило sudoers с NOPASSWD. Больше ничего от root не требуется — ни общего доступа, ни произвольных команд. Правило узкое настолько, насколько это возможно.
Установка делается одним запросом пароля администратора, дальше он не понадобится никогда.
Таймер и порог батареи
Два сценария, которые я использую чаще всего.
По времени. Уходишь на час — ставишь час:
clamshell on --for 1h
Дальше работает задание в launchd: оно ждёт время, вызывает helper с аргументом off по тому же правилу sudoers и убирает за собой. Второго запроса пароля нет.
По батарее. Если уходишь с запасом, но не хочешь разрядить ноутбук в ноль:
clamshell on --battery 20
Двадцать процентов — и сон возвращается. Причём порог работает только на батарее: пока ноутбук в розетке, разряда нет и срабатывать нечему. Это сэкономило мне немало нервов — не нужно каждый раз переключать режим.
Оба условия можно совместить — сработает то, что наступит раньше:
clamshell on --for 4h --battery 20
Дисплей
Одна мелочь, которую я не ожидал, но которая пригодилась. При закрытой крышке встроенный экран обычно гаснет сам — видеовыхода для него нет. Но на некоторых сборках панель остаётся горящей даже под крышкой. На этот случай есть флаг:
clamshell on --darken
Он гасит дисплей принудительно через pmset displaysleepnow. Специально сделал это опцией, а не поведением по умолчанию: если запускаешь режим с открытой крышкой, внезапно чёрный экран — не то, чего ждёшь.
Приложение в меню-баре
Терминал хорош, но держать его рядом с собой всё время — неудобно. Поэтому есть приложение: луна в строке меню, а в нём — выбор длительности, порог батареи, переключатель дисплея и кнопка остановки.
Одна деталь, за которую я особенно рад. Рядом с иконкой идёт живой счётчик оставшегося времени: 3h52m, и он тикает каждую секунду. Устроено просто: обратный отсчёт идёт локально от известного срока, а с настоящим состоянием он сверяется раз в пять секунд. Так он выглядит живым, но не запускает Python-процесс тысячу раз в час.
Ещё приложение честно говорит о проблемах. Первая версия молча показывала «сон включён», даже когда режим был активен. Причина оказалась интересной: macOS не пускает приложение в папку ~/Documents, а оттуда запускался интерпретатор. Ошибка при этом проглатывалась. Теперь исходники ставятся в ~/Library/Application Support, а сбой CLI виден прямо в меню, а не превращается в тихо врущее «всё хорошо».
Где взять
Проект открытый, под лицензией MIT: github.com/Bagilov/clamshell
Установка — скачать пакет и открыть его. Или, если привык к терминалу:
git clone https://github.com/Bagilov/clamshell
cd clamshell
python3 clamshell_cli.py install
Загрузка…