Управление 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.

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

ссылка на оригинал статьи http://habrahabr.ru/post/207782/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *