From c83b39060193ee135f065d5d9614c0dd0b1fa96e Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 31 Jan 2020 11:43:26 +0700 Subject: [PATCH 1/4] feat(linux): Add ability to configure keyboard options --- linux/.editorconfig | 7 + linux/ibus-keyman/src/engine.c | 122 +++++++++++++++++- linux/ibus-keyman/src/engine.h | 8 ++ linux/keyman-config/Makefile | 2 +- linux/keyman-config/README.md | 19 ++- linux/keyman-config/com.keyman.gschema.xml | 15 +++ .../keyman-config/keyman_config/dconf_util.py | 73 +++++++++++ .../keyman_config/list_installed_kmp.py | 53 ++++++-- linux/keyman-config/keyman_config/options.py | 86 ++++++++++++ .../keyman_config/view_installed.py | 54 ++++++-- linux/scripts/install.sh | 3 + 11 files changed, 407 insertions(+), 35 deletions(-) create mode 100644 linux/.editorconfig create mode 100644 linux/keyman-config/com.keyman.gschema.xml create mode 100644 linux/keyman-config/keyman_config/dconf_util.py create mode 100644 linux/keyman-config/keyman_config/options.py diff --git a/linux/.editorconfig b/linux/.editorconfig new file mode 100644 index 0000000000..0d465fe281 --- /dev/null +++ b/linux/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.py] +trim_trailing_whitespace = true +intent_stype = space +intent_size = 4 + diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 586a85a203..9eff9dfdd5 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2009-2018 SIL International + * Copyright (C) 2009-2020 SIL International * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public @@ -26,6 +26,8 @@ #include #include +#include +#include #include #include "keymanutil.h" @@ -118,6 +120,10 @@ static void ibus_keyman_engine_commit_string (IBusKeymanEngine *kmfl, const gchar *string); +static GQueue* ibus_keyman_engine_keyboard_options + (gchar *package_id, + gchar *keyboard_id); + static IBusEngineClass *parent_class = NULL; GType @@ -325,7 +331,14 @@ ibus_keyman_engine_constructor (GType type, } g_free(kmx_file); - km_kbp_option_item *keyboard_opts = g_new0(km_kbp_option_item, 4); + // Retrieve keyboard options from DConf + // TODO: May need unique packageID and keyboard ID + g_message("Loading options for kb_name: %s", keyman->kb_name); + GQueue *queue_options = ibus_keyman_engine_keyboard_options(keyman->kb_name, keyman->kb_name); + int num_options = g_queue_get_length(queue_options); + + // Allocate enough options for: 3 environments plus num_options plus 1 pad struct of 0's + km_kbp_option_item *keyboard_opts = g_new0(km_kbp_option_item, KEYMAN_ENVIRONMENT_OPTIONS + num_options + 1); keyboard_opts[0].scope = KM_KBP_OPT_ENVIRONMENT; km_kbp_cp *cp = g_utf8_to_utf16 ("platform", -1, NULL, NULL, NULL); @@ -371,7 +384,13 @@ ibus_keyman_engine_constructor (GType type, // g_free(lang); keyboard_opts[2].value = cp; - // keyboard_opts[3] already initialised to {0, 0, 0} + // If queue_options contains keyboard options, pop them into keyboard_opts[3] onward + for(int i=0; ikeyboard)); g_free(abs_kmx_path); @@ -388,10 +407,11 @@ ibus_keyman_engine_constructor (GType type, { g_warning("problem creating km_kbp_state"); } - for (int i =0; i < 3; i++) { + for (int i=0; i < KEYMAN_ENVIRONMENT_OPTIONS + num_options + 1; i++) { g_free((km_kbp_cp *)keyboard_opts[i].key); g_free((km_kbp_cp *)keyboard_opts[i].value); } + g_queue_free_full(queue_options, NULL); g_free(keyboard_opts); reset_context(engine); @@ -793,6 +813,50 @@ ibus_keyman_engine_process_key_event (IBusEngine *engine, break; case KM_KBP_IT_PERSIST_OPT: g_message("PERSIST_OPT action %d/%d", i+1, (int)num_action_items); + // Save keyboard option + if (action_items[i].option != NULL) + { + // Allocate for 1 option plus 1 pad struct of 0's + km_kbp_option_item *keyboard_opts = g_new0(km_kbp_option_item, 2); + memmove(&(keyboard_opts[0]), action_items[i].option, sizeof(km_kbp_option_item)); + event_status = km_kbp_state_options_update(keyman->state, keyboard_opts); + if (event_status != KM_KBP_STATUS_OK) + { + g_warning("problem saving option for km_kbp_keyboard"); + } + g_free(keyboard_opts); + + // Put the keyboard option into DConf + if (action_items[i].option != NULL && action_items[i].option->key != NULL && + action_items[i].option->value != NULL) + { + g_message("Saving keyboard option to DConf"); + // Load the current keyboard options from DConf + GQueue *queue_options = ibus_keyman_engine_keyboard_options(keyman->kb_name, keyman->kb_name); + int num_options = g_queue_get_length(queue_options); + gchar *option_kvp = g_strdup_printf("'%s=%s'", action_items[i].option->key, action_items[i].option->value); + gboolean kvp_added = FALSE; + + // Update with the keyboard option to save + for(int i=0; ichar_buffer != NULL) @@ -976,3 +1040,53 @@ ibus_keyman_engine_property_activate (IBusEngine *engine, parent_class->property_activate (engine, prop_name, prop_state); } +// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item +// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] +// +// Parameters: +// package_id (gchar *): Package ID +// keyboard_id (gchar *): Keyboard ID +// +// Return a newly allocated GQueue; free with g_queue_free_full() +static GQueue* +ibus_keyman_engine_keyboard_options(gchar *package_id, + gchar *keyboard_id) +{ + g_message("ibus_keyman_engine_keyboard_options"); + GQueue *queue_options = g_queue_new(); + + // Obtain keyboard options from DConf + gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, package_id, keyboard_id); + GSettings *child_settings = g_settings_new_with_path(KEYMAN_CHILD_DCONF_NAME, path); + gchar **options = NULL; + if (child_settings != NULL) + { + options = g_settings_get_strv(child_settings, KEYMAN_DCONF_OPTIONS_KEY); + } + + // Parse options into queue_options + if (*options != NULL) + { + int index = 0; + while (options[index] != NULL) + { + gchar **option_tokens = g_strsplit(options[index], "=", 2); + if (option_tokens != NULL && option_tokens[0] != NULL && option_tokens[1] != NULL) + { + g_message("Keyboard Option [%d], %s=%s", index, option_tokens[0], option_tokens[1]); + km_kbp_option_item *opt = g_new0(km_kbp_option_item, 1); + opt[0].scope = KM_KBP_OPT_KEYBOARD; + km_kbp_cp *ocp = g_utf8_to_utf16(option_tokens[0], -1, NULL, NULL, NULL); + opt[0].key = ocp; + ocp = g_utf8_to_utf16 (option_tokens[1], -1, NULL, NULL, NULL); + opt[0].value = ocp; + g_queue_push_tail(queue_options, opt); + } + index++; + } + } + + g_free(path); + + return queue_options; +} diff --git a/linux/ibus-keyman/src/engine.h b/linux/ibus-keyman/src/engine.h index 88d1b2a6ff..308521820c 100644 --- a/linux/ibus-keyman/src/engine.h +++ b/linux/ibus-keyman/src/engine.h @@ -26,6 +26,14 @@ #include +#define KEYMAN_DCONF_NAME "com.keyman.options" +#define KEYMAN_CHILD_DCONF_NAME "com.keyman.options.child" +#define KEYMAN_DCONF_PATH "/desktop/ibus/keyman/options/" +#define KEYMAN_DCONF_OPTIONS_KEY "options" + +// Number of default Keyboard processor environment options for: "platform", "baseLayout", and "baseLayoutAlt" +#define KEYMAN_ENVIRONMENT_OPTIONS 3 + #define IBUS_TYPE_KEYMAN_ENGINE \ (ibus_keyman_engine_get_type ()) diff --git a/linux/keyman-config/Makefile b/linux/keyman-config/Makefile index c0efaab064..7ee8dedd78 100644 --- a/linux/keyman-config/Makefile +++ b/linux/keyman-config/Makefile @@ -16,7 +16,7 @@ install-temp: mkdir -p /tmp/kmfl/`python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)'` PYTHONUSERBASE=/tmp/kmfl python3 setup.py install --user -uninstall: # run as sudo +uninstall: clean # run as sudo rm -rf /usr/local/share/keyman/icons rm -f /usr/local/share/man/man1/km-*.1 pip3 uninstall keyman_config diff --git a/linux/keyman-config/README.md b/linux/keyman-config/README.md index d1283bfe4a..e1be590472 100644 --- a/linux/keyman-config/README.md +++ b/linux/keyman-config/README.md @@ -10,9 +10,16 @@ python3-requests-cache python3 python3-gi gir1.2-webkit-3.0 dconf-cli python3-se You will also need kmflcomp either from a package or built and installed locally. -run the script `./createkeymandirs.sh` to create the directories for these programs to +Run the script `./createkeymandirs.sh` to create the directories for these programs to install the packages to +Also copy and compile the GSettings schema +```bash +cd keyman_config +sudo cp com.keyman.gschema.xml /usr/share/glib-2.0/schemas +sudo glib-compile-schemas /usr/share/glib-2.0/schemas +``` + ### Installing manually from the repo `make && sudo make install` will install locally to /usr/local @@ -32,6 +39,7 @@ This displays a configuration panel that shows the currently installed Keyman ke * `Uninstall` - uninstall selected keyboard * `About` - show information about selected keyboard * `Help` - display help documentation about selected keyboard +* `Options` - display options.htm form for setting keyboard options ----------------------------------- * `Refresh` - useful if you install or uninstall on the commandline while running km-config. @@ -43,12 +51,9 @@ This displays a configuration panel that shows the currently installed Keyman ke This uses the keyman.com website to install kmps. -The website doesn't know about linux yet (until after 10.0 release) so -pretending to be a mac for now. - -Search for a language or keyboard in the search box -Select a keyboard from the list -In 'Downloads for your device' there will be a 'Install keyboard' button for the keyboard for macOS +Search for a language or keyboard in the search box. +Select a keyboard from the list. +In 'Downloads for your device' there will be an 'Install keyboard' button for the keyboard for Linux. Click it to download the keyboard and bring up the `InstallKmpWindow` for more details and to confirm installing. Secondary-click gives you a menu including 'Back' to go back a page. diff --git a/linux/keyman-config/com.keyman.gschema.xml b/linux/keyman-config/com.keyman.gschema.xml new file mode 100644 index 0000000000..8f05a42bf9 --- /dev/null +++ b/linux/keyman-config/com.keyman.gschema.xml @@ -0,0 +1,15 @@ + + + + + + + + + + [] + keyboard options.htm settings + List of strings + + + diff --git a/linux/keyman-config/keyman_config/dconf_util.py b/linux/keyman-config/keyman_config/dconf_util.py new file mode 100644 index 0000000000..116551d1e0 --- /dev/null +++ b/linux/keyman-config/keyman_config/dconf_util.py @@ -0,0 +1,73 @@ +#!/usr/bin/python3 + +import logging +from gi.repository import Gio, Gtk + +# DConf path destkop/ibus/keyman/options +DCONF_BASE = "com.keyman.options" + +# Utilities to get and set Keyman options in DConf: +# /desktop/ibus/keyman/options/packageID/keyboardID/options + + +def get_child_schema(info): + settings = Gio.Settings.new(DCONF_BASE) + path = settings.get_property('path') + if not path.endswith('/'): + path += '/' + path += info['packageID'] + '/' + info['keyboardID'] + '/' + return Gio.Settings(DCONF_BASE + '.child', path) + + +def get_option(info): + """ + Get the Keyman keyboard options from DConf + Convert from list of comma-separated strings into dictionary + + Args: + info: dictionary + packageID (str): Package ID + keyboardID(str): Keyboard ID + + Returns: + result (dictionary): Keyboard options + """ + result = {} + if "packageID" in info and "keyboardID" in info: + child_schema = get_child_schema(info) + list_options = child_schema.get_strv("options") + result = dict(option.split("=") for option in list_options) + return result + + +def set_option(info, options): + """ + Store the Keyman keyboard options in DConf as a list of strings + + Args: + info: dictionary + packageID (str): package ID + keyboardID (str): keyboard ID + options: dictionary + key and values to store + """ + if "packageID" in info and "keyboardID" in info and options: + # Convert dictionary of options into a list of comma-separated option strings + list_options = [] + for key, value in options.items(): + list_options.append(key+"="+value) + + child_schema = get_child_schema(info) + child_schema.set_strv("options", list_options) + + +if __name__ == '__main__': + info = {"packageID": "invalid", "keyboardID": "invalid"} + options = get_option(info) + assert(options == {}) + + info = {"packageID": "sil_cipher_music", "keyboardID": "sil_cipher_music"} + options = get_option(info) + + options["set_nfc"] = "1" + set_option(info, options) diff --git a/linux/keyman-config/keyman_config/list_installed_kmp.py b/linux/keyman-config/keyman_config/list_installed_kmp.py index a024afa738..dd0cb1aa9e 100755 --- a/linux/keyman-config/keyman_config/list_installed_kmp.py +++ b/linux/keyman-config/keyman_config/list_installed_kmp.py @@ -15,6 +15,32 @@ class InstallArea(GObject.GEnum): IA_USER = 3 IA_UNKNOWN = 99 + +def get_install_area_path(area): + """ + Get the path of an install area. + + Args: + area (InstallArea): install area to check + InstallArea.IA_USER: ~/.local/share/keyman + InstallArea.IA_SHARED: /usr/local/share/keyman + InstallArea.IA_OS: /usr/share/keyman + InstallArea.IA_UNKNOWN: /usr/share/keyman + + Returns: + string: path of the install area + """ + check_path = "/usr/share/keyman" + if area == InstallArea.IA_USER: + check_path = user_keyman_dir() + elif area == InstallArea.IA_SHARED: + check_path = "/usr/local/share/keyman" + elif area == InstallArea.IA_OS: + check_path = "/usr/share/keyman" + + return check_path + + def get_installed_kmp(area): """ Get list of installed kmp in an install area. @@ -35,13 +61,7 @@ def get_installed_kmp(area): path (str): base path where keyboard is installed description (str): Keyboard description """ - check_paths = [] - if area == InstallArea.IA_USER: - check_paths = [ user_keyman_dir() ] - elif area == InstallArea.IA_SHARED: - check_paths = [ "/usr/local/share/keyman" ] - elif area == InstallArea.IA_OS: - check_paths = [ "/usr/share/keyman" ] + check_paths = [ get_install_area_path(area) ] return get_installed_kmp_paths(check_paths) @@ -57,12 +77,15 @@ def get_installed_kmp_paths(check_paths): list: Installed kmp dict: Keyboard packageID (str): kmp ID + keyboardID (str): Keyboard ID name (str): Keyboard name kmpname (str): Keyboard name in local version (str): Keyboard version kmpversion (str): - path (str): base path of area where kmp is installed + areapath (str): base path of area where kmp is installed description (str): Keyboard description + has_kbjson (bool): Keyboard is in the json file + has_kboptions (bool): Keyboard uses options.htm form """ installed_keyboards = {} for keymanpath in check_paths: @@ -96,7 +119,15 @@ def get_installed_kmp_paths(check_paths): version = md_version name = md_name - installed_keyboards[o] = { "packageID" : o, "keyboardID" : keyboardID, "name" : name, "kmpname" : md_name, "version" : version, "kmpversion" : md_version, "areapath" : keymanpath, "description" : description, "has_kbjson" : has_kbjson} + has_kboptions = False + if files: + for fileinfo in files: + if fileinfo['name'] == "options.htm": + has_kboptions = True + break + installed_keyboards[o] = {"packageID" : o, "keyboardID" : keyboardID, "name" : name, "kmpname" : md_name, + "version" : version, "kmpversion" : md_version, "areapath" : keymanpath, + "description" : description, "has_kbjson" : has_kbjson, "has_kboptions" : has_kboptions} return installed_keyboards @@ -117,7 +148,7 @@ def get_kmp_version(packageID): os_kmp = get_installed_kmp(InstallArea.IA_OS) if packageID in os_kmp: - version = os_kmp[keyboardid]['version'] + version = os_kmp[packageID]['version'] if packageID in shared_kmp: shared_version = shared_kmp[packageID]['version'] @@ -148,7 +179,7 @@ def get_kmp_version_user(packageID): str: kmp version if kmp ID is installed None: if not found """ - user_kmp = get_installed_kmp_user() + user_kmp = get_installed_kmp(InstallArea.IA_USER) if packageID in user_kmp: return user_kmp[packageID]['version'] else: diff --git a/linux/keyman-config/keyman_config/options.py b/linux/keyman-config/keyman_config/options.py new file mode 100644 index 0000000000..a2273a195a --- /dev/null +++ b/linux/keyman-config/keyman_config/options.py @@ -0,0 +1,86 @@ +#!/usr/bin/python3 + +import ast +import gi +import logging +import webbrowser +import urllib.parse +gi.require_version('Gtk', '3.0') +gi.require_version('WebKit2', '4.0') +from gi.repository import Gtk, WebKit2 +from urllib.parse import parse_qsl, urlencode +from keyman_config.accelerators import bind_accelerator, init_accel +from keyman_config.dconf_util import get_option, set_option + + +class OptionsView(Gtk.Window): + + def __init__(self, info): + self.accelerators = None + self.optionurl = info["optionurl"] + self.packageID = info["packageID"] + self.keyboardID = info["keyboardID"] + kbtitle = self.packageID + " Settings" + Gtk.Window.__init__(self, title=kbtitle) + init_accel(self) + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + + # Keyman Desktop gets current option settings from the registry. + # Similarly, we'll read Keyman options from DConf and update optionurl + info = {"packageID": self.packageID, "keyboardID": self.keyboardID} + self.options = get_option(info) + params = "" + + if self.options: + # Convert dictionary to query + params = "?" + urlencode(self.options) + + s = Gtk.ScrolledWindow() + self.webview = WebKit2.WebView() + self.webview.connect("decide-policy", self.doc_policy) + self.webview.load_uri(self.optionurl + params) + s.add(self.webview) + vbox.pack_start(s, True, True, 0) + + hbox = Gtk.Box(spacing=12) + vbox.pack_start(hbox, False, False, 6) + + self.add(vbox) + + def doc_policy(self, web_view, decision, decision_type): + logging.info("Checking policy") + logging.debug("received policy decision request of type: {0}".format(decision_type.value_name)) + if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION or \ + decision_type == WebKit2.PolicyDecisionType.NEW_WINDOW_ACTION: + nav_action = decision.get_navigation_action() + request = nav_action.get_request() + uri = request.get_uri() + logging.debug("nav request is for uri %s", uri) + parsed = urllib.parse.urlparse(uri) + if parsed.scheme == "keyman": + logging.debug("using keyman scheme") + if parsed.path == "cancel": + pass + elif parsed.path == "ok": + logging.debug("submit options form") + # Parse the response for the option_key value and merge the updates + if parsed.query: + updated_options = parse_qsl(parsed.query, keep_blank_values=True) + self.options.update(updated_options) + self.process_option() + + self.close() + return True + + if not "options.htm" in uri: + logging.debug("opening uri %s in webbrowser") + webbrowser.open(uri) + decision.ignore() + return True + return False + + def process_option(self): + # Write the Keyman options to DConf + info = { "packageID": self.packageID, "keyboardID": self.keyboardID} + set_option(info, self.options) diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index dc78e74916..e1eb7f5d87 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -9,8 +9,9 @@ gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') from gi.repository import Gtk, Gdk, GdkPixbuf, GObject -from keyman_config.list_installed_kmp import get_installed_kmp, InstallArea +from keyman_config.list_installed_kmp import get_install_area_path, get_installed_kmp, InstallArea from keyman_config.welcome import WelcomeView +from keyman_config.options import OptionsView from keyman_config.keyboard_details import KeyboardDetailsView from keyman_config.downloadkeyboard import DownloadKmpWindow from keyman_config.install_window import InstallKmpWindow, find_keyman_image @@ -68,7 +69,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): # window is split left/right hbox # right is ButtonBox # possibly 2 ButtonBox in a vbox -# top one with _Remove, _About, ?_Welcome? or ?Read_Me? +# top one with _Remove, _About, ?_Welcome? or ?Read_Me?, _Options # bottom one with _Download, _Install, Re_fresh, _Close # left is GtkTreeView - does it need to be inside anything else apart from the hbox? # with liststore which defines columns @@ -91,7 +92,8 @@ class ViewInstalledWindow(ViewInstalledWindowBase): str, # version str, # packageID int, # enum InstallArea (KmpArea is GObject version) - str) # path to welcome file if it exists or None + str, # path to welcome file if it exists or None + str) # path to options file if it exists or None # add installed keyboards to the the store e.g. # treeiter = store.append([GdkPixbuf.Pixbuf.new_from_file_at_size("/usr/local/share/keyman/libtralo/libtralo.ico.png", 16, 16), \ @@ -136,6 +138,12 @@ class ViewInstalledWindow(ViewInstalledWindowBase): self.help_button.connect("clicked", self.on_help_clicked) bbox_top.add(self.help_button) + self.options_button = Gtk.Button.new_with_mnemonic("_Options") + self.options_button.set_tooltip_text("Settings for keyboard") + self.options_button.connect("clicked", self.on_options_clicked) + self.options_button.set_sensitive(False) + bbox_top.add(self.options_button) + vbox.pack_start(bbox_top, False, False, 12) @@ -176,16 +184,19 @@ class ViewInstalledWindow(ViewInstalledWindowBase): if install_area == InstallArea.IA_USER: welcome_file = os.path.join(user_keyboard_dir(kmpdata['packageID']), "welcome.htm") + options_file = os.path.join(user_keyboard_dir(kmpdata['packageID']), "options.htm") icofile = os.path.join(user_keyboard_dir(kmpdata['packageID']), kmpdata['packageID'] + bmppng) if not os.path.isfile(icofile): icofile = os.path.join(user_keyboard_dir(kmpdata['packageID']), kmpdata['keyboardID'] + bmppng) elif install_area == InstallArea.IA_SHARED: welcome_file = os.path.join("/usr/local/share/keyman", kmpdata['packageID'], "welcome.htm") + options_file = os.path.join("/usr/local/share/keyman", kmpdata['packageID'], "options.htm") icofile = os.path.join("/usr/local/share/keyman", kmpdata['packageID'], kmpdata['packageID'] + bmppng) if not os.path.isfile(icofile): icofile = os.path.join("/usr/local/share/keyman", kmpdata['packageID'], kmpdata['keyboardID'] + bmppng) else: welcome_file = os.path.join("/usr/share/keyman", kmpdata['packageID'], "welcome.htm") + options_file = os.path.join("/usr/share/keyman", kmpdata['packageID'], "options.htm") icofile = os.path.join("/usr/share/keyman", kmpdata['packageID'], kmpdata['packageID'] + bmppng) if not os.path.isfile(icofile): icofile = os.path.join("/usr/share/keyman", kmpdata['packageID'], kmpdata['keyboardID'] + bmppng) @@ -194,13 +205,15 @@ class ViewInstalledWindow(ViewInstalledWindowBase): if not os.path.isfile(welcome_file): welcome_file = None - + if not os.path.isfile(options_file): + options_file = None treeiter = store.append([GdkPixbuf.Pixbuf.new_from_file_at_size(icofile, 16, 16), \ kmpdata['name'], \ kmpdata['version'], \ kmpdata['packageID'], \ install_area, \ - welcome_file]) + welcome_file, \ + options_file]) def refresh_installed_kmp(self): logging.debug("Refreshing listview") @@ -225,13 +238,13 @@ class ViewInstalledWindow(ViewInstalledWindowBase): self.incomplete_kmp.append(kmpdata) self.addlistitems(os_kmp, self.store, InstallArea.IA_OS) - def on_tree_selection_changed(self, selection): model, treeiter = selection.get_selected() if treeiter is not None: self.uninstall_button.set_tooltip_text("Uninstall keyboard package " + model[treeiter][1]) self.help_button.set_tooltip_text("Help for keyboard package " + model[treeiter][1]) self.about_button.set_tooltip_text("About keyboard package " + model[treeiter][1]) + self.options_button.set_tooltip_text("Settings for keyboard package " + model[treeiter][1]) logging.debug("You selected %s version %s", model[treeiter][1], model[treeiter][2]) if model[treeiter][4] == InstallArea.IA_USER: logging.debug("Enabling uninstall button for %s in %s", model[treeiter][3], model[treeiter][4]) @@ -239,10 +252,16 @@ class ViewInstalledWindow(ViewInstalledWindowBase): else: self.uninstall_button.set_sensitive(False) logging.debug("Disabling uninstall button for %s in %s ", model[treeiter][3], model[treeiter][4]) + # welcome file if it exists if model[treeiter][5]: self.help_button.set_sensitive(True) else: self.help_button.set_sensitive(False) + # options file if it exists + if model[treeiter][6]: + self.options_button.set_sensitive(True) + else: + self.options_button.set_sensitive(False) def on_help_clicked(self, button): model, treeiter = self.tree.get_selection().get_selected() @@ -258,6 +277,22 @@ class ViewInstalledWindow(ViewInstalledWindowBase): else: logging.info("welcome.htm not available") + def on_options_clicked(self, button): + model, treeiter = self.tree.get_selection().get_selected() + if treeiter is not None: + logging.info("Open options.htm for %s if available", model[treeiter][1]) + options_file = model[treeiter][6] + if options_file and os.path.isfile(options_file): + uri_path = pathlib.Path(options_file).as_uri() + logging.info("opening" + uri_path) + # TODO: Determine keyboardID + info = { "optionurl": uri_path, "packageID": model[treeiter][3], "keyboardID": model[treeiter][3] } + w = OptionsView(info) + w.resize(800, 600) + w.show_all() + else: + logging.info("options.htm not available") + def on_uninstall_clicked(self, button): model, treeiter = self.tree.get_selection().get_selected() if treeiter is not None: @@ -281,12 +316,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): model, treeiter = self.tree.get_selection().get_selected() if treeiter is not None: logging.info("Show keyboard details of " + model[treeiter][1]) - if model[treeiter][4] == InstallArea.IA_USER: - areapath = user_keyman_dir() - elif model[treeiter][4] == InstallArea.IA_SHARED: - areapath = "/usr/local/share/keyman" - else: - areapath = "/usr/share/keyman" + areapath = get_install_area_path(model[treeiter][4]) kmp = { "name" : model[treeiter][1], "version" : model[treeiter][2], "packageID" : model[treeiter][3], "areapath" : areapath} w = KeyboardDetailsView(kmp) w.resize(800, 450) diff --git a/linux/scripts/install.sh b/linux/scripts/install.sh index 2f8f3346a0..d9db4f4ec0 100755 --- a/linux/scripts/install.sh +++ b/linux/scripts/install.sh @@ -78,6 +78,9 @@ if [[ "${SUDOINSTALL}" == "yes" ]]; then echo "keyman-config must be built before it is installed. Run 'make configure' if needed then 'make'" exit 1 fi + echo "doing sudo glib-compile-schemas for keyman-config" + cp com.keyman.gschema.xml /usr/share/glib-2.0/schemas/ + glib-compile-schemas /usr/share/glib-2.0/schemas/ echo "doing sudo install of keyman-config" make install elif [[ "${SUDOINSTALL}" == "uninstall" ]]; then From 12a6d5e95cea6c1e79d067f4f2a1014cf7dbf9f8 Mon Sep 17 00:00:00 2001 From: Darcy Date: Fri, 31 Jan 2020 14:22:14 +0700 Subject: [PATCH 2/4] feat(linux): Start refactoring the dconf utilities --- linux/ibus-keyman/src/engine.c | 64 ++------------- linux/ibus-keyman/src/engine.h | 8 -- linux/ibus-keyman/src/keymanutil.c | 127 ++++++++++++++++++++++++++++- linux/ibus-keyman/src/keymanutil.h | 28 ++++++- 4 files changed, 160 insertions(+), 67 deletions(-) diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 9eff9dfdd5..4af84438cd 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -27,7 +27,6 @@ #include #include -#include #include #include "keymanutil.h" @@ -120,10 +119,6 @@ static void ibus_keyman_engine_commit_string (IBusKeymanEngine *kmfl, const gchar *string); -static GQueue* ibus_keyman_engine_keyboard_options - (gchar *package_id, - gchar *keyboard_id); - static IBusEngineClass *parent_class = NULL; GType @@ -334,7 +329,7 @@ ibus_keyman_engine_constructor (GType type, // Retrieve keyboard options from DConf // TODO: May need unique packageID and keyboard ID g_message("Loading options for kb_name: %s", keyman->kb_name); - GQueue *queue_options = ibus_keyman_engine_keyboard_options(keyman->kb_name, keyman->kb_name); + GQueue *queue_options = keyman_get_options_queue_fromdconf(keyman->kb_name, keyman->kb_name); int num_options = g_queue_get_length(queue_options); // Allocate enough options for: 3 environments plus num_options plus 1 pad struct of 0's @@ -832,7 +827,10 @@ ibus_keyman_engine_process_key_event (IBusEngine *engine, { g_message("Saving keyboard option to DConf"); // Load the current keyboard options from DConf - GQueue *queue_options = ibus_keyman_engine_keyboard_options(keyman->kb_name, keyman->kb_name); + keyman_put_options_todconf(keyman->kb_name, keyman->kb_name, action_items[i].option->key, action_items[i].option->value); + + /* + GQueue *queue_options = keyman_get_options_queue_fromdconf(keyman->kb_name, keyman->kb_name); int num_options = g_queue_get_length(queue_options); gchar *option_kvp = g_strdup_printf("'%s=%s'", action_items[i].option->key, action_items[i].option->value); gboolean kvp_added = FALSE; @@ -840,7 +838,7 @@ ibus_keyman_engine_process_key_event (IBusEngine *engine, // Update with the keyboard option to save for(int i=0; iproperty_activate (engine, prop_name, prop_state); } -// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item -// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] -// -// Parameters: -// package_id (gchar *): Package ID -// keyboard_id (gchar *): Keyboard ID -// -// Return a newly allocated GQueue; free with g_queue_free_full() -static GQueue* -ibus_keyman_engine_keyboard_options(gchar *package_id, - gchar *keyboard_id) -{ - g_message("ibus_keyman_engine_keyboard_options"); - GQueue *queue_options = g_queue_new(); - // Obtain keyboard options from DConf - gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, package_id, keyboard_id); - GSettings *child_settings = g_settings_new_with_path(KEYMAN_CHILD_DCONF_NAME, path); - gchar **options = NULL; - if (child_settings != NULL) - { - options = g_settings_get_strv(child_settings, KEYMAN_DCONF_OPTIONS_KEY); - } - - // Parse options into queue_options - if (*options != NULL) - { - int index = 0; - while (options[index] != NULL) - { - gchar **option_tokens = g_strsplit(options[index], "=", 2); - if (option_tokens != NULL && option_tokens[0] != NULL && option_tokens[1] != NULL) - { - g_message("Keyboard Option [%d], %s=%s", index, option_tokens[0], option_tokens[1]); - km_kbp_option_item *opt = g_new0(km_kbp_option_item, 1); - opt[0].scope = KM_KBP_OPT_KEYBOARD; - km_kbp_cp *ocp = g_utf8_to_utf16(option_tokens[0], -1, NULL, NULL, NULL); - opt[0].key = ocp; - ocp = g_utf8_to_utf16 (option_tokens[1], -1, NULL, NULL, NULL); - opt[0].value = ocp; - g_queue_push_tail(queue_options, opt); - } - index++; - } - } - - g_free(path); - - return queue_options; -} diff --git a/linux/ibus-keyman/src/engine.h b/linux/ibus-keyman/src/engine.h index 308521820c..88d1b2a6ff 100644 --- a/linux/ibus-keyman/src/engine.h +++ b/linux/ibus-keyman/src/engine.h @@ -26,14 +26,6 @@ #include -#define KEYMAN_DCONF_NAME "com.keyman.options" -#define KEYMAN_CHILD_DCONF_NAME "com.keyman.options.child" -#define KEYMAN_DCONF_PATH "/desktop/ibus/keyman/options/" -#define KEYMAN_DCONF_OPTIONS_KEY "options" - -// Number of default Keyboard processor environment options for: "platform", "baseLayout", and "baseLayoutAlt" -#define KEYMAN_ENVIRONMENT_OPTIONS 3 - #define IBUS_TYPE_KEYMAN_ENGINE \ (ibus_keyman_engine_get_type ()) diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 9f2734bc3d..44230b14a2 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -55,7 +55,7 @@ #include #include #include - +#include #include "keymanutil.h" #include "kmpdetails.h" @@ -300,6 +300,131 @@ ibus_keyman_get_component (void) return component; } + +// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item +// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] +// +// Parameters: +// package_id (gchar *): Package ID +// keyboard_id (gchar *): Keyboard ID +// +// Returns a newly allocated ghcar**; +gchar** +keyman_get_options_fromdconf(gchar *package_id, + gchar *keyboard_id) +{ + g_message("keyman_get_options_fromdconf"); + + // Obtain keyboard options from DConf + gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, package_id, keyboard_id); + GSettings *child_settings = g_settings_new_with_path(KEYMAN_CHILD_DCONF_NAME, path); + gchar **options = NULL; + if (child_settings != NULL) + { + options = g_settings_get_strv(child_settings, KEYMAN_DCONF_OPTIONS_KEY); + } + + g_free(path); + + return options; +} + +// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item +// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] +// +// Parameters: +// package_id (gchar *): Package ID +// keyboard_id (gchar *): Keyboard ID +// +// Return a newly allocated GQueue; free with g_queue_free_full() +GQueue* +keyman_get_options_queue_fromdconf(gchar *package_id, + gchar *keyboard_id) +{ + g_message("keyman_get_options_queue_fromdconf"); + GQueue *queue_options = g_queue_new(); + + // Obtain keyboard options from DConf + gchar **options = keyman_get_options_fromdconf(package_id, keyboard_id); + + // Parse options into queue_options + if (*options != NULL) + { + int index = 0; + while (options[index] != NULL) + { + gchar **option_tokens = g_strsplit(options[index], "=", 2); + if (option_tokens != NULL && option_tokens[0] != NULL && option_tokens[1] != NULL) + { + g_message("Keyboard Option [%d], %s=%s", index, option_tokens[0], option_tokens[1]); + km_kbp_option_item *opt = g_new0(km_kbp_option_item, 1); + opt[0].scope = KM_KBP_OPT_KEYBOARD; + km_kbp_cp *ocp = g_utf8_to_utf16(option_tokens[0], -1, NULL, NULL, NULL); + opt[0].key = ocp; + ocp = g_utf8_to_utf16 (option_tokens[1], -1, NULL, NULL, NULL); + opt[0].value = ocp; + g_queue_push_tail(queue_options, opt); + } + index++; + } + } + + return queue_options; +} + +void +keyman_put_options_todconf(gchar *package_id, + gchar *keyboard_id, + gchar *option_key, + gchar *option_value) +{ + g_message("keyman_put_options_todconf"); + if (package_id == NULL || keyboard_id == NULL || option_key == NULL || option_value == NULL) + { + return; + } + + // Obtain keyboard options from DConf + gchar **options = keyman_get_options_fromdconf(package_id, keyboard_id); + gchar *needle = g_strdup_printf("%s=", option_key); + gchar *kvp = g_strdup_printf("%s=%s", option_key, option_value); + + // See if option_key already exists + if (*options != NULL) + { + int index = 0; + gboolean option_updated = FALSE; + while (options[index] != NULL) + { + if (g_strrstr(options[index], needle) != NULL) + { + options[index] = kvp; + option_updated = TRUE; + break; + } + index++; + } + + if (!option_updated) + { + // Resize to add new option and null-terminate + options = g_realloc(options, strlen(kvp) * sizeof(gchar) + sizeof(gchar)); + options[index] = kvp; + options[index+1] = '\0'; + } + } + + // Write to DConf + gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, package_id, keyboard_id); + GSettings *child_settings = g_settings_new_with_path(KEYMAN_CHILD_DCONF_NAME, path); + if (child_settings != NULL) + { + g_message("writing keyboard options to DConf"); + g_settings_set_strv(child_settings, KEYMAN_DCONF_OPTIONS_KEY, options); + } +} + + #ifdef DEBUG #include diff --git a/linux/ibus-keyman/src/keymanutil.h b/linux/ibus-keyman/src/keymanutil.h index d2e32faa54..66f1226c10 100644 --- a/linux/ibus-keyman/src/keymanutil.h +++ b/linux/ibus-keyman/src/keymanutil.h @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018 SIL International + * Copyright (C) 2018, 2020 SIL International * * kmflutil is dual licensed under the MIT or GPL licenses as described below. * @@ -53,9 +53,35 @@ #define __KEYMANUTIL_H__ #include +#include + #include +// Number of default Keyboard processor environment options for: "platform", "baseLayout", and "baseLayoutAlt" +#define KEYMAN_ENVIRONMENT_OPTIONS 3 + +// Path information for Keyman keyboard options in DConf +#define KEYMAN_DCONF_NAME "com.keyman.options" +#define KEYMAN_CHILD_DCONF_NAME "com.keyman.options.child" +#define KEYMAN_DCONF_PATH "/desktop/ibus/keyman/options/" +#define KEYMAN_DCONF_OPTIONS_KEY "options" + void ibus_keyman_init (void); GList *ibus_keyman_list_engines (void); IBusComponent *ibus_keyman_get_component (void); + +gchar** keyman_get_options_fromdconf + (gchar *package_id, + gchar *keyboard_id); + +GQueue* keyman_get_options_queue_fromdconf + (gchar *package_id, + gchar *keyboard_id); + +void keyman_put_options_todconf + (gchar *package_id, + gchar *keyboard_id, + gchar *option_key, + gchar *option_value); + #endif From a148463dca3f9f4505633f1065fb5776759354de Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Sat, 1 Feb 2020 17:04:09 +0700 Subject: [PATCH 3/4] feat(linux): Save new options into DConf --- linux/ibus-keyman/src/engine.c | 29 ++------------------ linux/ibus-keyman/src/keymanutil.c | 43 +++++++++++++++++++++++------- linux/ibus-keyman/src/keymanutil.h | 25 +++++++++++++++++ 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 4af84438cd..080c3edbd9 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -827,33 +827,8 @@ ibus_keyman_engine_process_key_event (IBusEngine *engine, { g_message("Saving keyboard option to DConf"); // Load the current keyboard options from DConf - keyman_put_options_todconf(keyman->kb_name, keyman->kb_name, action_items[i].option->key, action_items[i].option->value); - - /* - GQueue *queue_options = keyman_get_options_queue_fromdconf(keyman->kb_name, keyman->kb_name); - int num_options = g_queue_get_length(queue_options); - gchar *option_kvp = g_strdup_printf("'%s=%s'", action_items[i].option->key, action_items[i].option->value); - gboolean kvp_added = FALSE; - - // Update with the keyboard option to save - for(int i=0; ikb_name, keyman->kb_name, + action_items[i].option->key, action_items[i].option->value); } } break; diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 44230b14a2..75a7344fed 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -305,13 +305,13 @@ ibus_keyman_get_component (void) // DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] // // Parameters: -// package_id (gchar *): Package ID +// package_id (gchar *): Package ID // keyboard_id (gchar *): Keyboard ID // -// Returns a newly allocated ghcar**; +// Returns a newly allocated gchar**; free with g_strfreev() gchar** keyman_get_options_fromdconf(gchar *package_id, - gchar *keyboard_id) + gchar *keyboard_id) { g_message("keyman_get_options_fromdconf"); @@ -324,6 +324,7 @@ keyman_get_options_fromdconf(gchar *package_id, options = g_settings_get_strv(child_settings, KEYMAN_DCONF_OPTIONS_KEY); } + g_object_unref(G_OBJECT(child_settings)); g_free(path); return options; @@ -333,13 +334,13 @@ keyman_get_options_fromdconf(gchar *package_id, // DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] // // Parameters: -// package_id (gchar *): Package ID +// package_id (gchar *): Package ID // keyboard_id (gchar *): Keyboard ID // // Return a newly allocated GQueue; free with g_queue_free_full() GQueue* keyman_get_options_queue_fromdconf(gchar *package_id, - gchar *keyboard_id) + gchar *keyboard_id) { g_message("keyman_get_options_queue_fromdconf"); GQueue *queue_options = g_queue_new(); @@ -365,15 +366,24 @@ keyman_get_options_queue_fromdconf(gchar *package_id, opt[0].value = ocp; g_queue_push_tail(queue_options, opt); } - index++; + index++; } } return queue_options; } +// Write new keyboard option to DConf. +// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] +// If the option key already exists, the value is updated. Otherwise a new string 'option_key=option_value' is appended. +// +// Parameters: +// package_id (gchar *): Package ID +// keyboard_id (gchar *): Keyboard ID +// option_key (gchar *): Key for the new option +// option_value (gchar *): Value of the new option void -keyman_put_options_todconf(gchar *package_id, +keyman_put_options_todconf(gchar *package_id, gchar *keyboard_id, gchar *option_key, gchar *option_value) @@ -389,13 +399,13 @@ keyman_put_options_todconf(gchar *package_id, gchar *needle = g_strdup_printf("%s=", option_key); gchar *kvp = g_strdup_printf("%s=%s", option_key, option_value); - // See if option_key already exists if (*options != NULL) { int index = 0; gboolean option_updated = FALSE; while (options[index] != NULL) { + // If option_key already exists, update value with option_value if (g_strrstr(options[index], needle) != NULL) { options[index] = kvp; @@ -408,11 +418,18 @@ keyman_put_options_todconf(gchar *package_id, if (!option_updated) { // Resize to add new option and null-terminate - options = g_realloc(options, strlen(kvp) * sizeof(gchar) + sizeof(gchar)); + options = g_realloc(options, strlen(kvp) + 1); options[index] = kvp; - options[index+1] = '\0'; + options[index+1] = NULL; } } + else + { + // If options don't exist, allocate space for new option and null-terminate + options = g_malloc(strlen(kvp) + 1); + options[0] = kvp; + options[1] = NULL; + } // Write to DConf gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, package_id, keyboard_id); @@ -422,6 +439,12 @@ keyman_put_options_todconf(gchar *package_id, g_message("writing keyboard options to DConf"); g_settings_set_strv(child_settings, KEYMAN_DCONF_OPTIONS_KEY, options); } + + g_object_unref(G_OBJECT(child_settings)); + g_free(path); + g_free(kvp); + g_free(needle); + g_free(options); } diff --git a/linux/ibus-keyman/src/keymanutil.h b/linux/ibus-keyman/src/keymanutil.h index 66f1226c10..9f95a9fecd 100644 --- a/linux/ibus-keyman/src/keymanutil.h +++ b/linux/ibus-keyman/src/keymanutil.h @@ -70,14 +70,39 @@ void ibus_keyman_init (void); GList *ibus_keyman_list_engines (void); IBusComponent *ibus_keyman_get_component (void); +// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item +// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] +// +// Parameters: +// package_id (gchar *): Package ID +// keyboard_id (gchar *): Keyboard ID +// +// Returns a newly allocated gchar**; free with g_strfreev() gchar** keyman_get_options_fromdconf (gchar *package_id, gchar *keyboard_id); +// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item +// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] +// +// Parameters: +// package_id (gchar *): Package ID +// keyboard_id (gchar *): Keyboard ID +// +// Return a newly allocated GQueue; free with g_queue_free_full() GQueue* keyman_get_options_queue_fromdconf (gchar *package_id, gchar *keyboard_id); +// Write new keyboard option to DConf. +// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] +// If the option key already exists, the value is updated. Otherwise a new string 'option_key=option_value' is appended. +// +// Parameters: +// package_id (gchar *): Package ID +// keyboard_id (gchar *): Keyboard ID +// option_key (gchar *): Key for the new option +// option_value (gchar *): Value of the new option void keyman_put_options_todconf (gchar *package_id, gchar *keyboard_id, From c7270b6682a1f1418c7aa17b661507bff5cb0c74 Mon Sep 17 00:00:00 2001 From: Darcy Date: Wed, 5 Feb 2020 15:24:59 +0700 Subject: [PATCH 4/4] chore(linux): update history.md --- linux/history.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linux/history.md b/linux/history.md index 56b83d873e..5b971bbc2c 100644 --- a/linux/history.md +++ b/linux/history.md @@ -13,6 +13,9 @@ * Work on Linux packaging (#2263, #2290, #2320, #2321, #2325, #2474, #2515, #2551) ## 2019-09-19 12.0.18 beta +* New Feature: + * Add ability to set keyboard options from options.htm dialog (#2566) + * Bug Fix: * Extract icon from kmx instead of downloading it (#2075)