mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 09:25:33 +00:00
206 lines
7.8 KiB
Swift
206 lines
7.8 KiB
Swift
/*
|
|
* KeyboardRepository.swift
|
|
* FirstVoices app
|
|
*
|
|
* License: MIT
|
|
*
|
|
* Copyright © 2022 FirstVoices.
|
|
*
|
|
* Created by Shawn Schantz on 2022-01-25.
|
|
*
|
|
* Class that loads data related to the definition of the Keyboard and installs and removes keyboards from Keyman.
|
|
* Keyboard definitions are read from the First Voices kmp file and saved in an FVKeyboardDefinition
|
|
* object to associate it with its corresponding language and language tag.
|
|
*
|
|
*/
|
|
|
|
import Foundation
|
|
import KeymanEngine
|
|
|
|
class KeyboardRepository {
|
|
|
|
static let shared: KeyboardRepository = {
|
|
let instance = KeyboardRepository()
|
|
return instance
|
|
}()
|
|
|
|
private init() {
|
|
self.loadAvailableKeyboards()
|
|
}
|
|
|
|
private var _availableKeyboards: [String:FVKeyboardDefinition] = [:]
|
|
public var availableKeyboards: [String:FVKeyboardDefinition] {
|
|
get {
|
|
return _availableKeyboards
|
|
}
|
|
}
|
|
|
|
private func loadKeyboardPackage() -> KeyboardKeymanPackage? {
|
|
let keyboardPackagePath: String = Bundle.main.path(forResource: FVConstants.keyboardsPackage,
|
|
ofType: FVConstants.keyboardsPackageExt,
|
|
inDirectory: FVConstants.keyboardsPath)!
|
|
let pathUrl = URL(fileURLWithPath: keyboardPackagePath)
|
|
var keyboardsPackage: KeyboardKeymanPackage?
|
|
|
|
do {
|
|
let package = try ResourceFileManager.shared.prepareKMPInstall(from: pathUrl)
|
|
guard package as? KeyboardKeymanPackage != nil else {
|
|
print("Failed to load \(FVConstants.keyboardsPackage).\(FVConstants.keyboardsPackageExt)")
|
|
return keyboardsPackage
|
|
}
|
|
|
|
keyboardsPackage = (package as? KeyboardKeymanPackage)!
|
|
} catch {
|
|
print("Failed to load \(FVConstants.keyboardsPackage).\(FVConstants.keyboardsPackageExt)")
|
|
}
|
|
|
|
return keyboardsPackage
|
|
}
|
|
|
|
/*
|
|
* Read available keyboards from the kmp file.
|
|
* This is called so that we have the BCP 47 tag needed to query for a lexical model.
|
|
*
|
|
*/
|
|
func loadAvailableKeyboards() {
|
|
if let keyboardsPackage = loadKeyboardPackage() {
|
|
for keyboardArray in keyboardsPackage.installables {
|
|
// assume one keyboard per package per language as that's currently all we have in practice
|
|
let keyboard = keyboardArray[0]
|
|
let keyboardId = keyboard.id
|
|
let keyboardDefinition = FVKeyboardDefinition(name: keyboard.name,
|
|
keyboardId: keyboardId,
|
|
keyboardVersion: keyboard.version,
|
|
languageTag: keyboard.lgCode,
|
|
languageName: keyboard.languageName)
|
|
// add this keyboard to the map
|
|
_availableKeyboards[keyboardId] = keyboardDefinition
|
|
}
|
|
}
|
|
}
|
|
|
|
func findKeyboardDefinition(for keyboardId: String) -> FVKeyboardDefinition? {
|
|
return KeyboardRepository.shared.availableKeyboards[keyboardId]
|
|
}
|
|
|
|
func installKeyboard(keyboard: FVKeyboardDefinition) -> Bool {
|
|
// make a copy of all the active keyboards
|
|
var newKeyboardsMap = Dictionary(uniqueKeysWithValues:
|
|
KeyboardSettingsRepository.shared.activeKeyboards.map { key, value in (key, value.languageTag) })
|
|
// add new keyboard
|
|
newKeyboardsMap[keyboard.keyboardId] = keyboard.languageTag
|
|
|
|
// Rebuilding the keyboard list will cause the pre-installed EuroLatin SIL keyboard to be deleted if it is installed.
|
|
// EuroLatin SIL is not tracked as an active keyboard in the KeyboardSettingsRepository.
|
|
// This is intentional as it should go away once FV keyboards are added.
|
|
|
|
rebuildKeyboardsListInKeymanEngine(keyboards: newKeyboardsMap)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
func removeKeyboard(keyboard: FVKeyboardDefinition) -> Bool {
|
|
// make a copy of all the active keyboards
|
|
var newKeyboardsMap = Dictionary(uniqueKeysWithValues:
|
|
KeyboardSettingsRepository.shared.activeKeyboards.map { key, value in (key, value.languageTag) })
|
|
// remove keyboard to be deleted
|
|
newKeyboardsMap.removeValue(forKey: keyboard.keyboardId)
|
|
|
|
rebuildKeyboardsListInKeymanEngine(keyboards: newKeyboardsMap)
|
|
|
|
return true
|
|
}
|
|
|
|
func rebuildKeyboardsListInKeymanEngine(keyboards: [String:String]) {
|
|
|
|
// call Keyman Engine to remove all keyboards
|
|
while Manager.shared.removeKeyboard(at: 0) {
|
|
print("removed existing keyboard")
|
|
}
|
|
|
|
// re-install specified keyboards
|
|
if let keyboardsPackage = loadKeyboardPackage() {
|
|
for (keyboardId, languageTag) in keyboards {
|
|
let fullKeyboardId = FullKeyboardID(keyboardID: keyboardId, languageID: languageTag)
|
|
do {
|
|
try ResourceFileManager.shared.install(resourceWithID: fullKeyboardId, from: keyboardsPackage)
|
|
print("Installed keyboard \(keyboardId)")
|
|
} catch {
|
|
let installError = error
|
|
print("Failed to load preload \(keyboardId) installError: \(installError.localizedDescription)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class func updateActiveKeyboardsList(keyboardList: FVRegionList, loadedKeyboards: [String]) {
|
|
|
|
// Remove all installed keyboards first -- we'll re-add them below
|
|
|
|
while Manager.shared.removeKeyboard(at: 0) {
|
|
}
|
|
|
|
// Load the primary keyboards package so that we can install keyboards from it.
|
|
let keyboardPackagePath: String = Bundle.main.path(forResource: FVConstants.keyboardsPackage,
|
|
ofType: FVConstants.keyboardsPackageExt,
|
|
inDirectory: FVConstants.keyboardsPath)!
|
|
let pathUrl = URL(fileURLWithPath: keyboardPackagePath)
|
|
let keyboardsPackage: KeyboardKeymanPackage
|
|
do {
|
|
let package = try ResourceFileManager.shared.prepareKMPInstall(from: pathUrl)
|
|
guard package as? KeyboardKeymanPackage != nil else {
|
|
print("Failed to load \(FVConstants.keyboardsPackage).\(FVConstants.keyboardsPackageExt)")
|
|
return
|
|
}
|
|
|
|
keyboardsPackage = (package as? KeyboardKeymanPackage)!
|
|
} catch {
|
|
print("Failed to load \(FVConstants.keyboardsPackage).\(FVConstants.keyboardsPackageExt)")
|
|
return
|
|
}
|
|
|
|
// Iterate through the available keyboards
|
|
for region in keyboardList {
|
|
let keyboards = region.keyboards
|
|
for kb in keyboards {
|
|
if loadedKeyboards.contains(kb.id) {
|
|
// Install the keyboard from its package.
|
|
do {
|
|
// Find the matching keyboard entry from the package.
|
|
// We currently rely on the package to track each keyboard's available language tags.
|
|
if let packageKbId = keyboardsPackage.installables.first(where: { entry in
|
|
// Each entry in the returned array corresponds to one supported language for the keyboard.
|
|
entry.contains { $0.id == kb.id }
|
|
// We only install the first available language. Easy to change, though.
|
|
}).map({ $0[0] })?.fullID { // then
|
|
try ResourceFileManager.shared.install(resourceWithID: packageKbId, from: keyboardsPackage)
|
|
} else {
|
|
print("Keyboard "+kb.id+" not found in primary keyboards package")
|
|
continue
|
|
}
|
|
} catch {
|
|
print("Failed to load preload "+kb.id+": " + error.localizedDescription)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class FVKeyboardDefinition {
|
|
let name: String
|
|
let keyboardId: String
|
|
let keyboardVersion: String
|
|
let languageTag: String
|
|
let languageName: String
|
|
|
|
init(name: String, keyboardId: String, keyboardVersion: String, languageTag: String, languageName: String) {
|
|
self.name = name
|
|
self.keyboardId = keyboardId
|
|
self.keyboardVersion = keyboardVersion
|
|
self.languageTag = languageTag
|
|
self.languageName = languageName
|
|
}
|
|
}
|