среда, 26 сентября 2012 г.

Виджет раскладки клавиатуры в QTile

    В Qtile мне очень не хватало аплета отображающего текущую раскладку клавиатуры и поэтому я решил озадачиться этим вопросам. В прошлой статье я описал как получить текущую раскладку клавиатуры в Python, теперь осталось прикрутить это к qtile.
Что оказалось совсем не сложно сделать. За основу я взял виджет часов, который является TextBox'ом. Весь Qtile у меня находить в директории
 /usr/local/lib/python2.7/dist-packages/libqtile/  
виджеты во вложеной директории widget
Класс виджета часов выглядит следующим образом:
import datetime
from .. import hook, bar, manager
import base

class Clock(base._TextBox):
    """
        A simple but flexible text-based clock.
    """
    defaults = manager.Defaults(
        ("font", "Arial", "Clock font"),
        ("fontsize", None, "Clock pixel size. Calculated if None."),
        ("padding", None, "Clock padding. Calculated if None."),
        ("background", "000000", "Background colour"),
        ("foreground", "ffffff", "Foreground colour")
    )
    def __init__(self, fmt="%H:%M", width=bar.CALCULATED, **config):
        """
            - fmt: A Python datetime format string.

            - width: A fixed width, or bar.CALCULATED to calculate the width
            automatically (which is recommended).
        """
        self.fmt = fmt
        base._TextBox.__init__(self, " ", width, **config)

    def _configure(self, qtile, bar):
        base._TextBox._configure(self, qtile, bar)
        self.timeout_add(1, self.update)

    def update(self):
        now = datetime.datetime.now().strftime(self.fmt)
        if self.text != now:
            self.text = now
            self.bar.draw()
        return True

Как видно в методе update производиться присвоение значения текстового поля, в _configure прописывается обновлять виджет каждую секунду, что для нас является очень полезным. Скопируем данный класс под именем kblayout.py. Это и будет наш новый виджет. Так же в директорию с виджетами необходимо скопировать класс который занимется получением текущей раскладки клавиатуры из прошлой статьи под именем kb.py.
После изменения kblayout.py получаем:
from .. import hook, bar, manager
import base
import subprocess
from kb import kb


class kblayout(base._TextBox):
    """
        A simple but flexible text-based kblayout.
    """
    defaults = manager.Defaults(
        ("font", "Arial", "kblayout font"),
        ("fontsize", None, "kblayout pixel size. Calculated if None."),
        ("padding", None, "kblayout padding. Calculated if None."),
        ("background", "000000", "Background colour"),
        ("foreground", "ffffff", "Foreground colour")
    )

    def __init__(self, width=bar.CALCULATED, **config):
        base._TextBox.__init__(self, "ru", width, **config)

    def _configure(self, qtile, bar):
        base._TextBox._configure(self, qtile, bar)
        self.timeout_add(1, self.update)

    def update(self):
        k = kb()
        l = k.curkb()
        if self.text != l:
            self.text = l
            self.bar.draw()
        return True

    Тут у меня вылезлал какая то проблема с gobject, который используется в qtile, которую я так и не смог решить, но смог ее обойти. Для этого в kb.py добавляем строку
del sys.modules['gobject']
получается
import sys
import os
import subprocess
os.environ['GI_TYPELIB_PATH'] =\
    'libxklavier:' + os.environ.get('GI_TYPELIB_PATH', '')

del sys.modules['gobject']
from gi.repository import Xkl, Gdk, GdkX11


class kb():

    def __init__(self):
        display = GdkX11.x11_get_default_xdisplay()
        self.engine = Xkl.Engine.get_instance(display)

    def curkb(self):
        t = subprocess.check_output(["xset", "-q"])
        num = int(t.splitlines()[1].split()[9][4])
        groups = self.engine.get_groups_names()
        if (len(groups) > num):
            return str(groups[int(num)])[:2]
        else:
            return "Err"

k = kb()
print k.curkb()


 И в классе виджета mpriswidget.py за меняем строку
import gobject

на
from gi.repository import Xkl, Gdk, GdkX11

Теперь проблем не должно возникнуть. Осталось прописать наш виджет в файл __init__.py директории widget
from kblayout import kblayout
и прописать наш виджет в своем конфиге qtile - ~/.config/qtile/config.py
У меня это выглядит вот так:
screens = [
    Screen(
        top=bar.Bar([widget.GroupBox(
            borderwidth=2, font='Consolas', fontsize=13,
            padding=1, margin_x=1, margin_y=1),
            widget.Sep(),
            widget.WindowName(
                font='Consolas', fontsize=13, margin_x=6),
            widget.Sep(),
            widget.Battery(
                font='Consolas', fontsize=13, margin_x=6,
                energy_now_file="charge_now",
                energy_full_file="charge_full",
                power_now_file="current_now"),
            widget.Sep(),
            widget.Systray(),
            widget.kblayout(font='Consolas', fontsize=13, padding=6),
            widget.Sep(),
            widget.Clock(
                '%H:%M:%S %d.%m.%Y',
                font='Consolas', fontsize=13, padding=6), ], 24,),
    ),
]






Получение текущей раскладки клавиатуры в python

     Я так и не смог найти нормального способа получить текущую раскладку клавиатуры из командрой строки. В интернете много разный примеров, но ни один у меня не выдал нужного результата(система Ubuntu 12.04). Единственное что дает хоть какой-то результат:

xset -q

 на выходе получаем
 Keyboard Control:  
  auto repeat: on  key click percent: 0  LED mask: 00000002  
  XKB indicators:  
   00: Caps Lock:  off  01: Num Lock:  on   02: Scroll Lock: off  
   03: Compose:   off  04: Kana:    off  05: Sleep:    off  
   06: Suspend:   off  07: Mute:    off  08: Misc:    off  
   09: Mail:    off  10: Charging:  off  11: Shift Lock: off  
   12: Group 2:   off  13: Mouse Keys: off  
  auto repeat delay: 500  repeat rate: 33  
  auto repeating keys: 00ffffffdffffbbf  
             fadfffefffedffff  
             9fffffffffffffff  
             fff7ffffffffffff  
  bell percent: 50  bell pitch: 400  bell duration: 100  
 Pointer Control:  
  acceleration: 2/1  threshold: 4  
 Screen Saver:  
  prefer blanking: yes  allow exposures: yes  
  timeout: 600  cycle: 600  
 Colors:  
  default colormap: 0x20  BlackPixel: 0  WhitePixel: 16777215  
 Font Path:  
  /usr/share/fonts/X11/misc,/usr/share/fonts/X11/100dpi/:unscaled,/usr/share/fonts/X11/75dpi/:unscaled,/usr/share/fonts/X11/Type1,/usr/share/fonts/X11/100dpi,/usr/share/fonts/X11/75dpi,/var/lib/defoma/x-ttcidfont-conf.d/dirs/TrueType,built-ins  
 DPMS (Energy Star):  
  Standby: 0  Suspend: 0  Off: 0  
  DPMS is Enabled  
  Monitor is On  

Для нас важна вторая строка, а конкретно
 LED mask: 00000002  

если мы переключим раскладку с default на другую то получим
 LED mask: 00001002

вот у все что у нас есть. Если у вас больше двух раскладок, то вы не узнаете какая у вас сейчас. Данным способом мы можем узнать только дефолтная раскладка сейчас или нет. Что на самом деле достаточно для большенства.

В bash можно использовать в таком виде
 xset -q | grep LED | awk '{print $10}' | cut -c 5  

возвращает 0 при дефолтной раскладке и 1 при альтернативной.

Но мне раскладка клавиатуры нужна в python.
Для получения группы раскладок в Python мы можем использовать следующий код:

import sys

import os

os.environ['GI_TYPELIB_PATH'] = 'libxklavier:' + os.environ.get('GI_TYPELIB_PATH', '')

from gi.repository import Xkl, Gdk, GdkX11

display = GdkX11.x11_get_default_xdisplay()

engine = Xkl.Engine.get_instance(display)

print('group names:', engine.get_groups_names())


Теперь имея список раскладок, мы можем поставить в соответствие значение получаемое из
  xset -q  
с раскладкой из группы engine.get_groups_names()

в итоге получился такой не большой класс:

import sys
import sys
import os
import subprocess
os.environ['GI_TYPELIB_PATH'] =\
    'libxklavier:' + os.environ.get('GI_TYPELIB_PATH', '')

from gi.repository import Xkl, Gdk, GdkX11


class kb():

    def __init__(self):
        display = GdkX11.x11_get_default_xdisplay()
        self.engine = Xkl.Engine.get_instance(display)

    def curkb(self):
        t = subprocess.check_output(["xset", "-q"])
        num = int(t.splitlines()[1].split()[9][4])
        groups = self.engine.get_groups_names()
        if (len(groups) > num):
            return str(groups[int(num)])[:2]
        else:
            return "Err"