mirror of
https://github.com/keymanapp/keyman.git
synced 2026-11-02 09:07:41 +00:00
Merge pull request #8029 from keymanapp/chore/merge-master20230116-epic-ldml
chore: merge master into feature-ldml 🙀
This commit is contained in:
commit
ed164805ab
22 changed files with 431 additions and 76 deletions
29
HISTORY.md
29
HISTORY.md
|
|
@ -1,5 +1,34 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 17.0.29 alpha 2023-01-16
|
||||
|
||||
* fix(linux): Fix crash (un-)installing shared keyboard (#8020)
|
||||
* chore(linux): Don't report KeyboardInterrupt to Sentry (#8021)
|
||||
* chore(linux): Update sample settings (#8018)
|
||||
* feat(linux): Enhance tab completion in km-package-install (#8005)
|
||||
|
||||
## 17.0.28 alpha 2023-01-12
|
||||
|
||||
* feat(linux): Display error messages in the UI (#8006)
|
||||
* bug(linux): Empty keyboard after failed installation (#8008)
|
||||
|
||||
## 17.0.27 alpha 2023-01-11
|
||||
|
||||
* chore(linux): Refactor completion script (#8002)
|
||||
|
||||
## 17.0.26 alpha 2023-01-10
|
||||
|
||||
* feat(linux): Add Back button to "Download Keyman Keyboards" dialog (#7994)
|
||||
* feat(linux): List fonts in the uninstall confirmation dialog (#7995)
|
||||
|
||||
## 17.0.25 alpha 2023-01-09
|
||||
|
||||
* chore(linux): Remove unnecessary variable (#7988)
|
||||
|
||||
## 17.0.24 alpha 2023-01-06
|
||||
|
||||
* chore(common/resources): Add Docker readme (#7980)
|
||||
|
||||
## 17.0.23 alpha 2023-01-03
|
||||
|
||||
* fix(linux): add IBUS_HAS_PREFILTER ifdef to linux/ibus-keyman/tests (#7958)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
17.0.24
|
||||
17.0.30
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"ms-python.python",
|
||||
"mads-hartmann.bash-ide-vscode",
|
||||
"timonwong.shellcheck",
|
||||
"maelvalais.autoconf"
|
||||
"maelvalais.autoconf",
|
||||
"webfreak.debug",
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,14 +24,17 @@
|
|||
"valuesFormatting": "parseText"
|
||||
},
|
||||
{
|
||||
// Mount the Keyman root directory as a Shared Folder in the VM.
|
||||
// kill ibus-engine-keyman, if running
|
||||
// start: gdbserver 10.3.0.53:2345 /media/sf_Develop/keyman/keyman/linux/ibus-keyman/src/ibus-engine-keyman
|
||||
// then attach debugger in vscode
|
||||
// Replace the IP address after `gdbserver` with the IP address of the host machine.
|
||||
// Replace the IP address below with the address of the VM running gdbserver.
|
||||
"type": "gdb",
|
||||
"request": "attach",
|
||||
"name": "Attach to gdbserver",
|
||||
"executable": "./linux/ibus-keyman/src/ibus-engine-keyman",
|
||||
"target": "10.3.0.53:2345",
|
||||
"name": "Attach to gdbserver (ibus-keyman)",
|
||||
"executable": "${workspaceFolder}/linux/ibus-keyman/src/ibus-engine-keyman",
|
||||
"target": "10.3.0.52:2345",
|
||||
"remote": true,
|
||||
"cwd": "${workspaceFolder}/linux/ibus-keyman",
|
||||
"valuesFormatting": "parseText"
|
||||
|
|
|
|||
|
|
@ -145,9 +145,9 @@
|
|||
"CPPFLAGS=\"-DG_MESSAGES_DEBUG -I${workspaceFolder}/core/build/arch/debug/include/ -I${workspaceFolder}/common/include/ -I${workspaceFolder}/core/include/\"",
|
||||
"CFLAGS=\"-g -O0\"",
|
||||
"CXXFLAGS=\"-g -O0\"",
|
||||
"KEYMAN_PROC_LIBS=\"-L${workspaceFolder}/common/core/desktop/build/arch/debug/src -lkmnkbp0\"",
|
||||
"KEYMAN_PROC_CFLAGS=\"-I${workspaceFolder}/common/core/desktop/build/arch/debug/include -I${workspaceFolder}/common/core/desktop/include\"",
|
||||
"PKG_CONFIG_PATH=${workspaceFolder}/common/core/desktop/build/arch/debug/meson-private"
|
||||
"KEYMAN_PROC_LIBS=\"-L${workspaceFolder}/core/build/arch/debug/src -lkmnkbp0\"",
|
||||
"KEYMAN_PROC_CFLAGS=\"-I${workspaceFolder}/core/build/arch/debug/include -I${workspaceFolder}/core/include -I${workspaceFolder}/common/include\"",
|
||||
"PKG_CONFIG_PATH=${workspaceFolder}/core/build/arch/debug/meson-private"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/linux/ibus-keyman"
|
||||
|
|
|
|||
113
docs/websites/README.md
Normal file
113
docs/websites/README.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# How to Set Up a Local Web Server for the Keyman Web Pages
|
||||
|
||||
Currently, most of the Keyman websites are running via IIS. Refer to the Keyman [wiki](https://github.com/keymanapp/keyman/wiki/How-to-set-up-a-local-web-server-for-the-Keyman-web-pages) for setting that up on Windows 10.
|
||||
|
||||
To make IIS redirects compatible with the Docker sites below, see
|
||||
https://github.com/keymanapp/keyman.com/issues/337#issuecomment-1336339387
|
||||
|
||||
As the websites get migrated to Apache via Docker, follow the installation steps below:
|
||||
|
||||
## Pre-requisite Installs
|
||||
* [Docker Desktop](https://www.docker.com/products/docker-desktop/)
|
||||
|
||||
On Windows, Docker will need either:
|
||||
* Hyper-V or
|
||||
* WSL 2.0 - Install with this guide:
|
||||
https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-containers
|
||||
* WSL will then need a Linux image (e.g. Ubuntu app) from the Microsoft Store
|
||||
|
||||
#### Other Docker Notes
|
||||
Docker tends to throttle Docker image downloads, so some developer offices may want to set up a proxy server. If the proxy server is set up, carefully edit the JSON file per in Docker Settings -> Docker Engine https://docs.docker.com/registry/recipes/mirror/#configure-the-docker-daemon and click 'Apply & Restart'. Note the example (lingnet) is for running inside the Linguistics Institute (Chiang Mai)
|
||||
|
||||
```
|
||||
"registry-mirrors": ["https://docker.io.registry.lingnet/"],
|
||||
"insecure-registries" : [
|
||||
"docker.io.registry.lingnet",
|
||||
"registry.lingnet"
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
### These pre-reqs are still needed but will be removed in keymanapp/help.keyman.com#674
|
||||
* PHP 7.4
|
||||
* Composer
|
||||
|
||||
## Builder BASH Script Actions
|
||||
|
||||
#### Configure
|
||||
Installs PHP dependencies in vendor/ folder. This step will be removed in keymanapp/help.keyman.com#674
|
||||
1. Run `./build.sh configure`.
|
||||
|
||||
#### Stop the Docker container
|
||||
1. Run `./build.sh stop`
|
||||
|
||||
#### Build the Docker image
|
||||
1. Run `./build.sh build`.
|
||||
|
||||
#### Start the Docker container
|
||||
1. Run `./build.sh start`.
|
||||
|
||||
After this, you can access the website at the following ports:
|
||||
|
||||
| Website | URL |
|
||||
|------------|-----|
|
||||
|help.keyman | http://localhost:8055 |
|
||||
|
||||
|
||||
#### Remove the Docker container and image
|
||||
1. Run `./build.sh clean`.
|
||||
|
||||
#### Running tests
|
||||
Checks for broken links
|
||||
1. Run `./build.sh test`
|
||||
|
||||
---------
|
||||
|
||||
## Kubernetes Deployment
|
||||
For production, the websites are deployed with Kubernetes.
|
||||
|
||||
|
||||
### How to run help.keyman.com locally with Docker Desktop's Kubernetes singlenode cluster
|
||||
|
||||
For testing Kubernetes deployment, there are yaml files under the corresponding website's repo: `/resources/kubectl`, that cover local developer testing.
|
||||
|
||||
### Pre-requisites
|
||||
On the host machine, install [Docker](https://docs.docker.com/get-docker/), then enable Kubernetes in the settings. Ensure you have built a help-keyman-app Docker image, and either tag it `docker.dallas.languagetechnology.org/keyman/help-keyman-app` or modify the `app-php` containers `image:` value to match you local copy's name.
|
||||
|
||||
### Deploying to a desktop cluster
|
||||
To deploy the dev version to the cluster do the following:
|
||||
1. Ensure your `kubectl` context is set to `docker-desktop`, though the Docker Desktop systray icon or by running:
|
||||
```bash
|
||||
$> kubectl config use-context docker-desktop
|
||||
```
|
||||
2. Create a keyman namespace if it does not already exist:
|
||||
```bash
|
||||
$> kubectl create ns keyman
|
||||
```
|
||||
3. Apply the configs for the resources and start the pod:
|
||||
```bash
|
||||
$> kubectl --namespace keyman apply \
|
||||
-f resources/kubectl/help-kubectl-dev.yaml \
|
||||
-f resources/kubectl/help-kubectl.yaml
|
||||
```
|
||||
### Testing the site and `/api/deploy` webhook endpoint
|
||||
The site can be reached on http://localhost:30080/ via web browser, and the deploy api is on http://localhost:30900/api/deploy, and can be activated like so:
|
||||
```bash
|
||||
$> curl -v --request POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Hub-Signature-256: sha256=49af8531106a369bfee369f91dadec597e8ea3992ec2802bbe655be0ece17f15" \
|
||||
--data '{"action":"push","ref":"refs/heads/staging"}' \
|
||||
http://localhost:30900/api/deploy
|
||||
```
|
||||
This simulates enough of a GitHub webhook push event to pass validation on the responder.
|
||||
|
||||
### Clean up after testing
|
||||
|
||||
To remove the k8s pod and resources, and delete everything do:
|
||||
```bash
|
||||
$> kubectl --namespace=keyman delete {pod,cm,svc,secret,pvc}/help-keyman-com
|
||||
```
|
||||
Or just delete the pod and keep the resources for further testing:
|
||||
```bash
|
||||
$> kubectl --namespace=keyman delete pod/help-keyman-com
|
||||
```
|
||||
|
|
@ -86,6 +86,7 @@ Depends:
|
|||
python3-bs4,
|
||||
python3-gi,
|
||||
python3-sentry-sdk (>= 1.1) | python3-raven,
|
||||
dbus-x11,
|
||||
${misc:Depends},
|
||||
${python3:Depends},
|
||||
Description: Keyman for Linux configuration
|
||||
|
|
|
|||
|
|
@ -37,6 +37,15 @@ def secure_lookup(data, key1, key2 = None):
|
|||
return None
|
||||
|
||||
|
||||
def before_send(event, hint):
|
||||
if 'exc_info' in hint:
|
||||
exc_type, exc_value, tb = hint['exc_info']
|
||||
if isinstance(exc_value, KeyboardInterrupt):
|
||||
# Ignore KeyboardInterrupt exception
|
||||
return None
|
||||
return event
|
||||
|
||||
|
||||
gettext.bindtextdomain('keyman-config', '/usr/share/locale')
|
||||
gettext.textdomain('keyman-config')
|
||||
|
||||
|
|
@ -77,6 +86,7 @@ else:
|
|||
environment=__environment__,
|
||||
release='release-' + __versionwithtag__,
|
||||
integrations=[sentry_logging],
|
||||
before_send=before_send
|
||||
)
|
||||
set_user({'id': hash(getpass.getuser())})
|
||||
with configure_scope() as scope:
|
||||
|
|
|
|||
|
|
@ -30,13 +30,26 @@ class DownloadKmpWindow(Gtk.Dialog):
|
|||
s = Gtk.ScrolledWindow()
|
||||
self.webview = WebKit2.WebView()
|
||||
self.webview.connect("decide-policy", self._keyman_policy)
|
||||
self.webview.connect("load-changed", self._update_back_button)
|
||||
url = KeymanComUrl + "/go/linux/" + __releaseversion__ + "/download-keyboards"
|
||||
self.webview.load_uri(url)
|
||||
s.add(self.webview)
|
||||
|
||||
self.get_content_area().pack_start(s, True, True, 0)
|
||||
|
||||
self.add_button(_("_Close"), Gtk.ResponseType.CLOSE)
|
||||
hbox = Gtk.Box(spacing=6)
|
||||
self.get_content_area().pack_start(hbox, False, False, 0)
|
||||
|
||||
self.back_button = Gtk.Button.new_with_mnemonic(_("_Back"))
|
||||
self.back_button.set_tooltip_text(_("Back to search"))
|
||||
self.back_button.connect("clicked", self._on_back_clicked)
|
||||
self.back_button.set_sensitive(False)
|
||||
hbox.pack_start(self.back_button, False, False, 0)
|
||||
|
||||
close_button = Gtk.Button.new_with_mnemonic(_("_Close"))
|
||||
close_button.set_tooltip_text(_("Close dialog"))
|
||||
close_button.connect("clicked", self._on_close_clicked)
|
||||
hbox.pack_end(close_button, False, False, 0)
|
||||
|
||||
if self.parentWindow is not None:
|
||||
self.getinfo = GetInfo(self.parentWindow.incomplete_kmp)
|
||||
|
|
@ -44,6 +57,15 @@ class DownloadKmpWindow(Gtk.Dialog):
|
|||
self.resize(800, 450)
|
||||
self.show_all()
|
||||
|
||||
def _update_back_button(self, webview, load_event):
|
||||
self.back_button.set_sensitive(webview.can_go_back())
|
||||
|
||||
def _on_back_clicked(self, button):
|
||||
self.webview.go_back()
|
||||
|
||||
def _on_close_clicked(self, button):
|
||||
self.response(Gtk.ResponseType.CLOSE)
|
||||
|
||||
def _process_kmp(self, url, downloadfile: str):
|
||||
logging.info("Downloading kmp file to %s", downloadfile)
|
||||
if download_kmp_file(url, downloadfile):
|
||||
|
|
@ -52,6 +74,12 @@ class DownloadKmpWindow(Gtk.Dialog):
|
|||
self.response(Gtk.ResponseType.OK)
|
||||
self.close()
|
||||
return True
|
||||
logging.error(_("Downloading kmp file failed"))
|
||||
dialog = Gtk.MessageDialog(
|
||||
self, 0, Gtk.MessageType.ERROR,
|
||||
Gtk.ButtonsType.OK, _("Downloading keyboard file failed"))
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
return False
|
||||
|
||||
def _keyman_policy(self, web_view, decision, decision_type):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ def download_and_install_package(url):
|
|||
"""
|
||||
parsedUrl = urlparse(url)
|
||||
bcp47 = _extract_bcp47(parsedUrl.query)
|
||||
severity = logging.ERROR
|
||||
|
||||
if parsedUrl.scheme == 'keyman':
|
||||
logging.info("downloading " + url)
|
||||
|
|
@ -49,7 +48,7 @@ def download_and_install_package(url):
|
|||
return
|
||||
|
||||
if packageFile and not _install_package(packageFile, bcp47):
|
||||
logging.log(severity, "Can't find file " + url)
|
||||
logging.error("Can't find file " + url)
|
||||
|
||||
|
||||
def _extract_bcp47(query):
|
||||
|
|
|
|||
|
|
@ -106,14 +106,14 @@ class InstallKmp():
|
|||
self.kmpdocdir = get_keyman_doc_dir(area, self.packageID)
|
||||
self.kmpfontdir = get_keyman_font_dir(area, self.packageID)
|
||||
|
||||
if not self._safeMakeDirs(self.packageDir):
|
||||
return
|
||||
|
||||
if not os.path.isfile(inputfile):
|
||||
message = _("File {kmpfile} doesn't exist").format(kmpfile=inputfile)
|
||||
logging.error("install_kmp.py: %s", message)
|
||||
raise InstallError(InstallStatus.Abort, message)
|
||||
|
||||
if not self._safeMakeDirs(self.packageDir):
|
||||
return
|
||||
|
||||
extract_kmp(inputfile, self.packageDir)
|
||||
|
||||
if is_fcitx_running():
|
||||
|
|
|
|||
|
|
@ -506,7 +506,6 @@ def get_metadata(tmpdirname):
|
|||
If it does not exist then will return get_and_convert_infdata
|
||||
|
||||
Args:
|
||||
inputfile (str): path to kmp file
|
||||
tmpdirname(str): temp directory to extract kmp
|
||||
|
||||
Returns:
|
||||
|
|
|
|||
|
|
@ -37,31 +37,32 @@ def uninstall_kmp_shared(packageID):
|
|||
"""
|
||||
kbdir = get_keyboard_dir(InstallLocation.Shared, packageID)
|
||||
if not os.path.isdir(kbdir):
|
||||
logging.error("Keyboard directory for %s does not exist. Aborting", packageID)
|
||||
exit(3)
|
||||
msg = _("Keyboard directory for %s does not exist." % packageID)
|
||||
logging.error(msg)
|
||||
return msg
|
||||
|
||||
kbdocdir = get_keyman_doc_dir(InstallLocation.Shared, packageID)
|
||||
kbfontdir = get_keyman_font_dir(InstallLocation.Shared, packageID)
|
||||
|
||||
logging.info("Uninstalling shared keyboard: %s", packageID)
|
||||
if not os.access(kbdir, os.X_OK | os.W_OK): # Check for write access of keyman dir
|
||||
logging.error(
|
||||
"You do not have permissions to uninstall the keyboard files. You need to run this with `sudo`")
|
||||
exit(3)
|
||||
msg = _("You do not have permissions to uninstall the keyboard files. You need to run this with `sudo`")
|
||||
logging.error(msg)
|
||||
return msg
|
||||
if os.path.isdir(kbdocdir):
|
||||
if not os.access(kbdocdir, os.X_OK | os.W_OK): # Check for write access of keyman doc dir
|
||||
logging.error(
|
||||
"You do not have permissions to uninstall the documentation. You need to run this with `sudo`")
|
||||
exit(3)
|
||||
msg = _("You do not have permissions to uninstall the documentation. You need to run this with `sudo`")
|
||||
logging.error(msg)
|
||||
return msg
|
||||
delete_dir(kbdocdir)
|
||||
logging.info("Removed documentation directory: %s", kbdocdir)
|
||||
else:
|
||||
logging.info("No documentation directory")
|
||||
if os.path.isdir(kbfontdir):
|
||||
if not os.access(kbfontdir, os.X_OK | os.W_OK): # Check for write access of keyman fonts
|
||||
logging.error(
|
||||
"You do not have permissions to uninstall the font files. You need to run this with `sudo`")
|
||||
exit(3)
|
||||
msg = _("You do not have permissions to uninstall the font files. You need to run this with `sudo`")
|
||||
logging.error(msg)
|
||||
return msg
|
||||
delete_dir(kbfontdir)
|
||||
logging.info("Removed font directory: %s", kbfontdir)
|
||||
else:
|
||||
|
|
@ -80,6 +81,7 @@ def uninstall_kmp_shared(packageID):
|
|||
delete_dir(kbdir)
|
||||
logging.info("Removed keyman directory: %s", kbdir)
|
||||
logging.info("Finished uninstalling shared keyboard: %s", packageID)
|
||||
return ''
|
||||
|
||||
|
||||
def uninstall_keyboards_from_ibus(keyboards, packageDir):
|
||||
|
|
@ -130,8 +132,9 @@ def uninstall_kmp_user(packageID):
|
|||
"""
|
||||
kbdir = get_keyboard_dir(InstallLocation.User, packageID)
|
||||
if not os.path.isdir(kbdir):
|
||||
logging.error("Keyboard directory for %s does not exist. Aborting", packageID)
|
||||
exit(3)
|
||||
msg = _("Keyboard directory for %s does not exist." % packageID)
|
||||
logging.error(msg)
|
||||
return msg
|
||||
logging.info("Uninstalling local keyboard: %s", packageID)
|
||||
info, system, options, keyboards, files = get_metadata(kbdir)
|
||||
if keyboards:
|
||||
|
|
@ -150,6 +153,7 @@ def uninstall_kmp_user(packageID):
|
|||
delete_dir(fontdir)
|
||||
logging.info("Removed user keyman font directory: %s", fontdir)
|
||||
logging.info("Finished uninstalling local keyboard: %s", packageID)
|
||||
return ''
|
||||
|
||||
|
||||
def uninstall_kmp(packageID, sharedarea=False):
|
||||
|
|
@ -161,8 +165,9 @@ def uninstall_kmp(packageID, sharedarea=False):
|
|||
sharedarea (str): whether to uninstall from shared /usr/local or ~/.local
|
||||
"""
|
||||
if sharedarea:
|
||||
uninstall_kmp_shared(packageID)
|
||||
msg = uninstall_kmp_shared(packageID)
|
||||
else:
|
||||
uninstall_kmp_user(packageID)
|
||||
msg = uninstall_kmp_user(packageID)
|
||||
|
||||
get_keyman_config_service().keyboard_list_changed()
|
||||
return msg
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from keyman_config.get_kmp import (InstallLocation, get_keyboard_dir,
|
|||
get_keyman_dir)
|
||||
from keyman_config.install_window import InstallKmpWindow, find_keyman_image
|
||||
from keyman_config.keyboard_details import KeyboardDetailsView
|
||||
from keyman_config.kmpmetadata import get_fonts, parsemetadata
|
||||
from keyman_config.list_installed_kmp import get_installed_kmp
|
||||
from keyman_config.options import OptionsView
|
||||
from keyman_config.uninstall_kmp import uninstall_kmp
|
||||
|
|
@ -351,18 +352,40 @@ class ViewInstalledWindow(ViewInstalledWindowBase):
|
|||
model, treeiter = self.tree.get_selection().get_selected()
|
||||
if treeiter is not None:
|
||||
logging.info("Uninstall keyboard " + model[treeiter][3] + "?")
|
||||
dialog = Gtk.MessageDialog(
|
||||
self, 0, Gtk.MessageType.QUESTION,
|
||||
Gtk.ButtonsType.YES_NO, _("Uninstall keyboard package?"))
|
||||
dialog.format_secondary_text(
|
||||
_("Are you sure that you want to uninstall the {keyboard} keyboard and its fonts?")
|
||||
.format(keyboard=model[treeiter][1]))
|
||||
dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
|
||||
_("Uninstall keyboard package?"))
|
||||
msg = _("Are you sure that you want to uninstall the {keyboard} keyboard?").format(
|
||||
keyboard=model[treeiter][1])
|
||||
kbdir = get_keyboard_dir(InstallLocation.User, model[treeiter][3])
|
||||
kmpjson = os.path.join(kbdir, "kmp.json")
|
||||
if os.path.isfile(kmpjson):
|
||||
info, system, options, keyboards, files = parsemetadata(kmpjson, False)
|
||||
fonts = get_fonts(files)
|
||||
if fonts:
|
||||
# Fonts are optional
|
||||
fontlist = ""
|
||||
for font in fonts:
|
||||
if 'description' in font:
|
||||
if fontlist != "":
|
||||
fontlist = fontlist + "\n"
|
||||
if font['description'][:5] == "Font ":
|
||||
fontdesc = font['description'][5:]
|
||||
else:
|
||||
fontdesc = font['description']
|
||||
fontlist = fontlist + fontdesc
|
||||
msg += "\n\n" + _("The following fonts will also be uninstalled:\n") + fontlist
|
||||
dialog.format_secondary_text(msg)
|
||||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
if response == Gtk.ResponseType.YES:
|
||||
logging.info("Uninstalling keyboard" + model[treeiter][1])
|
||||
# can only uninstall with the gui from user area
|
||||
uninstall_kmp(model[treeiter][3])
|
||||
msg = uninstall_kmp(model[treeiter][3])
|
||||
if not msg == '':
|
||||
md = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR,
|
||||
Gtk.ButtonsType.OK, _("Uninstalling keyboard failed.\n\nError message: ") + msg)
|
||||
md.run()
|
||||
md.destroy()
|
||||
logging.info("need to restart window after uninstalling a keyboard")
|
||||
self.restart()
|
||||
elif response == Gtk.ResponseType.NO:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#/usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_km-config_completions()
|
||||
{
|
||||
|
|
@ -9,14 +9,15 @@ _km-config_completions()
|
|||
opts="-h --help -v --verbose -vv --veryverbose --version -i --install"
|
||||
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# default IFS splits 'file://' into 'file', ':' and '//'
|
||||
if [ "${prev}" == ":" -a "${COMP_WORDS[COMP_CWORD-2]}" == "file" ]; then
|
||||
if [ "${prev}" == ":" ] && [ "${COMP_WORDS[COMP_CWORD-2]}" == "file" ]; then
|
||||
local flag=${COMP_WORDS[COMP_CWORD-3]}
|
||||
if [ $flag == "-i" -o $flag == "--install" ]; then
|
||||
if [ "$flag" == "-i" ] || [ "$flag" == "--install" ]; then
|
||||
prev=$flag
|
||||
fi
|
||||
fi
|
||||
|
|
@ -25,7 +26,8 @@ _km-config_completions()
|
|||
"-i"|"--install")
|
||||
local IFS=$'\n'
|
||||
compopt -o filenames
|
||||
COMPREPLY=( $(compgen -f -X "!"*.kmp -- $cur) $(compgen -d -- $cur) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -f -X "!"*.kmp -- "$cur") $(compgen -d -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#/usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_km-kvk2ldml_completions()
|
||||
{
|
||||
|
|
@ -9,7 +9,8 @@ _km-kvk2ldml_completions()
|
|||
opts="-h --help -p --print -k --keys -o --output -v --verbose -vv --veryverbose --version"
|
||||
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
|
@ -17,7 +18,8 @@ _km-kvk2ldml_completions()
|
|||
"-o"|"--output")
|
||||
local IFS=$'\n'
|
||||
compopt -o filenames
|
||||
COMPREPLY=( $(compgen -f -- $cur) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -f -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
|
@ -26,7 +28,8 @@ _km-kvk2ldml_completions()
|
|||
|
||||
local IFS=$'\n'
|
||||
compopt -o filenames
|
||||
COMPREPLY=( $(compgen -f -X "!"*.kvk -- $cur) $(compgen -d -- $cur) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -f -X "!"*.kvk -- "$cur") $(compgen -d -- "$cur") )
|
||||
}
|
||||
|
||||
complete -F _km-kvk2ldml_completions km-kvk2ldml
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
#/usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_km-package-get_completions()
|
||||
{
|
||||
local cur prev opts cache
|
||||
local cur opts cache pkg_install_path
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts="-h --help -v --verbose -vv --veryverbose --version"
|
||||
|
||||
cache=${XDG_CACHE_HOME:-~/.cache}/keyman/kmpdirlist
|
||||
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
|
@ -21,15 +21,17 @@ _km-package-get_completions()
|
|||
# Unfortunately with bash completion scripts it's not possible to factor out
|
||||
# common code.
|
||||
if [[ -e ./km-package-install ]]; then
|
||||
python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('km_package_install', './km-package-install');spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.list_keyboards()"
|
||||
pkg_install_path='./km-package-install'
|
||||
else
|
||||
python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('km_package_install', '/usr/bin/km-package-install');spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.list_keyboards()"
|
||||
pkg_install_path='/usr/bin/km-package-install'
|
||||
fi
|
||||
python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('km_package_install', $pkg_install_path);spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.list_keyboards()"
|
||||
fi
|
||||
|
||||
if [[ -r $cache ]] ; then
|
||||
for file in `cat $cache`; do words="${words} ${file}"; done
|
||||
COMPREPLY=($(compgen -W "${words}" -- ${cur}))
|
||||
while read -r file; do words="${words} ${file}"; done < "$cache"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=($(compgen -W "${words}" -- "${cur}"))
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ from pkg_resources import parse_version
|
|||
from zipfile import is_zipfile
|
||||
|
||||
from keyman_config import KeymanApiUrl, __version__, secure_lookup
|
||||
from keyman_config.install_kmp import extract_kmp
|
||||
from keyman_config.kmpmetadata import get_metadata
|
||||
from keyman_config.uninstall_kmp import uninstall_kmp
|
||||
|
||||
|
||||
|
|
@ -84,6 +86,33 @@ def list_keyboards():
|
|||
write_kmpdirlist(kmpdirfile)
|
||||
|
||||
|
||||
def _list_languages_for_keyboard_impl(packageId, packageDir):
|
||||
from keyman_config.get_kmp import keyman_cache_dir
|
||||
kmpfile = os.path.join(keyman_cache_dir(), packageId)
|
||||
if not os.path.exists(kmpfile):
|
||||
kmpfile = kmpfile + '.kmp'
|
||||
if not os.path.exists(kmpfile):
|
||||
return ''
|
||||
extract_kmp(kmpfile, packageDir)
|
||||
info, system, options, keyboards, files = get_metadata(packageDir)
|
||||
if not keyboards:
|
||||
return ''
|
||||
firstKeyboard = keyboards[0]
|
||||
if not secure_lookup(firstKeyboard, 'languages') or len(firstKeyboard['languages']) <= 0:
|
||||
return ''
|
||||
result = ''
|
||||
for lang in firstKeyboard['languages']:
|
||||
if result:
|
||||
result += '\n'
|
||||
result += lang['id']
|
||||
return result
|
||||
|
||||
|
||||
def list_languages_for_keyboard(packageId, packageDir):
|
||||
result = _list_languages_for_keyboard_impl(packageId, packageDir)
|
||||
print(result)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Install a Keyman keyboard package, either a local .kmp file or specify a ' +
|
||||
|
|
|
|||
|
|
@ -1,20 +1,29 @@
|
|||
#/usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_km-package-install_completions()
|
||||
{
|
||||
local cur prev opts cache
|
||||
local cur prev opts cache pkg_install_path
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts="-h --help -v --verbose -vv --veryverbose --version -p --package -f --file -s --shared"
|
||||
opts="-h --help -v --verbose -vv --veryverbose --version -p --package -f --file -s --shared -l --bcp47"
|
||||
|
||||
cache=${XDG_CACHE_HOME:-~/.cache}/keyman/kmpdirlist
|
||||
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -e ./km-package-install ]]; then
|
||||
pkg_install_path='./km-package-install'
|
||||
get_kmp_path='keyman_config/get_kmp.py'
|
||||
else
|
||||
pkg_install_path='/usr/bin/km-package-install'
|
||||
get_kmp_path='/usr/lib/python3/dist-packages/keyman_config/get_kmp.py'
|
||||
fi
|
||||
|
||||
case "${prev}" in
|
||||
"-p"|"--package")
|
||||
words=""
|
||||
|
|
@ -22,23 +31,58 @@ _km-package-install_completions()
|
|||
# NOTE: identical code in `km-package-get.bash-completion`.
|
||||
# Unfortunately with bash completion scripts it's not possible to factor out
|
||||
# common code.
|
||||
if [[ -e ./km-package-install ]]; then
|
||||
python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('km_package_install', './km-package-install');spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.list_keyboards()"
|
||||
else
|
||||
python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('km_package_install', '/usr/bin/km-package-install');spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.list_keyboards()"
|
||||
fi
|
||||
|
||||
python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('km_package_install', '$pkg_install_path');spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.list_keyboards()"
|
||||
fi
|
||||
|
||||
if [[ -r $cache ]] ; then
|
||||
for file in `cat $cache`; do words="${words} ${file}"; done
|
||||
COMPREPLY=($(compgen -W "${words}" -- ${cur}))
|
||||
while read -r file; do words="${words} ${file}"; done < "$cache"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=($(compgen -W "${words}" -- "${cur}"))
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
"-f"|"--file")
|
||||
local IFS=$'\n'
|
||||
compopt -o filenames
|
||||
COMPREPLY=( $(compgen -f -X "!"*.kmp -- $cur) $(compgen -d -- $cur) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -f -X "!"*.kmp -- "$cur") $(compgen -d -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
"-l"|"--bcp47")
|
||||
local packageId=""
|
||||
local packageDir
|
||||
packageDir=$(mktemp -d)
|
||||
for ((i=0;i<$COMP_CWORD;i++)); do
|
||||
case ${COMP_WORDS[$i]} in
|
||||
"-p"|"--package")
|
||||
packageId="${COMP_WORDS[$i+1]}"
|
||||
# shellcheck disable=SC2086 # doesn't work with quotes
|
||||
if [ ! -f ${XDG_CACHE_HOME:-~/.cache}/keyman/$packageId ] && [ ! -f ${XDG_CACHE_HOME:-~/.cache}/keyman/${packageId}.kmp ]; then
|
||||
python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('get_kmp', '$get_kmp_path');spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.get_kmp('$packageId')"
|
||||
fi
|
||||
break
|
||||
;;
|
||||
"-f"|"--file")
|
||||
package="${COMP_WORDS[$i+1]}"
|
||||
packageId=$(basename "$package")
|
||||
# shellcheck disable=SC2086 # doesn't work with quotes
|
||||
cp "$package" ${XDG_CACHE_HOME:-~/.cache}/keyman/${packageId}
|
||||
break
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if [ -z "$packageId" ]; then
|
||||
return 0
|
||||
fi
|
||||
words=""
|
||||
while read -r lang; do
|
||||
words="$words $lang"
|
||||
done < <(python3 -c "from importlib.machinery import SourceFileLoader;from importlib.util import module_from_spec, spec_from_loader;loader = SourceFileLoader('km_package_install', '$pkg_install_path');spec = spec_from_loader(loader.name, loader);mod = module_from_spec(spec);loader.exec_module(mod);mod.list_languages_for_keyboard('$packageId', '$packageDir')")
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=($(compgen -W "${words}" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
#/usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_km-package-list-installed_completions()
|
||||
{
|
||||
local cur prev opts
|
||||
local cur opts
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts="-h --help -l --long -v --verbose -vv --veryverbose --version -u --user -o --os -s --shared"
|
||||
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#/usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_km-package-uninstall_completions()
|
||||
{
|
||||
|
|
@ -9,7 +9,8 @@ _km-package-uninstall_completions()
|
|||
opts="-h --help -s --shared -v --verbose -vv --veryverbose --version"
|
||||
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
|
@ -23,12 +24,15 @@ _km-package-uninstall_completions()
|
|||
shared=""
|
||||
case "${prev}" in
|
||||
"-s"|"--shared")
|
||||
for file in $(ls -1 /usr/local/share/keyman); do kbid="`basename ${file}`"; shared="${shared} ${kbid}"; done
|
||||
COMPREPLY=($(compgen -W "${shared}" -- ${cur}))
|
||||
for file in /usr/local/share/keyman/*; do kbid="$(basename "${file}")"; shared="${shared} ${kbid}"; done
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=($(compgen -W "${shared}" -- "${cur}"))
|
||||
;;
|
||||
*)
|
||||
for file in $(ls -1 ${XDG_DATA_HOME:-~/.local/share}/keyman); do kbid="`basename ${file}`"; words="${words} ${kbid}"; done
|
||||
COMPREPLY=($(compgen -W "${words}" -- ${cur}))
|
||||
# shellcheck disable=SC2231 # doesn't work with quotes
|
||||
for file in ${XDG_DATA_HOME:-~/.local/share}/keyman/*; do kbid="$(basename "${file}")"; words="${words} ${kbid}"; done
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=($(compgen -W "${words}" -- "${cur}"))
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
|
|
|||
60
linux/keyman-config/tests/test_package_install_completion.py
Normal file
60
linux/keyman-config/tests/test_package_install_completion.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/python3
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch, ANY
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from importlib.util import module_from_spec, spec_from_loader
|
||||
|
||||
|
||||
class PackageInstallCompletionTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
patcher1 = patch('keyman_config.install_kmp.extract_kmp')
|
||||
self.mockExtractKmp = patcher1.start()
|
||||
self.addCleanup(patcher1.stop)
|
||||
patcher2 = patch('keyman_config.kmpmetadata.get_metadata')
|
||||
self.mockGetMetadata = patcher2.start()
|
||||
self.addCleanup(patcher2.stop)
|
||||
|
||||
loader = SourceFileLoader('km_package_install', os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), '../km-package-install'))
|
||||
spec = spec_from_loader(loader.name, loader)
|
||||
if spec:
|
||||
self.mod = module_from_spec(spec)
|
||||
loader.exec_module(self.mod)
|
||||
|
||||
self.tempDir = tempfile.TemporaryDirectory()
|
||||
os.environ["XDG_CACHE_HOME"] = self.tempDir.name
|
||||
self.cacheDir = os.path.join(self.tempDir.name, 'keyman')
|
||||
os.makedirs(self.cacheDir)
|
||||
|
||||
def _list_languages_for_keyboard_impl(self, packageId):
|
||||
return self.mod._list_languages_for_keyboard_impl(packageId, 'someDir')
|
||||
|
||||
def test_PackageCompletionNoLanguage(self):
|
||||
open(os.path.join(self.cacheDir, 'foo'), 'w')
|
||||
self.mockGetMetadata.return_value = (None, None, None, [{}], None)
|
||||
result = self._list_languages_for_keyboard_impl('foo')
|
||||
self.assertEqual(result, "")
|
||||
|
||||
def test_PackageCompletionOneLanguage(self):
|
||||
open(os.path.join(self.cacheDir, 'khmer_angkor'), 'w')
|
||||
self.mockGetMetadata.return_value = (
|
||||
None, None, None,
|
||||
[{'languages': [{'name': 'Central Khmer (Khmer, Cambodia)', 'id': 'km'}]}],
|
||||
None)
|
||||
result = self._list_languages_for_keyboard_impl('khmer_angkor')
|
||||
self.assertEqual(result, "km")
|
||||
|
||||
def test_PackageCompletionMultipleLanguages(self):
|
||||
open(os.path.join(self.cacheDir, 'sil_euro_latin'), 'w')
|
||||
self.mockGetMetadata.return_value = (
|
||||
None, None, None,
|
||||
[{'languages': [
|
||||
{'name': 'English', 'id': 'en'},
|
||||
{'name': 'French', 'id': 'fr'},
|
||||
{'name': 'German', 'id': 'de'}]}],
|
||||
None)
|
||||
result = self._list_languages_for_keyboard_impl('sil_euro_latin')
|
||||
self.assertEqual(result, 'en\nfr\nde')
|
||||
Loading…
Add table
Reference in a new issue