From 412e48c1ebb23bdc29aef5c0a73bcd1493ea57de Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 13:55:46 +0530 Subject: [PATCH 01/11] Use Logger --- Input Source/VarnamController.swift | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Input Source/VarnamController.swift b/Input Source/VarnamController.swift index 686034f..5412fa9 100644 --- a/Input Source/VarnamController.swift +++ b/Input Source/VarnamController.swift @@ -1,6 +1,9 @@ /* * VarnamIME is a user-configurable phonetic Input Method Engine for Mac OS X. - * Copyright (C) 2018 Ranganath Atreya + * Copyright (C) 2018 Ranganath Atreya - LipikaIME + * https://github.com/ratreya/lipika-ime + * Copyright (C) 2021 Subin Siby - VarnamIME + * https://github.com/varnamproject/varnam-macOS * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -11,6 +14,7 @@ import InputMethodKit import Carbon.HIToolbox class Log { + // TODO implement setting from app to enable log levels public static func warning(_ text: Any) { print(text) } @@ -51,7 +55,7 @@ public class VarnamController: IMKInputController { super.init(server: server, delegate: delegate, client: inputClient) initVarnam() - print("Initialized Controller for Client: \(clientManager)") + Log.debug("Initialized Controller for Client: \(clientManager)") } func clearState() { @@ -204,7 +208,7 @@ public class VarnamController: IMKInputController { /// This message is sent when our client looses focus public override func deactivateServer(_ sender: Any!) { - print("Client: \(clientManager) loosing focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Log.debug("Client: \(clientManager) loosing focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") // Do this in case the application is quitting, otherwise we will end up with a SIGSEGV dispatch.cancelAll() clearState() @@ -212,17 +216,17 @@ public class VarnamController: IMKInputController { /// This message is sent when our client gains focus public override func activateServer(_ sender: Any!) { - print("Client: \(clientManager) gained focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Log.debug("Client: \(clientManager) gained focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") // There are three sources for current script selection - (a) self.currentScriptName, (b) config.scriptName and (c) selectedMenuItem.title // (b) could have changed while we were in background - converge (a) -> (b) if global script selection is configured if config.globalScriptSelection, currentScriptName != config.scriptName { - print("Refreshing Literators from: \(currentScriptName) to: \(config.scriptName)") + Log.debug("Refreshing Literators from: \(currentScriptName) to: \(config.scriptName)") // refreshLiterators() aka initVarnam() be called again ? } } public override func menu() -> NSMenu! { - print("Returning menu") + Log.debug("Returning menu") // Set the system trey menu selection to reflect our literators; converge (c) -> (a) let systemTrayMenu = (NSApp.delegate as! AppDelegate).systemTrayMenu! systemTrayMenu.items.forEach() { $0.state = .off } @@ -231,23 +235,23 @@ public class VarnamController: IMKInputController { } public override func candidates(_ sender: Any!) -> [Any]! { - print("Returning Candidates for sender: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Log.debug("Returning Candidates for sender: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") return clientManager.candidates } public override func candidateSelected(_ candidateString: NSAttributedString!) { - print("Candidate selected: \(candidateString!)") + Log.debug("Candidate selected: \(candidateString!)") commitText(candidateString.string) } public override func commitComposition(_ sender: Any!) { - print("Commit Composition called by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Log.debug("Commit Composition called by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") commit() } @objc public func menuItemSelected(sender: NSDictionary) { let item = sender.value(forKey: kIMKCommandMenuItemName) as! NSMenuItem - print("Menu Item Selected: \(item.title)") + Log.debug("Menu Item Selected: \(item.title)") // Converge (b) -> (c) config.scriptName = item.representedObject as! String // Converge (a) -> (b) From 74f6545a20c15d80373e796f1d956287d77fc341 Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 14:55:28 +0530 Subject: [PATCH 02/11] Improve varnam initing --- GoVarnam/Varnam.swift | 4 ++++ Input Source/VarnamController.swift | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/GoVarnam/Varnam.swift b/GoVarnam/Varnam.swift index 6c5f429..0ce4681 100644 --- a/GoVarnam/Varnam.swift +++ b/GoVarnam/Varnam.swift @@ -39,6 +39,10 @@ public class Varnam { throw VarnamException(getLastError()) } } + + public func close() { + varnam_close(varnamHandle) + } public func transliterate(_ input: String) -> [String] { var arr: UnsafeMutablePointer? = varray_init() diff --git a/Input Source/VarnamController.swift b/Input Source/VarnamController.swift index 5412fa9..8ea7191 100644 --- a/Input Source/VarnamController.swift +++ b/Input Source/VarnamController.swift @@ -15,6 +15,9 @@ import Carbon.HIToolbox class Log { // TODO implement setting from app to enable log levels + public static func error(_ text: Any) { + print(text) + } public static func warning(_ text: Any) { print(text) } @@ -35,11 +38,18 @@ public class VarnamController: IMKInputController { private var cursorPos = 0 private var preedit = "" private (set) var candidates = autoreleasepool { return [String]() } - private (set) var varnam: Varnam! + private (set) var varnam: Varnam! = nil private func initVarnam() { + if (varnam != nil) { + varnam.close() + } currentScriptName = config.scriptName - varnam = try! Varnam("ml") + do { + varnam = try Varnam("ml") + } catch let error { + Log.error(error) + } } public override init!(server: IMKServer, delegate: Any!, client inputClient: Any) { From a105890fe0f1d19ff40797a7bb0ecdb21b87f235 Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 15:40:43 +0530 Subject: [PATCH 03/11] Include VST, VLF in bundle resources --- .gitmodules | 3 ++ GoVarnam/.gitignore | 3 ++ GoVarnam/Varnam.swift | 19 ++++++++++-- GoVarnam/schemes | 1 + GoVarnam/update_assets.py | 45 +++++++++++++++++++++++++++++ Input Source/VarnamController.swift | 17 ++++++----- VarnamIME.xcodeproj/project.pbxproj | 15 ++++++++++ 7 files changed, 94 insertions(+), 9 deletions(-) create mode 160000 GoVarnam/schemes create mode 100644 GoVarnam/update_assets.py diff --git a/.gitmodules b/.gitmodules index d5900aa..d537bf4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "GoVarnam/govarnam"] path = GoVarnam/govarnam url = git@github.com:varnamproject/govarnam.git +[submodule "GoVarnam/schemes"] + path = GoVarnam/schemes + url = https://github.com/varnamproject/schemes.git diff --git a/GoVarnam/.gitignore b/GoVarnam/.gitignore index e5d0467..b0d44ce 100644 --- a/GoVarnam/.gitignore +++ b/GoVarnam/.gitignore @@ -1 +1,4 @@ *.dylib +assets/ +*.vst +*.vlf diff --git a/GoVarnam/Varnam.swift b/GoVarnam/Varnam.swift index 0ce4681..05e96ac 100644 --- a/GoVarnam/Varnam.swift +++ b/GoVarnam/Varnam.swift @@ -20,10 +20,26 @@ struct VarnamException: Error { } } +func strToCStr(_ str: String) -> UnsafeMutablePointer? { + return UnsafeMutablePointer(mutating: (str as NSString).utf8String) +} + public class Varnam { private var varnamHandle: Int32 = 0; + // This will only run once + struct Once { + static let once = Once() + init() { + let assetsFolderPath = Bundle.main.resourceURL!.appendingPathComponent("assets").path + print(assetsFolderPath) + varnam_set_vst_lookup_dir(strToCStr(assetsFolderPath)) + } + } + internal init(_ schemeID: String = "ml") throws { + _ = Once.once + schemeID.withCString { let rc = varnam_init_from_id(UnsafeMutablePointer(mutating: $0), &varnamHandle) try! checkError(rc) @@ -46,11 +62,10 @@ public class Varnam { public func transliterate(_ input: String) -> [String] { var arr: UnsafeMutablePointer? = varray_init() - let cInput = (input as NSString).utf8String varnam_transliterate( varnamHandle, 1, - UnsafeMutablePointer(mutating: cInput), + strToCStr(input), &arr ) diff --git a/GoVarnam/schemes b/GoVarnam/schemes new file mode 160000 index 0000000..0fd013a --- /dev/null +++ b/GoVarnam/schemes @@ -0,0 +1 @@ +Subproject commit 0fd013ada3ee8c27952badbebf0993c8e686bcf7 diff --git a/GoVarnam/update_assets.py b/GoVarnam/update_assets.py new file mode 100644 index 0000000..703d0f9 --- /dev/null +++ b/GoVarnam/update_assets.py @@ -0,0 +1,45 @@ +import gzip +import json +from os.path import basename +from pathlib import Path +import shutil +from urllib import request + +# Copies .vst, .vlf from schemes folder to assets +# You need to build inside schemes first before running this script +# Use build_all_packs.sh script to do that + +def copyScheme(schemeID): + programDir = str(Path(__file__).parent.absolute()) + source = programDir + '/schemes/schemes/' + schemeID + target = programDir + '/assets' + + packsInfo = [] + + for path in Path(source + '/').rglob('*'): + if basename(path) == schemeID + '.vst': + shutil.copy2(path, target) + continue + + for packPath in Path(path).rglob('*'): + if basename(packPath) == 'pack.json': + packsInfo.append(json.load(open(packPath, 'r'))) + continue + + if ".vlf" not in basename(packPath): + continue + + with open( + packPath, 'rb' + ) as f_in, gzip.open( + target + '/' + basename(packPath), + 'wb' + ) as f_out: + f_out.writelines(f_in) + + with open(target + '/packs.json', 'w') as f: + json.dump(packsInfo, f, ensure_ascii=False) + +# For now just Malayalam for Indic Keyboard +for schemeID in ["ml"]: + copyScheme(schemeID) diff --git a/Input Source/VarnamController.swift b/Input Source/VarnamController.swift index 8ea7191..2268d49 100644 --- a/Input Source/VarnamController.swift +++ b/Input Source/VarnamController.swift @@ -33,20 +33,23 @@ public class VarnamController: IMKInputController { let config = VarnamConfig() let dispatch = AsyncDispatcher() private let clientManager: ClientManager - private var currentScriptName = "" private var cursorPos = 0 private var preedit = "" private (set) var candidates = autoreleasepool { return [String]() } + + private var schemeID = "ml" private (set) var varnam: Varnam! = nil private func initVarnam() { if (varnam != nil) { varnam.close() } - currentScriptName = config.scriptName + // TODO add schemeID to config + // config.scriptName is "Malayalam", change that to shortcode "ml" + schemeID = "ml" do { - varnam = try Varnam("ml") + varnam = try Varnam(schemeID) } catch let error { Log.error(error) } @@ -229,9 +232,9 @@ public class VarnamController: IMKInputController { Log.debug("Client: \(clientManager) gained focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") // There are three sources for current script selection - (a) self.currentScriptName, (b) config.scriptName and (c) selectedMenuItem.title // (b) could have changed while we were in background - converge (a) -> (b) if global script selection is configured - if config.globalScriptSelection, currentScriptName != config.scriptName { - Log.debug("Refreshing Literators from: \(currentScriptName) to: \(config.scriptName)") -// refreshLiterators() aka initVarnam() be called again ? + if config.globalScriptSelection, schemeID != config.scriptName { + Log.debug("Initializing varnam: \(schemeID) to: \(config.scriptName)") + initVarnam() } } @@ -240,7 +243,7 @@ public class VarnamController: IMKInputController { // Set the system trey menu selection to reflect our literators; converge (c) -> (a) let systemTrayMenu = (NSApp.delegate as! AppDelegate).systemTrayMenu! systemTrayMenu.items.forEach() { $0.state = .off } - systemTrayMenu.items.first(where: { ($0.representedObject as! String) == currentScriptName } )?.state = .on + systemTrayMenu.items.first(where: { ($0.representedObject as! String) == schemeID } )?.state = .on return systemTrayMenu } diff --git a/VarnamIME.xcodeproj/project.pbxproj b/VarnamIME.xcodeproj/project.pbxproj index 4f37aed..623aa9b 100644 --- a/VarnamIME.xcodeproj/project.pbxproj +++ b/VarnamIME.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 04D0A680273FC2BC006C3B54 /* libgovarnam.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 04D0A67F273FC2BC006C3B54 /* libgovarnam.dylib */; }; 04D0A681273FC2C0006C3B54 /* libgovarnam.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04D0A67F273FC2BC006C3B54 /* libgovarnam.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 04D0A683273FC367006C3B54 /* Varnam.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D0A682273FC367006C3B54 /* Varnam.swift */; }; + 04D0A697274112BE006C3B54 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 04D0A696274112BE006C3B54 /* assets */; }; A5122A4E20D42A2000575848 /* InputSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53AD01A20CC658000C95844 /* InputSource.swift */; }; A5122A4F20D42A2000575848 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5122A4220D420E100575848 /* main.swift */; }; A5122A5020D42A9500575848 /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A546182A20CC6BF3003A73EB /* InputMethodKit.framework */; }; @@ -60,6 +61,16 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 04D0A69527411260006C3B54 /* Copy Files */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + ); + name = "Copy Files"; + runOnlyForDeploymentPostprocessing = 0; + }; A53AD01320CC643700C95844 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -90,6 +101,7 @@ 04D0A67B273FC20D006C3B54 /* VarnamIME-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VarnamIME-Bridging-Header.h"; sourceTree = ""; }; 04D0A67F273FC2BC006C3B54 /* libgovarnam.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libgovarnam.dylib; sourceTree = ""; }; 04D0A682273FC367006C3B54 /* Varnam.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Varnam.swift; sourceTree = ""; }; + 04D0A696274112BE006C3B54 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = ""; }; A5122A4220D420E100575848 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; A5122A4720D429D300575848 /* Installer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Installer; sourceTree = BUILT_PRODUCTS_DIR; }; A52905DC234EC67900A8D95E /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; @@ -169,6 +181,7 @@ 04D0A67A273FC1F1006C3B54 /* GoVarnam */ = { isa = PBXGroup; children = ( + 04D0A696274112BE006C3B54 /* assets */, 04D0A682273FC367006C3B54 /* Varnam.swift */, 04D0A67F273FC2BC006C3B54 /* libgovarnam.dylib */, 04D0A67B273FC20D006C3B54 /* VarnamIME-Bridging-Header.h */, @@ -316,6 +329,7 @@ A53ACFFA20CC347900C95844 /* Frameworks */, A53ACFFB20CC347900C95844 /* Resources */, A53AD01320CC643700C95844 /* Embed Frameworks */, + 04D0A69527411260006C3B54 /* Copy Files */, ); buildRules = ( ); @@ -403,6 +417,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 04D0A697274112BE006C3B54 /* assets in Resources */, A546182720CC6BB2003A73EB /* LICENSE in Resources */, A59281E120D45653003013B8 /* TrayIcon.icns in Resources */, A5BDA2F620D06FFC00C9D006 /* MainMenu.xib in Resources */, From 39b5c58c3ce42a76e1db6fe4495c66d15477216a Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 16:04:31 +0530 Subject: [PATCH 04/11] Get list of languages from varnam --- GoVarnam/Varnam.swift | 49 +++++++++++++++++++++++++++------ Input Source/VarnamConfig.swift | 23 ++++++++++++++-- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/GoVarnam/Varnam.swift b/GoVarnam/Varnam.swift index 05e96ac..fb5b7b3 100644 --- a/GoVarnam/Varnam.swift +++ b/GoVarnam/Varnam.swift @@ -8,7 +8,7 @@ import Foundation -struct VarnamException: Error { +public struct VarnamException: Error { let message: String init(_ message: String) { @@ -20,25 +20,36 @@ struct VarnamException: Error { } } -func strToCStr(_ str: String) -> UnsafeMutablePointer? { - return UnsafeMutablePointer(mutating: (str as NSString).utf8String) +public struct SchemeDetails { + var Identifier: String + var LangCode: String + var DisplayName: String + var Author: String + var CompiledDate: String + var IsStable: Bool +} + +extension String { + func toCStr() -> UnsafeMutablePointer? { + return UnsafeMutablePointer(mutating: (self as NSString).utf8String) + } } public class Varnam { private var varnamHandle: Int32 = 0; // This will only run once - struct Once { - static let once = Once() + struct VarnamInit { + static let once = VarnamInit() init() { let assetsFolderPath = Bundle.main.resourceURL!.appendingPathComponent("assets").path print(assetsFolderPath) - varnam_set_vst_lookup_dir(strToCStr(assetsFolderPath)) + varnam_set_vst_lookup_dir(assetsFolderPath.toCStr()) } } internal init(_ schemeID: String = "ml") throws { - _ = Once.once + _ = VarnamInit.once schemeID.withCString { let rc = varnam_init_from_id(UnsafeMutablePointer(mutating: $0), &varnamHandle) @@ -65,7 +76,7 @@ public class Varnam { varnam_transliterate( varnamHandle, 1, - strToCStr(input), + input.toCStr(), &arr ) @@ -78,4 +89,26 @@ public class Varnam { } return results } + + public static func getAllSchemeDetails() -> [SchemeDetails] { + _ = VarnamInit.once + + var schemes = [SchemeDetails]() + + let arr = varnam_get_all_scheme_details() + for i in (0.. Date: Sun, 14 Nov 2021 16:08:27 +0530 Subject: [PATCH 05/11] Move to schemeID instead of scriptName --- Input Source/AppDelegate.swift | 2 +- Input Source/VarnamController.swift | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Input Source/AppDelegate.swift b/Input Source/AppDelegate.swift index e82c3f1..f48bd2a 100644 --- a/Input Source/AppDelegate.swift +++ b/Input Source/AppDelegate.swift @@ -31,7 +31,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { item.keyEquivalentModifierMask = NSEvent.ModifierFlags(rawValue: flags) item.keyEquivalent = item.keyEquivalentModifierMask.contains(.shift) ? key : key.lowercased() } - if entry.identifier == config.scriptName { + if entry.identifier == config.schemeID { item.state = .on } item.representedObject = entry.identifier diff --git a/Input Source/VarnamController.swift b/Input Source/VarnamController.swift index 2268d49..03eb7b9 100644 --- a/Input Source/VarnamController.swift +++ b/Input Source/VarnamController.swift @@ -45,9 +45,7 @@ public class VarnamController: IMKInputController { if (varnam != nil) { varnam.close() } - // TODO add schemeID to config - // config.scriptName is "Malayalam", change that to shortcode "ml" - schemeID = "ml" + schemeID = config.schemeID do { varnam = try Varnam(schemeID) } catch let error { @@ -230,10 +228,10 @@ public class VarnamController: IMKInputController { /// This message is sent when our client gains focus public override func activateServer(_ sender: Any!) { Log.debug("Client: \(clientManager) gained focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") - // There are three sources for current script selection - (a) self.currentScriptName, (b) config.scriptName and (c) selectedMenuItem.title + // There are three sources for current script selection - (a) self.schemeID, (b) config.schemeID and (c) selectedMenuItem.title // (b) could have changed while we were in background - converge (a) -> (b) if global script selection is configured - if config.globalScriptSelection, schemeID != config.scriptName { - Log.debug("Initializing varnam: \(schemeID) to: \(config.scriptName)") + if config.globalScriptSelection, schemeID != config.schemeID { + Log.debug("Initializing varnam: \(schemeID) to: \(config.schemeID)") initVarnam() } } @@ -266,7 +264,7 @@ public class VarnamController: IMKInputController { let item = sender.value(forKey: kIMKCommandMenuItemName) as! NSMenuItem Log.debug("Menu Item Selected: \(item.title)") // Converge (b) -> (c) - config.scriptName = item.representedObject as! String + config.schemeID = item.representedObject as! String // Converge (a) -> (b) initVarnam() } From 6cbc5e6817988daae594b781e359cb7e737b66e0 Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 16:18:07 +0530 Subject: [PATCH 06/11] Remove dependency on LipikaEngine from IME --- Input Source/AppDelegate.swift | 1 - Input Source/Common.swift | 62 +++++++++++ Input Source/Config.swift | 44 ++++++++ Input Source/Logger.swift | 164 ++++++++++++++++++++++++++++ Input Source/MappingStore.swift | 73 ------------- Input Source/VarnamConfig.swift | 23 +--- VarnamIME.xcodeproj/project.pbxproj | 22 ++-- 7 files changed, 284 insertions(+), 105 deletions(-) create mode 100644 Input Source/Common.swift create mode 100644 Input Source/Config.swift create mode 100644 Input Source/Logger.swift delete mode 100644 Input Source/MappingStore.swift diff --git a/Input Source/AppDelegate.swift b/Input Source/AppDelegate.swift index f48bd2a..6dba9aa 100644 --- a/Input Source/AppDelegate.swift +++ b/Input Source/AppDelegate.swift @@ -8,7 +8,6 @@ */ import InputMethodKit -import LipikaEngine_OSX @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { diff --git a/Input Source/Common.swift b/Input Source/Common.swift new file mode 100644 index 0000000..cc2c5f9 --- /dev/null +++ b/Input Source/Common.swift @@ -0,0 +1,62 @@ +/* + * LipikaEngine is a multi-codepoint, user-configurable, phonetic, Transliteration Engine. + * Copyright (C) 2017 Ranganath Atreya + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +import Foundation + +func synchronize(_ lockObject: AnyObject, _ closure: () -> T) -> T { + objc_sync_enter(lockObject) + defer { objc_sync_exit(lockObject) } + return closure() +} + +func synchronize(_ lockObject: AnyObject, _ closure: () throws -> T) throws -> T { + objc_sync_enter(lockObject) + defer { objc_sync_exit(lockObject) } + return try closure() +} + +let keyBase = Bundle.main.bundleIdentifier ?? "LipikaEngine" + +func getThreadLocalData(key: String) -> Any? { + let fullKey: NSString = "\(keyBase).\(key)" as NSString + return Thread.current.threadDictionary.object(forKey: fullKey) +} + +func setThreadLocalData(key: String, value: Any) { + let fullKey: NSString = "\(keyBase).\(key)" as NSString + Thread.current.threadDictionary.setObject(value, forKey: fullKey) +} + +func removeThreadLocalData(key: String) { + let fullKey: NSString = "\(keyBase).\(key)" as NSString + Thread.current.threadDictionary.removeObject(forKey: fullKey) +} + +func filesInDirectory(directory: URL, withExtension ext: String) throws -> [String] { + let files = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: [], options: []) + return files.filter({$0.pathExtension == ext}).compactMap { $0.deletingPathExtension().lastPathComponent } +} + +extension String { + func unicodeScalars() -> [UnicodeScalar] { + return Array(self.unicodeScalars) + } + + func unicodeScalarReversed() -> String { + var result = "" + result.unicodeScalars.append(contentsOf: self.unicodeScalars.reversed()) + return result + } + + static func + (lhs: String, rhs: [UnicodeScalar]) -> String { + var stringRHS = "" + stringRHS.unicodeScalars.append(contentsOf: rhs) + return lhs + stringRHS + } +} diff --git a/Input Source/Config.swift b/Input Source/Config.swift new file mode 100644 index 0000000..90ab44e --- /dev/null +++ b/Input Source/Config.swift @@ -0,0 +1,44 @@ +/* + * LipikaEngine is a multi-codepoint, user-configurable, phonetic, Transliteration Engine. + * Copyright (C) 2018 Ranganath Atreya + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +import Foundation + +/// This class provides default config values that the client can override, typically using `UserDefaults` and pass an instance into `LiteratorFactory`. +open class Config { + /** + Empty public init to enable clients to call super.init() + */ + public init() {} + + /** + This character is used to break input aggregation. Typically this is the forward-slash character (`\`). + + __Example__: if `a` maps to `1` and `b` maps to `2` and `ab` maps to `3` then inputting `ab` will output `3` but inputting `a\b` will output `12` + */ + open var stopCharacter: UnicodeScalar { return "\\" } + + /** + All input characters enclosed by this character will be echoed to the output as-is and not converted. + + __Example__: if `a` maps to `1` and `b` maps to `2` and `ab` maps to `3` then inputting `ab` will output `3` but inputting `` `ab` `` will output `ab` + */ + open var escapeCharacter: UnicodeScalar { return "`" } + + /** + The URL path to the top-level directory where the schemes files are present. Usually this would return something like `Bundle.main.bundleURL.appendingPathComponent("Mapping")` + */ + open var mappingDirectory: URL { return Bundle(for: Config.self).bundleURL.appendingPathComponent("Resources", isDirectory: true).appendingPathComponent("Mapping", isDirectory: true) } + + /** + The level at which to NSLog log messages generated by LipikaEngine. + + - Important: This configuration only holds within the same thread in which `LiteratorFactory` was initialized. + */ + open var logLevel: Logger.Level { return .warning } +} diff --git a/Input Source/Logger.swift b/Input Source/Logger.swift new file mode 100644 index 0000000..ef683d7 --- /dev/null +++ b/Input Source/Logger.swift @@ -0,0 +1,164 @@ +/* + * LipikaEngine is a multi-codepoint, user-configurable, phonetic, Transliteration Engine. + * Copyright (C) 2017 Ranganath Atreya + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +import Foundation + +/** + If clients want to use the same log formatting and logger features of LipikaEngine, they are free to use this class. + Logger exposes a thread-local instance called `Logger.log` that needs to be used. Logger itself cannot be instantiated. + + - Important: `logLevel` is thread-local specific and not global. + - Note: message strings passed into Logger are @autoclosure and hence are not *evaluated* unless they are logged. + + __Usage__ + ``` + Logger.logLevel = .warning + Logger.log.debug("you don't need to know") + Logger.log.warning("you may want to know") + ``` + */ +public final class Logger { + + /// Enumeration of errors thrown from `Logger` + public enum LoggerError: Error { + /// Indicates that `startCapture` was invoked again without calling `endCapture` in between. + case alreadyCapturing + } + + /// Enumeration of logging levels in the decreasing order of verbosity and increasing order of importance: `Level.debug`, `Level.warning`, `Level.error`, `Level.fatal`. + public enum Level: String, CaseIterable { + /// Lots of informative messages only useful for developers while debugging + case debug = "Debug" + /// Some unexpected execution paths that may be useful for power-users + case warning = "Warning" + /// Only those errors that are real and cause visible issues to the end-users + case error = "Error" + /// Completely unexpected events that are usually indicative of fundamental bugs + case fatal = "Fatal" + + private var weight: Int { + switch self { + case .debug: + return 0 + case .warning: + return 1 + case .error: + return 2 + case .fatal: + return 3 + } + } + + static func < (lhs: Level, rhs: Level) -> Bool { + return lhs.weight < rhs.weight + } + static func > (lhs: Level, rhs: Level) -> Bool { + return lhs.weight > rhs.weight + } + static func >= (lhs: Level, rhs: Level) -> Bool { + return lhs.weight >= rhs.weight + } + static func <= (lhs: Level, rhs: Level) -> Bool { + return lhs.weight <= rhs.weight + } + } + + private static let logLevelKey = "logLevel" + private static let loggerInstanceKey = "logger" + + private var capture: [String]? + private let minLevel = Logger.logLevel + private init() { } + + deinit { + if let capture = self.capture { + log(level: .warning, message: "Log capture started but not ended with \(capture.count) log entries!") + } + } + + /// Thread-local singleton instance of Logger that clients must use. `Logger` itself cannot be instantiated. + public static var log: Logger { + var instance = getThreadLocalData(key: loggerInstanceKey) as? Logger + if instance == nil { + instance = Logger() + setThreadLocalData(key: loggerInstanceKey, value: instance!) + } + return instance! + } + + /** + Get or set the level at and after which logs will be recorded. + Levels with decreasing verbosity and increasing importance are `Level.debug`, `Level.warning`, `Level.error` and `Level.fatal`. + When a level of certain level of verbosity is set, all levels at and with lower verbosity are recorded. + + - Returns: Level or defaults to `Level.warning` if a log level has not been set on this thread + */ + public static var logLevel: Level { + get { + return getThreadLocalData(key: Logger.logLevelKey) as? Level ?? .warning + } + set(value) { + setThreadLocalData(key: Logger.logLevelKey, value: value) + removeThreadLocalData(key: Logger.loggerInstanceKey) + } + } + + private func log(level: Level, message: @autoclosure() -> String) { + if level < minLevel { return } + let log = "[\(level.rawValue)] \(message())" + NSLog(log) + if var capture = self.capture { + capture.append(log) + } + } + + /// Log the given message at `Level.debug` level of importance + public func debug(_ message: @autoclosure() -> String) { + log(level: .debug, message: message()) + } + + /// Log the given message at `Level.warning` level of importance + public func warning(_ message: @autoclosure() -> String) { + log(level: .warning, message: message()) + } + + /// Log the given message at `Level.error` level of importance + public func error(_ message: @autoclosure() -> String) { + log(level: .error, message: message()) + } + + /// Log the given message at `Level.fatal` level of importance + public func fatal(_ message: @autoclosure() -> String) { + log(level: .fatal, message: message()) + } + + /** + Start capturing all messages that is also going to be logged. + This is useful for programatically inspecting or showing logs to end users. + + - Throws: LoggerError.alreadyCapturing if this method is double invoked + */ + public func startCapture() throws { + if capture != nil { + throw LoggerError.alreadyCapturing + } + capture = [String]() + } + + /** + End capturing logs. + + - Returns: list of messages that were logged at or above the specified `logLevel`. + */ + public func endCapture() -> [String]? { + let result = capture + capture = nil + return result + } +} diff --git a/Input Source/MappingStore.swift b/Input Source/MappingStore.swift deleted file mode 100644 index 4f9e925..0000000 --- a/Input Source/MappingStore.swift +++ /dev/null @@ -1,73 +0,0 @@ -/* - * VarnamIME is a user-configurable phonetic Input Method Engine for Mac OS X. - * Copyright (C) 2018 Ranganath Atreya - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - */ - -import Foundation -import LipikaEngine_OSX - -class MappingStore { - - static let kAppGroupId = "group.varnamproject.Varnam" - - class func read(schemeName: String, scriptName: String) -> [[String]]? { - let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: kAppGroupId) - let customMap = container!.appendingPathComponent(schemeName + "-" + scriptName).appendingPathExtension("map") - if FileManager.default.fileExists(atPath: customMap.path) { - return NSArray(contentsOf: customMap) as? [[String]] - } - return nil - } - - class func read(schemeName: String, scriptName: String) -> [String: MappingValue]? { - if let denested: [[String]] = read(schemeName: schemeName, scriptName: scriptName) { - return nest(denested: denested) - } - return nil - } - - class func write(schemeName: String, scriptName: String, mappings: [[String]]) -> Bool { - let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: kAppGroupId) - let customMap = container!.appendingPathComponent(schemeName + "-" + scriptName).appendingPathExtension("map") - if FileManager.default.fileExists(atPath: customMap.path) { - Logger.log.warning("Overwriting mapping for \(schemeName) and \(scriptName)") - } - return (mappings as NSArray).write(to: customMap, atomically: true) - } - - class func write(schemeName: String, scriptName: String, mappings: [String: MappingValue]) -> Bool { - return write(schemeName: schemeName, scriptName: scriptName, mappings: denest(nested: mappings)) - } - - class func delete(schemeName: String, scriptName: String) { - let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: kAppGroupId) - let customMap = container!.appendingPathComponent(schemeName + "-" + scriptName).appendingPathExtension("map") - if FileManager.default.fileExists(atPath: customMap.path) { - try! FileManager.default.removeItem(at: customMap) - } - } - - class func denest(nested: [String: MappingValue]) -> [[String]] { - var mappings = [[String]]() - for type in nested.keys.sorted() { - for key in nested[type]!.keys { - if let script = nested[type]![key]!.script { - mappings.append([type, key, nested[type]![key]!.scheme.joined(separator: ", "), script]) - } - } - } - return mappings - } - - class func nest(denested: [[String]]) -> [String: MappingValue] { - var mappings = [String: MappingValue]() - for row in denested { - mappings[row[0], default: MappingValue()][row[1]] = (scheme: row[2].components(separatedBy: ",").map({$0.trimmingCharacters(in: CharacterSet.whitespaces)}), script: row[3]) - } - return mappings - } -} diff --git a/Input Source/VarnamConfig.swift b/Input Source/VarnamConfig.swift index 131cd0c..3e29d77 100644 --- a/Input Source/VarnamConfig.swift +++ b/Input Source/VarnamConfig.swift @@ -1,6 +1,7 @@ /* * VarnamIME is a user-configurable phonetic Input Method Engine for Mac OS X. - * Copyright (C) 2018 Ranganath Atreya + * Copyright (C) 2018 Ranganath Atreya - LipikaIME + * Copyright (C) 2021 Subin Siby - VarnamIME * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -8,7 +9,6 @@ */ import Foundation -import LipikaEngine_OSX struct LanguageConfig: Codable, Equatable, Hashable { var identifier: String // Factory default name of the language @@ -82,25 +82,6 @@ class VarnamConfig: Config { } } - var schemeName: String { - get { - return try! userDefaults.string(forKey: #function) ?? LiteratorFactory(config: self).availableSchemes().first! - } - set(value) { - userDefaults.set(value, forKey: #function) - } - } - - // TODO remove this - var scriptName: String { - get { - return userDefaults.string(forKey: #function) ?? languageConfig.first(where: { $0.isEnabled })?.identifier ?? languageConfig.first!.identifier - } - set(value) { - userDefaults.set(value, forKey: #function) - } - } - // Varnam schemeID to use var schemeID: String { get { diff --git a/VarnamIME.xcodeproj/project.pbxproj b/VarnamIME.xcodeproj/project.pbxproj index 623aa9b..a4777ae 100644 --- a/VarnamIME.xcodeproj/project.pbxproj +++ b/VarnamIME.xcodeproj/project.pbxproj @@ -11,14 +11,15 @@ 04D0A681273FC2C0006C3B54 /* libgovarnam.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04D0A67F273FC2BC006C3B54 /* libgovarnam.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 04D0A683273FC367006C3B54 /* Varnam.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D0A682273FC367006C3B54 /* Varnam.swift */; }; 04D0A697274112BE006C3B54 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 04D0A696274112BE006C3B54 /* assets */; }; + 04D0A69927411F47006C3B54 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D0A69827411F47006C3B54 /* Config.swift */; }; + 04D0A69B27411F6D006C3B54 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D0A69A27411F6D006C3B54 /* Logger.swift */; }; + 04D0A69D27411FB1006C3B54 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D0A69C27411FB1006C3B54 /* Common.swift */; }; A5122A4E20D42A2000575848 /* InputSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53AD01A20CC658000C95844 /* InputSource.swift */; }; A5122A4F20D42A2000575848 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5122A4220D420E100575848 /* main.swift */; }; A5122A5020D42A9500575848 /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A546182A20CC6BF3003A73EB /* InputMethodKit.framework */; }; A5122A5120D42AA600575848 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A546182820CC6BEA003A73EB /* Cocoa.framework */; }; A53AD00120CC347900C95844 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53AD00020CC347900C95844 /* AppDelegate.swift */; }; A53AD00320CC347900C95844 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A53AD00220CC347900C95844 /* Assets.xcassets */; }; - A53AD01220CC643700C95844 /* LipikaEngine_OSX.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A53AD00F20CC641900C95844 /* LipikaEngine_OSX.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - A53AD01520CC64FD00C95844 /* LipikaEngine_OSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A53AD00F20CC641900C95844 /* LipikaEngine_OSX.framework */; }; A53AD01720CC654700C95844 /* VarnamConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53AD01620CC654700C95844 /* VarnamConfig.swift */; }; A53AD01D20CC659C00C95844 /* ClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53AD01C20CC659C00C95844 /* ClientManager.swift */; }; A53AD01F20CC660900C95844 /* VarnamController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53AD01E20CC660900C95844 /* VarnamController.swift */; }; @@ -33,8 +34,6 @@ A546182920CC6BEA003A73EB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A546182820CC6BEA003A73EB /* Cocoa.framework */; }; A546182B20CC6BF3003A73EB /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A546182A20CC6BF3003A73EB /* InputMethodKit.framework */; }; A54B15D724900FC000E013A1 /* LiteratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54B15D624900FC000E013A1 /* LiteratorView.swift */; }; - A55381F521D703EA00A3CD8A /* MappingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55381F421D703EA00A3CD8A /* MappingStore.swift */; }; - A55381F621D706E500A3CD8A /* MappingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55381F421D703EA00A3CD8A /* MappingStore.swift */; }; A5698AE9247EFA8E00443158 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52905DC234EC67900A8D95E /* MainView.swift */; }; A5698AEA247EFA8E00443158 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58C00A32353705C00B53051 /* SettingsView.swift */; }; A585A17D24AC28FC00816D1E /* PersistenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A585A17C24AC28FC00816D1E /* PersistenceView.swift */; }; @@ -77,7 +76,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - A53AD01220CC643700C95844 /* LipikaEngine_OSX.framework in Embed Frameworks */, 04D0A681273FC2C0006C3B54 /* libgovarnam.dylib in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -102,6 +100,9 @@ 04D0A67F273FC2BC006C3B54 /* libgovarnam.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libgovarnam.dylib; sourceTree = ""; }; 04D0A682273FC367006C3B54 /* Varnam.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Varnam.swift; sourceTree = ""; }; 04D0A696274112BE006C3B54 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = ""; }; + 04D0A69827411F47006C3B54 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + 04D0A69A27411F6D006C3B54 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 04D0A69C27411FB1006C3B54 /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; A5122A4220D420E100575848 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; A5122A4720D429D300575848 /* Installer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Installer; sourceTree = BUILT_PRODUCTS_DIR; }; A52905DC234EC67900A8D95E /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; @@ -130,7 +131,6 @@ A546182820CC6BEA003A73EB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; A546182A20CC6BF3003A73EB /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = System/Library/Frameworks/InputMethodKit.framework; sourceTree = SDKROOT; }; A54B15D624900FC000E013A1 /* LiteratorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiteratorView.swift; sourceTree = ""; }; - A55381F421D703EA00A3CD8A /* MappingStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappingStore.swift; sourceTree = ""; }; A585A17C24AC28FC00816D1E /* PersistenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceView.swift; sourceTree = ""; }; A58C00A32353705C00B53051 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; A59281E020D45634003013B8 /* TrayIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = TrayIcon.icns; sourceTree = ""; }; @@ -161,7 +161,6 @@ A546182920CC6BEA003A73EB /* Cocoa.framework in Frameworks */, 04D0A680273FC2BC006C3B54 /* libgovarnam.dylib in Frameworks */, A546182B20CC6BF3003A73EB /* InputMethodKit.framework in Frameworks */, - A53AD01520CC64FD00C95844 /* LipikaEngine_OSX.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -220,8 +219,10 @@ A53AD00020CC347900C95844 /* AppDelegate.swift */, A53AD01C20CC659C00C95844 /* ClientManager.swift */, A53AD01E20CC660900C95844 /* VarnamController.swift */, - A55381F421D703EA00A3CD8A /* MappingStore.swift */, A5DC681E22335F72006FC519 /* AsyncDispatcher.swift */, + 04D0A69827411F47006C3B54 /* Config.swift */, + 04D0A69A27411F6D006C3B54 /* Logger.swift */, + 04D0A69C27411FB1006C3B54 /* Common.swift */, ); path = "Input Source"; sourceTree = ""; @@ -470,8 +471,10 @@ A53AD01F20CC660900C95844 /* VarnamController.swift in Sources */, 04D0A683273FC367006C3B54 /* Varnam.swift in Sources */, A53AD01720CC654700C95844 /* VarnamConfig.swift in Sources */, + 04D0A69B27411F6D006C3B54 /* Logger.swift in Sources */, A53AD01D20CC659C00C95844 /* ClientManager.swift in Sources */, - A55381F521D703EA00A3CD8A /* MappingStore.swift in Sources */, + 04D0A69927411F47006C3B54 /* Config.swift in Sources */, + 04D0A69D27411FB1006C3B54 /* Common.swift in Sources */, A5DC681F22335F72006FC519 /* AsyncDispatcher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -487,7 +490,6 @@ A59364ED2481D12100674FBD /* MappingsView.swift in Sources */, A5E20D8424944B6E00EF7B1C /* LanguageView.swift in Sources */, A5698AEA247EFA8E00443158 /* SettingsView.swift in Sources */, - A55381F621D706E500A3CD8A /* MappingStore.swift in Sources */, A585A17D24AC28FC00816D1E /* PersistenceView.swift in Sources */, A54397E020D4D6840011C7B7 /* VarnamConfig.swift in Sources */, A54397CC20D4D2F20011C7B7 /* AppDelegate.swift in Sources */, From 5da7546dd2e67abbd1550b1844268b9800a9c6f4 Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 16:24:14 +0530 Subject: [PATCH 07/11] Use Logger from Lipika-engine --- Input Source/ClientManager.swift | 15 ++++++------ Input Source/VarnamController.swift | 37 ++++++++++------------------- 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/Input Source/ClientManager.swift b/Input Source/ClientManager.swift index 0f801f6..da1adca 100644 --- a/Input Source/ClientManager.swift +++ b/Input Source/ClientManager.swift @@ -31,22 +31,22 @@ class ClientManager: CustomStringConvertible { init?(client: IMKTextInput) { guard let bundleId = client.bundleIdentifier(), let clientId = client.uniqueClientIdentifierString() else { - Log.warning("bundleIdentifier: \(client.bundleIdentifier() ?? "nil") or uniqueClientIdentifierString: \(client.uniqueClientIdentifierString() ?? "nil") - failing ClientManager.init()") + Logger.log.warning("bundleIdentifier: \(client.bundleIdentifier() ?? "nil") or uniqueClientIdentifierString: \(client.uniqueClientIdentifierString() ?? "nil") - failing ClientManager.init()") return nil } - Log.debug("Initializing client: \(bundleId) with Id: \(clientId)") + Logger.log.debug("Initializing client: \(bundleId) with Id: \(clientId)") self.client = client if !client.supportsUnicode() { - Log.warning("Client: \(bundleId) does not support Unicode!") + Logger.log.warning("Client: \(bundleId) does not support Unicode!") } if !client.supportsProperty(TSMDocumentPropertyTag(kTSMDocumentSupportDocumentAccessPropertyTag)) { - Log.warning("Client: \(bundleId) does not support Document Access!") + Logger.log.warning("Client: \(bundleId) does not support Document Access!") } _description = "\(bundleId) with Id: \(clientId)" } func setGlobalCursorLocation(_ location: Int) { - Log.debug("Setting global cursor location to: \(location)") + Logger.log.debug("Setting global cursor location to: \(location)") client.setMarkedText("|", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(location, 0)) client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(location, 0)) } @@ -56,7 +56,6 @@ class ClientManager: CustomStringConvertible { } func updateCandidates(_ sugs: [String]) { - Log.debug(sugs) // Remove duplicates // For some weird reason, when there are duplicates, // candidate window makes them hidden @@ -92,13 +91,13 @@ class ClientManager: CustomStringConvertible { } func finalize(_ output: String) { - Log.debug("Finalizing with: \(output)") + Logger.log.debug("Finalizing with: \(output)") client.insertText(output, replacementRange: notFoundRange) candidatesWindow.hide() } func clear() { - Log.debug("Clearing MarkedText and Candidate window") + Logger.log.debug("Clearing MarkedText and Candidate window") client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: notFoundRange) candidates = [] candidatesWindow.hide() diff --git a/Input Source/VarnamController.swift b/Input Source/VarnamController.swift index 03eb7b9..601e174 100644 --- a/Input Source/VarnamController.swift +++ b/Input Source/VarnamController.swift @@ -13,19 +13,6 @@ import InputMethodKit import Carbon.HIToolbox -class Log { - // TODO implement setting from app to enable log levels - public static func error(_ text: Any) { - print(text) - } - public static func warning(_ text: Any) { - print(text) - } - public static func debug(_ text: Any) { - print(text) - } -} - @objc(VarnamController) public class VarnamController: IMKInputController { static let validInputs = CharacterSet.alphanumerics.union(CharacterSet.whitespaces).union(CharacterSet.punctuationCharacters).union(.symbols) @@ -49,24 +36,24 @@ public class VarnamController: IMKInputController { do { varnam = try Varnam(schemeID) } catch let error { - Log.error(error) + Logger.log.error(error.localizedDescription) } } public override init!(server: IMKServer, delegate: Any!, client inputClient: Any) { guard let client = inputClient as? IMKTextInput & NSObjectProtocol else { - Log.warning("Client does not conform to the necessary protocols - refusing to initiate VarnamController!") + Logger.log.warning("Client does not conform to the necessary protocols - refusing to initiate VarnamController!") return nil } guard let clientManager = ClientManager(client: client) else { - Log.warning("Client manager failed to initialize - refusing to initiate VarnamController!") + Logger.log.warning("Client manager failed to initialize - refusing to initiate VarnamController!") return nil } self.clientManager = clientManager super.init(server: server, delegate: delegate, client: inputClient) initVarnam() - Log.debug("Initialized Controller for Client: \(clientManager)") + Logger.log.debug("Initialized Controller for Client: \(clientManager)") } func clearState() { @@ -219,7 +206,7 @@ public class VarnamController: IMKInputController { /// This message is sent when our client looses focus public override func deactivateServer(_ sender: Any!) { - Log.debug("Client: \(clientManager) loosing focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Logger.log.debug("Client: \(clientManager) loosing focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") // Do this in case the application is quitting, otherwise we will end up with a SIGSEGV dispatch.cancelAll() clearState() @@ -227,17 +214,17 @@ public class VarnamController: IMKInputController { /// This message is sent when our client gains focus public override func activateServer(_ sender: Any!) { - Log.debug("Client: \(clientManager) gained focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Logger.log.debug("Client: \(clientManager) gained focus by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") // There are three sources for current script selection - (a) self.schemeID, (b) config.schemeID and (c) selectedMenuItem.title // (b) could have changed while we were in background - converge (a) -> (b) if global script selection is configured if config.globalScriptSelection, schemeID != config.schemeID { - Log.debug("Initializing varnam: \(schemeID) to: \(config.schemeID)") + Logger.log.debug("Initializing varnam: \(schemeID) to: \(config.schemeID)") initVarnam() } } public override func menu() -> NSMenu! { - Log.debug("Returning menu") + Logger.log.debug("Returning menu") // Set the system trey menu selection to reflect our literators; converge (c) -> (a) let systemTrayMenu = (NSApp.delegate as! AppDelegate).systemTrayMenu! systemTrayMenu.items.forEach() { $0.state = .off } @@ -246,23 +233,23 @@ public class VarnamController: IMKInputController { } public override func candidates(_ sender: Any!) -> [Any]! { - Log.debug("Returning Candidates for sender: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Logger.log.debug("Returning Candidates for sender: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") return clientManager.candidates } public override func candidateSelected(_ candidateString: NSAttributedString!) { - Log.debug("Candidate selected: \(candidateString!)") + Logger.log.debug("Candidate selected: \(candidateString!)") commitText(candidateString.string) } public override func commitComposition(_ sender: Any!) { - Log.debug("Commit Composition called by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") + Logger.log.debug("Commit Composition called by: \((sender as? IMKTextInput)?.bundleIdentifier() ?? "unknown")") commit() } @objc public func menuItemSelected(sender: NSDictionary) { let item = sender.value(forKey: kIMKCommandMenuItemName) as! NSMenuItem - Log.debug("Menu Item Selected: \(item.title)") + Logger.log.debug("Menu Item Selected: \(item.title)") // Converge (b) -> (c) config.schemeID = item.representedObject as! String // Converge (a) -> (b) From 6de1977d9c736b318ae04fb62b598fa2a8df252f Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 16:33:44 +0530 Subject: [PATCH 08/11] Close varnam if out of focus, better consistent DB --- Input Source/ClientManager.swift | 2 +- Input Source/Common.swift | 9 +++++++++ Input Source/VarnamController.swift | 11 ++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Input Source/ClientManager.swift b/Input Source/ClientManager.swift index da1adca..5820dee 100644 --- a/Input Source/ClientManager.swift +++ b/Input Source/ClientManager.swift @@ -59,7 +59,7 @@ class ClientManager: CustomStringConvertible { // Remove duplicates // For some weird reason, when there are duplicates, // candidate window makes them hidden - candidates = NSOrderedSet(array: sugs).array as! [String] + candidates = sugs.uniqued() updateLookupTable() } diff --git a/Input Source/Common.swift b/Input Source/Common.swift index cc2c5f9..d2b92fc 100644 --- a/Input Source/Common.swift +++ b/Input Source/Common.swift @@ -60,3 +60,12 @@ extension String { return lhs + stringRHS } } + +// Copyright mxcl, CC-BY-SA 4.0 +// https://stackoverflow.com/a/46354989/1372424 +public extension Array where Element: Hashable { + func uniqued() -> [Element] { + var seen = Set() + return filter{ seen.insert($0).inserted } + } +} diff --git a/Input Source/VarnamController.swift b/Input Source/VarnamController.swift index 601e174..5209626 100644 --- a/Input Source/VarnamController.swift +++ b/Input Source/VarnamController.swift @@ -30,7 +30,7 @@ public class VarnamController: IMKInputController { private func initVarnam() { if (varnam != nil) { - varnam.close() + closeVarnam() } schemeID = config.schemeID do { @@ -40,6 +40,11 @@ public class VarnamController: IMKInputController { } } + private func closeVarnam() { + varnam.close() + varnam = nil + } + public override init!(server: IMKServer, delegate: Any!, client inputClient: Any) { guard let client = inputClient as? IMKTextInput & NSObjectProtocol else { Logger.log.warning("Client does not conform to the necessary protocols - refusing to initiate VarnamController!") @@ -210,6 +215,7 @@ public class VarnamController: IMKInputController { // Do this in case the application is quitting, otherwise we will end up with a SIGSEGV dispatch.cancelAll() clearState() + closeVarnam() } /// This message is sent when our client gains focus @@ -221,6 +227,9 @@ public class VarnamController: IMKInputController { Logger.log.debug("Initializing varnam: \(schemeID) to: \(config.schemeID)") initVarnam() } + if (varnam == nil) { + initVarnam() + } } public override func menu() -> NSMenu! { From 2fa539c2a44e9ffa3b708308a6d70db80f8f81ab Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 16:40:14 +0530 Subject: [PATCH 09/11] Add Kannada support --- GoVarnam/update_assets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) mode change 100644 => 100755 GoVarnam/update_assets.py diff --git a/GoVarnam/update_assets.py b/GoVarnam/update_assets.py old mode 100644 new mode 100755 index 703d0f9..b868547 --- a/GoVarnam/update_assets.py +++ b/GoVarnam/update_assets.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import gzip import json from os.path import basename @@ -40,6 +42,6 @@ def copyScheme(schemeID): with open(target + '/packs.json', 'w') as f: json.dump(packsInfo, f, ensure_ascii=False) -# For now just Malayalam for Indic Keyboard -for schemeID in ["ml"]: +# For now just Malayalam, Kannada for govarnam-macOS +for schemeID in ["ml", "kn"]: copyScheme(schemeID) From 1b8873423720cdd4175919ec8fea6fb89d537988 Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 17:12:42 +0530 Subject: [PATCH 10/11] Don't gzip .vlf --- GoVarnam/update_assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoVarnam/update_assets.py b/GoVarnam/update_assets.py index b868547..27ca4bc 100755 --- a/GoVarnam/update_assets.py +++ b/GoVarnam/update_assets.py @@ -33,7 +33,7 @@ def copyScheme(schemeID): with open( packPath, 'rb' - ) as f_in, gzip.open( + ) as f_in, open( target + '/' + basename(packPath), 'wb' ) as f_out: From 92a626569d8f591493fb1f1f61adf1f132bf766b Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Sun, 14 Nov 2021 17:12:53 +0530 Subject: [PATCH 11/11] Import VLF on postinstall --- GoVarnam/Varnam.swift | 26 +++++++++++++++++++++++++- Input Source/AppDelegate.swift | 11 +++++++++++ Installation/Scripts/postinstall | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/GoVarnam/Varnam.swift b/GoVarnam/Varnam.swift index fb5b7b3..f843426 100644 --- a/GoVarnam/Varnam.swift +++ b/GoVarnam/Varnam.swift @@ -38,11 +38,31 @@ extension String { public class Varnam { private var varnamHandle: Int32 = 0; + static let assetsFolderPath = Bundle.main.resourceURL!.appendingPathComponent("assets").path + static func importAllVLFInAssets() { + // TODO import only necessary ones + let fm = FileManager.default + for scheme in getAllSchemeDetails() { + do { + let varnam = try! Varnam(scheme.Identifier) + let items = try fm.contentsOfDirectory(atPath: assetsFolderPath) + + for item in items { + if item.hasSuffix(".vlf") && item.hasPrefix(scheme.Identifier) { + let path = assetsFolderPath + "/" + item + varnam.importFromFile(path) + } + } + } catch { + Logger.log.error("Couldn't import") + } + } + } + // This will only run once struct VarnamInit { static let once = VarnamInit() init() { - let assetsFolderPath = Bundle.main.resourceURL!.appendingPathComponent("assets").path print(assetsFolderPath) varnam_set_vst_lookup_dir(assetsFolderPath.toCStr()) } @@ -90,6 +110,10 @@ public class Varnam { return results } + public func importFromFile(_ path: String) { + varnam_import(varnamHandle, path.toCStr()) + } + public static func getAllSchemeDetails() -> [SchemeDetails] { _ = VarnamInit.once diff --git a/Input Source/AppDelegate.swift b/Input Source/AppDelegate.swift index 6dba9aa..d5f3c9a 100644 --- a/Input Source/AppDelegate.swift +++ b/Input Source/AppDelegate.swift @@ -41,6 +41,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { }} func applicationDidFinishLaunching(_ aNotification: Notification) { + for arg in CommandLine.arguments { + if arg == "-import" { + importVLF() + exit(0) + } + } + guard let connectionName = Bundle.main.infoDictionary?["InputMethodConnectionName"] as? String else { fatalError("Unable to get Connection Name from Info dictionary!") } @@ -63,4 +70,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { Logger.log.debug("Comitting all editing before terminating") server.commitComposition(self) } + + func importVLF() { + Varnam.importAllVLFInAssets() + } } diff --git a/Installation/Scripts/postinstall b/Installation/Scripts/postinstall index e36f273..c8624b5 100755 --- a/Installation/Scripts/postinstall +++ b/Installation/Scripts/postinstall @@ -9,4 +9,5 @@ until ./installer --enable do sleep 1 done +/Library/Input\ Methods/VarnamIME.app/Contents/MacOS/VarnamIME -import exit 0