...

воскресенье, 29 декабря 2013 г.

[Из песочницы] Управление awesome-wm вне конфигурационного файла

Привет. Во время использования awesome-wm (легкий тайлинговый оконный менеджер), даже когда всё нужное, казалось бы, уже настроено, всё равно замечаешь, что чего-то таки не хватает. Не может радовать то, что в большинстве случаев готового решения нету. Cлава б-гу, разработчики позаботились о хорошей документации и прокомментированному коду, что позволяет в короткие сроки дописать то, что тебе нужно. Горячие клавиши, управление окнами, собственные виджеты, даже создание собственных layout'ов — всё это делается проще, чем может сперва показаться. Но в ситуации с виджетами, как оказалось, не всё так гладко, как хотелось бы. Можно заметить, что добавив пару-тройку, обновляющихся посредством сети, либо просто занимающих сравнительно большое время на обновление информации, на панель, ваш awesome стал тормозить с реакцией на нажатие клавиш, кнопок мыши и всего прочего. Становится очевидно, что уже по менее очевидным причинам обработка событий находится в одном потоке с обновлением виджетов.



Поиск решения описанной проблемы результатов не дал, лень победила, я удалил всё лишнее из панели, установил conky и не стал заморачиваться, но через пару недель всё равно почему-то вспомнил этот случай, решив разобраться. К сожалению, в С/C++ я абсолютный ноль, исправить сабж напрямую не могу, поэтому дальнейшее содержание поста — простой в реализации костыль.

Сначала отмечу, что вышеизложенная проблема касается только кода, выполняемого прямо в awesome любым возможным способом. Если действие выполняется сторонним скриптом ( и строго через awful.util.spawn ), то почти никаких задержек не будет, чем мы и воспользуемся. Наша задача сводится к тому, чтобы вынести 'тяжелый' код за пределы awesome.


В нашем распоряжении dbus и любой удобный вам инструмент ( в моём случае — scala ).

В добавок к уже хвалёным вещам, разработчики позаботились о слегка упрощающем процесс управления менеджером скрипте — awesome-client. Я использовал его, но можно и, наверное, будет правильней пользоваться dbus напрямую, подключив соответствующую библиотеку. В любом случае, не забудьте в rc.lua сделать глобальными переменные / функции, которые вы в дальнейшем будете использовать.


Первым примером будет виджет, показывающий количество доступных обновлений ПО. Lua-версия порой заметно тормозила весь менеджер и выглядела примерно вот так:


Lua


pacwidget = wibox.widget.textbox()
pacwidget.list = ""
pacwidget.timer = timer({ timeout = 600 })
pacwidget.timer:connect_signal("timeout",
function()
local io = { popen = io.popen }
local s = io.popen("pacman -Qu")
local str = ''
local count = 0
for line in s:lines() do
count = count + 1
str = str .. line .. "\n"
end
pacwidget:set_text(tostring(count))
pacwidget.list = str
s:close()
end)
pacwidget.timer:start()
pacwidget.notify = nil
pacwidget:connect_signal("mouse::enter",
function()
pacwidget.notify = naughty.notify({
text = pacwidget.list,
title = "Available updates",
timeout = 0
})
end)
pacwidget:connect_signal("mouse::leave",
function ()
if pacwidget.notify then
naughty.destroy(pacwidget.notify)
pacwidget.notify = nil
end
end)





Нас интересует только функция, выполняющаяся каждый раз по истечению опеределенного времени ( см. 'connect_signal timeout' ).

Для удобства создадим простую иерархию классов. Пока ограничимся только нашими потребностями.

Scala


import sys.process._
trait LuaObject {
val name: String
def eval(code: String) =
("echo "+code) #| "awesome-client" !!
}


Трейт LuaObject ( да, я знаю, что в lua нет объектов ), который реализуют все классы-'обёртки' таблиц lua

abstract class Widget(val name: String) extends LuaObject {
}


Абстрактный класс, который ничего не делает и будет родителем для всех виджетов

abstract class TextWidget(name: String) extends Widget(name) {
private var _text = ""
def text = _text
def text_=(arg: String) {
_text = arg
eval(f"$name:set_text('$arg')")
}
}


Абстрактный класс, который якобы наследует наш pacwidget из rc.lua

import sys.process._
object Pacman extends App {
val pacman = new Pacman(args.head)
}
class Pacman(name: String) extends TextWidget(name) {
val result = "pacman -Qu" !!;
text = result.split('\n').length.toString
eval(f"$name.list = '${result.replace("\n", "\\n")}'")
}


Наш обработчик, который при запуске получает в качестве аргумента название виджета в rc.lua и заполняет его нужной информацией.




И, в принципе, всё. Код предельно прост и в подробном объяснении, наверное, не нуждается. Запакуем результат в .jar и поместим в любую угодную вам папку ( в моём случае — 'jars/' в директории с rc.lua ). Создадим функцию, запускающую скрипт, и поставим её на таймер вместо аналогичной lua-функции.

Lua


creator =
function(jar, arg, time)
local path = awful.util.getdir("config").."/jars/"
local timer = timer({ timeout = time })
local updater =
function()
awful.util.spawn_with_shell("java -jar "..path..jar.." "..arg)
end
timer:connect_signal("timeout", updater)
updater()
return timer
end
pacwidget.timer = creator("pacman.jar", "pacwidget", 60)


Принимает на вход название файла-обработчика, название виджета и период; возвращает таймер.


Теперь сделаем вариант чуть сложнеё — будем слушать новые письма на gmail. Добавим скромную обёртку для nauhgty, метод apply в LuaObject и, собственно, сам обработчик события.


Scala


class Naughty extends LuaObject {
val name = "naughty"
def notify(arg: String) =
eval(f"$name.notify({ text = '$arg' })")
}


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

def apply(arg: String) = {
val result = eval(f"return $name.$arg")
result.trim match {
case r if r.startsWith("string") => _.splitAt(_.indexOf(' '))._2.drop(2).dropRight(1)
case _ => ""
}
}


Такая реализация не совсем правильная, но нам подойдёт.

В scala object.apply(arg) — это то же самое, что и object(arg)

object Gmail extends App {
val gmail = new Gmail(args.head, args.tail.head.split(":"))
}
class Gmail(name: String, account: Array[String]) extends TextWidget(name) {
Authenticator.setDefault(new Authenticator() {
override def getPasswordAuthentication = new PasswordAuthentication(account(0), account(1).toCharArray)
}
)
{
var stream:BufferedSource = null
try {
stream = io.Source.fromURL("https://mail.google.com/mail/feed/atom/")
val result = stream.getLines().mkString
val count = XML.loadString(result).child.find(_.label == "fullcount") match {
case Some(x) => x.text
case None => "-1"
}
val value = count.toInt
val message = if(value == 0)
"No unread mail"
else
if(value == -1)
"Connection error"
else {
if(this("count") < count)
Globals.naughty.notify("New e-mail")
value+" unread mails"
}
eval(f"$name.count = '$count'")
eval(f"$name.list = '$message'")
}
finally {
if(stream != null)
stream.close()
}
}
}


Вкратце опишу, что тут происходит: конструктор принимает название поля в rc.lua и массив из двух элементов — логин и пароль пользователя, затем происходит известная из java аутентификация, подключение по url и парсинг текущего кол-ва непрочитанных писем; Остальное, я думаю, всем понятно.




Пакуем в .jar, перемещаем в нашу папку, добавляем виджет в rc.lua

Lua


gmailwidget = wibox.widget.imagebox()
gmailwidget:set_image(beautiful.mail)
gmailwidget.list = ""
gmailwidget.count = "0"
gmailwidget.timer = creator("gmail.jar", "gmailwidget user:password", 60)
gmailwidget.timer:start()
gmailwidget.notify = nil
gmailwidget:connect_signal("mouse::enter",
function()
gmailwidget.notify = naughty.notify({
text = gmailwidget.list,
title = "Mail",
timeout = 0
})
end)
gmailwidget:connect_signal("mouse::leave",
function ()
if gmailwidget.notify then
naughty.destroy(gmailwidget.notify)
gmailwidget.notify = nil
end
end)







Перезапускаем менеджер, вуаля.


Однако и этот костыль не без изъяна, по крайней мере, если вы будете использовать jvm языки, — если порожденный вами процесс не успевает завершиться до начала следующего, обновляющего тот же самый виджет, то может произойти следующая ситуация:


Скрытый текст


Но на практике такое вероятно только если вы будете обновлять что-либо каждые n < *время работы предыдущего процесса* секунд. Тут уже потребуются какие-либо оптимизации, которые лично мне делать уже лень.

Дальше можно расширить нашу иерархию, возможности, установить более тесное взаимодействие с менеджером, обработку ошибок и так далее. Заодно сделаете один пункт из TODO.


Спасибо за внимание.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий