AppInstaller // Tearing down AppDelegate into parts.
This commit is contained in:
parent
dc3ed0a864
commit
77d219fde8
|
@ -12,41 +12,41 @@ import Cocoa
|
|||
import IMKUtils
|
||||
import InputMethodKit
|
||||
|
||||
private let kTargetBin = "vChewing"
|
||||
private let kTargetType = "app"
|
||||
private let kTargetBundle = "vChewing.app"
|
||||
private let kTargetBundleWithComponents = "Library/Input%20Methods/vChewing.app"
|
||||
public let kTargetBin = "vChewing"
|
||||
public let kTargetType = "app"
|
||||
public let kTargetBundle = "vChewing.app"
|
||||
public let kTargetBundleWithComponents = "Library/Input%20Methods/vChewing.app"
|
||||
|
||||
private let realHomeDir = URL(
|
||||
public let realHomeDir = URL(
|
||||
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
|
||||
)
|
||||
private let urlDestinationPartial = realHomeDir.appendingPathComponent("Library/Input Methods")
|
||||
private let urlTargetPartial = realHomeDir.appendingPathComponent(kTargetBundleWithComponents)
|
||||
private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS")
|
||||
public let urlDestinationPartial = realHomeDir.appendingPathComponent("Library/Input Methods")
|
||||
public let urlTargetPartial = realHomeDir.appendingPathComponent(kTargetBundleWithComponents)
|
||||
public let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS")
|
||||
.appendingPathComponent(kTargetBin)
|
||||
|
||||
private let kDestinationPartial = urlDestinationPartial.path
|
||||
private let kTargetPartialPath = urlTargetPartial.path
|
||||
private let kTargetFullBinPartialPath = urlTargetFullBinPartial.path
|
||||
public let kDestinationPartial = urlDestinationPartial.path
|
||||
public let kTargetPartialPath = urlTargetPartial.path
|
||||
public let kTargetFullBinPartialPath = urlTargetFullBinPartial.path
|
||||
|
||||
private let kTranslocationRemovalTickInterval: TimeInterval = 0.5
|
||||
private let kTranslocationRemovalDeadline: TimeInterval = 60.0
|
||||
public let kTranslocationRemovalTickInterval: TimeInterval = 0.5
|
||||
public let kTranslocationRemovalDeadline: TimeInterval = 60.0
|
||||
|
||||
@NSApplicationMain
|
||||
@objc(AppDelegate)
|
||||
class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||
@IBOutlet private var installButton: NSButton!
|
||||
@IBOutlet private var cancelButton: NSButton!
|
||||
@IBOutlet private var progressSheet: NSWindow!
|
||||
@IBOutlet private var progressIndicator: NSProgressIndicator!
|
||||
@IBOutlet private var appVersionLabel: NSTextField!
|
||||
@IBOutlet private var appCopyrightLabel: NSTextField!
|
||||
@IBOutlet private var appEULAContent: NSTextView!
|
||||
@IBOutlet var installButton: NSButton!
|
||||
@IBOutlet var cancelButton: NSButton!
|
||||
@IBOutlet var progressSheet: NSWindow!
|
||||
@IBOutlet var progressIndicator: NSProgressIndicator!
|
||||
@IBOutlet var appVersionLabel: NSTextField!
|
||||
@IBOutlet var appCopyrightLabel: NSTextField!
|
||||
@IBOutlet var appEULAContent: NSTextView!
|
||||
|
||||
private var installingVersion = ""
|
||||
private var upgrading = false
|
||||
private var translocationRemovalStartTime: Date?
|
||||
private var currentVersionNumber: Int = 0
|
||||
var installingVersion = ""
|
||||
var upgrading = false
|
||||
var translocationRemovalStartTime: Date?
|
||||
var currentVersionNumber: Int = 0
|
||||
|
||||
let imeURLInstalled = realHomeDir.appendingPathComponent("Library/Input Methods/vChewing.app")
|
||||
|
||||
|
@ -138,208 +138,13 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
if elapsed >= kTranslocationRemovalDeadline {
|
||||
timer.invalidate()
|
||||
window.endSheet(progressSheet, returnCode: .cancel)
|
||||
} else if isAppBundleTranslocated(atPath: kTargetPartialPath) == false {
|
||||
} else if Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath) == false {
|
||||
progressIndicator.doubleValue = 1.0
|
||||
timer.invalidate()
|
||||
window.endSheet(progressSheet, returnCode: .continue)
|
||||
}
|
||||
}
|
||||
|
||||
func removeThenInstallInputMethod() {
|
||||
// if !FileManager.default.fileExists(atPath: kTargetPartialPath) {
|
||||
// installInputMethod(
|
||||
// previousExists: false, previousVersionNotFullyDeactivatedWarning: false
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
|
||||
guard let window = window else { return }
|
||||
|
||||
let shouldWaitForTranslocationRemoval =
|
||||
isAppBundleTranslocated(atPath: kTargetPartialPath)
|
||||
&& window.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:)))
|
||||
|
||||
// 將既存輸入法扔到垃圾桶內
|
||||
do {
|
||||
let sourceDir = kDestinationPartial
|
||||
let fileManager = FileManager.default
|
||||
let fileURLString = sourceDir + "/" + kTargetBundle
|
||||
let fileURL = URL(fileURLWithPath: fileURLString)
|
||||
|
||||
// 檢查檔案是否存在
|
||||
if fileManager.fileExists(atPath: fileURLString) {
|
||||
// 塞入垃圾桶
|
||||
try fileManager.trashItem(at: fileURL, resultingItemURL: nil)
|
||||
} else {
|
||||
NSLog("File does not exist")
|
||||
}
|
||||
|
||||
} catch let error as NSError {
|
||||
NSLog("An error took place: \(error)")
|
||||
}
|
||||
|
||||
let killTask = Process()
|
||||
killTask.launchPath = "/usr/bin/killall"
|
||||
killTask.arguments = ["-9", kTargetBin]
|
||||
killTask.launch()
|
||||
killTask.waitUntilExit()
|
||||
|
||||
if shouldWaitForTranslocationRemoval {
|
||||
progressIndicator.startAnimation(self)
|
||||
window.beginSheet(progressSheet) { returnCode in
|
||||
DispatchQueue.main.async {
|
||||
if returnCode == .continue {
|
||||
self.installInputMethod(
|
||||
previousExists: true,
|
||||
previousVersionNotFullyDeactivatedWarning: false
|
||||
)
|
||||
} else {
|
||||
self.installInputMethod(
|
||||
previousExists: true,
|
||||
previousVersionNotFullyDeactivatedWarning: true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
translocationRemovalStartTime = Date()
|
||||
Timer.scheduledTimer(
|
||||
timeInterval: kTranslocationRemovalTickInterval, target: self,
|
||||
selector: #selector(timerTick(_:)), userInfo: nil, repeats: true
|
||||
)
|
||||
} else {
|
||||
installInputMethod(
|
||||
previousExists: false, previousVersionNotFullyDeactivatedWarning: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func installInputMethod(
|
||||
previousExists _: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool
|
||||
) {
|
||||
guard
|
||||
let targetBundle = Bundle.main.path(forResource: kTargetBin, ofType: kTargetType)
|
||||
else {
|
||||
return
|
||||
}
|
||||
let cpTask = Process()
|
||||
cpTask.launchPath = "/bin/cp"
|
||||
print(kDestinationPartial)
|
||||
cpTask.arguments = [
|
||||
"-R", targetBundle, kDestinationPartial,
|
||||
]
|
||||
cpTask.launch()
|
||||
cpTask.waitUntilExit()
|
||||
|
||||
if cpTask.terminationStatus != 0 {
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Install Failed", comment: ""),
|
||||
message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""),
|
||||
buttonTitle: NSLocalizedString("Cancel", comment: "")
|
||||
)
|
||||
endAppWithDelay()
|
||||
}
|
||||
|
||||
_ = try? shell("/usr/bin/xattr -drs com.apple.quarantine \(kTargetPartialPath)")
|
||||
|
||||
guard let theBundle = Bundle(url: imeURLInstalled),
|
||||
let imeIdentifier = theBundle.bundleIdentifier
|
||||
else {
|
||||
endAppWithDelay()
|
||||
return
|
||||
}
|
||||
|
||||
let imeBundleURL = theBundle.bundleURL
|
||||
|
||||
if allRegisteredInstancesOfThisInputMethod.isEmpty {
|
||||
NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).")
|
||||
let status = (TISRegisterInputSource(imeBundleURL as CFURL) == noErr)
|
||||
if !status {
|
||||
let message = String(
|
||||
format: NSLocalizedString(
|
||||
"Cannot find input source %@ after registration.", comment: ""
|
||||
),
|
||||
imeIdentifier
|
||||
)
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||
buttonTitle: NSLocalizedString("Abort", comment: "")
|
||||
)
|
||||
endAppWithDelay()
|
||||
return
|
||||
}
|
||||
|
||||
if allRegisteredInstancesOfThisInputMethod.isEmpty {
|
||||
let message = String(
|
||||
format: NSLocalizedString(
|
||||
"Cannot find input source %@ after registration.", comment: ""
|
||||
),
|
||||
imeIdentifier
|
||||
)
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||
buttonTitle: NSLocalizedString("Abort", comment: "")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var isMacOS12OrAbove = false
|
||||
if #available(macOS 12.0, *) {
|
||||
NSLog("macOS 12 or later detected.")
|
||||
isMacOS12OrAbove = true
|
||||
} else {
|
||||
NSLog("Installer runs with the pre-macOS 12 flow.")
|
||||
}
|
||||
|
||||
// Unconditionally enable the IME on macOS 12.0+,
|
||||
// as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not*
|
||||
// enabled in the user's current set of IMEs (which means the IME does not show up in
|
||||
// the user's input menu).
|
||||
|
||||
var mainInputSourceEnabled = false
|
||||
|
||||
allRegisteredInstancesOfThisInputMethod.forEach {
|
||||
if $0.activate() {
|
||||
NSLog("Input method enabled: \(imeIdentifier)")
|
||||
} else {
|
||||
NSLog("Failed to enable input method: \(imeIdentifier)")
|
||||
}
|
||||
mainInputSourceEnabled = $0.isActivated
|
||||
}
|
||||
|
||||
// Alert Panel
|
||||
let ntfPostInstall = NSAlert()
|
||||
if warning {
|
||||
ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.",
|
||||
comment: ""
|
||||
)
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
} else {
|
||||
if !mainInputSourceEnabled, !isMacOS12OrAbove {
|
||||
ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.",
|
||||
comment: ""
|
||||
)
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
||||
} else {
|
||||
ntfPostInstall.messageText = NSLocalizedString(
|
||||
"Installation Successful", comment: ""
|
||||
)
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account.",
|
||||
comment: ""
|
||||
)
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
}
|
||||
}
|
||||
ntfPostInstall.beginSheetModal(for: window!) { _ in
|
||||
self.endAppWithDelay()
|
||||
}
|
||||
}
|
||||
|
||||
func endAppWithDelay() {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
||||
NSApp.terminate(self)
|
||||
|
@ -354,7 +159,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
NSApp.terminate(self)
|
||||
}
|
||||
|
||||
private func shell(_ command: String) throws -> String {
|
||||
func shell(_ command: String) throws -> String {
|
||||
let task = Process()
|
||||
let pipe = Pipe()
|
||||
|
||||
|
@ -377,26 +182,4 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
|
||||
return output
|
||||
}
|
||||
|
||||
// Determines if an app is translocated by Gatekeeper to a randomized path.
|
||||
// See https://weblog.rogueamoeba.com/2016/06/29/sierra-and-gatekeeper-path-randomization/
|
||||
// Originally written by Zonble Yang in Objective-C (MIT License).
|
||||
// Swiftified by: Rob Mayoff. Ref: https://forums.swift.org/t/58719/5
|
||||
func isAppBundleTranslocated(atPath bundlePath: String) -> Bool {
|
||||
var entryCount = getfsstat(nil, 0, 0)
|
||||
var entries: [statfs] = .init(repeating: .init(), count: Int(entryCount))
|
||||
let absPath = bundlePath.cString(using: .utf8)
|
||||
entryCount = getfsstat(&entries, entryCount * Int32(MemoryLayout<statfs>.stride), MNT_NOWAIT)
|
||||
for entry in entries.prefix(Int(entryCount)) {
|
||||
let isMatch = withUnsafeBytes(of: entry.f_mntfromname) { mntFromName in
|
||||
strcmp(absPath, mntFromName.baseAddress) == 0
|
||||
}
|
||||
if isMatch {
|
||||
var stat = statfs()
|
||||
let rc = statfs(absPath, &stat)
|
||||
return rc == 0
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (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
|
||||
import InputMethodKit
|
||||
|
||||
extension AppDelegate {
|
||||
func removeThenInstallInputMethod() {
|
||||
// if !FileManager.default.fileExists(atPath: kTargetPartialPath) {
|
||||
// installInputMethod(
|
||||
// previousExists: false, previousVersionNotFullyDeactivatedWarning: false
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
|
||||
guard let window = window else { return }
|
||||
|
||||
let shouldWaitForTranslocationRemoval =
|
||||
Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath)
|
||||
&& window.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:)))
|
||||
|
||||
// 將既存輸入法扔到垃圾桶內
|
||||
do {
|
||||
let sourceDir = kDestinationPartial
|
||||
let fileManager = FileManager.default
|
||||
let fileURLString = sourceDir + "/" + kTargetBundle
|
||||
let fileURL = URL(fileURLWithPath: fileURLString)
|
||||
|
||||
// 檢查檔案是否存在
|
||||
if fileManager.fileExists(atPath: fileURLString) {
|
||||
// 塞入垃圾桶
|
||||
try fileManager.trashItem(at: fileURL, resultingItemURL: nil)
|
||||
} else {
|
||||
NSLog("File does not exist")
|
||||
}
|
||||
|
||||
} catch let error as NSError {
|
||||
NSLog("An error took place: \(error)")
|
||||
}
|
||||
|
||||
let killTask = Process()
|
||||
killTask.launchPath = "/usr/bin/killall"
|
||||
killTask.arguments = ["-9", kTargetBin]
|
||||
killTask.launch()
|
||||
killTask.waitUntilExit()
|
||||
|
||||
if shouldWaitForTranslocationRemoval {
|
||||
progressIndicator.startAnimation(self)
|
||||
window.beginSheet(progressSheet) { returnCode in
|
||||
DispatchQueue.main.async {
|
||||
if returnCode == .continue {
|
||||
self.installInputMethod(
|
||||
previousExists: true,
|
||||
previousVersionNotFullyDeactivatedWarning: false
|
||||
)
|
||||
} else {
|
||||
self.installInputMethod(
|
||||
previousExists: true,
|
||||
previousVersionNotFullyDeactivatedWarning: true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
translocationRemovalStartTime = Date()
|
||||
Timer.scheduledTimer(
|
||||
timeInterval: kTranslocationRemovalTickInterval, target: self,
|
||||
selector: #selector(timerTick(_:)), userInfo: nil, repeats: true
|
||||
)
|
||||
} else {
|
||||
installInputMethod(
|
||||
previousExists: false, previousVersionNotFullyDeactivatedWarning: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func installInputMethod(
|
||||
previousExists _: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool
|
||||
) {
|
||||
guard
|
||||
let targetBundle = Bundle.main.path(forResource: kTargetBin, ofType: kTargetType)
|
||||
else {
|
||||
return
|
||||
}
|
||||
let cpTask = Process()
|
||||
cpTask.launchPath = "/bin/cp"
|
||||
print(kDestinationPartial)
|
||||
cpTask.arguments = [
|
||||
"-R", targetBundle, kDestinationPartial,
|
||||
]
|
||||
cpTask.launch()
|
||||
cpTask.waitUntilExit()
|
||||
|
||||
if cpTask.terminationStatus != 0 {
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Install Failed", comment: ""),
|
||||
message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""),
|
||||
buttonTitle: NSLocalizedString("Cancel", comment: "")
|
||||
)
|
||||
endAppWithDelay()
|
||||
}
|
||||
|
||||
_ = try? shell("/usr/bin/xattr -drs com.apple.quarantine \(kTargetPartialPath)")
|
||||
|
||||
guard let theBundle = Bundle(url: imeURLInstalled),
|
||||
let imeIdentifier = theBundle.bundleIdentifier
|
||||
else {
|
||||
endAppWithDelay()
|
||||
return
|
||||
}
|
||||
|
||||
let imeBundleURL = theBundle.bundleURL
|
||||
|
||||
if allRegisteredInstancesOfThisInputMethod.isEmpty {
|
||||
NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).")
|
||||
let status = (TISRegisterInputSource(imeBundleURL as CFURL) == noErr)
|
||||
if !status {
|
||||
let message = String(
|
||||
format: NSLocalizedString(
|
||||
"Cannot find input source %@ after registration.", comment: ""
|
||||
),
|
||||
imeIdentifier
|
||||
)
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||
buttonTitle: NSLocalizedString("Abort", comment: "")
|
||||
)
|
||||
endAppWithDelay()
|
||||
return
|
||||
}
|
||||
|
||||
if allRegisteredInstancesOfThisInputMethod.isEmpty {
|
||||
let message = String(
|
||||
format: NSLocalizedString(
|
||||
"Cannot find input source %@ after registration.", comment: ""
|
||||
),
|
||||
imeIdentifier
|
||||
)
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||
buttonTitle: NSLocalizedString("Abort", comment: "")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var isMacOS12OrAbove = false
|
||||
if #available(macOS 12.0, *) {
|
||||
NSLog("macOS 12 or later detected.")
|
||||
isMacOS12OrAbove = true
|
||||
} else {
|
||||
NSLog("Installer runs with the pre-macOS 12 flow.")
|
||||
}
|
||||
|
||||
// Unconditionally enable the IME on macOS 12.0+,
|
||||
// as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not*
|
||||
// enabled in the user's current set of IMEs (which means the IME does not show up in
|
||||
// the user's input menu).
|
||||
|
||||
var mainInputSourceEnabled = false
|
||||
|
||||
allRegisteredInstancesOfThisInputMethod.forEach {
|
||||
if $0.activate() {
|
||||
NSLog("Input method enabled: \(imeIdentifier)")
|
||||
} else {
|
||||
NSLog("Failed to enable input method: \(imeIdentifier)")
|
||||
}
|
||||
mainInputSourceEnabled = $0.isActivated
|
||||
}
|
||||
|
||||
// Alert Panel
|
||||
let ntfPostInstall = NSAlert()
|
||||
if warning {
|
||||
ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.",
|
||||
comment: ""
|
||||
)
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
} else {
|
||||
if !mainInputSourceEnabled, !isMacOS12OrAbove {
|
||||
ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.",
|
||||
comment: ""
|
||||
)
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
||||
} else {
|
||||
ntfPostInstall.messageText = NSLocalizedString(
|
||||
"Installation Successful", comment: ""
|
||||
)
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account.",
|
||||
comment: ""
|
||||
)
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
}
|
||||
}
|
||||
ntfPostInstall.beginSheetModal(for: window!) { _ in
|
||||
self.endAppWithDelay()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (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 Foundation
|
||||
|
||||
public enum Reloc {
|
||||
// Determines if an app is translocated by Gatekeeper to a randomized path.
|
||||
// See https://weblog.rogueamoeba.com/2016/06/29/sierra-and-gatekeeper-path-randomization/
|
||||
// Originally written by Zonble Yang in Objective-C (MIT License).
|
||||
// Swiftified by: Rob Mayoff. Ref: https://forums.swift.org/t/58719/5
|
||||
public static func isAppBundleTranslocated(atPath bundlePath: String) -> Bool {
|
||||
var entryCount = getfsstat(nil, 0, 0)
|
||||
var entries: [statfs] = .init(repeating: .init(), count: Int(entryCount))
|
||||
let absPath = bundlePath.cString(using: .utf8)
|
||||
entryCount = getfsstat(&entries, entryCount * Int32(MemoryLayout<statfs>.stride), MNT_NOWAIT)
|
||||
for entry in entries.prefix(Int(entryCount)) {
|
||||
let isMatch = withUnsafeBytes(of: entry.f_mntfromname) { mntFromName in
|
||||
strcmp(absPath, mntFromName.baseAddress) == 0
|
||||
}
|
||||
if isMatch {
|
||||
var stat = statfs()
|
||||
let rc = statfs(absPath, &stat)
|
||||
return rc == 0
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -54,6 +54,8 @@
|
|||
5BBC2D9F28F51C0400C986F6 /* LICENSE-CHT.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */; };
|
||||
5BBC2DA028F51C0400C986F6 /* LICENSE-JPN.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B18BA7327C7BD8C0056EB19 /* LICENSE-JPN.txt */; };
|
||||
5BBC2DA128F51C0400C986F6 /* LICENSE-CHS.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B18BA6F27C7BD8B0056EB19 /* LICENSE-CHS.txt */; };
|
||||
5BBC2DA328F5212100C986F6 /* RelocationDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC2DA228F5212100C986F6 /* RelocationDetector.swift */; };
|
||||
5BBC2DA528F521C200C986F6 /* AppDelegate_Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBC2DA428F521C200C986F6 /* AppDelegate_Extension.swift */; };
|
||||
5BC2652227E04B7E00700291 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2652127E04B7B00700291 /* uninstall.sh */; };
|
||||
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 */; };
|
||||
|
@ -243,6 +245,8 @@
|
|||
5BBBB77127AED70B0023B93A /* MenuIcon-SCVIM.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MenuIcon-SCVIM.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>"; };
|
||||
5BBC2DA228F5212100C986F6 /* RelocationDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelocationDetector.swift; sourceTree = "<group>"; };
|
||||
5BBC2DA428F521C200C986F6 /* AppDelegate_Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate_Extension.swift; 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>"; };
|
||||
5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPostInstall.sh; sourceTree = "<group>"; };
|
||||
|
@ -695,6 +699,8 @@
|
|||
5B44B97C28D2F283004508BF /* PKGRoot */,
|
||||
5BBBB77827AEDB330023B93A /* Resources */,
|
||||
D4F0BBE0279AF8B30071253C /* AppDelegate.swift */,
|
||||
5BBC2DA428F521C200C986F6 /* AppDelegate_Extension.swift */,
|
||||
5BBC2DA228F5212100C986F6 /* RelocationDetector.swift */,
|
||||
6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */,
|
||||
5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */,
|
||||
5BC0AAC927F58472002D33E9 /* pkgPreInstall.sh */,
|
||||
|
@ -1094,6 +1100,8 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D4F0BBE1279AF8B30071253C /* AppDelegate.swift in Sources */,
|
||||
5BBC2DA528F521C200C986F6 /* AppDelegate_Extension.swift in Sources */,
|
||||
5BBC2DA328F5212100C986F6 /* RelocationDetector.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue