Repo // Introducing UpdateSputnik.

This commit is contained in:
ShikiSuen 2022-09-08 19:07:23 +08:00
parent 1cbcb446ee
commit 59b0cc1c45
8 changed files with 197 additions and 5 deletions

View File

@ -0,0 +1,159 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Cocoa
class UpdateSputnik: NSObject, URLSessionDataDelegate {
static let kUpdateInfoPageURLKey: String = "UpdateInfoSite"
static let kUpdateCheckDateKeyPrevious: String = "PreviousUpdateCheckDate"
static let kUpdateCheckDateKeyNext: String = "NextUpdateCheckDate"
static let kUpdateCheckInterval: TimeInterval = 114_514
static var shared = UpdateSputnik()
func checkForUpdate(forced: Bool = false) {
guard !busy else { return }
if !forced {
if !mgrPrefs.checkUpdateAutomatically { return }
if let nextCheckDate = nextUpdateCheckDate, Date().compare(nextCheckDate) == .orderedAscending {
return
}
}
isCurrentCheckForced = forced //
let request = URLRequest(
url: kUpdateInfoSourceURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 5
)
let task = URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
DispatchQueue.main.async {
self.showError(message: error.localizedDescription)
self.currentTask = nil
}
return
}
self.data = data
}
task.resume()
currentTask = task
}
// MARK: - Private properties
private var isCurrentCheckForced = false
var sessionConfiguration = URLSessionConfiguration.background(withIdentifier: Bundle.main.bundleIdentifier!)
private var busy: Bool { currentTask != nil }
private var currentTask: URLSessionDataTask?
private var data: Data? {
didSet {
if let data = data {
DispatchQueue.main.async {
self.dataDidSet(data: data)
self.currentTask = nil
}
}
}
}
private var nextUpdateCheckDate: Date? {
get {
UserDefaults.standard.object(forKey: UpdateSputnik.kUpdateCheckDateKeyNext) as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: UpdateSputnik.kUpdateCheckDateKeyNext)
}
}
// MARK: - Private functions.
internal func dataDidSet(data: Data) {
var plist: [AnyHashable: Any]?
plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [AnyHashable: Any]
nextUpdateCheckDate = .init().addingTimeInterval(UpdateSputnik.kUpdateCheckInterval)
cleanUp()
guard let plist = plist else {
DispatchQueue.main.async {
self.showError(message: "Plist downloaded is nil.")
self.currentTask = nil
}
return
}
NSLog("update check plist: \(plist)")
guard let intRemoteVersion = Int(plist[kCFBundleVersionKey] as? String ?? ""),
let strRemoteVersionShortened = plist["CFBundleShortVersionString"] as? String
else {
DispatchQueue.main.async {
self.showError(message: "Plist downloaded cannot be parsed correctly.")
self.currentTask = nil
}
return
}
guard let dicMainBundle = Bundle.main.infoDictionary,
let intCurrentVersion = Int(dicMainBundle[kCFBundleVersionKey as String] as? String ?? ""),
let strCurrentVersionShortened = dicMainBundle["CFBundleShortVersionString"] as? String
else { return } // Shouldn't happen.
if intRemoteVersion <= intCurrentVersion, isCurrentCheckForced {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Update Check Completed", comment: "")
alert.informativeText = NSLocalizedString("You are already using the latest version.", comment: "")
alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
alert.runModal()
NSApp.setActivationPolicy(.accessory)
return
}
let content = String(
format: NSLocalizedString(
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?",
comment: ""
),
strCurrentVersionShortened,
intCurrentVersion.description,
strRemoteVersionShortened,
intRemoteVersion.description
)
let alert = NSAlert()
alert.messageText = NSLocalizedString("New Version Available", comment: "")
alert.informativeText = content
alert.addButton(withTitle: NSLocalizedString("Visit Website", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Not Now", comment: ""))
NSApp.setActivationPolicy(.accessory)
let result = alert.runModal()
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
if let siteInfoURLString = plist[UpdateSputnik.kUpdateInfoPageURLKey] as? String,
let siteURL = URL(string: siteInfoURLString)
{
DispatchQueue.main.async {
NSWorkspace.shared.open(siteURL)
}
}
}
}
private func cleanUp() {
currentTask = nil
data = nil
}
private func showError(message: String = "") {
NSLog("Update check: plist error, forced check: \(isCurrentCheckForced)")
if !isCurrentCheckForced { return }
let alert = NSAlert()
let content = NSLocalizedString(message, comment: "")
alert.messageText = NSLocalizedString("Update Check Failed", comment: "")
alert.informativeText = content
alert.addButton(withTitle: NSLocalizedString("Dismiss", comment: ""))
alert.runModal()
NSApp.setActivationPolicy(.accessory)
}
}

View File

@ -55,6 +55,15 @@ else {
exit(-1) exit(-1)
} }
guard let mainBundleInfoDict = Bundle.main.infoDictionary,
let strUpdateInfoSource = mainBundleInfoDict["UpdateInfoEndpoint"] as? String,
let urlUpdateInfoSource = URL(string: strUpdateInfoSource)
else {
NSLog("Fatal error: Info.plist wrecked It needs to have correct 'UpdateInfoEndpoint' value.")
exit(-1)
}
public let theServer = server public let theServer = server
public let kUpdateInfoSourceURL = urlUpdateInfoSource
NSApp.run() NSApp.run()

View File

@ -1,4 +1,8 @@
"vChewing" = "vChewing"; "vChewing" = "vChewing";
"Update Check Completed" = "Update Check Completed";
"You are already using the latest version." = "You are already using the latest version.";
"Plist downloaded is nil." = "Plist downloaded is nil.";
"Plist downloaded cannot be parsed correctly." = "Plist downloaded cannot be parsed correctly.";
"vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability."; "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability.";
"About vChewing…" = "About vChewing…"; "About vChewing…" = "About vChewing…";
"vChewing Preferences…" = "vChewing Preferences…"; "vChewing Preferences…" = "vChewing Preferences…";
@ -17,7 +21,7 @@
"New Version Available" = "New Version Available"; "New Version Available" = "New Version Available";
"Not Now" = "Not Now"; "Not Now" = "Not Now";
"Visit Website" = "Visit Website"; "Visit Website" = "Visit Website";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?";
"Force KangXi Writing" = "Force KangXi Writing"; "Force KangXi Writing" = "Force KangXi Writing";
"NotificationSwitchON" = "✔ ON"; "NotificationSwitchON" = "✔ ON";
"NotificationSwitchOFF" = "✘ OFF"; "NotificationSwitchOFF" = "✘ OFF";

View File

@ -1,4 +1,8 @@
"vChewing" = "vChewing"; "vChewing" = "vChewing";
"Update Check Completed" = "Update Check Completed";
"You are already using the latest version." = "You are already using the latest version.";
"Plist downloaded is nil." = "Plist downloaded is nil.";
"Plist downloaded cannot be parsed correctly." = "Plist downloaded cannot be parsed correctly.";
"vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability."; "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability.";
"About vChewing…" = "About vChewing…"; "About vChewing…" = "About vChewing…";
"vChewing Preferences…" = "vChewing Preferences…"; "vChewing Preferences…" = "vChewing Preferences…";
@ -17,7 +21,7 @@
"New Version Available" = "New Version Available"; "New Version Available" = "New Version Available";
"Not Now" = "Not Now"; "Not Now" = "Not Now";
"Visit Website" = "Visit Website"; "Visit Website" = "Visit Website";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?";
"Force KangXi Writing" = "Force KangXi Writing"; "Force KangXi Writing" = "Force KangXi Writing";
"NotificationSwitchON" = "✔ ON"; "NotificationSwitchON" = "✔ ON";
"NotificationSwitchOFF" = "✘ OFF"; "NotificationSwitchOFF" = "✘ OFF";

View File

@ -1,4 +1,8 @@
"vChewing" = "威注音入力アプリ"; "vChewing" = "威注音入力アプリ";
"Update Check Completed" = "新バージョンチェック完了";
"You are already using the latest version." = "現在稼働中のは最新バージョンである。";
"Plist downloaded is nil." = "受けた新バージョンお知らせ情報データは Plist ではないため、失敗とみなす。";
"Plist downloaded cannot be parsed correctly." = "受けた新バージョンお知らせ情報 Plist データは解読できないため、失敗とみなす。";
"vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "臨時記憶モジュールの観測行為による威注音入力アプリの意外中止は発生した。威注音入力アプリの無事利用のために、既存臨時記憶データは全てお消しした。"; "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "臨時記憶モジュールの観測行為による威注音入力アプリの意外中止は発生した。威注音入力アプリの無事利用のために、既存臨時記憶データは全てお消しした。";
"About vChewing…" = "威注音について…"; "About vChewing…" = "威注音について…";
"vChewing Preferences…" = "入力機能設定…"; "vChewing Preferences…" = "入力機能設定…";
@ -17,7 +21,7 @@
"New Version Available" = "最新版利用可能"; "New Version Available" = "最新版利用可能";
"Not Now" = "後で"; "Not Now" = "後で";
"Visit Website" = "公式サイト"; "Visit Website" = "公式サイト";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "今のご使用していた威注音入力アプリのバージョンは「%1$@ (%2$@)」であり、ネットでもっと新しいバージョン「%3$@ (%4$@)」の下載せはできるらしい。公式サイトへバージョン「%5$@」を下載せますか?"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?" = "今のご使用していた威注音入力アプリのバージョンは「%1$@ (%2$@)」であり、ネットでもっと新しいバージョン「%3$@ (%4$@)」の下載せはできるらしい。お下載せしますか?";
"Force KangXi Writing" = "康熙文字変換モード"; "Force KangXi Writing" = "康熙文字変換モード";
"NotificationSwitchON" = "✔ 機能起動"; "NotificationSwitchON" = "✔ 機能起動";
"NotificationSwitchOFF" = "✘ 機能停止"; "NotificationSwitchOFF" = "✘ 機能停止";

View File

@ -1,4 +1,8 @@
"vChewing" = "威注音输入法"; "vChewing" = "威注音输入法";
"Update Check Completed" = "更新检查完毕";
"You are already using the latest version." = "您正在使用目前最新的发行版。";
"Plist downloaded is nil." = "下载来的更新资讯并非 Plist 档案。";
"Plist downloaded cannot be parsed correctly." = "下载来的更新资讯 Plist 档案无法正常解析。";
"vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "威注音输入法的使用者半衰记忆模组在观测时崩溃,相关半衰记忆资料档案内容已全部清空。"; "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "威注音输入法的使用者半衰记忆模组在观测时崩溃,相关半衰记忆资料档案内容已全部清空。";
"About vChewing…" = "关于威注音…"; "About vChewing…" = "关于威注音…";
"vChewing Preferences…" = "威注音偏好设定…"; "vChewing Preferences…" = "威注音偏好设定…";
@ -17,7 +21,7 @@
"New Version Available" = "有新版可下载"; "New Version Available" = "有新版可下载";
"Not Now" = "以后再说"; "Not Now" = "以后再说";
"Visit Website" = "前往网站"; "Visit Website" = "前往网站";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?%5$@"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?" = "目前使用的威注音官方版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?";
"Force KangXi Writing" = "康熙正体字模式"; "Force KangXi Writing" = "康熙正体字模式";
"NotificationSwitchON" = "✔ 已启用"; "NotificationSwitchON" = "✔ 已启用";
"NotificationSwitchOFF" = "✘ 已停用"; "NotificationSwitchOFF" = "✘ 已停用";

View File

@ -1,4 +1,8 @@
"vChewing" = "威注音輸入法"; "vChewing" = "威注音輸入法";
"Update Check Completed" = "更新檢查完畢";
"You are already using the latest version." = "您正在使用目前最新的發行版。";
"Plist downloaded is nil." = "下載來的更新資訊並非 Plist 檔案。";
"Plist downloaded cannot be parsed correctly." = "下載來的更新資訊 Plist 檔案無法正常解析。";
"vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "威注音輸入法的使用者半衰記憶模組在觀測時崩潰,相關半衰記憶資料檔案內容已全部清空。"; "vChewing crashed while handling previously loaded UOM observation data. These data files are cleaned now to ensure the usability." = "威注音輸入法的使用者半衰記憶模組在觀測時崩潰,相關半衰記憶資料檔案內容已全部清空。";
"About vChewing…" = "關於威注音…"; "About vChewing…" = "關於威注音…";
"vChewing Preferences…" = "威注音偏好設定…"; "vChewing Preferences…" = "威注音偏好設定…";
@ -17,7 +21,7 @@
"New Version Available" = "有新版可下載"; "New Version Available" = "有新版可下載";
"Not Now" = "以後再說"; "Not Now" = "以後再說";
"Visit Website" = "前往網站"; "Visit Website" = "前往網站";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?%5$@"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?" = "目前使用的威注音官方版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?";
"Force KangXi Writing" = "康熙正體字模式"; "Force KangXi Writing" = "康熙正體字模式";
"NotificationSwitchON" = "✔ 已啟用"; "NotificationSwitchON" = "✔ 已啟用";
"NotificationSwitchOFF" = "✘ 已停用"; "NotificationSwitchOFF" = "✘ 已停用";

View File

@ -85,6 +85,7 @@
5BBBB77527AED70B0023B93A /* MenuIcon-SCVIM.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB77127AED70B0023B93A /* MenuIcon-SCVIM.png */; }; 5BBBB77527AED70B0023B93A /* MenuIcon-SCVIM.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB77127AED70B0023B93A /* MenuIcon-SCVIM.png */; };
5BBBB77627AED70B0023B93A /* MenuIcon-TCVIM.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */; }; 5BBBB77627AED70B0023B93A /* MenuIcon-TCVIM.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */; };
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBBB77927AEDC690023B93A /* clsSFX.swift */; }; 5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBBB77927AEDC690023B93A /* clsSFX.swift */; };
5BBC9EFC28CA042500041196 /* UpdateSputnik.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC9EFB28CA042500041196 /* UpdateSputnik.swift */; };
5BC2652227E04B7E00700291 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2652127E04B7B00700291 /* uninstall.sh */; }; 5BC2652227E04B7E00700291 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2652127E04B7B00700291 /* uninstall.sh */; };
5BC4479D2865686500EDC323 /* data-chs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.plist */; }; 5BC4479D2865686500EDC323 /* data-chs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.plist */; };
5BC4479E2865686500EDC323 /* data-cht.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB720283B4AEA0078EB25 /* data-cht.plist */; }; 5BC4479E2865686500EDC323 /* data-cht.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB720283B4AEA0078EB25 /* data-cht.plist */; };
@ -303,6 +304,7 @@
5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MenuIcon-TCVIM.png"; sourceTree = "<group>"; }; 5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MenuIcon-TCVIM.png"; sourceTree = "<group>"; };
5BBBB77727AEDB290023B93A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = "<group>"; }; 5BBBB77727AEDB290023B93A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = "<group>"; };
5BBBB77927AEDC690023B93A /* clsSFX.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = clsSFX.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; }; 5BBBB77927AEDC690023B93A /* clsSFX.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = clsSFX.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
5BBC9EFB28CA042500041196 /* UpdateSputnik.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateSputnik.swift; sourceTree = "<group>"; };
5BBD627827B6C4D900271480 /* Update-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Update-Info.plist"; sourceTree = "<group>"; }; 5BBD627827B6C4D900271480 /* Update-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Update-Info.plist"; sourceTree = "<group>"; };
5BC0AAC927F58472002D33E9 /* pkgPreInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPreInstall.sh; sourceTree = "<group>"; }; 5BC0AAC927F58472002D33E9 /* pkgPreInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPreInstall.sh; sourceTree = "<group>"; };
5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPostInstall.sh; sourceTree = "<group>"; }; 5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPostInstall.sh; sourceTree = "<group>"; };
@ -542,6 +544,7 @@
5B5E535127EF261400C6AA1E /* IME.swift */, 5B5E535127EF261400C6AA1E /* IME.swift */,
5B175FFA28C5CDDC0078D1B4 /* IMKHelper.swift */, 5B175FFA28C5CDDC0078D1B4 /* IMKHelper.swift */,
5B62A33527AE795800A19448 /* mgrPrefs.swift */, 5B62A33527AE795800A19448 /* mgrPrefs.swift */,
5BBC9EFB28CA042500041196 /* UpdateSputnik.swift */,
); );
path = IMEModules; path = IMEModules;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1214,6 +1217,7 @@
5B40730C281672610023DFFF /* lmAssociates.swift in Sources */, 5B40730C281672610023DFFF /* lmAssociates.swift in Sources */,
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */, 5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */,
5BBC9EFC28CA042500041196 /* UpdateSputnik.swift in Sources */,
5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */, 5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */,
5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */, 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */,
5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */, 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */,