Добавлена поддержка выбора раскладки клавиатуры отдельно от локали

* Добавлен объект получения параметров xorg.conf
legacy27
Mike Hiretsky 6 years ago
parent 7488b12f39
commit ac3aacf343

@ -24,6 +24,7 @@ import sys
import getpass
from types import StringType
import string
import glob
import pwd
import itertools
@ -579,3 +580,9 @@ def getPortageUidGid():
return data.pw_uid, data.pw_gid
else:
return 250, 250
def getXorgConfContent(prefix="/"):
return "\n".join(open(x,'r').read() for x in
[path.join(prefix, "etc/X11/xorg.conf")] +
glob.glob(path.join(prefix,"etc/X11/xorg.conf.d/*"))
if path.isfile(x))

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
class XorgConfig(object):
"""
Объект получения параметров из xorg.conf подобного файла
"""
section_pattern = '^Section "%s"\s*\n(.*?)^EndSection'
class Section(object):
value_pattern = '%s\s*"([^"]+)"'
option_pattern = 'Option\s*"%s"\s*"([^"]+)"'
def __init__(self, sectionname, content):
self.sectionname = sectionname
self.content = content
@property
def identifier(self):
re_identifier = re.compile(self.value_pattern % "Identifier")
m = re_identifier.search(self.content)
if m:
return m.group(1)
return ""
def values(self, name):
re_val = re.compile(self.value_pattern % name)
for value in re_val.findall(self.content):
yield value
def value(self, name):
for val in self.values(name):
return val
return ""
def option(self, optname):
re_opt = re.compile(self.option_pattern % optname)
m = re_opt.search(self.content)
if m:
return m.group(1)
return ""
def grab(self, data):
"""
Отбросить пустые строки и комментарии
"""
lines = (x.partition("#")[0].strip() for x in data.split("\n"))
return "\n".join(x for x in lines if x)
def __init__(self, content):
self.content = self.grab(content)
def get_sections(self, sectionname):
"""
Получить все секции с заданным именем
"""
re_section = re.compile(self.section_pattern % sectionname, re.M | re.S)
for sectiondata in re_section.findall(self.content):
yield self.Section(sectionname, sectiondata)
def __contains__(self, sectionname):
for x in self.get_sections(sectionname):
return True
else:
return False

@ -17,8 +17,9 @@
from os import path
import os
from calculate.lib.datavars import ReadonlyVariable
from calculate.lib.utils.common import (getValueFromCmdLine,
from calculate.lib.utils.common import (getValueFromCmdLine, getXorgConfContent,
getValueFromConfig, CmdlineParams)
from calculate.lib.utils.format import XorgConfig
from collections import OrderedDict
import sys
@ -34,7 +35,8 @@ class Locale(object):
# http://www.localeplanet.com/icu/
langData = OrderedDict([
('be_BY', {
'name': 'Беларуская (Беларусь)',
'name': 'Беларуский (Беларусь)',
'layoutname': 'Беларуская',
'locale': 'be_BY.utf8',
'keymap': 'by',
'timezone': 'Europe/Minsk',
@ -45,6 +47,7 @@ class Locale(object):
}),
('bg_BG', {
'name': 'Български (България)',
'layoutname': 'Български',
'locale': 'bg_BG.utf8',
'keymap': 'bg_bds-utf8',
'dumpkeys_charset': '',
@ -55,6 +58,7 @@ class Locale(object):
}),
('bs_BA', {
'name': 'Bosanski (Bosna i Hercegovina)',
'layoutname': 'Bosanski',
'locale': 'bs_BA.utf8',
'keymap': 'slovene',
'timezone': 'Europe/Sarajevo',
@ -65,6 +69,7 @@ class Locale(object):
}),
('cs_CZ', {
'name': 'Čeština (Česká republika)',
'layoutname': 'Čeština',
'locale': 'cs_CZ.utf8',
'keymap': 'cz',
'timezone': 'Europe/Prague',
@ -75,6 +80,7 @@ class Locale(object):
}),
('da_DK', {
'name': 'Dansk (Danmark)',
'layoutname': 'Dansk',
'locale': 'da_DK.utf8',
'keymap': 'dk-latin1',
'timezone': 'Europe/Copenhagen',
@ -85,6 +91,7 @@ class Locale(object):
}),
('de_DE', {
'name': 'Deutsch (Deutschland)',
'layoutname': 'Deutsch',
'locale': 'de_DE.utf8',
'keymap': 'de-latin1',
'timezone': 'Europe/Berlin',
@ -95,6 +102,7 @@ class Locale(object):
}),
('en_AU', {
'name': 'English (Australia)',
'layoutname': 'English',
'locale': 'en_AU.utf8',
'keymap': 'us',
'timezone': 'Australia/Canberra',
@ -105,6 +113,7 @@ class Locale(object):
}),
('en_GB', {
'name': 'English (United Kingdom)',
'layoutname': 'United Kingdom',
'locale': 'en_GB.utf8',
'keymap': 'uk',
'timezone': 'Europe/London',
@ -115,6 +124,7 @@ class Locale(object):
}),
('en_US', {
'name': 'English (United States)',
'layoutname': 'English',
'locale': 'en_US.utf8',
'keymap': 'us',
'timezone': 'America/New_York',
@ -125,6 +135,7 @@ class Locale(object):
}),
('es_ES', {
'name': 'Español (España)',
'layoutname': 'Español',
'locale': 'es_ES.utf8',
'keymap': 'es euro2',
'timezone': 'Europe/Madrid',
@ -135,6 +146,7 @@ class Locale(object):
}),
('es_UY', {
'name': 'Español (Uruguay)',
'layoutname': 'Latin American',
'locale': 'es_UY.utf8',
'keymap': 'la-latin1',
'timezone': 'America/Montevideo',
@ -145,6 +157,7 @@ class Locale(object):
}),
('es_VE', {
'name': 'Español (Venezuela)',
'layoutname': 'Latin American',
'locale': 'es_VE.utf8',
'keymap': 'la-latin1',
'timezone': 'America/Caracas',
@ -155,6 +168,7 @@ class Locale(object):
}),
('et_EE', {
'name': 'Eesti (Eesti)',
'layoutname': 'Eesti',
'locale': 'et_EE.utf8',
'keymap': 'et',
'timezone': 'Europe/Tallinn',
@ -165,6 +179,7 @@ class Locale(object):
}),
('fr_BE', {
'name': 'Français (Belgique)',
'layoutname': 'Belgique',
'locale': 'fr_BE.utf8',
'keymap': 'be-latin1',
'timezone': 'Europe/Brussels',
@ -175,6 +190,7 @@ class Locale(object):
}),
('fr_CA', {
'name': 'Français (Canada)',
'layoutname': 'French-Canadian',
'locale': 'fr_CA.utf8',
'keymap': 'cf',
'timezone': 'Canada/Pacific',
@ -185,6 +201,7 @@ class Locale(object):
}),
('fr_FR', {
'name': 'Français (France)',
'layoutname': 'Français',
'locale': 'fr_FR.utf8',
'keymap': 'fr-latin9',
'timezone': 'Europe/Paris',
@ -195,6 +212,7 @@ class Locale(object):
}),
('hr_HR', {
'name': 'Hrvatski (Hrvatska)',
'layoutname': 'Hrvatski',
'locale': 'hr_HR.utf8',
'keymap': 'croat',
'timezone': 'Europe/Zagreb',
@ -205,6 +223,7 @@ class Locale(object):
}),
('is_IS', {
'name': 'Íslenska (Ísland)',
'layoutname': 'Íslenska',
'locale': 'is_IS.utf8',
'keymap': 'is-latin1',
'timezone': 'Atlantic/Reykjavik',
@ -215,6 +234,7 @@ class Locale(object):
}),
('it_IT', {
'name': 'Italiano (Italia)',
'layoutname': 'Italiano',
'locale': 'it_IT.utf8',
'keymap': 'it',
'dumpkeys_charset': '',
@ -225,6 +245,7 @@ class Locale(object):
}),
('kk_KZ', {
'name': 'Қазақ тілі (Қазақстан)',
'layoutname': 'Қазақ тілі',
'locale': 'kk_KZ.utf8',
'keymap': 'kaz_gost-unicode',
'timezone': 'Asia/Almaty',
@ -235,6 +256,7 @@ class Locale(object):
}),
('lt_LT', {
'name': 'Lietuvių (Lietuva)',
'layoutname': 'Lietuvių',
'locale': 'lt_LT.utf8',
'keymap': 'lt.baltic',
'timezone': 'Europe/Vilnius',
@ -245,6 +267,7 @@ class Locale(object):
}),
('lv_LV', {
'name': 'Latviešu (Latvija)',
'layoutname': 'Latviešu',
'locale': 'lv_LV.utf8',
'keymap': 'lv',
'timezone': 'Europe/Riga',
@ -255,6 +278,7 @@ class Locale(object):
}),
('hu_HU', {
'name': 'Magyar (Magyarország)',
'layoutname': 'Magyar',
'locale': 'hu_HU.utf8',
'keymap': 'hu',
'timezone': 'Europe/Budapest',
@ -265,6 +289,7 @@ class Locale(object):
}),
('nl_BE', {
'name': 'Nederlands (België)',
'layoutname': 'Dutch',
'locale': 'nl_BE.utf8',
'keymap': 'nl',
'timezone': 'Europe/Brussels',
@ -275,6 +300,7 @@ class Locale(object):
}),
('nl_NL', {
'name': 'Nederlands (Nederland)',
'layoutname': 'Dutch',
'locale': 'nl_NL.utf8',
'keymap': 'nl',
'timezone': 'Europe/Amsterdam',
@ -285,6 +311,7 @@ class Locale(object):
}),
('nn_NO', {
'name': 'Nynorsk (Noreg)',
'layoutname': 'Nynorsk',
'locale': 'nn_NO.utf8',
'keymap': 'no-latin1',
'timezone': 'Europe/Oslo',
@ -295,6 +322,7 @@ class Locale(object):
}),
('pl_PL', {
'name': 'Polski (Polska)',
'layoutname': 'Polski',
'locale': 'pl_PL.utf8',
'keymap': 'pl',
'timezone': 'Europe/Warsaw',
@ -305,6 +333,7 @@ class Locale(object):
}),
('pt_BR', {
'name': 'Português (Brasil)',
'layoutname': 'Brasil',
'locale': 'pt_BR.utf8',
'keymap': 'br-abnt2',
'timezone': 'Brazil/East',
@ -315,6 +344,7 @@ class Locale(object):
}),
('pt_PT', {
'name': 'Português (Portugal)',
'layoutname': 'Português',
'locale': 'pt_PT.utf8@euro',
'keymap': 'pt-latin9',
'dumpkeys_charset': '',
@ -325,6 +355,7 @@ class Locale(object):
}),
('ro_RO', {
'name': 'Română (România)',
'layoutname': 'Română',
'locale': 'ro_RO.utf8',
'keymap': 'ro_win',
'timezone': 'Europe/Bucharest',
@ -335,6 +366,7 @@ class Locale(object):
}),
('ru_RU', {
'name': 'Русский (Россия)',
'layoutname': 'Русская',
'locale': 'ru_RU.utf8',
'keymap': '-u ruwin_cplk-UTF-8',
'timezone': 'Europe/Moscow',
@ -345,6 +377,7 @@ class Locale(object):
}),
('sk_SK', {
'name': 'Slovenčina (Slovenská republika)',
'layoutname': 'Slovenčina',
'locale': 'sk_SK.utf8',
'keymap': 'sk-qwertz',
'dumpkeys_charset': '',
@ -355,6 +388,7 @@ class Locale(object):
}),
('sl_SI', {
'name': 'Slovenščina (Slovenija)',
'layoutname': 'Slovenščina',
'locale': 'sl_SI.utf8',
'keymap': 'slovene',
'timezone': 'Europe/Ljubljana',
@ -365,6 +399,7 @@ class Locale(object):
}),
('sq_AL', {
'name': 'Shqip (Shqipëria)',
'layoutname': 'Shqip',
'locale': 'sq_AL.utf8',
'keymap': 'al',
'timezone': 'Europe/Tirane',
@ -375,6 +410,7 @@ class Locale(object):
}),
('sr_RS', {
'name': 'Српски (Србија)',
'layoutname': 'Српски',
'locale': 'sr_RS.utf8',
'keymap': 'sr-unicode',
'timezone': 'Europe/Belgrade',
@ -385,6 +421,7 @@ class Locale(object):
}),
('fi_FI', {
'name': 'Suomi (Suomi)',
'layoutname': 'Suomi',
'locale': 'fi_FI.utf8',
'keymap': 'fi',
'timezone': 'Europe/Helsinki',
@ -395,6 +432,7 @@ class Locale(object):
}),
('sv_SE', {
'name': 'Svenska (Sverige)',
'layoutname': 'Svenska',
'locale': 'sv_SE.utf8',
'keymap': 'sv-latin1',
'timezone': 'Europe/Stockholm',
@ -405,6 +443,7 @@ class Locale(object):
}),
('uk_UA', {
'name': 'Українська (Україна)',
'layoutname': 'Українська',
'locale': 'uk_UA.utf8',
'keymap': 'ua-utf',
'timezone': 'Europe/Kiev',
@ -415,9 +454,23 @@ class Locale(object):
})
])
default_lang = "en_US"
fields = list(langData[default_lang].keys())
aliases = {
'en_AU': 'en_US',
'es_UY': 'es_VE',
'nl_BE': 'nl_NL',
}
def getLangs(self):
return self.langData.keys()
def getKeyboardLayouts(self):
skip = self.aliases.keys()
return [x for x in self.langData.keys() if x not in skip]
def getLanguages(self):
return map(lambda x: self.langData[x]['language'],
self.langData.keys())
@ -433,11 +486,42 @@ class Locale(object):
return [l[1][field] for l in self.langData.items()]
def getFieldByLang(self, field, lang):
return self.langData.get(lang, self.langData['en_US'])[field]
def getFieldByKeymap(self, field, keymap):
return self.langData.get(self.getLangByField('keymap', keymap),
self.langData['en_US'])[field]
return self.langData.get(lang, self.langData[self.default_lang])[field]
def getLang(self, **kw):
"""
Получить язык по указанному полю.
getLang(keymap="ua-utf")
"""
if not kw:
raise AttributeError("getLang: field missed")
field = next(iter(kw.keys()))
if field not in self.fields:
raise AttributeError("getLang: wrong field")
return self.getLangByField(field, kw[field])
def getKeyboardLayout(self, **kw):
if "lang" in kw:
if kw["lang"] in self.langData:
lang = kw["lang"]
else:
lang = self.default_lang
else:
lang = self.getLang(**kw)
return self.aliases.get(lang, lang)
def __getattr__(self, attrname):
"""
Обработка getKeymap, getXkblayout
"""
if attrname.startswith('get'):
fieldname = attrname[3:].lower()
if fieldname in self.fields:
def wrapper(fieldvalue):
return self.getFieldByLang(fieldname, fieldvalue)
return wrapper
raise AttributeError("'Locale' object has no attribute '%s'" % attrname)
def getLangByField(self, field, value):
value = value or ""
@ -449,7 +533,7 @@ class Locale(object):
langs = [lang[0] for lang in self.langData.items()
if lang[1][field] in value]
if not langs:
return 'en_US'
return self.default_lang
else:
return langs[0]
@ -481,28 +565,90 @@ class VariableOsLocaleLocale(ReadonlyVariable, Locale):
env_var = "LANG"
def get(self):
# get locale from boot calculate param
locale_file = '/etc/env.d/02locale'
# получить значение локали из параметров загрузки ядра
# выбранный язык должен присутствовать в списке поддерживаемых
locale_val = getValueFromCmdLine(
CmdlineParams.Calculate, CmdlineParams.Locale)
if self.isLangExists(locale_val):
cmd_val = self.getFieldByLang('locale', locale_val)
cmd_val = self.getLocale(locale_val)
else:
cmd_val = None
# получить значение из файла локали параметров окружения
# выбранный язык должен присутствовать в списке поддерживаемых
locale_file = '/etc/env.d/02locale'
locale_val = getValueFromConfig(locale_file, self.env_var) or ""
locale_val = locale_val.replace('UTF-8','utf8')
if self.isValueInFieldExists('locale', locale_val):
file_val = locale_val
else:
file_val = None
# если параметры локали получены из параетров ядра или окружения
if file_val or cmd_val:
# если система настраивается во время загрузки, то приоритет
# значения параметров загрузки ядра
if self.GetBool('cl_system_boot_set'):
return cmd_val or file_val
else:
return file_val or cmd_val
# если значение окружения не входит с список поддерживаемых
# то используем это значения как есть
if self.env_var in os.environ and os.environ[self.env_var] != "C":
return os.environ[self.env_var]
return self.getFieldByLang("locale", "default")
# во всех остальных случаях вернуть локаль по умолчанию
return self.getLocale("default")
class VariableOsLocaleKeyboardLayout(ReadonlyVariable, Locale):
"""
Раскладка клавиатуры "притянутая" к алиасам ru_RU, en_US и т.д.
"""
def get_xorg_xkblayout(self):
xc = XorgConfig(getXorgConfContent())
for x in (section.option("XkbLayout")
for section in xc.get_sections("InputClass")):
if x:
return x
else:
return ""
def get(self):
# получить значение локали из параметров загрузки ядра
# выбранный язык должен присутствовать в списке поддерживаемых
lang_val = getValueFromCmdLine(
CmdlineParams.Calculate, CmdlineParams.Locale)
if self.isLangExists(lang_val):
cmd_keylayout = self.getKeyboardLayout(lang=lang_val)
else:
cmd_keylayout = None
# получить значение из xorg.conf
xorg_xkb = self.get_xorg_xkblayout()
if self.isValueInFieldExists('xkblayout', xorg_xkb):
xorg_keylayout = self.getKeyboardLayout(xkblayout=xorg_xkb)
else:
xorg_keylayout = None
# получить значение из файла раскладки
# выбранный язык должен присутствовать в списке поддерживаемых
keymap_file = '/etc/conf.d/keymap'
keymap_val = getValueFromConfig(keymap_file, "keymap") or ""
if self.isValueInFieldExists('keymap', keymap_val):
file_keylayout = self.getKeyboardLayout(keymap=keymap_val)
else:
file_keylayout = None
# если параметры локали получены из параетров ядра или окружения
if file_keylayout or cmd_keylayout or xorg_keylayout:
# если система настраивается во время загрузки, то приоритет
# значения параметров загрузки ядра
if self.GetBool('cl_system_boot_set'):
return cmd_keylayout or xorg_keylayout or file_keylayout
else:
return xorg_keylayout or file_keylayout or cmd_keylayout
# во всех остальных случаях вернуть локаль по умолчанию
return self.getKeyboardLayout(lang=self.Get('os_locale_lang'))
class VariableOsLocaleLang(ReadonlyVariable, Locale):
@ -512,7 +658,7 @@ class VariableOsLocaleLang(ReadonlyVariable, Locale):
def get(self):
"""lang (example: ru_RU)"""
return self.getLangByField("locale", self.Get('os_locale_locale'))
return self.getLang(locale=self.Get('os_locale_locale'))
class VariableOsLocaleLanguage(ReadonlyVariable, Locale):
@ -521,7 +667,7 @@ class VariableOsLocaleLanguage(ReadonlyVariable, Locale):
"""
def get(self):
return self.getFieldByLang("language", self.Get('os_locale_lang'))
return self.getLanguage(self.Get('os_locale_lang'))
class VariableOsLocaleXkb(ReadonlyVariable, Locale):
@ -531,7 +677,7 @@ class VariableOsLocaleXkb(ReadonlyVariable, Locale):
def get(self):
"""xkb layouts (example: en,ru)"""
return self.getFieldByLang("xkblayout", self.Get('os_locale_lang'))
return self.getXkblayout(self.Get('os_locale_keyboard_layout'))
class VariableOsLocaleXkbname(ReadonlyVariable, Locale):
@ -572,5 +718,16 @@ class VariableOsLang(ReadonlyVariable, Locale):
return self.getLangs()
def humanReadable(self):
return map(lambda x: self.getFieldByLang("name", x),
self.Get())
return map(lambda x: self.getName(x), self.Get())
class VariableOsKeyboardLayout(ReadonlyVariable, Locale):
"""
Supported keyboard layouts
"""
type = "list"
def get(self):
return self.getKeyboardLayouts()
def humanReadable(self):
return map(lambda x: self.getLayoutname(x), self.Get())

Loading…
Cancel
Save