From b0d0993a000618aadd1ff08d04cded63cda1a031 Mon Sep 17 00:00:00 2001 From: Tokuhiro Matsuno Date: Thu, 3 Sep 2020 01:11:08 +0900 Subject: [PATCH] initial import --- .gitattributes | 1 + .gitignore | 2 + Makefile | 36 ++++++ comb.svg | 1 + comb.xml.in | 32 +++++ config.py.in | 1 + ibus.py | 342 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 415 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 comb.svg create mode 100644 comb.xml.in create mode 100644 config.py.in create mode 100755 ibus.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8e71ca5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +akaza-data/data/lm_v2_1gram.trie filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9e5707 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/config.py +/comb.xml diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4c5253a --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +# only really known to work on ubuntu, if you're using anything else, hopefully +# it should at least give you a clue how to install it by hand + +PREFIX ?= /usr +SYSCONFDIR ?= /etc +DATADIR ?= $(PREFIX)/share +DESTDIR ?= + +PYTHON ?= /usr/bin/python3 + +all: comb.xml config.py + +comb.xml: comb.xml.in + sed -e "s:@PYTHON@:$(PYTHON):g;" \ + -e "s:@DATADIR@:$(DATADIR):g" $< > $@ + +config.py: config.py.in + sed -e "s:@SYSCONFDIR@:$(SYSCONFDIR):g" $< > $@ + +install: all + install -m 0755 -d $(DESTDIR)$(DATADIR)/ibus-comb $(DESTDIR)$(SYSCONFDIR)/xdg/comb $(DESTDIR)$(DATADIR)/ibus/component + install -m 0644 comb.svg $(DESTDIR)$(DATADIR)/ibus-comb + install -m 0644 ibus.py $(DESTDIR)$(DATADIR)/ibus-comb + install -m 0644 comb.xml $(DESTDIR)$(DATADIR)/ibus/component + +uninstall: + rm -f $(DESTDIR)$(DATADIR)/ibus-comb/comb.svg + rm -f $(DESTDIR)$(DATADIR)/ibus-comb/config.py + rm -f $(DESTDIR)$(DATADIR)/ibus-comb/ibus.py + rmdir $(DESTDIR)$(DATADIR)/ibus-comb + rmdir $(DESTDIR)$(SYSCONFDIR)/xdg/comb + rm -f $(DESTDIR)$(DATADIR)/ibus/component/comb.xml + +clean: + rm -f comb.xml + rm -f config.py diff --git a/comb.svg b/comb.svg new file mode 100644 index 0000000..4f07705 --- /dev/null +++ b/comb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/comb.xml.in b/comb.xml.in new file mode 100644 index 0000000..4549b5f --- /dev/null +++ b/comb.xml.in @@ -0,0 +1,32 @@ + + + + org.freedesktop.IBus.Comb + Comb - kana kanji converter + 0.0.1 + GPL + Tokuhiro Matsuno <tokuhirom@gmail.com> + https://github.com/tokuhirom/ibus-comb + @PYTHON@ @DATADIR@/ibus-comb/ibus.py --ibus + comb + + + comb + comb + Comb - Kana Kanji Converter + ja + GPL + Tokuhiro Matsuno <tokuhirom@gmail.com> + @DATADIR@/ibus-comb/comb.svg + us + + + + + + + + 0 + + + diff --git a/config.py.in b/config.py.in new file mode 100644 index 0000000..d0f44b3 --- /dev/null +++ b/config.py.in @@ -0,0 +1 @@ +SYS_CONF_DIR = '@SYSCONFDIR@' diff --git a/ibus.py b/ibus.py new file mode 100755 index 0000000..44f669c --- /dev/null +++ b/ibus.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ibus-comb: ibus engine for japanese characters +# +# Copyright (c) 2020 Tokuhiro Matsuno +# +# based on https://github.com/ibus/ibus-tmpl/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import gi +gi.require_version('IBus', '1.0') +from gi.repository import IBus +from gi.repository import GLib +from gi.repository import GObject + +import os +import sys +import getopt +import locale + +import romkan + + +__base_dir__ = os.path.dirname(__file__) + +logfp = open('/tmp/ibus-comb.log', 'w') + +debug_on = True +def debug(msg): + if debug_on: + logfp.write(msg + "\n") + logfp.flush() +debug("bootstrap") + + +# gee thank you IBus :-) +num_keys = [] +for n in range(1, 10): + num_keys.append(getattr(IBus, str(n))) +num_keys.append(getattr(IBus, '0')) +del n + +numpad_keys = [] +for n in range(1, 10): + numpad_keys.append(getattr(IBus, 'KP_' + str(n))) +numpad_keys.append(getattr(IBus, 'KP_0')) +del n + +########################################################################### +# the engine +class CombIBusEngine(IBus.Engine): + __gtype_name__ = 'CombIBusEngine' + + def __init__(self): + super(CombIBusEngine, self).__init__() + self.is_invalidate = False + self.preedit_string = '' + self.lookup_table = IBus.LookupTable.new(10, 0, True, True) + self.prop_list = IBus.PropList() + + debug("Create Comb engine OK") + + def set_lookup_table_cursor_pos_in_current_page(self, index): + '''Sets the cursor in the lookup table to index in the current page + + Returns True if successful, False if not. + ''' + page_size = self.lookup_table.get_page_size() + if index > page_size: + return False + page, pos_in_page = divmod(self.lookup_table.get_cursor_pos(), + page_size) + new_pos = page * page_size + index + if new_pos > self.lookup_table.get_number_of_candidates(): + return False + self.lookup_table.set_cursor_pos(new_pos) + return True + + def do_candidate_clicked(self, index, dummy_button, dummy_state): + if self.set_lookup_table_cursor_pos_in_current_page(index): + self.commit_candidate() + + def do_process_key_event(self, keyval, keycode, state): + debug("process_key_event(%04x, %04x, %04x)" % (keyval, keycode, state)) + + # ignore key release events + is_press = ((state & IBus.ModifierType.RELEASE_MASK) == 0) + if not is_press: + return False + + if self.preedit_string: + if keyval in (IBus.Return, IBus.KP_Enter): + if self.lookup_table.get_number_of_candidates() > 0: + self.commit_candidate() + else: + self.commit_string(self.preedit_string) + return True + elif keyval == IBus.Escape: + self.preedit_string = '' + self.update_candidates() + return True + elif keyval == IBus.BackSpace: + self.preedit_string = self.preedit_string[:-1] + self.invalidate() + return True + elif keyval in num_keys: + index = num_keys.index(keyval) + if self.set_lookup_table_cursor_pos_in_current_page(index): + self.commit_candidate() + return True + return False + elif keyval in numpad_keys: + index = numpad_keys.index(keyval) + if self.set_lookup_table_cursor_pos_in_current_page(index): + self.commit_candidate() + return True + return False + elif keyval in (IBus.Page_Up, IBus.KP_Page_Up, IBus.Left, IBus.KP_Left): + self.page_up() + return True + elif keyval in (IBus.Page_Down, IBus.KP_Page_Down, IBus.Right, IBus.KP_Right): + self.page_down() + return True + elif keyval in (IBus.Up, IBus.KP_Up): + self.cursor_up() + return True + elif keyval in (IBus.Down, IBus.KP_Down): + self.cursor_down() + return True + + if keyval == IBus.space and len(self.preedit_string) == 0: + # Insert space if that's all you typed (so you can more easily + # type a bunch of emoji separated by spaces) + return False + + # Allow typing all ASCII letters and punctuation, except digits + if ord(' ') <= keyval < ord('0') or \ + ord('9') < keyval <= ord('~'): + if state & (IBus.ModifierType.CONTROL_MASK | IBus.ModifierType.MOD1_MASK) == 0: + self.preedit_string += chr(keyval) + self.invalidate() + return True + else: + if keyval < 128 and self.preedit_string: + self.commit_string(self.preedit_string) + + return False + + def invalidate(self): + if self.is_invalidate: + return + self.is_invalidate = True + GLib.idle_add(self.update_candidates) + + + def page_up(self): + if self.lookup_table.page_up(): + self._update_lookup_table() + return True + return False + + def page_down(self): + if self.lookup_table.page_down(): + self._update_lookup_table() + return True + return False + + def cursor_up(self): + if self.lookup_table.cursor_up(): + self._update_lookup_table() + return True + return False + + def cursor_down(self): + if self.lookup_table.cursor_down(): + self._update_lookup_table() + return True + return False + + def commit_string(self, text): + self.commit_text(IBus.Text.new_from_string(text)) + self.preedit_string = '' + self.update_candidates() + + def commit_candidate(self): + self.commit_string(self.candidates[self.lookup_table.get_cursor_pos()]) + + def update_candidates(self): + preedit_len = len(self.preedit_string) + attrs = IBus.AttrList() + self.lookup_table.clear() + self.candidates = [] + + if preedit_len > 0: + comb_results = [ + # KANA / KANJI KOUHO + (romkan.to_hiragana(self.preedit_string), + romkan.to_hiragana(self.preedit_string)), + ] + debug("HAHAHA %s" % self.preedit_string) + # self.uniemoji.find_characters(self.preedit_string) + for char_sequence, display_str in comb_results: + candidate = IBus.Text.new_from_string(display_str) + self.candidates.append(char_sequence) + self.lookup_table.append_candidate(candidate) + + # にほんご ですね. + text = IBus.Text.new_from_string(self.candidates[0] if len(self.candidates)>0 else self.preedit_string) + # text = IBus.Text.new_from_string(self.preedit_string) + text.set_attributes(attrs) + self.update_auxiliary_text(text, preedit_len > 0) + + attrs.append(IBus.Attribute.new(IBus.AttrType.UNDERLINE, + IBus.AttrUnderline.SINGLE, 0, preedit_len)) + text = IBus.Text.new_from_string(self.preedit_string) + text.set_attributes(attrs) + self.update_preedit_text(text, preedit_len, preedit_len > 0) + self._update_lookup_table() + self.is_invalidate = False + + def _update_lookup_table(self): + visible = self.lookup_table.get_number_of_candidates() > 0 + self.update_lookup_table(self.lookup_table, visible) + + def do_focus_in(self): + debug("focus_in") + self.register_properties(self.prop_list) + + def do_focus_out(self): + debug("focus_out") + self.do_reset() + + def do_reset(self): + debug("reset") + self.preedit_string = '' + + def do_property_activate(self, prop_name): + debug("PropertyActivate(%s)" % prop_name) + + def do_page_up(self): + return self.page_up() + + def do_page_down(self): + return self.page_down() + + def do_cursor_up(self): + return self.cursor_up() + + def do_cursor_down(self): + return self.cursor_down() + +########################################################################### +# the app (main interface to ibus) + +class IMApp: + def __init__(self, exec_by_ibus): + if not exec_by_ibus: + global debug_on + debug_on = True + self.mainloop = GLib.MainLoop() + self.bus = IBus.Bus() + self.bus.connect("disconnected", self.bus_disconnected_cb) + self.factory = IBus.Factory.new(self.bus.get_connection()) + self.factory.add_engine("comb", GObject.type_from_name("CombIBusEngine")) + if exec_by_ibus: + self.bus.request_name("org.freedesktop.IBus.Comb", 0) + else: + xml_path = os.path.join(__base_dir__, 'comb.xml') + if os.path.exists(xml_path): + component = IBus.Component.new_from_file(xml_path) + else: + xml_path = os.path.join(os.path.dirname(__base_dir__), + 'ibus', 'component', 'comb.xml') + component = IBus.Component.new_from_file(xml_path) + self.bus.register_component(component) + + def run(self): + self.mainloop.run() + + def bus_disconnected_cb(self, bus): + self.mainloop.quit() + + +def launch_engine(exec_by_ibus): + IBus.init() + IMApp(exec_by_ibus).run() + +def print_help(out, v = 0): + print("-i, --ibus executed by IBus.", file=out) + print("-h, --help show this message.", file=out) + print("-d, --daemonize daemonize ibus", file=out) + sys.exit(v) + +def main(): + try: + locale.setlocale(locale.LC_ALL, "") + except: + pass + + exec_by_ibus = False + daemonize = False + + shortopt = "ihd" + longopt = ["ibus", "help", "daemonize"] + + try: + opts, args = getopt.getopt(sys.argv[1:], shortopt, longopt) + except getopt.GetoptError: + print_help(sys.stderr, 1) + + for o, a in opts: + if o in ("-h", "--help"): + print_help(sys.stdout) + elif o in ("-d", "--daemonize"): + daemonize = True + elif o in ("-i", "--ibus"): + exec_by_ibus = True + else: + print("Unknown argument: %s" % o, file=sys.stderr) + print_help(sys.stderr, 1) + + if daemonize: + if os.fork(): + sys.exit() + + launch_engine(exec_by_ibus) + +if __name__ == "__main__": + main()