%PDF- %PDF-
| Direktori : /usr/share/ibus-table/engine/ |
| Current File : //usr/share/ibus-table/engine/main.py |
# vim:et sts=4 sw=4
#
# ibus-table - The Tables engine for IBus
#
# Copyright (c) 2008-2009 Yu Yuwei <acevery@gmail.com>
# Copyright (c) 2009-2014 Caius "kaio" CHANCE <me@kaio.net>
# Copyright (c) 2012-2015, 2021-2022 Mike FABIAN <mfabian@redhat.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
from typing import Any
from typing import Union
import os
import re
import sys
import argparse
from signal import signal, SIGTERM, SIGINT
import logging
import logging.handlers
from gi import require_version # type: ignore
require_version('IBus', '1.0')
from gi.repository import IBus # type: ignore
from gi.repository import GLib
import tabsqlitedb
import ibus_table_location
LOGGER = logging.getLogger('ibus-table')
DEBUG_LEVEL = int(0)
try:
DEBUG_LEVEL = int(str(os.getenv('IBUS_TABLE_DEBUG_LEVEL')))
except (TypeError, ValueError):
DEBUG_LEVEL = int(0)
DB_DIR = os.path.join(ibus_table_location.data(), 'tables')
BYO_DB_DIR = os.path.join(ibus_table_location.data_home(), "byo-tables")
ICON_DIR = os.path.join(ibus_table_location.data(), 'icons')
SETUP_CMD = os.path.join(ibus_table_location.lib(), "ibus-setup-table")
LOGFILE = os.path.join(ibus_table_location.cache_home(), 'debug.log')
def parse_args() -> Any:
'''Parse the command line arguments'''
parser = argparse.ArgumentParser()
parser.add_argument(
'--table', '-t',
action='store',
type=str,
dest='db',
default='',
help='Set the IME table file, default: %(default)s')
parser.add_argument(
'--daemon', '-d',
action='store_true',
dest='daemon',
default=False,
help='Run as daemon, default: %(default)s')
parser.add_argument(
'--ibus', '-i',
action='store_true',
dest='ibus',
default=False,
help='Set the IME icon file, default: %(default)s')
parser.add_argument(
'--xml', '-x',
action='store_true',
dest='xml',
default=False,
help='output the engines xml part, default: %(default)s')
parser.add_argument(
'--no-debug', '-n',
action='store_false',
dest='debug',
default=True,
help='Write log file to ' + LOGFILE + ', default: %(default)s')
parser.add_argument(
'--profile', '-p',
action='store_true',
dest='profile',
default=False,
help=('Print profiling information into the debug log. '
'Works only when --no-debug is not used. '
'default: %(default)s'))
return parser.parse_args()
_ARGS = parse_args()
if _ARGS.profile:
import cProfile
import pstats
import io
_PROFILE = cProfile.Profile()
if _ARGS.xml:
import locale
from xml.etree.ElementTree import Element, SubElement, tostring
else:
# Avoid importing factory when the --xml option is used because
# factory imports other stuff which imports Gtk and that needs a
# display.
#
# But by moving the import of factory here, it is possible to
# use the --xml option in an environment where there is no
# display without getting an error message like this:
#
# $ env -u DISPLAY python3 main.py --xml
# Unable to init server: Could not connect: Connection refused
#
# The --xml option is used by “ibus write-cache” which is used
# during rpm updates and then there is often no display and
# the above error message appears.
#
# See: https://bugzilla.redhat.com/show_bug.cgi?id=1955283
import factory
class IMApp:
def __init__(self, dbfile: str, exec_by_ibus: bool) -> None:
if DEBUG_LEVEL > 1:
LOGGER.debug('IMApp.__init__(exec_by_ibus=%s)\n', exec_by_ibus)
self.__mainloop = GLib.MainLoop()
self.__bus: IBus.Bus = IBus.Bus()
self.__bus.connect("disconnected", self.__bus_destroy_cb)
self.__factory = factory.EngineFactory(self.__bus, dbfile)
self.destroyed = False
if exec_by_ibus:
self.__bus.request_name("org.freedesktop.IBus.Table", 0)
else:
self.__component = IBus.Component(
name='org.freedesktop.IBus.Table',
description='Table Component',
version='0.1.0',
license='GPL',
author='Yuwei Yu <acevery@gmail.com>',
homepage='http://mike-fabian.github.io/ibus-table/',
textdomain='ibus-table')
# now we get IME info from self.__factory.db
engine_name = ''
name = ''
longname = ''
description = ''
language = 'en'
credit = ''
author = ''
icon = ''
layout = 'us'
symbol = ''
if self.__factory.db:
engine_name = os.path.basename(
self.__factory.db.filename).replace('.db', '')
name = 'table:'+engine_name
longname = self.__factory.db.ime_properties.get("name")
description = self.__factory.db.ime_properties.get(
"description")
language = self.__factory.db.ime_properties.get("languages")
credit = self.__factory.db.ime_properties.get("credit")
author = self.__factory.db.ime_properties.get("author")
icon = self.__factory.db.ime_properties.get("icon")
layout = self.__factory.db.ime_properties.get("layout")
symbol = self.__factory.db.ime_properties.get("symbol")
if icon:
icon = os.path.join(ICON_DIR, icon)
if not os.access(icon, os.F_OK):
icon = ''
setup_arg = "{} --engine-name {}".format(SETUP_CMD, name)
engine = IBus.EngineDesc(name=name,
longname=longname,
description=description,
language=language,
license=credit,
author=author,
icon=icon,
layout=layout,
symbol=symbol,
setupdsis=setup_arg)
self.__component.add_engine(engine)
self.__bus.register_component(self.__component)
def run(self) -> None:
if DEBUG_LEVEL > 1:
LOGGER.debug('IMApp.run()\n')
if _ARGS.profile:
_PROFILE.enable()
self.__mainloop.run()
self.__bus_destroy_cb()
def quit(self) -> None:
if DEBUG_LEVEL > 1:
LOGGER.debug('IMApp.quit()\n')
self.__bus_destroy_cb()
def __bus_destroy_cb(self, bus: Any = None) -> None:
if DEBUG_LEVEL > 1:
LOGGER.debug('IMApp.__bus_destroy_cb(bus=%s)\n', bus)
if self.destroyed:
return
LOGGER.info('finalizing:)')
self.__factory.do_destroy()
self.destroyed = True
self.__mainloop.quit()
if _ARGS.profile:
_PROFILE.disable()
stats_stream = io.StringIO()
stats = pstats.Stats(_PROFILE, stream=stats_stream)
stats.strip_dirs()
stats.sort_stats('cumulative')
stats.print_stats('main', 25)
stats.print_stats('factory', 25)
stats.print_stats('tabsqlite', 25)
stats.print_stats('table', 25)
LOGGER.info('Profiling info:\n%s', stats_stream.getvalue())
def cleanup(ima_ins: IMApp) -> None:
ima_ins.quit()
sys.exit()
def indent(element: Any, level: int = 0) -> None:
'''Use to format xml Element pretty :)'''
i = "\n" + level*" "
if element:
if not element.text or not element.text.strip():
element.text = i + " "
for subelement in element:
indent(subelement, level+1)
if not subelement.tail or not subelement.tail.strip():
subelement.tail = i + " "
if not subelement.tail or not subelement.tail.strip():
subelement.tail = i
else:
if level and (not element.tail or not element.tail.strip()):
element.tail = i
def write_xml() -> None:
'''
Writes the XML to describe the engine(s) to standard output.
'''
# 1. we find all dbs in DB_DIR and extract the infos into
# Elements
dbs = os.listdir(DB_DIR)
dbs = list(filter(lambda x: x.endswith('.db'), dbs))
_all_dbs = []
for _db in dbs:
_all_dbs.append(os.path.join(DB_DIR, _db))
try:
byo_dbs = os.listdir(BYO_DB_DIR)
byo_dbs = list(filter(lambda x: x.endswith('.db'), byo_dbs))
for _db in byo_dbs:
_all_dbs.append(os.path.join(BYO_DB_DIR, _db))
except OSError:
# BYO_DB_DIR does not exist or is not accessible
pass
egs = Element('engines')
for _db in _all_dbs:
_sq_db = tabsqlitedb.TabSqliteDb(_db, user_db='')
_engine = SubElement(egs, 'engine')
_name = SubElement(_engine, 'name')
engine_name = os.path.basename(_db).replace('.db', '')
_name.text = 'table:'+engine_name
setup_arg = "{} --engine-name {}".format(SETUP_CMD, _name.text)
_longname = SubElement(_engine, 'longname')
_longname.text = ''
# getdefaultlocale() returns something like ('ja_JP', 'UTF-8').
# In case of C/POSIX locale it returns (None, None)
locale.setlocale(locale.LC_ALL, '')
_locale = locale.getlocale(locale.LC_MESSAGES)[0]
if _locale:
_locale = _locale.lower()
if not _locale:
_locale = 'en'
_longname.text = _sq_db.ime_properties.get(
'.'.join(['name', _locale]))
if not _longname.text:
_longname.text = _sq_db.ime_properties.get(
'.'.join(['name', _locale.split('_')[0]]))
if not _longname.text:
_longname.text = _sq_db.ime_properties.get('name')
if not _longname.text:
_longname.text = engine_name
_language = SubElement(_engine, 'language')
_languages = _sq_db.ime_properties.get('languages')
if _languages:
_langs = _languages.split(',')
if len(_langs) == 1:
_language.text = _langs[0].strip()
else:
# we ignore the place
_language.text = _langs[0].strip().split('_')[0]
_license = SubElement(_engine, 'license')
_license.text = _sq_db.ime_properties.get('license')
_author = SubElement(_engine, 'author')
_author.text = _sq_db.ime_properties.get('author')
_icon = SubElement(_engine, 'icon')
_icon_basename = _sq_db.ime_properties.get('icon')
if _icon_basename:
_icon.text = os.path.join(ICON_DIR, _icon_basename)
_layout = SubElement(_engine, 'layout')
_layout.text = _sq_db.ime_properties.get('layout')
_symbol = SubElement(_engine, 'symbol')
_symbol.text = _sq_db.ime_properties.get('symbol')
_desc = SubElement(_engine, 'description')
_desc.text = _sq_db.ime_properties.get('description')
_setup = SubElement(_engine, 'setup')
_setup.text = setup_arg
_icon_prop_key = SubElement(_engine, 'icon_prop_key')
_icon_prop_key.text = 'InputMode'
# now format the xmlout pretty
indent(egs)
egsout = tostring(egs, encoding='utf8').decode('utf-8')
patt = re.compile(r'<\?.*\?>\n')
egsout = patt.sub('', egsout)
# Always write xml output in UTF-8 encoding, not in the
# encoding of the current locale, otherwise it might fail
# if conversion into the encoding of the current locale is
# not possible:
sys.stdout.buffer.write((egsout+'\n').encode('utf-8'))
def main() -> None:
'''Main program'''
if _ARGS.xml:
write_xml()
return
log_handler: Union[
logging.NullHandler, logging.handlers.TimedRotatingFileHandler] = (
logging.NullHandler())
if _ARGS.debug:
log_handler = logging.handlers.TimedRotatingFileHandler(
LOGFILE,
when='midnight',
interval=1,
backupCount=7,
encoding='UTF-8',
delay=False,
utc=False,
atTime=None)
log_formatter = logging.Formatter(
'%(asctime)s %(filename)s '
'line %(lineno)d %(funcName)s %(levelname)s: '
'%(message)s')
log_handler.setFormatter(log_formatter)
LOGGER.setLevel(logging.DEBUG)
LOGGER.addHandler(log_handler)
LOGGER.info('********** STARTING **********')
IBus.init()
if _ARGS.daemon:
if os.fork():
sys.exit()
if _ARGS.db:
if os.access(_ARGS.db, os.F_OK):
db = _ARGS.db
else:
db = '%s%s%s' % (DB_DIR,
os.path.sep,
os.path.basename(_ARGS.db))
else:
db = ""
ima = IMApp(db, _ARGS.ibus)
signal(SIGTERM, lambda signum, stack_frame: cleanup(ima))
signal(SIGINT, lambda signum, stack_frame: cleanup(ima))
try:
ima.run()
except KeyboardInterrupt:
ima.quit()
if __name__ == "__main__":
main()