Skip to content

Commit

Permalink
Merge pull request #3278 from keymanapp/feat/issue-2728
Browse files Browse the repository at this point in the history
feat(linux): Install keyboard on Gnome
  • Loading branch information
ermshiperete committed Jun 25, 2020
2 parents cf3c050 + 071df63 commit 7c9b13d
Show file tree
Hide file tree
Showing 11 changed files with 412 additions and 38 deletions.
3 changes: 3 additions & 0 deletions .flake8
@@ -1,2 +1,5 @@
[flake8]
max-line-length = 120
ignore =
E402, # module level import not at top of file
W504, # line break after binary operator
6 changes: 3 additions & 3 deletions linux/keyman-config/keyman_config/downloadkeyboard.py
Expand Up @@ -6,16 +6,16 @@
import webbrowser

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')

from gi.repository import Gtk, WebKit2
from keyman_config.get_kmp import get_download_folder, download_kmp_file
from keyman_config.install_window import InstallKmpWindow
from keyman_config.accelerators import init_accel
from keyman_config.get_info import GetInfo
from keyman_config import __releaseversion__

gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')


class DownloadKmpWindow(Gtk.Dialog):

Expand Down
76 changes: 76 additions & 0 deletions linux/keyman-config/keyman_config/gnome_keyboards_util.py
@@ -0,0 +1,76 @@
#!/usr/bin/python3
import logging
import os
from gi.repository import Gio

from gi.overrides.GLib import Variant


class GnomeKeyboardsUtil():
def __init__(self):
self.input_sources = Gio.Settings.new("org.gnome.desktop.input-sources")

def read_input_sources(self):
sourcesVal = self.input_sources.get_value("sources")
sources = self._convert_variant_to_array(sourcesVal)
return sources

def write_input_sources(self, sources):
sourcesVal = self._convert_array_to_variant(sources)
self.input_sources.set_value("sources", sourcesVal)

def _convert_variant_to_array(self, variant):
if variant is None:
return []

values = []
# a(ss)
nChildren = variant.n_children()
for i in range(nChildren):
# (ss)
val = variant.get_child_value(i)
typeVariant = val.get_child_value(0)
type = typeVariant.get_string()
idVariant = val.get_child_value(1)
id = idVariant.get_string()
values.append((type, id))
return values

def _convert_array_to_variant(self, array):
if len(array) == 0:
return None

children = []
for (type, id) in array:
typeVariant = Variant.new_string(type)
idVariant = Variant.new_string(id)
child = Variant.new_tuple(typeVariant, idVariant)
children.append(child)
return Variant.new_array(None, children)


__is_gnome_shell = None


def is_gnome_shell():
global __is_gnome_shell

if __is_gnome_shell is None:
code = os.system('pidof gnome-shell >/dev/null 2>&1')
__is_gnome_shell = (code == 0)
return __is_gnome_shell


def _reset_gnome_shell():
# used in unit tests
global __is_gnome_shell

__is_gnome_shell = None


def get_keyboard_id(keyboard, packageDir, ignore_language=False):
kmx_file = os.path.join(packageDir, keyboard['id'] + ".kmx")
if not ignore_language and "languages" in keyboard and len(keyboard["languages"]) > 0:
logging.debug(keyboard["languages"][0])
return "%s:%s" % (keyboard["languages"][0]['id'], kmx_file)
return kmx_file
3 changes: 1 addition & 2 deletions linux/keyman-config/keyman_config/ibus_util.py
Expand Up @@ -4,9 +4,8 @@
import logging
import subprocess

from gi.repository import IBus, Gio

gi.require_version('IBus', '1.0')
from gi.repository import IBus, Gio


def get_ibus_bus():
Expand Down
45 changes: 30 additions & 15 deletions linux/keyman-config/keyman_config/install_kmp.py
Expand Up @@ -6,17 +6,14 @@
import zipfile
from os import listdir
from shutil import rmtree
# from ast import literal_eval
from enum import Enum

from gi.repository import Gio

from keyman_config.get_kmp import get_keyboard_data, get_kmp, user_keyboard_dir, user_keyman_dir, user_keyman_font_dir
from keyman_config.kmpmetadata import parseinfdata, parsemetadata, get_metadata, infmetadata_to_json, KMFileTypes
from keyman_config.uninstall_kmp import uninstall_kmp
from keyman_config.get_kmp import get_keyboard_data, user_keyboard_dir, user_keyman_font_dir
from keyman_config.kmpmetadata import get_metadata, KMFileTypes
from keyman_config.convertico import extractico, checkandsaveico
from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml
from keyman_config.ibus_util import install_to_ibus, restart_ibus, get_ibus_bus
from keyman_config.gnome_keyboards_util import GnomeKeyboardsUtil, get_keyboard_id, is_gnome_shell

# TODO userdir install
# special processing for kmn if needed
Expand Down Expand Up @@ -184,15 +181,18 @@ def install_kmp_shared(inputfile, online=False):
message = "install_kmp.py: error: No kmp.json or kmp.inf found in %s" % (inputfile)
raise InstallError(InstallStatus.Abort, message)


def install_kmp_user(inputfile, online=False):
packageID = extract_package_id(inputfile)
packageDir = user_keyboard_dir(packageID)
if not os.path.isdir(packageDir):
os.makedirs(packageDir)

extract_kmp(inputfile, packageDir)
#restart IBus so it knows about the keyboards being installed
restart_ibus()
if not is_gnome_shell():
# restart IBus so it knows about the keyboards being installed
restart_ibus()

info, system, options, keyboards, files = get_metadata(packageDir)

if keyboards:
Expand Down Expand Up @@ -240,7 +240,7 @@ def install_kmp_user(inputfile, online=False):
fpath = os.path.join(packageDir, kb['id'] + '.kmx')
extractico(fpath)

install_keyboards_to_ibus(keyboards, packageDir)
install_keyboards(keyboards, packageDir)
else:
logging.error("install_kmp.py: error: No kmp.json or kmp.inf found in %s", inputfile)
logging.info("Contents of %s:", inputfile)
Expand All @@ -250,24 +250,39 @@ def install_kmp_user(inputfile, online=False):
message = "install_kmp.py: error: No kmp.json or kmp.inf found in %s" % (inputfile)
raise InstallError(InstallStatus.Abort, message)


def install_keyboards(keyboards, packageDir):
if is_gnome_shell():
install_keyboards_to_gnome(keyboards, packageDir)
else:
install_keyboards_to_ibus(keyboards, packageDir)


def install_keyboards_to_ibus(keyboards, packageDir):
bus = get_ibus_bus()
if bus:
# install all kmx for first lang not just packageID
for kb in keyboards:
kmx_file = os.path.join(packageDir, kb['id'] + ".kmx")
if "languages" in kb and len(kb["languages"]) > 0:
logging.debug(kb["languages"][0])
keyboard_id = "%s:%s" % (kb["languages"][0]['id'], kmx_file)
else:
keyboard_id = kmx_file
keyboard_id = get_keyboard_id(kb, packageDir)
install_to_ibus(bus, keyboard_id)
restart_ibus(bus)
bus.destroy()
else:
logging.debug("could not install keyboards to IBus")


def install_keyboards_to_gnome(keyboards, packageDir):
gnomeKeyboardsUtil = GnomeKeyboardsUtil()
sources = gnomeKeyboardsUtil.read_input_sources()

# install all kmx for first lang not just packageID
for kb in keyboards:
keyboard_id = get_keyboard_id(kb, packageDir)
sources.append(('ibus', keyboard_id))

gnomeKeyboardsUtil.write_input_sources(sources)


def install_kmp(inputfile, online=False, sharedarea=False):
"""
Install a kmp file
Expand Down
45 changes: 35 additions & 10 deletions linux/keyman-config/keyman_config/uninstall_kmp.py
Expand Up @@ -7,6 +7,7 @@
from keyman_config.get_kmp import user_keyboard_dir, user_keyman_font_dir
from keyman_config.kmpmetadata import get_metadata
from keyman_config.ibus_util import uninstall_from_ibus, get_ibus_bus, restart_ibus
from keyman_config.gnome_keyboards_util import GnomeKeyboardsUtil, get_keyboard_id, is_gnome_shell


def uninstall_kmp_shared(packageID):
Expand Down Expand Up @@ -51,9 +52,12 @@ def uninstall_kmp_shared(packageID):
# need to uninstall from ibus for all lang and all kmx in kmp
info, system, options, keyboards, files = get_metadata(kbdir)
if keyboards:
uninstall_keyboards_from_ibus(keyboards, kbdir)
if is_gnome_shell():
uninstall_keyboards_from_gnome(keyboards, kbdir)
else:
uninstall_keyboards_from_ibus(keyboards, kbdir)
else:
logging.warning("could not uninstall keyboards from IBus")
logging.warning("could not uninstall keyboards")

rmtree(kbdir)
logging.info("Removed keyman directory: %s", kbdir)
Expand All @@ -65,18 +69,36 @@ def uninstall_keyboards_from_ibus(keyboards, packageDir):
if bus:
# install all kmx for first lang not just packageID
for kb in keyboards:
kmx_file = os.path.join(packageDir, kb['id'] + ".kmx")
if "languages" in kb and len(kb["languages"]) > 0:
logging.debug(kb["languages"][0])
keyboard_id = "%s:%s" % (kb["languages"][0]['id'], kmx_file)
else:
keyboard_id = kmx_file
keyboard_id = get_keyboard_id(kb, packageDir)
uninstall_from_ibus(bus, keyboard_id)
restart_ibus(bus)
else:
logging.warning("could not uninstall keyboards from IBus")


def uninstall_keyboards_from_gnome(keyboards, packageDir):
gnomeKeyboardsUtil = GnomeKeyboardsUtil()
sources = gnomeKeyboardsUtil.read_input_sources()

# uninstall all kmx for all languages
for kb in keyboards:
keyboard_id = get_keyboard_id(kb, packageDir)
tuple = ('ibus', keyboard_id)
if tuple in sources:
sources.remove(tuple)

toRemove = []
match_id = ":%s" % get_keyboard_id(kb, packageDir, True)
for (type, id) in sources:
if type == 'ibus' and id.endswith(match_id):
toRemove.append((type, id))

for val in toRemove:
sources.remove(val)

gnomeKeyboardsUtil.write_input_sources(sources)


def uninstall_kmp_user(packageID):
"""
Uninstall a kmp from ~/.local/share/keyman
Expand All @@ -91,9 +113,12 @@ def uninstall_kmp_user(packageID):
logging.info("Uninstalling local keyboard: %s", packageID)
info, system, options, keyboards, files = get_metadata(kbdir)
if keyboards:
uninstall_keyboards_from_ibus(keyboards, kbdir)
if is_gnome_shell():
uninstall_keyboards_from_gnome(keyboards, kbdir)
else:
uninstall_keyboards_from_ibus(keyboards, kbdir)
else:
logging.warning("could not uninstall keyboards from IBus")
logging.warning("could not uninstall keyboards")
rmtree(kbdir)
logging.info("Removed user keyman directory: %s", kbdir)
fontdir = os.path.join(user_keyman_font_dir(), packageID)
Expand Down
5 changes: 2 additions & 3 deletions linux/keyman-config/keyman_config/welcome.py
Expand Up @@ -4,11 +4,10 @@
import logging
import webbrowser

from gi.repository import Gtk, WebKit2
from keyman_config.accelerators import bind_accelerator, init_accel

gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')
from gi.repository import Gtk, WebKit2
from keyman_config.accelerators import bind_accelerator, init_accel

# NOTE: WebKit2 is not able to load XHTML files nor files with an encoding other
# than ASCII or UTF-8
Expand Down
90 changes: 90 additions & 0 deletions linux/keyman-config/tests/test_gnome_keyboards_util.py
@@ -0,0 +1,90 @@
#!/usr/bin/python3
import unittest
from unittest.mock import patch

from keyman_config.gnome_keyboards_util import GnomeKeyboardsUtil
from keyman_config.gnome_keyboards_util import is_gnome_shell, _reset_gnome_shell


class GnomeKeyboardsUtilTests(unittest.TestCase):
def setUp(self):
_reset_gnome_shell()
patcher = patch('keyman_config.ibus_util.Gio.Settings.new')
self.MockSettingsClass = patcher.start()
self.addCleanup(patcher.stop)

def test_ConvertArrayToVariantToArray_Empty(self):
# Setup
children = []
sut = GnomeKeyboardsUtil()
# Execute
variant = sut._convert_array_to_variant(children)
array = sut._convert_variant_to_array(variant)

# Verify
self.assertEqual(array, children)

def test_ConvertArrayToVariantToArray_OneElement(self):
# Setup
children = [('t1', 'id1')]
sut = GnomeKeyboardsUtil()
# Execute
variant = sut._convert_array_to_variant(children)
array = sut._convert_variant_to_array(variant)

# Verify
self.assertEqual(array, children)

def test_ConvertArrayToVariantToArray_MultipleElements(self):
# Setup
children = [('t1', 'id1'), ('t2', 'id2')]
sut = GnomeKeyboardsUtil()
# Execute
variant = sut._convert_array_to_variant(children)
array = sut._convert_variant_to_array(variant)

# Verify
self.assertEqual(array, children)

@patch.object(GnomeKeyboardsUtil, '_convert_variant_to_array')
def test_ReadInputSources(self, convertVariantToArrayMethod):
# Setup
keyboards = [('xkb', 'en')]
mock_settingsInstance = self.MockSettingsClass.return_value
mock_settingsInstance.get_value.return_value = keyboards
convertVariantToArrayMethod.side_effect = lambda value: value
sut = GnomeKeyboardsUtil()
# Execute
result = sut.read_input_sources()
# Verify
self.assertEqual(result, keyboards)

@patch.object(GnomeKeyboardsUtil, '_convert_array_to_variant')
def test_WriteInputSources(self, convertArrayToVariantMethod):
# Setup
convertArrayToVariantMethod.side_effect = lambda value: value
sut = GnomeKeyboardsUtil()
keyboards = [('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')]
# Execute
sut.write_input_sources(keyboards)
# Verify
self.MockSettingsClass.return_value.set_value.assert_called_once_with(
"sources", keyboards)

@patch('keyman_config.install_kmp.os.system')
def test_IsGnomeShell_RunningGnomeShell(self, mockSystem):
# Setup
mockSystem.return_value = 0
# Execute/Verify
self.assertEqual(is_gnome_shell(), True)

@patch('keyman_config.install_kmp.os.system')
def test_IsGnomeShell_NotRunningGnomeShell(self, mockSystem):
# Setup
mockSystem.return_value = 1
# Execute/Verify
self.assertEqual(is_gnome_shell(), False)


if __name__ == '__main__':
unittest.main()

0 comments on commit 7c9b13d

Please sign in to comment.