mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-15 12:07:42 +00:00
269 lines
8.5 KiB
Swift
269 lines
8.5 KiB
Swift
/*
|
|
* Keyman is copyright (C) SIL Global. MIT License.
|
|
*
|
|
* Created by Shawn Schantz on 2026-02-24
|
|
*
|
|
* Represents the state of the installation of the Keyman input method
|
|
* Communicates with the Keyman input method when the configuration is changed
|
|
*
|
|
*/
|
|
import SwiftUI
|
|
import Combine
|
|
import KeymanSettings
|
|
|
|
public class InstallationContainer : ObservableObject {
|
|
@Published public var state: String
|
|
|
|
fileprivate let defaultsRepository: DefaultsRepo
|
|
fileprivate let notificationCenter:DistributedNotificationCenter
|
|
fileprivate let inputMethodUtil: InputMethodUtil
|
|
|
|
public init() {
|
|
// create the settings repository, gaining access to the app group UserDefaults
|
|
do {
|
|
try self.defaultsRepository = DefaultsRepository(suiteName: KeymanPaths.groupId)
|
|
print("Found group container")
|
|
} catch UserDefaultsError.unknownSuite {
|
|
fatalError("Group container not found.")
|
|
} catch {
|
|
fatalError("Unable to access settings in group container.")
|
|
}
|
|
|
|
notificationCenter = DistributedNotificationCenter.default()
|
|
inputMethodUtil = InputMethodUtil()
|
|
state = self.defaultsRepository.readInstallationState()
|
|
|
|
if self.isInstallationComplete() {
|
|
self.validateInstallationComplete()
|
|
}
|
|
}
|
|
|
|
public func isInstallationComplete() -> Bool {
|
|
let isComplete = self.state == kInstallComplete
|
|
|
|
print("isInstallationComplete, state: \(self.state)")
|
|
|
|
return isComplete
|
|
}
|
|
|
|
/**
|
|
* Check the state of the installation to make sure it is really complete.
|
|
* Something may have been tampered with since the last time the configuration app was run.
|
|
*/
|
|
public func validateInstallationComplete() {
|
|
// TODO: check the state of the installation and ensure that is complete
|
|
}
|
|
|
|
public func executeNextInstallationStep() {
|
|
let originalState = self.state
|
|
|
|
if self.state == kNewInstall {
|
|
_ = self.migrateData()
|
|
} else if self.state == kMigrationComplete {
|
|
_ = self.registerKeymanInputMethod()
|
|
} else if self.state == kInputMethodRegistered {
|
|
_ = self.enableKeymanInputMethod()
|
|
} else if self.state == kInputMethodEnabled {
|
|
_ = self.requestAccessibilityPermission()
|
|
} else if self.state == kAccessGranted {
|
|
_ = self.promptUserToRestart()
|
|
} else if self.state == kRestartRequired {
|
|
_ = self.validateRestarted()
|
|
}
|
|
|
|
if (self.state != originalState) {
|
|
self.defaultsRepository.writeInstallationState(self.state)
|
|
}
|
|
}
|
|
|
|
public func migrateData() -> Bool {
|
|
let success = self.inputMethodUtil.invokeKeymanInputMethodMigration()
|
|
if (success) {
|
|
self.state = kMigrationComplete
|
|
}
|
|
print("migration suceeded: \(success)")
|
|
|
|
return success
|
|
}
|
|
|
|
public func checkAccessibilityPermission() {
|
|
if (inputMethodUtil.isKeymanInputMethodRunning()) {
|
|
let killed = self.inputMethodUtil.killKeymanInputMethod()
|
|
print("checkAccessibilityPermission, Keyman input method killed: \(killed)")
|
|
} else {
|
|
print("checkAccessibilityPermission, Keyman input method not running")
|
|
}
|
|
|
|
let hasAccess = self.inputMethodUtil.invokeKeymanInputMethodCheckAccess()
|
|
print("checkAccessibilityPermission, Keyman input method has permission: \(hasAccess)")
|
|
}
|
|
|
|
public func requestAccessibilityPermission() -> Bool {
|
|
let grantedAccess = self.inputMethodUtil.invokeKeymanInputMethodGrantAccess()
|
|
if (grantedAccess) {
|
|
self.state = kAccessGranted
|
|
}
|
|
print("requestAccess suceeded: \(grantedAccess)")
|
|
|
|
return grantedAccess
|
|
}
|
|
|
|
/**
|
|
* save the time that the user was requested to restart their machine
|
|
*/
|
|
fileprivate func writeRestartRequestTime() {
|
|
self.defaultsRepository.writeRestartRequestTime(Date())
|
|
}
|
|
|
|
/**
|
|
* get the time that the user was requested to restart their machine
|
|
*/
|
|
fileprivate func readRestartRequestTime() -> Date? {
|
|
return self.defaultsRepository.readRestartRequestTime()
|
|
}
|
|
|
|
/**
|
|
* check whether the user has restarted by comparing the latest startup time to the time we requested the user to restart
|
|
*/
|
|
public func promptUserToRestart() -> Bool {
|
|
self.writeRestartRequestTime()
|
|
self.state = kRestartRequired
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* check whether the user has restarted by comparing the latest startup time to the time we requested the user to restart
|
|
*/
|
|
public func validateRestarted() -> Bool {
|
|
var hasRestarted = false
|
|
|
|
if let timeRestartRequested = self.defaultsRepository.readRestartRequestTime() {
|
|
if let mostRecentStartupTime = self.getMostRecentRestartTime() {
|
|
hasRestarted = mostRecentStartupTime > timeRestartRequested
|
|
print("mostRecentStartupTime: \(mostRecentStartupTime), timeRestartRequested: \(timeRestartRequested)")
|
|
self.markInstallationComplete()
|
|
}
|
|
}
|
|
print("validateRestarted: \(hasRestarted)")
|
|
return hasRestarted
|
|
}
|
|
|
|
func resetInstallation() {
|
|
self.state = kNewInstall
|
|
self.defaultsRepository.writeInstallationState(kNewInstall)
|
|
self.defaultsRepository.clearRestartRequestTime()
|
|
}
|
|
|
|
func markInstallationComplete() {
|
|
self.state = kInstallComplete
|
|
self.defaultsRepository.writeInstallationState(kInstallComplete)
|
|
self.defaultsRepository.clearRestartRequestTime()
|
|
}
|
|
|
|
/**
|
|
* return the last time the system was booted
|
|
*/
|
|
func getMostRecentRestartTime() -> Date? {
|
|
var timeSince1970 = timeval()
|
|
var size = MemoryLayout<timeval>.size
|
|
|
|
// Query the kernel for the boot time
|
|
let result = sysctlbyname("kern.boottime", &timeSince1970, &size, nil, 0)
|
|
|
|
if result == 0 {
|
|
// Convert the timeval (seconds since 1970) into a Swift Date
|
|
return Date(timeIntervalSince1970: Double(timeSince1970.tv_sec) + Double(timeSince1970.tv_usec) / 1_000_000.0)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// if let bootDate = getLastRestartTime() {
|
|
// print("Last restart time: \(bootDate)")
|
|
// }
|
|
|
|
public func debug() {
|
|
let version = inputMethodUtil.getKeymanInputMethodVersion()
|
|
let enabled = inputMethodUtil.isKeymanInputMethodEnabled()
|
|
let running = inputMethodUtil.isKeymanInputMethodRunning()
|
|
print("Keyman status, version: \(version ?? ""), enabled: \(enabled), running: \(running)")
|
|
}
|
|
|
|
public func registerKeymanInputMethod() -> Bool {
|
|
let success = self.inputMethodUtil.registerKeymanInputMethod()
|
|
if (success) {
|
|
self.state = kInputMethodRegistered
|
|
}
|
|
print("registerKeymanInputMethod suceeded: \(success)")
|
|
|
|
return success
|
|
}
|
|
|
|
public func selectKeymanInputMethod() -> Bool {
|
|
let success = self.inputMethodUtil.selectKeymanInputMethod()
|
|
if (success) {
|
|
self.state = kInputMethodSelected
|
|
}
|
|
print("selectKeymanInputMethod suceeded: \(success)")
|
|
|
|
return success
|
|
}
|
|
|
|
public func enableKeymanInputMethod() -> Bool {
|
|
let success = self.inputMethodUtil.enableKeymanInputMethod()
|
|
if (success) {
|
|
self.state = kInputMethodEnabled
|
|
}
|
|
print("enableKeymanInputMethod suceeded: \(success)")
|
|
|
|
return success
|
|
}
|
|
|
|
public func runKeymanInputMethod() -> Bool {
|
|
return self.inputMethodUtil.runKeymanInputMethod()
|
|
}
|
|
|
|
public func killKeymanInputMethod() -> Bool {
|
|
return self.inputMethodUtil.killKeymanInputMethod()
|
|
}
|
|
|
|
public func disableKeymanInputMethod() -> Bool {
|
|
return self.inputMethodUtil.disableKeymanInputMethod()
|
|
}
|
|
|
|
public func uninstall() {
|
|
self.inputMethodUtil.uninstallKeyman()
|
|
}
|
|
|
|
/*
|
|
public init() {
|
|
self.settings = SettingsContainer()
|
|
|
|
notificationCenter = DistributedNotificationCenter.default()
|
|
self.keyboardPackages = []
|
|
|
|
if let keyboardsUrl = self.pathUtil.keymanKeyboardsDirectory {
|
|
|
|
if FileManager.default.fileExists(atPath: keyboardsUrl.path) {
|
|
ConfigLogger.shared.testLogger.debug("directory exists: \(keyboardsUrl.absoluteString)")
|
|
} else {
|
|
ConfigLogger.shared.testLogger.debug("non-existent directory: \(keyboardsUrl.absoluteString)")
|
|
}
|
|
}
|
|
|
|
load keyboards from disk
|
|
let packageSourceArray = self.dataRepo.readKeyboardPackageSource()
|
|
|
|
create a KeymanPackage object for each PackageSource and insert it in the array
|
|
for source in packageSourceArray {
|
|
let package = KeymanPackage(packageSource: source)
|
|
self.keyboardPackages.append(package)
|
|
}
|
|
|
|
ConfigLogger.shared.testLogger.debug("posting to notification center from KeyFig")
|
|
notificationCenter.postNotificationName(NSNotification.Name("com.keyman.removedkeyboard"), object: nil, userInfo: ["data": "khmer angkor"], deliverImmediately: false)
|
|
|
|
}
|
|
*/
|
|
|
|
}
|