From 848d9f89afa2aecbced8b3a67f149b0ac71db82a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 31 Mar 2022 12:16:07 +0800 Subject: [PATCH] Main // Use IME module to manage (+un)installation, etc. --- Source/Modules/IME.swift | 113 +++++++++++++++++++++++++++++++++++++- Source/Modules/main.swift | 51 ++--------------- 2 files changed, 115 insertions(+), 49 deletions(-) diff --git a/Source/Modules/IME.swift b/Source/Modules/IME.swift index 38f65151..df9e3666 100644 --- a/Source/Modules/IME.swift +++ b/Source/Modules/IME.swift @@ -22,15 +22,19 @@ import Cocoa static let dlgOpenPath = NSOpenPanel(); - // MARK: - Functions - - // Print debug information to the console. + // MARK: - Print debug information to the console. @objc static func prtDebugIntel(_ strPrint: String) { if mgrPrefs.isDebugModeEnabled { NSLog("vChewingErrorCallback: %@", strPrint) } } + // MARK: - Tell whether this IME is running with Root privileges. + @objc static var isSudoMode: Bool { + NSUserName() == "root" + } + + // MARK: - Initializing Language Models. @objc static func initLangModels(userOnly: Bool) { if !userOnly { mgrLangModel.loadDataModels() // 這句還是不要砍了。 @@ -43,6 +47,7 @@ import Cocoa mgrLangModel.loadUserAssociatedPhrases() } + // MARK: - System Dark Mode Status Detector. @objc static func isDarkMode() -> Bool { if #available(macOS 10.15, *) { let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription.lowercased() @@ -58,4 +63,106 @@ import Cocoa } return false } + + // MARK: - Trash a file if it exists. + @discardableResult static func trashTargetIfExists(_ path: String) -> Bool { + do { + if FileManager.default.fileExists(atPath: path) { + // 塞入垃圾桶 + try FileManager.default.trashItem(at: URL(fileURLWithPath: path), resultingItemURL: nil) + } else { + NSLog("Item doesn't exist: \(path)") + } + } catch let error as NSError { + NSLog("Failed from removing this object: \(path) || Error: \(error)") + return false + } + return true + } + // MARK: - Uninstalling the input method. + @discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 { + // 輸入法自毀處理。這裡不用「Bundle.main.bundleURL」是為了方便使用者以 sudo 身分來移除被錯誤安裝到系統目錄內的輸入法。 + guard let bundleID = Bundle.main.bundleIdentifier else { + NSLog("Failed to ensure the bundle identifier.") + return -1 + } + + let kTargetBin = "vChewing" + let kTargetBundle = "/vChewing.app" + let pathLibrary = isSudo ? "/Library" : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path + let pathIMELibrary = isSudo ? "/Library/Input Methods" : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path + let pathUnitKeyboardLayouts = "/Keyboard Layouts" + let arrKeyLayoutFiles = ["/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", "/vChewing Dachen.keylayout"] + + // 先移除各種鍵盤佈局。 + for objPath in arrKeyLayoutFiles { + let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath + if !IME.trashTargetIfExists(objFullPath) { return -1 } + } + if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" && CommandLine.arguments[1] == "uninstall" { + // 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。 + // 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。 + // 目前暫時無法應對 symbol link 的情況。 + IME.trashTargetIfExists(mgrLangModel.dataFolderPath(isDefaultFolder: true)) + IME.trashTargetIfExists(pathLibrary + "/Preferences/" + bundleID + ".plist") // 之後移除 App 偏好設定 + } + if !IME.trashTargetIfExists(pathIMELibrary + kTargetBundle) { return -1 } // 最後移除 App 自身 + // 幹掉殘留在記憶體內的執行緒。 + if selfKill { + let killTask = Process() + killTask.launchPath = "/usr/bin/killall" + killTask.arguments = ["-9", kTargetBin] + killTask.launch() + killTask.waitUntilExit() + } + return 0 + } + + // MARK: - Registering the input method. + @discardableResult static func registerInputMethod() -> Int32 { + guard let bundleID = Bundle.main.bundleIdentifier else { + return -1 + } + let bundleUrl = Bundle.main.bundleURL + var maybeInputSource = InputSourceHelper.inputSource(for: bundleID) + + if maybeInputSource == nil { + NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)"); + // then register + let status = InputSourceHelper.registerTnputSource(at: bundleUrl) + + if !status { + NSLog("Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString).") + return -1 + } + + maybeInputSource = InputSourceHelper.inputSource(for: bundleID) + } + + guard let inputSource = maybeInputSource else { + NSLog("Fatal error: Cannot find input source \(bundleID) after registration.") + return -1 + } + + if !InputSourceHelper.inputSourceEnabled(for: inputSource) { + NSLog("Enabling input source \(bundleID) at \(bundleUrl.absoluteString).") + let status = InputSourceHelper.enable(inputSource: inputSource) + if !status { + NSLog("Fatal error: Cannot enable input source \(bundleID).") + return -1 + } + if !InputSourceHelper.inputSourceEnabled(for: inputSource) { + NSLog("Fatal error: Cannot enable input source \(bundleID).") + return -1 + } + } + + if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" { + let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) + NSLog(enabled ? "All input sources enabled for \(bundleID)" : "Cannot enable all input sources for \(bundleID), but this is ignored") + } + return 0 + } + + } diff --git a/Source/Modules/main.swift b/Source/Modules/main.swift index 4bf09c56..9996268f 100644 --- a/Source/Modules/main.swift +++ b/Source/Modules/main.swift @@ -20,56 +20,15 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH import Cocoa import InputMethodKit -private func install() -> Int32 { - guard let bundleID = Bundle.main.bundleIdentifier else { - return -1 - } - let bundleUrl = Bundle.main.bundleURL - var maybeInputSource = InputSourceHelper.inputSource(for: bundleID) - - if maybeInputSource == nil { - NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)"); - // then register - let status = InputSourceHelper.registerTnputSource(at: bundleUrl) - - if !status { - NSLog("Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString).") - return -1 - } - - maybeInputSource = InputSourceHelper.inputSource(for: bundleID) - } - - guard let inputSource = maybeInputSource else { - NSLog("Fatal error: Cannot find input source \(bundleID) after registration.") - return -1 - } - - if !InputSourceHelper.inputSourceEnabled(for: inputSource) { - NSLog("Enabling input source \(bundleID) at \(bundleUrl.absoluteString).") - let status = InputSourceHelper.enable(inputSource: inputSource) - if !status { - NSLog("Fatal error: Cannot enable input source \(bundleID).") - return -1 - } - if !InputSourceHelper.inputSourceEnabled(for: inputSource) { - NSLog("Fatal error: Cannot enable input source \(bundleID).") - return -1 - } - } - - if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" { - let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) - NSLog(enabled ? "All input sources enabled for \(bundleID)" : "Cannot enable all input sources for \(bundleID), but this is ignored") - } - return 0 -} - let kConnectionName = "vChewing_1_Connection" if CommandLine.arguments.count > 1 { if CommandLine.arguments[1] == "install" { - let exitCode = install() + let exitCode = IME.registerInputMethod() + exit(exitCode) + } + if CommandLine.arguments[1] == "uninstall" { + let exitCode = IME.uninstall(isSudo: IME.isSudoMode) exit(exitCode) } }