Mouseless: весь рабочий стол Linux с клавиатуры
DraftМышь — самое медленное устройство ввода на моём столе. Обзор моей клавиатурной настройки на i3 и niri: подсказки в стиле Vimium для любого GUI-приложения, сетки keynav и warpd, dragon для drag-and-drop и меню rofi для перехода в чаты Telegram, вкладки браузера, закладки и историю. И почему после всего этого мне стала небезразлична доступность.
Тянуться к мыши — переключение контекста, которое давно перестаёшь замечать. Рука уходит с клавиатуры, находит курсор, ведёт его по экрану, который приходится заново обшаривать глазами, — и через секунду снова возвращается на клавиши. Сто раз за день, и это самая дорогая привычка в моей работе. Не потому что это медленно, а потому что кажется бесплатным.
Поэтому я завёл одно правило: всё должно быть доступно с клавиатуры — клики, перетаскивание, переход в чат, переключение вкладок, открытие закладок. Не потому что мышь плохая, а потому что убрать руки с основного ряда клавиш должно быть решением, а не рефлексом. Ниже — как всё это устроено. Всё описано в моей конфигурации NixOS и работает на i3, а на стороне Wayland — на niri.
Три глагола: перейти, нажать, перетащить
Всё, что мышь делает для меня, сводится к трём глаголам:
- Перейти — куда-то попасть: в другое приложение, чат, вкладку, закладку.
- Нажать — щёлкнуть по тому, что прямо здесь, на экране.
- Перетащить — взять что-то и положить в другое место.
На каждый глагол — свой инструмент. Когда есть все три, мыши просто нечего делать.
Перейти: rofi и несколько переключателей
Переход — это нечёткий поиск, а значит, меню rofi. Основа — запуск приложений и переключение окон в i3, а вокруг них несколько моих собственных инструментов:
- telepad — Super+g, пара букв, Enter — и я в любом чате, группе, канале, теме или архиве Telegram, во всех аккаунтах, из любого окна. Это Ctrl+K из Discord для приложения, в котором его так и не сделали.
- brave-rofi-rust — Super+t показывает все открытые вкладки браузера, а заодно закладки и историю.
С закладками — маленькая идея, которая нравится мне больше любой интеграции: это
обычный текстовый файл. Я храню их в формате Surfraw
— ~/.config/surfraw/bookmarks, по строке name url на закладку, — и surfraw сам читает
этот файл для sr <закладка> в терминале. Тот же файл питает меню rofi на
Super+b, а для истории браузера есть своё меню на
Super+Shift+h. Ни синхронизации, ни расширений, ни
базы данных: дописал строку через echo >> — и о ней знает любой пикер в системе.
Этот принцип — везде: данные лежат в простых текстовых файлах, по которым работает grep, а пикеры — скрипты на десять строк. Весь набор — закладки, история, исполняемые шпаргалки — лежит в репозитории dotfiles.
Нажать: hints — Vimium для всего рабочего стола
Переходы решают навигацию, но иногда нужно просто нажать вот эту кнопку — в диалоге,
на панели инструментов, на чужой веб-странице. Для этого есть
hints: жмёшь
Super+;, и у каждого кликабельного элемента в активном окне
появляется короткая буквенная метка. Набираешь метку — элемент нажат. Это клавиша f
из Vimium, вынесенная из браузера и работающая во
всех GUI-приложениях сразу.
hints — не мой проект, его автор — Alfredo Sequeida.
Но за время ежедневного использования я нормально переписал его настройку под NixOS, и
самое интересное — как раз в том, как всё подключено, в
system/hints.nix:
- Апстримный
sudo hints --setupправит системные файлы руками. В NixOS это один декларативный модуль: пакет, демон, переменные окружения. hintsdкликает, записывая события в/dev/uinput, — это виртуальная мышь, так что приложение не отличит клик по метке от настоящего.- В i3 оверлей с метками — прозрачное окно, которое без композитора рисуется чёрным,
поэтому
picomвходит в модуль. - В niri
Super+;поначалу не делал вообще ничего. hintsd определяет оконную систему, грепая выводps -e, а вPATHу systemd-юнитаpsне было — определение возвращало пустую строку, а клиент молча проглатывал ошибку. В X11 этого никто не замечал: тамXDG_SESSION_TYPE=x11срабатывает раньше этой проверки. Исправление — два пакета вPATHюнита иHINTS_DEBUG=1, чтобы следующая ошибка попала в журнал. - Настройка под конкретные приложения: AyuGram отдаёт ~2900 элементов доступности,
потому что каждая строка списка чатов — это пять ячеек таблицы, и расстановка меток
занимает ~700 мс. Одно правило в
application_rules, которое дополнительно исключает ячейки таблиц, снижает это до ~300 элементов и ~150 мс.
Там, где размечать нечего, по-прежнему выигрывает старый добрый
keynav: Ctrl+;
накрывает экран сеткой, h/j/k/l делят её пополам, y/u/b/n — до
четверти, пока курсор не окажется ровно там, где нужно. Это двоичный поиск вместо
меток. Конфиг — один файл
(keynavrc),
и мои шпаргалки читают его на лету, так что они не устаревают. keynav работает только
в X11, поэтому в niri его заменяет warpd с такой же
настройкой — та же сетка, те же клавиши. Хоткей для него назначен в композиторе: в
Wayland приложения не могут глобально перехватывать клавиши.
Перетащить: dragon для drag-and-drop
Последний глагол, для которого нужна мышь, — перетаскивание, и клавиатурный ответ на него — dragon-drop: он превращает файлы в маленькое окно, из которого их можно перетащить. Выбираешь файлы в терминальном файловом менеджере, жмёшь клавишу — и бросаешь их в форму загрузки в браузере или в диалог выбора файла:
run = "shell -- dragon-drop --and-exit --all %s"Работает и в обратную сторону — dragon как цель, которая печатает пути брошенных файлов, — и хорошо сочетается с hints: отметил цель, бросил. Некрасиво, но работает.
Урок доступности: она нужна и мне
Чего я не ожидал: после всего этого я начал всерьёз относиться к доступности, потому что теперь сам от неё завишу.
hints обходит дерево доступности приложения — AT-SPI в Linux, ту же структуру, что используют скринридеры, — находит элементы, которые действительно можно нажать, и рисует метки по их координатам. В нормальном случае он вообще не смотрит на пиксели, а спрашивает приложение: «что здесь есть и где?»
Это работает, только если у приложения есть дерево, и именно здесь веб тихо сломан.
Настоящий <button> сообщает о себе: он фокусируется, у него есть имя, он есть в дереве
доступности, его видят f из Vimium, скринридеры и любые клавиатурные инструменты. А
<div onclick=…> не сообщает ничего. Выглядит так же, нажимается так же — но для всех
них его не существует. Для работы без мыши такая «кнопка» — не мелкое нарушение
стандартов, а мёртвая зона на экране: hints не рисует над ней метку, и нажать её
можно только мышью. Ровно то, ради чего всё это затевалось.
Так что если спрашивают, зачем доступность, кроме как для пользователей скринридеров, —
вот ещё один ответ. Дерево доступности — не благотворительность, а машиночитаемое
описание того, «что здесь можно сделать», и всё больше инструментов — Vimium, hints,
голосовое управление, автоматизация рабочего стола — используют его как API. Правильно
размеченный интерфейс становится управляемым, и каждый настоящий <button> — это
метка, которую кто-то сможет нажать.
На стороне рабочего стола тот же подвох: приложения отдают дерево, только если их
попросить. В системе запущен at-spi2-core, и каждому тулкиту нужно сказать, чтобы
он включился:
services.gnome.at-spi2-core.enable = true;environment.sessionVariables = { ACCESSIBILITY_ENABLED = "1"; QT_ACCESSIBILITY = "1"; # Qt apps, e.g. Telegram/AyuGram QT_LINUX_ACCESSIBILITY_ALWAYS_ON = "1";};Браузеры на Chromium всё это игнорируют, им нужен флаг: свежезапущенный Brave отдавал
в AT-SPI 0 элементов, а с --force-renderer-accessibility — 83. Поэтому мой brave —
обёртка, которая всегда передаёт этот флаг; без него hints откатывается к поиску краёв
через OpenCV и рисует сотни бессмысленных меток. Система подсказок для всего рабочего
стола — по сути скринридер, который не читает, а нажимает, и он наследует все удачи и
все ошибки доступности, с которыми приходит приложение.
Где это ломается
- Wayland — меньшая половина. hints работает и в i3, и в niri, но keynav есть только под X11, а warpd в Wayland не может сам перехватить клавиши — его должен запускать композитор.
- hints нужна помощь приложения. Приложение без дерева доступности ничего хорошего не получит: запасной вариант со скриншотом и OpenCV умеет нажимать, но не знает, на что нажимает.
- У меток есть цена. Длинные списки (мессенджеры, таблицы) раздувают дерево; правило для AyuGram выше — заплатка, а не решение.
- Мышь — не враг. Для обработки фото или чтобы быстро пролистать и посмотреть, я ей пользуюсь. Правило не «никакой мыши», а «взять мышь — это осознанный выбор».
Стек
Всё это лежит в github.com/antlis/dotfiles; самый
интересный Nix-файл —
system/hints.nix.
| Глагол | Инструмент | Клавиши |
|---|---|---|
| Перейти | rofi + telepad + brave-rofi-rust | Super+g / t |
| Закладки | текстовый файл surfraw + rofi | Super+b |
| История | история Brave + rofi | Super+Shift+h |
| Нажать | hints | Super+; |
| Сетка | keynav в i3 · warpd в niri | Ctrl+; |
| Перетащить | dragon-drop | из yazi / nnn |
Если запомнить одно: в следующий раз попробуйте в браузере клавишу f из Vimium и
заметьте, как редко на самом деле нужна мышь. А потом спросите себя, почему остальной
рабочий стол так не умеет. Может.