feat(linux): Install keyboard on Gnome

This change adds the keyboard to the Gnome input sources after
installing the .kmx file, and removes it on uninstall.

This implements #2728.
This commit is contained in:
Eberhard Beilharz 2020-06-22 14:28:39 +02:00
parent 123dcfe235
commit 071df63ac7
No known key found for this signature in database
GPG key ID: 64A39A9E98B53105
11 changed files with 412 additions and 38 deletions

View file

@ -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

View file

@ -6,6 +6,9 @@ import urllib.parse
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
@ -13,9 +16,6 @@ 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):

View file

@ -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

View file

@ -4,9 +4,8 @@ import gi
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():

View file

@ -6,17 +6,14 @@ import os.path
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
@ -184,6 +181,7 @@ 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)
@ -191,8 +189,10 @@ def install_kmp_user(inputfile, online=False):
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:
@ -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)
@ -250,17 +250,20 @@ 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()
@ -268,6 +271,18 @@ def install_keyboards_to_ibus(keyboards, packageDir):
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

View file

@ -7,6 +7,7 @@ from shutil import rmtree
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):
@ -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)
@ -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
@ -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)

View file

@ -4,11 +4,10 @@ import gi
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

View file

@ -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()

View file

@ -1,8 +1,9 @@
#!/usr/bin/python3
import unittest
from unittest.mock import create_autospec, patch
from unittest.mock import patch
from keyman_config.ibus_util import get_ibus_bus, install_to_ibus, uninstall_from_ibus
from keyman_config.ibus_util import get_ibus_bus, install_to_ibus, uninstall_from_ibus, IBus, Gio
@patch('keyman_config.ibus_util.IBus.Bus')
class IbusUtilTests(unittest.TestCase):
@ -41,7 +42,7 @@ class IbusUtilTests(unittest.TestCase):
def test_installToIbus_AlreadyInstalled(self, MockSettingsClass, MockIbusBusClass):
# Setup
mock_settingsInstance = MockSettingsClass.return_value
mock_settingsInstance.get_strv.return_value = [ 'k1', 'k2', 'k3' ]
mock_settingsInstance.get_strv.return_value = ['k1', 'k2', 'k3']
mock_ibusBusInstance = MockIbusBusClass.return_value
# Execute
install_to_ibus(mock_ibusBusInstance, 'k3')
@ -93,5 +94,6 @@ class IbusUtilTests(unittest.TestCase):
mock_ibusBusInstance.preload_engines.assert_called_once_with(
['k1', 'k2', 'k3'])
if __name__ == '__main__':
unittest.main()

View file

@ -1,8 +1,9 @@
#!/usr/bin/python3
import unittest
from unittest.mock import create_autospec, patch, ANY
from unittest.mock import patch, ANY
from keyman_config.install_kmp import install_keyboards_to_ibus, install_keyboards_to_gnome
from keyman_config.install_kmp import install_keyboards_to_ibus
class InstallKmpTests(unittest.TestCase):
@ -16,6 +17,9 @@ class InstallKmpTests(unittest.TestCase):
patcher3 = patch('keyman_config.install_kmp.get_ibus_bus')
self.mockGetIbusBus = patcher3.start()
self.addCleanup(patcher3.stop)
patcher4 = patch('keyman_config.install_kmp.GnomeKeyboardsUtil')
self.mockGnomeKeyboardsUtilClass = patcher4.start()
self.addCleanup(patcher4.stop)
def test_InstallKeyboardsToIbus_NoIbus(self):
# Setup
@ -72,5 +76,54 @@ class InstallKmpTests(unittest.TestCase):
self.mockRestartIbus.assert_called_once()
bus.destroy.assert_called_once()
def test_InstallKeyboardsToGnome_SingleKbNoLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1'}]
# Execute
install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_MultipleKbsNoLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1'}, {'id': 'foo2'}]
# Execute
install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_SingleKbSingleLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}]
# Execute
install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_SingleKbMultipleLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,112 @@
#!/usr/bin/python3
import unittest
from unittest.mock import patch
from keyman_config.uninstall_kmp import uninstall_keyboards_from_gnome
class UninstallKmpTests(unittest.TestCase):
def setUp(self):
patcher = patch('keyman_config.uninstall_kmp.GnomeKeyboardsUtil')
self.mockGnomeKeyboardsUtilClass = patcher.start()
self.addCleanup(patcher.stop)
def test_UninstallKeyboardsFromGnome_RemoveOneKeyboardFromEmpty(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = []
# Execute
uninstall_keyboards_from_gnome([{'id': 'foo1'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with([])
def test_UninstallKeyboardsFromGnome_RemoveNonExistingKeyboard(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('ibus', 'fooDir/foo2.kmx')]
# Execute
uninstall_keyboards_from_gnome([{'id': 'foo1'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with([
('ibus', 'fooDir/foo2.kmx')])
def test_UninstallKeyboardsFromGnome_RemoveOneKeyboard(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx')]
# Execute
uninstall_keyboards_from_gnome([{'id': 'foo1'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveMultipleKeyboards(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')]
# Execute
uninstall_keyboards_from_gnome([{'id': 'foo1'}, {'id': 'foo2'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveAllKeyboards(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')]
# Execute
uninstall_keyboards_from_gnome([{'id': 'foo1'}, {'id': 'foo2'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with([])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_SingleLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')]
# Execute
uninstall_keyboards_from_gnome([{'id': 'foo1', 'languages': [{'id': 'en'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_MultipleLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'fr:fooDir/foo1.kmx')]
# Execute
uninstall_keyboards_from_gnome(
[{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_OneNotMatchingLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')]
# Execute
uninstall_keyboards_from_gnome(
[{'id': 'foo1', 'languages': [{'id': 'fr'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_RemovesAllMatching(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx'), ('ibus', 'fr:fooDir/foo1.kmx')]
# Execute
uninstall_keyboards_from_gnome(
[{'id': 'foo1', 'languages': [{'id': 'fr'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])