blog-public/content/post/sway-autotiling.md
Denis Zheleztsov a5dd73f274 Initial public
Some content was deleted before this project
has been moved to public
2021-10-18 19:45:57 +03:00

10 KiB
Raw Blame History

+++ date = "2019-04-06T09:13:27+03:00" draft = false title = "Автоматический тайлинг в Sway" tags = ["linux", "sway", "tiling", "i3wm"] comments = true +++

Я давно использую i3wm в работе, а после выхода версии 1.0 Sway я перешел на него с i3. Sway -- это пракатически полностью совместимый с i3 композитор Wayland. По-этому перейти на него оказалось очень просто. Мои конфиги Sway можно посмотреть на Github.

Почему-то я долгое время думал, что мне в i3/sway не хватает полностью ручного тайлинга, я пробовал различные оконные менеджеры с ручным тайлингом, такие как, bspwm, herbstlutfwm и другие, но они не заходили. Потом я решил попробовать AwesomeWM, все-таки это по сути фреймворк и на Lua можно написать все, что угодно. В нем мне очень понравилась стандартная возможность автоматического тайлинга. Вот, что мне переодически нужно, подумал я. Но во всех WM, что я пробовал не было нормальной поддержки скретчпадов - это такие плавающие окна, которые большую часть времени скрыты, и показать их можно, например по комбинации клавишь. Что же мне на самом деле хотелось от WM - скретчпады, полуручной и автоматический тайлинг, возможность менять поведение скриптами на любом языке. По всем параметрам подходил Sway за исключением автоматического тайлинга. Было решено добавить его самостоятельно.

После небольшого иследования этого вопроса оказалось, что у sway есть IPC с возможностью подписки на определенные события. Проблема возникла в том, что существующие биндинги к i3 ipc не подходили, т.к. формат дерева в JSON у Sway отличается, а так же добавляются новые возможности, типа, получения устройств ввода, чего не было в i3.

Так я решил написать(и написал) биндинг к Sway IPC на Go. И уже поверх этого начал писать демона, который реализует автоматический тайлинг.

На данный момент я реализовал spiral layout, который работает, так как я хочу и полностью меня устраивает. Так же на начальном этапе реализован left layout. Демонстрацию этих режимов можно посмотреть в видео ниже.

Как этим воспользоваться?

Для начала нужно скомпилировать swaymgr. Из зависимостей только Go - поставь его из репозиториев твоего дистрибутива.

git clone https://github.com/Difrex/gosway
cd gosway/swaymgr
go get -t -v ./...
go build -o ~/.local/bin/swaymgr

Теперь можно добавить swaymgr в конфигурацию sway. Добавь следующие строчки в ~/.config/sway/config

exec_always ~/.local/bin/swaymgr
bindsym $mod+Alt+m exec ~/.local/bin/swaymgr -s 'set manual'
bindsym $mod+Alt+l exec ~/.local/bin/swaymgr -s 'set left'
bindsym $mod+Alt+s exec ~/.local/bin/swaymgr -s 'set spiral'

Комбинации клавишь меняй по своему вкусу.

Swaymgr запоминает свою расскладку для каждого рабочего стола, а состояние хранит в ~/.autotiling.bolt. В репозитории ты так же найдешь скрипт, который можно использовать, например, с i3blocks.

Про внутреннее устройство

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

Swaymgr -- это отлельное приложение, которое использует gosway/ipc.

Для того, чтобы взаимодействовать со Sway через unix-socket необходимо два подключения:

  1. Соединение для передачи различных комманд
  2. Соединение на которое будут приниматься события от оконного менеджера

Так же для хранения настроек рабочих столов используется встроенная, минималистичная база данных Bolt. Функция newManager() (*manager, error) создает все необходимые подключения и вызывает функцию инициализации интерфейсов Layout. Возвращаемая структура имеет такой вид:

type manager struct {
	commandConn  *ipc.SwayConnection
	listenerConn *ipc.SwayConnection
	store        *store
	layouts      map[string]Layout
}

Главный интерфейс, с помощью которого реализуются все режимы -- Layout, выглядит он так: Файл swaymgr/layouts.go

type Layout interface {
	// PlaceWindow must receive an *ipc.Event
	// and do the container manipulation
	PlaceWindow(*ipc.Event) error
	// Manage must store WorkspaceConfig in the database with
	// the workspace name, layout name and with the Managed: true
	Manage() error
}

У этого интерфейса есть всего два метода:

  • Manage() error -- вызывается тогда, когда текущий рабочий стол переключается в какой-либо режим
  • PlaceWindow(*ipc.Event) error -- вызывается тогда, когда создается новый контейнер на рабочем столе, который является управляемым.

Как работает spiral layout

Я рассмотрю управлене окнами на примере реализации режима spiral.

Структура этого режима состоит всего из двух полей:

  • Conn *ipc.SwayConnection -- Коммандное подключение к unix-сокету Sway
  • store *store -- Открытое соединение к базе данных, в котой хранятся текущие настройки рабочих столов

При наступлении события создания нового окна, и если текущий рабочий стол управляется, данное событие передается в метод PlaceWindow(event *ipc.Event). Для начала необходимо найти текущее окно на котором находится фокус:

nodes, err := s.Conn.GetFocusedWorkspaceWindows()
if err != nil {
    return err
}
var result ipc.Node
for _, node := range nodes {
    if node.Focused {
        result = node
        break
    }
}

Далее вся логика позиционированния окон помещается в 7 строчек:

if result.WindowRect.Width > result.WindowRect.Height {
    _, err := s.Conn.RunSwayCommand(fmt.Sprintf("[con_id=%d] split h", event.Container.ID))
    return err
} else {
    _, err := s.Conn.RunSwayCommand(fmt.Sprintf("[con_id=%d] split v", event.Container.ID))
    return err
}

Если у окна с фокусом длина больше ширины, то новый контейнер разделяем по горизонтали, иначе по вертикали. Все. Это работает замечательно и эту логику работы менять я не буду.

Зарезервированные режимы

Я заранее зарезервировал некоторые режимы которые очень хочется реализовать. Вот они:

  • type FiberLayout struct{} -- режим подсмотренный в AwesomeWM. Окна размещаются симметрично друг под другом и вдоль
  • type TopLayout struct{} -- самое большое окно размещается вверху экрана, остальные внизу разделяясь по горизонтали
  • type BottomLayout struct{} -- тоже самое, что и для TopLayout, но главное окно находится внизу
  • type RightLayout struct{} -- тоже самое, что и LeftLayout, но главное окно размещается справа

Проект на Github. Учитывай, что это пока самая ранняя реализация и тут могут быть баги в больших колличествах, правда ничего критичного я пока не находил -- пользоваться можно. Буду очень рад баг-репортам и фич-реквестам, а так же особенно пулл-реквестам. Лицензия проекта: Apache.