Когда дурная голова рукам покоя не даёт

У каждого свой собственный workflow (адекватно перевести на русский этот термин не получится - можно даже не пытаться). Понятно, что это индивидуальный набор задач и методов их решения. Поскольку задачи могут быть специфическими, в ряде случаев выбор инструментов и методов заведомо ограничен. Это часто используется как аргумент позиции “Why not Linux”. Типа всем офисом на винде, потому что у нас софт (вставить нужное) и он только под Windows. Если такой аргумент приводится, то субъекта можно сразу исключать из контекста обсуждения: действительно, если мы обсуждаем преимущества стеклорезов, зачем убеждать в их полезности того, кто забивает молотком гвозди? Вообще, непригодность инструмента для решения специфической задачи не может являться основанием для ярлыка “плохой” - “хороший”. Исключительно - в данном случае не пригодный и на этом обсуждение можно заканчивать.

Иногда причина отрицания удобности инструмента просто обусловлена инертностью и ленью субъекта (ну или “тотальная нехватка времени”). Человек ленив, а ум человека ещё ленивее. Он не хочет вникать во что-то новое, однако не признаётся в этом и выдвигает “весомые аргументы”. Распознав данный паттерн, разумно сразу прекращать любое обсуждение (поскольку “обсуждение” возможно только в случае настройки передающих и принимающих на одну частоту). Ну и право человека на “не хочу” я бы вообще включил в конституцию (“Почему вы не ходите на выборы?” - “Не хочу”)

Возможность конфигурирования в мире свободных *nix представителей традиционно считалась признаваемой ценностью. В отдельных случаях она доходит до крайней степени - как в случае того же Emacs, когда вместо текстового редактора получилась среда для построения своего текстового редактора, при том, что может получиться не только редактор, а целый комбайн. Здесь возможно практически всё, но нужно разбираться и кропотливо приспосабливать под свой workflow. При этом, как говорил Бобук на Радио-Т, постоянно что-то не доделано, находится “в процессе”. “Сценарии” (script) как часть конфигурирования - реально крутая и нужная вещь. Единственное, что отталкивает - это разнообразие диалектов, на которых эти скрипты пишутся. Не всегда есть желание тратить время на то, что обязательно забудется и не будет применимо где-либо еще (попробуйте писать скрипты для FVWM - там диалект Scheme).

Тут стоит отметить, что возможность тонкого кофигурирования может быть и хороша, но для массового продукта способна воздвигнуть непреодолимый барьер для того, чтобы продукт мог претендовать на “массовый”. И на этом фоне появляются продукты иного рода. К примеру, Gnome. Одним из его базовых (может быть даже краеугольных) принципов является использование преимущественно тем образом, который предполагают разработчики. Расширения не в счёт - в глазах разработчиков DE - они субъекты второго сорта.1

При наличии альтернатив, существование инструментов, предполагающих заранее заданное и определённое использование не является чем-то отрицательным. Во-первых, заложенные принципы workflow могут оказаться достаточно удобными и подходящими. Во-вторых, имея нечто работающее “из коробки”, можно сэкономить время и потратить его на что-то другое. В контексте DE (desktop environment), если мы имеем дело с одним или двумя окнами, способы взаимодействия с ними - не так уж важны и имея красивую и оптимально настроенную систему - этого вполне достаточно. Плохо здесь другое. Если Xorg являлся действительно универсальным средством, что и привело к появлению множества DE и WM, то Wayland имеет риск стать эксклюзивом для Gnome (и ограниченного числа тайловых оконных менеджеров).2

Другое дело FVWM или i3. Здесь предварительная настройки под себя - обязательный процесс, который предполагается по умолчанию. Вообще, тема оконных менеджеров бесконечна. Вступи на эту дорогу и они бесконечна. Можно завидовать людям, которые выбирают что-то и успокаиваются на этом.

Говоря про творческий процесс приспособления инструментов вместо их использования, необходимо отметить, что целесообразность этого процесса вызывает вопросы. Многие добившиеся весьма выдающихся результатов люди, были крайне аскетичными в выборе инструментария и направляли творческую энергию на создание самого продукта, а не на условия, появление данного продукта обеспечивающего.3 Впрочем, настройка - это тоже творческий процесс и если она доставляет удовольствие, есть ли смысл себя ограничивать? Люди, зацикливающиеся на продуктивности и корящие себя за пустую трату времени, в итоге проигрывают, так как им, в итоге, приходится тратиться на антидепрессанты 😅

Gnome, KDE, Xfce, i3, Enlightenment, NsCDE, Sway - всех их я как-то настраивал, приспосабливая под свои нужды. И в каждом были находились как недостатки, так и преимущества. После долгого периода традиционных Gnome/KDE, естественно было открыть для себя тайловые оконные менеджеры. На протяжении значительного отрезка выбором выбором был i3: с ним провёл много времени - он лёгкий, быстрый и легко настраиваемый оконный менеджер.

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

В начале пути адепта тайлинга, разбираясь с имеющимися альтернативами, мне попался совет: “Начинайте с i3, чтобы потом перейти на что-то более серьёзное: Xmonad, Awesome, DWM”. Но распробовав i3 задался вопросом - а что вообще может быть лучше? Ведь он идеален! Как выяснилось в дальнейшем, не совсем.

Что не устраивало? Если обратиться к самой идеи “тайлинга”, то она заключается в том, что все окна всех запущенных программ (которые окна имеют) всегда занимают всё имеющееся экранное пространство. То есть если у нас открыто одно окно - оно открыто на весь экран. Если два - то они открыты и делят экран в определённых пропорциях и так далее.

Понятно, что это может быть не совсем удобно, и поэтому, наверное, все тайловые менеджеры всё же позволяют включать режим “плавающего окна” - когда его можно свободно перемещать по экрану, а также возможность складывать окна в “стопки” (stack) и переключаться посредством табов или ещё как-то.

То есть теоретически всё можно разложить вполне удобно. Другой вопрос - насколько удобен сам процесс такого раскладывания? Не знаю, в i3 у меня с этим были проблемы.

Кроме того, режим “одно окно на весь экран” при работе на экране больше 21 дюйма - так себе вариант. А если монитор 34 дюйма, то это вообще абсурдно (а ведь есть мониторы и по 40+ дюймов). В результате в i3 процесс выглядел так - если я загружал компьютер и хотел что-то почитать в браузере, то открывал его, переходил в “плавающий” режим, выставлял требуемые размеры. Если открывал ещё одно окно, то оно также занимало весь размер экрана и находилось под окном браузера. И приходилось снова переключаться на браузер, выключать “плавающий режим”. В общем, довольно всё быстро, на хоткеях, но не оптимально.

Пытался как-то этот вопрос решить, писал определённые правила в конфигах, которые определяли поведение окна в зависимости от класса и имени приложения, пытался делать скрипты через i3-msg. Но всё это было не удобно. Хотелось более изящного решения. В результате сформулировать задачу можно так:

Допустим, у меня есть WorkSpace 1.

  1. Если в данный момент времени, на нём нет открытых окон и я открываю одно окно, то хочу чтобы оно располагалось:

    • по центру экрана;
    • имело определённые размеры;
    • его можно было свободно перемещать (то есть должен быть активен плавающий режим)
  2. Если я открываю на WorkSpace 1 второе окно, то хочу, чтобы на WorkSpace 1 активировался режим “тайлинга” и экран делился на две равные половины по горизонтали.

  3. Если я закрываю окна и остается только одно окно, то хочу, чтобы Workspace 1 снова переходил в “плавающий режим”, а единственное оставшееся окно располагалось по центру, обладая заданными размерами.

Понятно, что для реализации данной задачи, нужен скрипт, который получает информацию о состоянии Workspace, может изменять его, имеет возможность управлять окнами - менять их базовые свойства: координаты и размер. Это казалось довольно сложным и копать в данном направлении я не стал. Решил, что можно пользоваться и Gnome, тупо перетаскивая окна и выставляя нужный размер.

Дорога FOSS-приключений привела к Lua. Модные тенденции в NeoVim сделали обязательным переписывание всех конфигов на Lua. Переписывание - это громкое заявление, большую часть тупо скопипастил. Что-то почитал про базовые принципы языка, понял, что там есть некие “таблицы”, которые являются краеугольной концепцией. В принципе, конфиг NeoVim работал неплохо, но вылезали шероховатости. Правишь конфиг, появляется какая-нибудь ошибка и методом тыка пытаешься что-то с ней сделать. Часто ошибка носила синтаксический характер, вызванная непониманием грамматики. Одним словом, в Lua нужно было вникнуть.

И тут я вспомнил про Awesome WM. Про который читал на форумах, что он конфигурируемый, но весьма сложный и не для начинающих. И конфиги пишутся именно на Lua! Получалось, что этот скриптовый язык нужен мне и для NeoVim, и (возможно) для Awesome. Значит нужно разбираться с Lua.

О Lua в нескольких абзацах

Lua позиционируется как простой язык. По нему фактически есть одна книжка, написанная создателем языка - “Программирование на языке Lua” Роберту Иерузалимски. Книжка интересная - автор больше акцентирует внимание на нюансах использования языка, чем на синтаксисе или возможностях стандартной библиотеки (логично - это можно и в документации посмотреть). Почитать книжку стоит, так как поняв основные концепции, с документацией разобраться проще.

Вообще, “простота” Lua сводится к относительно небольшой спецификации языка. Это следствие одного из его принципов: “Mechanism instead of policies”.4 Суть его в следующем: вместо того, чтобы запихнуть в язык всё, что только можно, Lua предоставляет механизм, чтобы программист мог легко напихать только то, что ему нужно. К примеру, основной тип данных (или даже “способ организации данных”) - это таблицы. Их можно представляющие собой гибрид массивов (точнее, списков) и словарей. Меня удивило, что в Lua нет функции, позволяющей создать копию таблицы. Вроде вполне себе востребованная вещь. Тут подобные вещи нужно писать самому.

Понимание “таблиц” в контексте Lua - ключевой момент и не особо сложный. Пример таблицы:

mytable = {
  colors = {"red", "blue", "yellow", "white"},
  favorite_color = "pink",
  255, 220, 50,
  testfunc = function() return #mytable end
}
print (mytable[1])
print (mytable[2])
print (mytable.colors)
print (mytable.favorite_color)
print (mytable.colors[2])
print (mytable.testfunc())

Результат:

: 255
: 220
: table: 0x55919bba34e0
: pink
: blue
: 3

Важно запомнить (рассказать окружающим, долго смеяться), что адресация элементов в Lua начинается не с нуля, а с единицы. Таблица mytable состоит из следующих элементов:

  • “ключ - значение”: colors - значение таблица с перечислением цветов;
  • “значение”: 255, 220, 50;
  • функция: testfunc

Значения в таблице индексируются как в массиве через индекс. Таким образом, первым элементом [1] будет 255, вторым 220, соответственно. Обращение к значению по ключу выполняются через запись: <таблица>.<ключ>. В примере получено значение “pink” получено по ключу favorite_colors. Следующим элементом таблицы является функция - testfunc. При обращении к ней, как к элементу таблицы, она непосредственно возвращает количество элементов в таблице (которыми выступают “значения”), их в нашей таблице три: 255, 220, 50 - оператор “#” позволяет получить длину списка.

Но ведь в таблице шесть сущностей! И чтобы их посчитать, необходимо прибегать к итераторам. И вот именно по теме итераторов желательно почитать книжку. Реализованы они с особенностями (а итерировать по таблицам приходится постоянно).

Посчитаем количество элементов в таблице с помощью итератора pairs.

i = 0
for k,v in pairs(mytable) do
  i = i + 1
end
print(i)

Результат ожидаемо будет равен 6.

При оформлении таблиц, главное не забывать запятые, отделяющие записи и следить за скобками.

Конкатенация строк выполняется с посощью оператора “..”

print ("Hello" .. "World!")

В принципе, обладая пониманием вышеизложенных вещей (ну и бегло просмотрев синтаксис управляющих конструкций, можно переходить к настройке Awesome WM, разбираясь с Lua по ходу).

Концепции AwesomeWM

Если зайти в раздел документации AwesomeWM, то можно закрыть его и никогда больше к вопросу использования данного оконного менеджера. Настолько она кажется объёмной и запутанной, что даже не понятно, с чего начинать распутывать этот клубок. Однако стоит отметить, что по дефолту AwesomeWM предоставляет вполне адекватные настройки и оконным менеджером можно практически сразу пользоваться, и даже быстрее, чем i3 или Sway. А чтобы прописать свой эмулятор терминала или обои для рабочего стола, не нужно вникать в тонкости устройства оконного менеджера или Lua, достаточно просто внести свою правку, заменив соответствующее поле в конфиге (хранится в xdg_config/awesome/rc.lua)

Впрочем, AwesomWM хочется конфигрурировать под себя. Разбираться стоит начинать с выделения основных сущностей (можем назвать их “объекты”):

  • screen - перемененная, отображающая физических экран. К примеру, screen.primary или screen.focus()
  • tag - можно представить как “workspace”, рабочее пространство. В каждый конкретный момент времени каждый клиент может быть связан с одним тэгом. У тэга есть свойство layout - определяющее расположение клиентов на нём. Это может быть тайлинг разных мастей или плавающий режим - floating. Интересно, что экран может отображать окна не только с одного, но и с нескольких тэгов.
  • client - сущность, которая отображается на экране в виде окна. Client имеет определённые размеры, координаты положения на экране. У него может быть titlebar - панель с заголовком, кнопками управления размером и состояния клиента (“развернуть на весь экран”, “свернуть”, “оставаться поверх других окон”, “закрыть” и т.п.), рамки. Таким образом, каждый клиент обладает определённым набором свойств. Свойства могут быть как статическими (заданными переменными темы), так и динамическими - например, получил ли клиент в данный момент фокус;
  • signal - это событие, которое вызывает исполнение заданной функции. К примеру, получение клиентом фокуса - сигнал, присоединение клиента к тэгу - тоже сигнал. Таким образом, сигналы - это основной механизм управления динамическими свойствами сущностей

Сущности описаны классами, которые подробно и внятно документированы. В контексте Lua, каждая сущность представляет собой таблицу. К примеру, таблица “clientkeys”, определяет клавиатурные сочетания взаимодействия с клиентами. Таблицу “clientkeys” помещается в таблицу “globalkeys”. Таблица “globalkeys” передаётся аргументом функции, задающей комбинации клавиш для оконного менеджера в целом. И так во всём. Смотришь конфиг, просекаешь логику, конфигруриуешь. Сперва всё выглядит запутанно, но стоит начать править конфигурационный файл и методом проб и ошибок, всё становится более или менее понятным.

Базовые настройки AwesomeWM

Как и положено в приличных домах, ценящих XDG традиции, конфигурация AwesomeWM лежит в ~/.config/rc.lua, а дефолтные темы в /usr/share/awesome. Для ручной правки целесообразно скопировать их в домашний .config.

Открываем rc.lua и поражаемся его размеру. Но это только сначала - неделька-другая вдумчивой медитации и можно будет чувствовать себя как дома. Первая строка, которая заслуживает интереса, это путь к теме:

beautiful.init("~/.config/awesome/themes/zenburn/theme.lua")

Далее определяем эмулятор терминала и текстовый редактор по умолчанию:

terminal = "kitty"
editor = os.getenv("EDITOR") or "nvim"
editor_cmd = terminal .. " -e " .. editor

Третья строка здесь определяет порядок запуска текстового редактора - он запускается в эмуляторе терминала.

Определяем клавиатурные модификаторы. Их можно рассматривать как псевдонимы. Чтобы не путаться в Mod1, Mod2 и т.д., стоит дать понятные имена.

modkey = "Mod4"
altkey = "Mod1"

Следующий заслуживающий внимания раздел начинается со строчки:

globalkeys = gears.table.join(...)

Это определение всех клавиатурных сочетаний оконного менеджера. Между круглых скобок в качестве аргументов помещаются таблицы, определяющие конкретное клавиатурное сочетание.

К примеру, если хотим, чтобы вместо стандартного лаунчера открывался rofi, ищем в конфиге строчку “run prompt”, комментируем и создаём свою запись (по аналогии с другими записями, сверяясь с доками):

awful.key ({modkey}, "d", function() awful.spawn("rofi -show run") end,
            {description = "Show rofi", group = "launcher"}),

Из этой записи следует, что

  • awful.key является функцией
  • первым аргументом передаётся таблица модификаторов. В данном случае модификатор один - это modkey. Modkey переменная, она определена в самом начале конфига: modkey = “Mod4”, то есть клавиша “Win”5. В эту же таблицу можно добавить другие модификаторы - “Shift”, “Ctrl”, “mod1” as Left_alt.
  • символ, связанный с нажимаемой клавишей - “d”
  • далее указывает функция, которая вызывает в случае нажатия комбинации. В данном случае вызывается встроенная функция awful.spawn - запуск внешней программы, т.е. rofi с требуемыми параметрами. Вполне возможно описать функцию где-то в другом месте конфига. Следующая таблица с ключами “description” и “group” содержит подсказку. При нажатии комбинации “Super-S” (по умолчанию) появляется всплывающее окно с перечнем всех клавиатурных сочетаний оконного менеджера (или открытой программы). В данном случае подсказка будет помещена в раздел “launcher”.

Таблица конфига “rule” определяет правила для групп клиентов, к примеру для класса Conky:

{ rule = { class = "Conky" },
       properties = { border_width = 0, sticky = true } },

Класс клиента можно определить с помощью консольной утилиты xprop, а свойства, допустимые для клиента перечислены в документации класса client

Ну и автозапуск приложений при старте оконного менеджера:

autorun = true
autorunApps = 
{
    "picom -b &",
    "yandex-disk-indicator &",
    "emacs --daemon &"
}

if autorun then
    for app = 1, #autorunApps do
        awful.spawn.easy_async_with_shell(autorunApps[app], {} )
    end
end

Создаём свою таблицу autorunApps, а при старте проходим по ней итерирующим циклом for.

Здесь я столкнулся с проблемой - хотя в Awesome есть встроенная функция spawn.once и spawn.single_instance, при каждом перезапуске все программы, указанные в autorun запускались снова. Проблемы была с Conky - получалось, что запущено несколько клиентов и соответственно открывалось несколько окон. На форумах есть разные рецепты решения проблемы, но я сделал грубо, но просто:

awful.spawn.easy_async_with_shell("ps -A | grep conky", function(stdout, exit_code) 
   if stdout == "" 
      then
       awful.spawn("conky")
    end
end)

То есть при каждом перезапуске проверяется, запущен ли процесс conky исходя из вывода команды ps -A. Если grep выдаёт пустую строку, следовательно conky не запущен😁

Ну и нельзя не сказать про обои. Они определяются в файле темы. Допустим, наша тема zenburn и лежит в ~/.config. Тогда правим файл theme.lua

Определяем путь к папке с обоями и прописываем имя файла с картинкой:

local wallpaper_path = "/usr/share/backgrounds/gnome/"
theme.wallpaper = wallpaper_path .. "brush-strokes-l.jpg"

В этом же файле определяем шрифт по умолчанию:

theme.font      = "sans 12"

Определение поведения окон в AwesomeWM

Посидев с вечерок, я смог настроить поведение клиентов на определённых тэгах так, как задумывал. Получилось вот что:

В первую очередь я определил собственные тэги. В функцию awful.screen.connect_for_each_screen добавил код:

awful.tag.add("NeoVim", {
        index = 1,
        layout  = awful.layout.suit.tile,
        gap = 15,
        screen = s,
    })

Тем самым тэг с именем “NeoVim” получил индекс 1, раскладку по умолчанию - тайлинг, и gap - расстояние между клиентами 15 единиц.

Далее определим реакцию на сигнал “tagged”. Это событие, которые инициируется когда какой-либо клиент присоединяется к тэгу. И реакцию на сигнал “untagged”, как его противоположность.

tag.connect_signal("tagged", function(tag) myNeoVimtag(tag) end)
tag.connect_signal("untagged", function(tag) myNeoVimtag(tag) end)

Принцип такой: когда возникает событие “присоединение клиента в тэгу”, вызывается функция, аргументом которой будет ссылка на объект - тэг, к которому произошло присоединение. Эта ссылка передаётся написанной нами функции myMeoVimtag, которая и выполняет требуемые действия.

Код функции myNeoVimtag:

myNeoVimtag = function(t)
    if t.name == "NeoVim" 
        local tag = t
        c_number = tag:clients() 
            if #c_number == 1 then 
                local c = tag:clients()
                local geom = screen.primary.geometry
                c[1].width = math.ceil(0.7 * geom.width)
                c[1].height = math.ceil(0.8 * geom.height)
                awful.placement.align(c[1], {position="centered"})
                t.layout  = awful.layout.suit.floating
            elseif #c_number > 1 then 
                t.layout = awful.layout.suit.tile
            end
    end
end

Механизм его работы таков:

  • в переменную c_number заносится таблица клиентов, присоединенных к тэгу;
  • если таблица c_number состоит из одной записи (т.е. к тэгу присоединён один клиент), создаётся локальная переменная с, куда заносится таблица клиентов, состоящая, соответственно из одной записи, так как клиент один;
  • затем определяется геометрия экрана и клиенту назначаются размеры окна, исходя из данной геометрии (0,7 ширины, 0,8 высоты с округлением до целого). Для 27-дюймового экрана это оптимально, для 14-дюймового экрана ноутбука - другие пропорции. Поэтому определение оптимального размера вполне можно вынести в отдельную функцию или привязать к dpi.
  • одинокий клиент с “правильными” размерами размещается по центру экрана;
  • для тэга определяется правило размещения окон - awful.layout.suit.floating. То есть окно можно перетаскивать по экрану, произвольно менять его размеры;
  • если же количество клиентов тэга больше одного, то для тэга включается тайловый режим и окна размещаются в соответствии с правилами конкретного режима тайлинга.

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

Создаём тэг:

awful.tag.add("Float", {
        index = 4,
        layout  = awful.layout.suit.floating,
        screen = s,
    })

Определяем сигнал tagged

client.connect_signal("tagged", function(c)  titlebar_for_floating_cl(c) end)

И функцию titlebar_for_floating_cl(c):

titlebar_for_floating_cl = function(c)
    local client = c
    local tag_check = client.first_tag
    if tag_check.name == "Float" then
        c.border_width = 5
        awful.titlebar.show(client)
    else
        c.border_width = 1
        awful.titlebar.hide(client)
    end
end

Таким образом, если клиент присоединился к тэгу (сигнал “tagged”), и его тэг с именем “float”, то он получает рамку с толщиной 5 и строку заголовка. Если же мы этот клиент перебросим на другой тэг, то строка заголовка отключается, а толщина рамки устанавливается в единицу. Просто, изящно, приятно.

Hello Friend

Всплывающие приложения в AwesomeWM

Наигравшись с динамическим определением правил расположения окон, я решил как-нибудь сделать механизм всплывающих приложений. Нажимаю определенную комбинацию, появляется, к примеру, плеер. Нажимаю данную комбинацию повторно и плеер скрывается. Казалось, ничего сложного здесь быть не должно, все необходимые средства есть. Однако реализация оказалась сложнее и времени я на неё потратил больше, чем на возню с тэгами.

В качестве “всплывающего” плеера выступает отличный консольный проигрыватель музыкальных файлов Cmus, а вызываться он должен по нажатию Win+Shift+x. Главная сложность заключалась в понимании следующего: чтобы реализовать данный механизм, необходимо иметь переменную, связанную с объектом - клиентом плеера. И невозможно в конфиге rc.lua определить данную глобальную переменную - каждый раз при нажатии на комбинацию клавиш нужно искать клиента по таблице клиентов и выполнять присваивание, а затем уже, взаимодействуя с данной переменной, определять свойства клиента.

Команда запуска плеера выглядит следующим образом:

kitty --class=my_cmus -e cmus

Таким образом, мы задаём класс для клиента kitty, который позволит идентифицировать клиента при поиске по глобальной таблице клиентов, а также определит его свойства.

Где-нибудь в конфиге определим функцию поиска клиента по классу (целесообразно оформить отдельный раздел, куда включать все свои функции, или вообще вынести их в отдельный файл):

function client_find_by_class(class)
    for _, c in ipairs(client.get()) do
        if c.class == class then
            return c
        end
    end
end

Далее определим свойства для класса клиента “my_cmus”, для этого добавим таблицу в awful.rules.rules = {}

{rule = { class = "my_cmus"},
        properties = {
            floating = true,
            width = 1280,
            height = 1024,
            ontop = true,
            placement = awful.placement.centered,
            skip_taskbar = true
        }
},

Параметром функции является класс искомого клиента. Далее итерирующий for пробегает по таблице клиентов client.get() и проверяет, соответствует ли класс клиента “c” переданному аргументу “class = “my_cmus”. Если да, то он возвращает объект - искомый клиент.

Сама функция вызова клиента по нажатию на комбинацию:

awful.key ({modkey, "Shift"}, "x", function()
            local cmus_client = client_find_by_class("my_cmus")
            if not cmus_client then
                awful.spawn("kitty --class=my_cmus -e cmus",
                    {id = "MyCmusClient",
                    skip_taskbar = true,
                    minimized = true,
                    })
                local cmus_client = client_find_by_class("my_cmus")
            end
            local tag = awful.screen.focused().selected_tag
            if cmus_client and cmus_client.minimized then
                cmus_client:move_to_tag(tag)
                cmus_client.minimized = false
                cmus_client:emit_signal(
                    "request::activate",
                    {raise = true})
            elseif cmus_client and not cmus_client.minimized then
                cmus_client.minimized = true
            end
    end, {description = "Toggle cmus", group = "launcher"}),

При нажатии на комбинацию клавиш происходит поиск Cmus клиента, ссылка на который заносится на локальную переменную cmus_client. Если поиск закончился неудачей, то производится запуск клиента посредством awful.spawn. Далее определяется тэг, на котором будет отображён клиент - активный тэг.

Если переменной cmus_client присвоено значение и клиент находится в состоянии minimized, то определяются его свойства, расположение, он всплывает поверх всех окон и получает фокус.

Однозначно нужно довести функцию до ума и попытаться реализовать в виде отдельного модуля, поскольку сама возможность полезная. Классно иметь всплывающие и скрывающиеся по нажатию на комбинацию плеер, блокнот и т.п.

Полезные ресурсы по AwesomeWM

Если на r/unixporn посмотреть сто самых высокооцененных кастомных окружений, то большая часть из них - AwesomeWM.6 И это не случайно, поскольку помимо описанных манипуляций с окнами, Awesome также является фреймворком для написания собственных виджетов. Все эти панельки, которые очень любят пользователи тайловых оконных менеджеров, на которые выводятся переключатели рабочих столов, сведения о температуре процессора, свободной памяти, погоде, курсе криптовалют - создаются в Awesome встроенными средствами. Принцип такой - есть набор контейнеров, в эти контейнеры укладываешь виджеты, определяешь их расположение, свойства, поведение и тем самым создаёшь нечто красивое-нужное-уникальное.

Широко известный набор кастомизаций AwesomeWM - awesome-copycats

Дополнительные раскладки окон, виджеты - Lain

Набор виджетов, очень полезных в случае использования AwesomeWM на ноутбуке (батарея, яркость и т.п.) - на странице Павла Махова


  1. Интересно следить за обсуждениями на сабреддите Gnome, когда очередной человек вопрошает, почему нельзя интегрировать в систему Dash-to-Dock, Dash-To-Panel или Pop Shell. И представители со стороны разработчиков неизменно отвечают, что включение данного расширения в состав поставки не планируется. На тему рекомендую почитать обсуждение на HackerNews, порожденное постом довольно известного Drew Devalt на тему, что он устал от хейтеров Wayland (поскольку Wayland, по его мнению, пригоден для подавляющего большинства). ↩︎

  2. Последняя новость - “Рассматривается возможность прекращения в GTK5 поддержки X11” ↩︎

  3. Очень верно на этот счёт высказался Рахим Давлеткалиев в 58-м выпуске “Мысли и методы” ↩︎

  4. Очень рекомендую посмотреть видео TJ DeVries: Why is Lua a good fit for NeoVim- маинтейнера NeoVim, в котором он объясняет основные концепции языка, а также отвечает на вопрос, почему именно Lua стал языком расширения для Neovim ↩︎

  5. Как же это здорово, что при появлении Windows 95 начали выпускать клавиатуры с выделенной клавишей Win. Без этого классного модификатора жизнь в Linux была бы менее приятной ↩︎

  6. Пруф: (https://www.youtube.com/watch?v=FhhyqkbtaR4↩︎