feat(linux): keyman:// protocol handler

This change implements the custom keyman:// protocol handler which
will cause the browser to launch km-config when it encounters a
URL using the keyman protocol.

Related to #3271.
This commit is contained in:
Eberhard Beilharz 2020-07-23 14:26:00 +02:00
parent d83cfffe51
commit 8fbdebc9df
No known key found for this signature in database
GPG key ID: 64A39A9E98B53105
6 changed files with 179 additions and 10 deletions

View file

@ -2,3 +2,6 @@
[*]
indent_style = space
indent_size = 4
[*.sharedmimeinfo]
indent_style = tab

View file

@ -6,4 +6,7 @@
<glob pattern="*.kmp"/>
<icon name="application-x-kmp"/>
</mime-type>
<mime-type type="application/keyman">
<comment>Keyman keyboard installation link</comment>
</mime-type>
</mime-info>

View file

@ -3,7 +3,7 @@ Name=Keyman Keyboards
Name[en_GB]=Keyman Keyboards
Comment=Configure Keyman keyboards
Comment[en_GB]=Configure Keyman keyboards
Exec=km-config -i %f
Exec=km-config -i %u
Icon=km-config
Terminal=false
Type=Application
@ -11,4 +11,4 @@ StartupNotify=true
Categories=Settings;
Keywords=keyman;keyboard;input;
X-Desktop-File-Install-Version=0.21
MimeType=application/x-kmp;x-scheme-handler/x-kmp
MimeType=application/x-kmp;x-scheme-handler/x-kmp;application/keyman;x-scheme-handler/keyman

View file

@ -0,0 +1,63 @@
import logging
import os
from urllib.parse import parse_qs, urlparse
from keyman_config import KeymanComUrl, __tier__
from keyman_config.install_window import InstallKmpWindow
from keyman_config.get_kmp import get_download_folder, download_kmp_file
def download_and_install_package(url):
"""
Handle the download and installation of the given package. This can either be a .kmp
file that got downloaded previously, or a keyman:// URL which will be downloaded and
installed.
Args:
url: a .kmp file, a keyman:// URL, or a file:// URL pointing to a .kmp file,
possibly with a bcp47=<language> specified
"""
parsedUrl = urlparse(url)
bcp47 = _extract_bcp47(parsedUrl.query)
if parsedUrl.scheme == 'keyman':
logging.info("downloading " + url)
if not url.startswith('keyman://download/keyboard/'):
logging.error("Don't know what to do with URL " + url)
return
packageId = parsedUrl.path[len('/keyboard/'):]
if not packageId:
logging.error("Missing package id")
return
downloadFile = os.path.join(get_download_folder(), packageId)
downloadUrl = KeymanComUrl + '/go/package/download/' + packageId + '?platform=linux&tier=' + __tier__
packageFile = download_kmp_file(downloadUrl, downloadFile)
elif parsedUrl.scheme == '' or parsedUrl.scheme == 'file':
packageFile = parsedUrl.path
else:
logging.error("Invalid URL: " + url)
return
if not _install_package(packageFile, bcp47):
logging.error("Can't find file " + url)
def _extract_bcp47(query):
if query:
queryStrings = parse_qs(query)
values = queryStrings['bcp47']
if len(values) > 0:
return values[0]
return ''
def _install_package(packageFile, bcp47):
if not os.path.isfile(packageFile):
return False
w = InstallKmpWindow(packageFile, language=bcp47)
w.run()
w.destroy()
return True

View file

@ -2,9 +2,9 @@
import argparse
import logging
import os
from keyman_config import __version__
from keyman_config.handle_install import download_and_install_package
if __name__ == '__main__':
@ -23,13 +23,7 @@ if __name__ == '__main__':
logging.basicConfig(format='%(levelname)s:%(message)s')
if args.install:
if os.path.isfile(args.install):
from keyman_config.install_window import InstallKmpWindow
w = InstallKmpWindow(args.install)
w.run()
w.destroy()
else:
logging.error("Can't find file " + args.install)
download_and_install_package(args.install)
else:
from keyman_config.view_installed import ViewInstalledWindow
w = ViewInstalledWindow()

View file

@ -0,0 +1,106 @@
#!/usr/bin/python3
import unittest
from unittest.mock import patch
from keyman_config import KeymanComUrl
from keyman_config.handle_install import download_and_install_package
class HandleInstallTests(unittest.TestCase):
@patch('keyman_config.handle_install._install_package')
def test_downloadAndInstallPackage_file(self, installPackageMethod):
# Execute
download_and_install_package('/tmp/keyboard/sil_euro_latin.kmp')
# Verify
installPackageMethod.assert_called_with('/tmp/keyboard/sil_euro_latin.kmp', '')
@patch('keyman_config.handle_install._install_package')
def test_downloadAndInstallPackage_fileUrl(self, installPackageMethod):
# Execute
download_and_install_package('file:///tmp/keyboard/sil_euro_latin.kmp')
# Verify
installPackageMethod.assert_called_with('/tmp/keyboard/sil_euro_latin.kmp', '')
@patch('keyman_config.handle_install._install_package')
def test_downloadAndInstallPackage_fileUrlWithBcp47(self, installPackageMethod):
# Execute
download_and_install_package('file:///tmp/keyboard/sil_euro_latin.kmp?bcp47=dyo')
# Verify
installPackageMethod.assert_called_with('/tmp/keyboard/sil_euro_latin.kmp', 'dyo')
@patch('keyman_config.handle_install._install_package')
def test_downloadAndInstallPackage_fileUrlWithBcp47AndVersion(self, installPackageMethod):
# Execute
download_and_install_package('file:///tmp/keyboard/sil_euro_latin.kmp?bcp47=dyo&version=1')
# Verify
installPackageMethod.assert_called_with('/tmp/keyboard/sil_euro_latin.kmp', 'dyo')
@patch('keyman_config.handle_install._install_package')
def test_downloadAndInstallPackage_InvalidUrl(self, installPackageMethod):
# Execute
download_and_install_package('http://localhost/keyboard/sil_euro_latin.kmp')
# Verify
installPackageMethod.assert_not_called()
@patch('keyman_config.handle_install._install_package')
@patch('keyman_config.get_kmp.download_kmp_file')
def test_downloadAndInstallPackage_invalidUrl(self, downloadKmpFileMethod,
installPackageMethod):
for url in ['foo://download/keyboard/sil_euro_latin', 'keyman://keyboard/sil_euro_latin',
'keyman://download/sil_euro_latin', 'keyman://download/keyboard/']:
with self.subTest(url=url):
# Execute
download_and_install_package(url)
# Verify
downloadKmpFileMethod.assert_not_called()
installPackageMethod.assert_not_called()
@patch('keyman_config.handle_install._install_package')
@patch('keyman_config.get_kmp.keyman_cache_dir')
@patch('keyman_config.handle_install.download_kmp_file')
def test_downloadAndInstallPackage_keymanUrl(self, downloadKmpFileMethod,
keymanCacheDirMethod, installPackageMethod):
# Setup
mockPackagePath = '/tmp/sil_euro_latin'
keymanCacheDirMethod.return_value = '/tmp'
downloadKmpFileMethod.return_value = mockPackagePath
# Execute
download_and_install_package('keyman://download/keyboard/sil_euro_latin')
# Verify
downloadKmpFileMethod.assert_called_with(
KeymanComUrl + '/go/package/download/sil_euro_latin?platform=linux&tier=alpha',
mockPackagePath)
installPackageMethod.assert_called_with(mockPackagePath, '')
@patch('keyman_config.handle_install._install_package')
@patch('keyman_config.get_kmp.keyman_cache_dir')
@patch('keyman_config.handle_install.download_kmp_file')
def test_downloadAndInstallPackage_keymanUrlWithBcp47(self, downloadKmpFileMethod,
keymanCacheDirMethod,
installPackageMethod):
# Setup
mockPackagePath = '/tmp/sil_euro_latin'
keymanCacheDirMethod.return_value = '/tmp'
downloadKmpFileMethod.return_value = mockPackagePath
# Execute
download_and_install_package('keyman://download/keyboard/sil_euro_latin?bcp47=de')
# Verify
downloadKmpFileMethod.assert_called_with(
KeymanComUrl + '/go/package/download/sil_euro_latin?platform=linux&tier=alpha',
mockPackagePath)
installPackageMethod.assert_called_with(mockPackagePath, 'de')
if __name__ == '__main__':
unittest.main()