Zonble: Swiftity the installer.
- Applied the "missing line" patch.
This commit is contained in:
parent
2df373fa83
commit
fac59f3650
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* AppDelegate.h
|
|
||||||
*
|
|
||||||
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
|
||||||
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
|
||||||
* Some rights reserved. See "LICENSE.TXT" for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "ArchiveUtil.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : NSWindowController <NSApplicationDelegate>
|
|
||||||
{
|
|
||||||
@protected
|
|
||||||
ArchiveUtil *_archiveUtil;
|
|
||||||
NSString *_installingVersion;
|
|
||||||
BOOL _upgrading;
|
|
||||||
NSButton *__weak _installButton;
|
|
||||||
NSButton *__weak _cancelButton;
|
|
||||||
NSTextView *__unsafe_unretained _textView;
|
|
||||||
NSWindow *__weak _progressSheet;
|
|
||||||
NSProgressIndicator *__weak _progressIndicator;
|
|
||||||
NSDate *_translocationRemovalStartTime;
|
|
||||||
NSInteger _currentVersionNumber;
|
|
||||||
}
|
|
||||||
- (IBAction)agreeAndInstallAction:(id)sender;
|
|
||||||
- (IBAction)cancelAction:(id)sender;
|
|
||||||
|
|
||||||
@property (weak) IBOutlet NSButton *installButton;
|
|
||||||
@property (weak) IBOutlet NSButton *cancelButton;
|
|
||||||
@property (unsafe_unretained) IBOutlet NSTextView *textView;
|
|
||||||
@property (weak) IBOutlet NSWindow *progressSheet;
|
|
||||||
@property (weak) IBOutlet NSProgressIndicator *progressIndicator;
|
|
||||||
@property (nonatomic) IBOutlet NSTextField *appNameLabel;
|
|
||||||
@property (nonatomic) IBOutlet NSTextField *appVersionLabel;
|
|
||||||
@property (nonatomic) IBOutlet NSTextField *appCopyrightLabel;
|
|
||||||
@property (nonatomic) IBOutlet NSTextView *appEULAContent;
|
|
||||||
@end
|
|
|
@ -1,283 +0,0 @@
|
||||||
/*
|
|
||||||
* AppDelegate.m
|
|
||||||
*
|
|
||||||
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
|
||||||
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
|
||||||
* Some rights reserved. See "LICENSE.TXT" for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import <sys/mount.h>
|
|
||||||
#import "vChewingInstaller-Swift.h"
|
|
||||||
|
|
||||||
static NSString *const kTargetBin = @"vChewing";
|
|
||||||
static NSString *const kTargetType = @"app";
|
|
||||||
static NSString *const kTargetBundle = @"vChewing.app";
|
|
||||||
static NSString *const kDestinationPartial = @"~/Library/Input Methods/";
|
|
||||||
static NSString *const kTargetPartialPath = @"~/Library/Input Methods/vChewing.app";
|
|
||||||
static NSString *const kTargetFullBinPartialPath = @"~/Library/Input Methods/vChewing.app/Contents/MacOS/vChewing";
|
|
||||||
|
|
||||||
static const NSTimeInterval kTranslocationRemovalTickInterval = 0.5;
|
|
||||||
static const NSTimeInterval kTranslocationRemovalDeadline = 60.0;
|
|
||||||
|
|
||||||
/// A simple replacement for the deprecated NSRunAlertPanel.
|
|
||||||
void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) {
|
|
||||||
NSAlert *alert = [[NSAlert alloc] init];
|
|
||||||
[alert setAlertStyle:NSAlertStyleInformational];
|
|
||||||
[alert setMessageText:title];
|
|
||||||
[alert setInformativeText:message];
|
|
||||||
[alert addButtonWithTitle:buttonTitle];
|
|
||||||
[alert runModal];
|
|
||||||
}
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
@synthesize installButton = _installButton;
|
|
||||||
@synthesize cancelButton = _cancelButton;
|
|
||||||
@synthesize textView = _textView;
|
|
||||||
@synthesize progressSheet = _progressSheet;
|
|
||||||
@synthesize progressIndicator = _progressIndicator;
|
|
||||||
@synthesize appNameLabel;
|
|
||||||
@synthesize appVersionLabel;
|
|
||||||
@synthesize appCopyrightLabel;
|
|
||||||
@synthesize appEULAContent;
|
|
||||||
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSLog(@"vChewing: Application Launched.");
|
|
||||||
_installingVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey];
|
|
||||||
NSString *versionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
|
||||||
|
|
||||||
_archiveUtil = [[ArchiveUtil alloc] initWithAppName:kTargetBin targetAppBundleName:kTargetBundle];
|
|
||||||
[_archiveUtil validateIfNotarizedArchiveExists];
|
|
||||||
|
|
||||||
[self.cancelButton setNextKeyView:self.installButton];
|
|
||||||
[self.installButton setNextKeyView:self.cancelButton];
|
|
||||||
[[self window] setDefaultButtonCell:[self.installButton cell]];
|
|
||||||
|
|
||||||
|
|
||||||
[self.window standardWindowButton:NSWindowCloseButton].hidden = true;
|
|
||||||
[self.window standardWindowButton:NSWindowMiniaturizeButton].hidden = true;
|
|
||||||
[self.window standardWindowButton:NSWindowZoomButton].hidden = true;
|
|
||||||
|
|
||||||
// Update Info
|
|
||||||
NSDictionary* localizedInfoDictionary = [[NSBundle mainBundle] localizedInfoDictionary];
|
|
||||||
|
|
||||||
self.appNameLabel.stringValue = [localizedInfoDictionary objectForKey:@"CFBundleName"];
|
|
||||||
// self.appVersionLabel.stringValue = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
|
|
||||||
self.appVersionLabel.stringValue = [NSString stringWithFormat:@"%@ Build %@", versionString, _installingVersion];
|
|
||||||
self.appCopyrightLabel.stringValue = [localizedInfoDictionary objectForKey:@"NSHumanReadableCopyright"];
|
|
||||||
self.appEULAContent.string = [localizedInfoDictionary objectForKey:@"CFEULAContent"];
|
|
||||||
|
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:[kTargetPartialPath stringByExpandingTildeInPath]]) {
|
|
||||||
NSBundle *currentBundle = [NSBundle bundleWithPath:[kTargetPartialPath stringByExpandingTildeInPath]];
|
|
||||||
|
|
||||||
NSString *shortVersion = [[currentBundle infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
|
||||||
NSString *currentVersion = [[currentBundle infoDictionary] objectForKey:(id)kCFBundleVersionKey];
|
|
||||||
|
|
||||||
_currentVersionNumber = [currentVersion integerValue];
|
|
||||||
if (shortVersion && currentVersion && [currentVersion compare:_installingVersion options:NSNumericSearch] == NSOrderedAscending) {
|
|
||||||
_upgrading = YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_upgrading) {
|
|
||||||
[_installButton setTitle:NSLocalizedString(@"Upgrade", nil)];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[self window] center];
|
|
||||||
[[self window] orderFrontRegardless];
|
|
||||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)agreeAndInstallAction:(id)sender
|
|
||||||
{
|
|
||||||
[_cancelButton setEnabled:NO];
|
|
||||||
[_installButton setEnabled:NO];
|
|
||||||
[self removeThenInstallInputMethod];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeThenInstallInputMethod
|
|
||||||
{
|
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:[kTargetPartialPath stringByExpandingTildeInPath]]) {
|
|
||||||
|
|
||||||
BOOL shouldWaitForTranslocationRemoval =
|
|
||||||
[self appBundleTranslocatedToARandomizedPath:kTargetPartialPath] &&
|
|
||||||
[self.window respondsToSelector:@selector(beginSheet:completionHandler:)];
|
|
||||||
|
|
||||||
// http://www.cocoadev.com/index.pl?MoveToTrash
|
|
||||||
NSString *sourceDir = [kDestinationPartial stringByExpandingTildeInPath];
|
|
||||||
NSString *trashDir = [NSHomeDirectory() stringByAppendingPathComponent:@".Trash"];
|
|
||||||
NSInteger tag;
|
|
||||||
|
|
||||||
[[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:sourceDir destination:trashDir files:[NSArray arrayWithObject:kTargetBundle] tag:&tag];
|
|
||||||
(void)tag;
|
|
||||||
|
|
||||||
NSTask *killTask = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/killall" arguments:[NSArray arrayWithObjects: @"-9", kTargetBin, nil]];
|
|
||||||
[killTask waitUntilExit];
|
|
||||||
|
|
||||||
if (shouldWaitForTranslocationRemoval) {
|
|
||||||
[self.progressIndicator startAnimation:self];
|
|
||||||
[self.window beginSheet:self.progressSheet completionHandler:^(NSModalResponse returnCode) {
|
|
||||||
// Schedule the install action in runloop so that the sheet gets a change to dismiss itself.
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
if (returnCode == NSModalResponseContinue) {
|
|
||||||
[self installInputMethodWithPreviousExists:YES previousVersionNotFullyDeactivatedWarning:NO];
|
|
||||||
} else {
|
|
||||||
[self installInputMethodWithPreviousExists:YES previousVersionNotFullyDeactivatedWarning:YES];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}];
|
|
||||||
|
|
||||||
_translocationRemovalStartTime = [NSDate date];
|
|
||||||
[NSTimer scheduledTimerWithTimeInterval:kTranslocationRemovalTickInterval target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[self installInputMethodWithPreviousExists:NO previousVersionNotFullyDeactivatedWarning:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)timerTick:(NSTimer *)timer
|
|
||||||
{
|
|
||||||
NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:_translocationRemovalStartTime];
|
|
||||||
[self.progressIndicator setDoubleValue:MIN(elapsed / kTranslocationRemovalDeadline, 1.0)];
|
|
||||||
|
|
||||||
if (elapsed >= kTranslocationRemovalDeadline) {
|
|
||||||
[timer invalidate];
|
|
||||||
[self.window endSheet:self.progressSheet returnCode:NSModalResponseCancel];
|
|
||||||
} else if (![self appBundleTranslocatedToARandomizedPath:kTargetPartialPath]) {
|
|
||||||
[self.progressIndicator setDoubleValue:1.0];
|
|
||||||
[timer invalidate];
|
|
||||||
[self.window endSheet:self.progressSheet returnCode:NSModalResponseContinue];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)installInputMethodWithPreviousExists:(BOOL)previousVersionExists previousVersionNotFullyDeactivatedWarning:(BOOL)warning
|
|
||||||
{
|
|
||||||
// If the unzipped archive does not exist, this must be a dev-mode installer.
|
|
||||||
NSString *targetBundle = [_archiveUtil unzipNotarizedArchive];
|
|
||||||
if (!targetBundle) {
|
|
||||||
targetBundle = [[NSBundle mainBundle] pathForResource:kTargetBin ofType:kTargetType];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSTask *cpTask = [NSTask launchedTaskWithLaunchPath:@"/bin/cp" arguments:[NSArray arrayWithObjects:@"-R", targetBundle, [kDestinationPartial stringByExpandingTildeInPath], nil]];
|
|
||||||
[cpTask waitUntilExit];
|
|
||||||
if ([cpTask terminationStatus] != 0) {
|
|
||||||
RunAlertPanel(NSLocalizedString(@"Install Failed", nil), NSLocalizedString(@"Cannot copy the file to the destination.", nil), NSLocalizedString(@"Cancel", nil));
|
|
||||||
[NSApp terminate:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSBundle *imeBundle = [NSBundle bundleWithPath:[kTargetPartialPath stringByExpandingTildeInPath]];
|
|
||||||
NSCAssert(imeBundle != nil, @"Target bundle must exists");
|
|
||||||
NSURL *imeBundleURL = imeBundle.bundleURL;
|
|
||||||
NSString *imeIdentifier = imeBundle.bundleIdentifier;
|
|
||||||
|
|
||||||
TISInputSourceRef inputSource = [InputSourceHelper inputSourceForInputSourceID:imeIdentifier];
|
|
||||||
|
|
||||||
// if this IME name is not found in the list of available IMEs
|
|
||||||
if (!inputSource) {
|
|
||||||
NSLog(@"Registering input source %@ at %@.", imeIdentifier, imeBundleURL.absoluteString);
|
|
||||||
// then register
|
|
||||||
BOOL status = [InputSourceHelper registerInputSource:imeBundleURL];
|
|
||||||
|
|
||||||
if (!status) {
|
|
||||||
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"Cannot register input source %@ at %@.", nil), imeIdentifier, imeBundleURL.absoluteString];
|
|
||||||
RunAlertPanel(NSLocalizedString(@"Fatal Error", nil), message, NSLocalizedString(@"Abort", nil));
|
|
||||||
[self endAppWithDelay];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputSource = [InputSourceHelper inputSourceForInputSourceID:imeIdentifier];
|
|
||||||
// if it still doesn't register successfully, bail.
|
|
||||||
if (!inputSource) {
|
|
||||||
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"Cannot find input source %@ after registration.", nil), imeIdentifier];
|
|
||||||
RunAlertPanel(NSLocalizedString(@"Fatal Error", nil), message, NSLocalizedString(@"Abort", nil));
|
|
||||||
[self endAppWithDelay];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL isMacOS12OrAbove = NO;
|
|
||||||
if (@available(macOS 12.0, *)) {
|
|
||||||
NSLog(@"macOS 12 or later detected.");
|
|
||||||
isMacOS12OrAbove = YES;
|
|
||||||
} else {
|
|
||||||
NSLog(@"Installer runs with the pre-macOS 12 flow.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the IME is not enabled, enable it. Also, unconditionally enable it 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).
|
|
||||||
BOOL mainInputSourceEnabled = [InputSourceHelper inputSourceEnabled:inputSource];
|
|
||||||
if (!mainInputSourceEnabled || isMacOS12OrAbove) {
|
|
||||||
|
|
||||||
mainInputSourceEnabled = [InputSourceHelper enableInputSource:inputSource];
|
|
||||||
if (mainInputSourceEnabled) {
|
|
||||||
NSLog(@"Input method enabled: %@", imeIdentifier);
|
|
||||||
} else {
|
|
||||||
NSLog(@"Failed to enable input method: %@", imeIdentifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (warning) {
|
|
||||||
RunAlertPanel(NSLocalizedString(@"Attention", nil), NSLocalizedString(@"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", nil), NSLocalizedString(@"OK", nil));
|
|
||||||
} else {
|
|
||||||
// Only prompt a warning if pre-macOS 12. The flag is not indicative of anything meaningful due to the need of user intervention in Prefernces.app on macOS 12.
|
|
||||||
if (!mainInputSourceEnabled && !isMacOS12OrAbove) {
|
|
||||||
RunAlertPanel(NSLocalizedString(@"Warning", nil), NSLocalizedString(@"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", nil), NSLocalizedString(@"Continue", nil));
|
|
||||||
} else {
|
|
||||||
RunAlertPanel(NSLocalizedString(@"Installation Successful", nil), NSLocalizedString(@"vChewing is ready to use.", nil), NSLocalizedString(@"OK", nil));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[self endAppWithDelay];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)endAppWithDelay
|
|
||||||
{
|
|
||||||
[[NSApplication sharedApplication] performSelector:@selector(terminate:) withObject:self afterDelay:0.1];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)cancelAction:(id)sender
|
|
||||||
{
|
|
||||||
[NSApp terminate:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)windowWillClose:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
[NSApp terminate:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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/
|
|
||||||
- (BOOL)appBundleTranslocatedToARandomizedPath:(NSString *)bundle
|
|
||||||
{
|
|
||||||
const char *bundleAbsPath = [[bundle stringByExpandingTildeInPath] UTF8String];
|
|
||||||
int entryCount = getfsstat(NULL, 0, 0);
|
|
||||||
int entrySize = sizeof(struct statfs);
|
|
||||||
struct statfs *bufs = (struct statfs *)calloc(entryCount, entrySize);
|
|
||||||
entryCount = getfsstat(bufs, entryCount * entrySize, MNT_NOWAIT);
|
|
||||||
for (int i = 0; i < entryCount; i++) {
|
|
||||||
if (!strcmp(bundleAbsPath, bufs[i].f_mntfromname)) {
|
|
||||||
free(bufs);
|
|
||||||
|
|
||||||
// getfsstat() may return us a cached result, and so we need to get the stat of the mounted fs.
|
|
||||||
// If statfs() returns an error, the mounted fs is already gone.
|
|
||||||
struct statfs stat;
|
|
||||||
int checkResult = statfs(bundleAbsPath, &stat);
|
|
||||||
if (checkResult != 0) {
|
|
||||||
// Meaning the app's bundle is not mounted, that is it's not translocated.
|
|
||||||
// It also means that the app is not loaded.
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(bufs);
|
|
||||||
return NO;
|
|
||||||
|
|
||||||
}
|
|
||||||
@end
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
* AppDelegate.swift
|
||||||
|
*
|
||||||
|
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
||||||
|
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
||||||
|
* Some rights reserved. See "LICENSE.TXT" for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
private let kTargetBin = "vChewing"
|
||||||
|
private let kTargetType = "app"
|
||||||
|
private let kTargetBundle = "vChewing.app"
|
||||||
|
private let kDestinationPartial = "~/Library/Input Methods/"
|
||||||
|
private let kTargetPartialPath = "~/Library/Input Methods/vChewing.app"
|
||||||
|
private let kTargetFullBinPartialPath = "~/Library/Input Methods/vChewing.app/Contents/MacOS/vChewing"
|
||||||
|
|
||||||
|
private let kTranslocationRemovalTickInterval: TimeInterval = 0.5
|
||||||
|
private let kTranslocationRemovalDeadline: TimeInterval = 60.0
|
||||||
|
|
||||||
|
@objc (AppDelegate)
|
||||||
|
class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
|
@IBOutlet weak private var installButton: NSButton!
|
||||||
|
@IBOutlet weak private var cancelButton: NSButton!
|
||||||
|
@IBOutlet weak private var progressSheet: NSWindow!
|
||||||
|
@IBOutlet weak private var progressIndicator: NSProgressIndicator!
|
||||||
|
@IBOutlet weak private var appVersionLabel: NSTextField!
|
||||||
|
@IBOutlet weak private var appCopyrightLabel: NSTextField!
|
||||||
|
@IBOutlet private var appEULAContent: NSTextView!
|
||||||
|
|
||||||
|
private var archiveUtil: ArchiveUtil?
|
||||||
|
private var installingVersion = ""
|
||||||
|
private var upgrading = false
|
||||||
|
private var translocationRemovalStartTime: Date?
|
||||||
|
private var currentVersionNumber: Int = 0
|
||||||
|
|
||||||
|
func runAlertPanel(title: String, message: String, buttonTitle: String) {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.alertStyle = .informational
|
||||||
|
alert.messageText = title
|
||||||
|
alert.informativeText = message
|
||||||
|
alert.addButton(withTitle: buttonTitle)
|
||||||
|
alert.runModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String,
|
||||||
|
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.installingVersion = installingVersion
|
||||||
|
self.archiveUtil = ArchiveUtil(appName: kTargetBin, targetAppBundleName: kTargetBundle)
|
||||||
|
_ = archiveUtil?.validateIfNotarizedArchiveExists()
|
||||||
|
|
||||||
|
cancelButton.nextKeyView = installButton
|
||||||
|
installButton.nextKeyView = cancelButton
|
||||||
|
if let cell = installButton.cell as? NSButtonCell {
|
||||||
|
window?.defaultButtonCell = cell
|
||||||
|
}
|
||||||
|
|
||||||
|
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String {
|
||||||
|
appCopyrightLabel.stringValue = copyrightLabel
|
||||||
|
}
|
||||||
|
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
|
||||||
|
appEULAContent.string = eulaContent
|
||||||
|
}
|
||||||
|
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion)
|
||||||
|
|
||||||
|
window?.title = String(format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "", versionString, installingVersion)
|
||||||
|
window?.standardWindowButton(.closeButton)?.isHidden = true
|
||||||
|
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||||
|
window?.standardWindowButton(.zoomButton)?.isHidden = true
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) {
|
||||||
|
let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath)
|
||||||
|
let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||||
|
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
|
||||||
|
currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0
|
||||||
|
if shortVersion != nil, let currentVersion = currentVersion, currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending {
|
||||||
|
upgrading = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if upgrading {
|
||||||
|
installButton.title = NSLocalizedString("Agree and Upgrade", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
window?.center()
|
||||||
|
window?.orderFront(self)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func agreeAndInstallAction(_ sender: AnyObject) {
|
||||||
|
cancelButton.isEnabled = false
|
||||||
|
installButton.isEnabled = false
|
||||||
|
removeThenInstallInputMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func timerTick(_ timer: Timer) {
|
||||||
|
let elapsed = Date().timeIntervalSince(translocationRemovalStartTime ?? Date())
|
||||||
|
if elapsed >= kTranslocationRemovalDeadline {
|
||||||
|
timer.invalidate()
|
||||||
|
window?.endSheet(progressSheet, returnCode: .cancel)
|
||||||
|
} else if appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) == false {
|
||||||
|
progressIndicator.doubleValue = 1.0
|
||||||
|
timer.invalidate()
|
||||||
|
window?.endSheet(progressSheet, returnCode: .continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeThenInstallInputMethod() {
|
||||||
|
if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) == false {
|
||||||
|
self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let shouldWaitForTranslocationRemoval = appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false)
|
||||||
|
|
||||||
|
// http://www.cocoadev.com/index.pl?MoveToTrash
|
||||||
|
let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath
|
||||||
|
let trashDir = (NSHomeDirectory() as NSString).appendingPathComponent(".Trash")
|
||||||
|
var tag = 0
|
||||||
|
|
||||||
|
NSWorkspace.shared.performFileOperation(.recycleOperation, source: sourceDir, destination: trashDir, files: [kTargetBundle], tag: &tag)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func installInputMethod(previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool) {
|
||||||
|
guard let targetBundle = archiveUtil?.unzipNotarizedArchive() ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let cpTask = Process()
|
||||||
|
cpTask.launchPath = "/bin/cp"
|
||||||
|
cpTask.arguments = ["-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath]
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath),
|
||||||
|
let imeIdentifier = imeBundle.bundleIdentifier
|
||||||
|
else {
|
||||||
|
endAppWithDelay()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let imeBundleURL = imeBundle.bundleURL
|
||||||
|
var inputSource = InputSourceHelper.inputSource(for: imeIdentifier)
|
||||||
|
|
||||||
|
if inputSource == nil {
|
||||||
|
NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).");
|
||||||
|
let status = InputSourceHelper.registerTnputSource(at: imeBundleURL)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
inputSource = InputSourceHelper.inputSource(for: imeIdentifier)
|
||||||
|
if inputSource == nil {
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the IME is not enabled, enable it. Also, unconditionally enable it 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 = InputSourceHelper.inputSourceEnabled(for: inputSource!)
|
||||||
|
if !mainInputSourceEnabled || isMacOS12OrAbove {
|
||||||
|
mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!)
|
||||||
|
if (mainInputSourceEnabled) {
|
||||||
|
NSLog("Input method enabled: \(imeIdentifier)");
|
||||||
|
} else {
|
||||||
|
NSLog("Failed to enable input method: \(imeIdentifier)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if warning {
|
||||||
|
runAlertPanel(title: NSLocalizedString("Attention", comment: ""), message: NSLocalizedString("vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", comment: ""), buttonTitle: NSLocalizedString("OK", comment: ""))
|
||||||
|
} else {
|
||||||
|
if !mainInputSourceEnabled && !isMacOS12OrAbove {
|
||||||
|
runAlertPanel(title: NSLocalizedString("Warning", comment: ""), message: NSLocalizedString("Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", comment: ""), buttonTitle: NSLocalizedString("Continue", comment: ""))
|
||||||
|
} else {
|
||||||
|
runAlertPanel(title: NSLocalizedString("Installation Successful", comment: ""), message: NSLocalizedString("vChewing is ready to use.", comment: ""), buttonTitle: NSLocalizedString("OK", comment: ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endAppWithDelay()
|
||||||
|
}
|
||||||
|
|
||||||
|
func endAppWithDelay() {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
||||||
|
NSApp.terminate(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func cancelAction(_ sender: AnyObject) {
|
||||||
|
NSApp.terminate(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowWillClose(_ Notification: Notification) {
|
||||||
|
NSApp.terminate(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* ArchiveUtil.h
|
|
||||||
*
|
|
||||||
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
|
||||||
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
|
||||||
* Some rights reserved. See "LICENSE.TXT" for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@interface ArchiveUtil : NSObject {
|
|
||||||
NSString *_appName;
|
|
||||||
NSString *_targetAppBundleName;
|
|
||||||
}
|
|
||||||
- (instancetype _Nonnull)initWithAppName:(NSString *_Nonnull)name
|
|
||||||
targetAppBundleName:(NSString *_Nonnull)invalidAppBundleName;
|
|
||||||
|
|
||||||
// Returns YES if (1) a zip file under
|
|
||||||
// Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if
|
|
||||||
// Resources/$_invalidAppBundleName does not exist.
|
|
||||||
- (BOOL)validateIfNotarizedArchiveExists;
|
|
||||||
|
|
||||||
- (NSString *_Nullable)unzipNotarizedArchive;
|
|
||||||
@end
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* ArchiveUtil.m
|
|
||||||
*
|
|
||||||
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
|
||||||
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
|
||||||
* Some rights reserved. See "LICENSE.TXT" for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "ArchiveUtil.h"
|
|
||||||
|
|
||||||
@implementation ArchiveUtil
|
|
||||||
- (instancetype)initWithAppName:(NSString *)name
|
|
||||||
targetAppBundleName:(NSString *)targetAppBundleName {
|
|
||||||
self = [super init];
|
|
||||||
if (self) {
|
|
||||||
_appName = name;
|
|
||||||
_targetAppBundleName = targetAppBundleName;
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)delloc {
|
|
||||||
_appName = nil;
|
|
||||||
_targetAppBundleName = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)validateIfNotarizedArchiveExists {
|
|
||||||
NSString *resourePath = [[NSBundle mainBundle] resourcePath];
|
|
||||||
NSString *devModeAppBundlePath =
|
|
||||||
[resourePath stringByAppendingPathComponent:_targetAppBundleName];
|
|
||||||
|
|
||||||
NSArray<NSString *> *notarizedArchivesContent =
|
|
||||||
[[NSFileManager defaultManager] subpathsAtPath:[self notarizedArchivesPath]];
|
|
||||||
NSInteger count = [notarizedArchivesContent count];
|
|
||||||
BOOL notarizedArchiveExists =
|
|
||||||
[[NSFileManager defaultManager] fileExistsAtPath:[self notarizedArchive]];
|
|
||||||
BOOL devModeAppBundleExists =
|
|
||||||
[[NSFileManager defaultManager] fileExistsAtPath:devModeAppBundlePath];
|
|
||||||
|
|
||||||
if (count > 0) {
|
|
||||||
// Not a valid distribution package.
|
|
||||||
if (count != 1 || !notarizedArchiveExists || devModeAppBundleExists) {
|
|
||||||
NSAlert *alert = [[NSAlert alloc] init];
|
|
||||||
[alert setAlertStyle:NSAlertStyleInformational];
|
|
||||||
[alert setMessageText:@"Internal Error"];
|
|
||||||
[alert
|
|
||||||
setInformativeText:
|
|
||||||
[NSString stringWithFormat:@"devMode installer, expected archive name: %@, "
|
|
||||||
@"archive exists: %d, devMode app bundle exists: %d",
|
|
||||||
[self notarizedArchive], notarizedArchiveExists,
|
|
||||||
devModeAppBundleExists]];
|
|
||||||
[alert addButtonWithTitle:@"Terminate"];
|
|
||||||
[alert runModal];
|
|
||||||
|
|
||||||
[[NSApplication sharedApplication] terminate:nil];
|
|
||||||
} else {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!devModeAppBundleExists) {
|
|
||||||
NSAlert *alert = [[NSAlert alloc] init];
|
|
||||||
[alert setAlertStyle:NSAlertStyleInformational];
|
|
||||||
[alert setMessageText:@"Internal Error"];
|
|
||||||
[alert
|
|
||||||
setInformativeText:[NSString stringWithFormat:@"Dev target bundle does not exist: %@",
|
|
||||||
devModeAppBundlePath]];
|
|
||||||
[alert addButtonWithTitle:@"Terminate"];
|
|
||||||
[alert runModal];
|
|
||||||
[[NSApplication sharedApplication] terminate:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notarized archive does not exist, but it's ok.
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)unzipNotarizedArchive {
|
|
||||||
if (![self validateIfNotarizedArchiveExists]) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *tempFilePath =
|
|
||||||
[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
|
|
||||||
NSArray *arguments = @[ [self notarizedArchive], @"-d", tempFilePath ];
|
|
||||||
|
|
||||||
NSTask *unzipTask = [[NSTask alloc] init];
|
|
||||||
[unzipTask setLaunchPath:@"/usr/bin/unzip"];
|
|
||||||
[unzipTask setCurrentDirectoryPath:[[NSBundle mainBundle] resourcePath]];
|
|
||||||
[unzipTask setArguments:arguments];
|
|
||||||
[unzipTask launch];
|
|
||||||
[unzipTask waitUntilExit];
|
|
||||||
|
|
||||||
NSAssert(unzipTask.terminationStatus == 0, @"Must successfully unzipped");
|
|
||||||
|
|
||||||
NSString *result = [tempFilePath stringByAppendingPathComponent:_targetAppBundleName];
|
|
||||||
NSAssert([[NSFileManager defaultManager] fileExistsAtPath:result],
|
|
||||||
@"App bundle must be unzipped at %@", result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)notarizedArchivesPath {
|
|
||||||
NSString *resourePath = [[NSBundle mainBundle] resourcePath];
|
|
||||||
NSString *notarizedArchivesPath =
|
|
||||||
[resourePath stringByAppendingPathComponent:@"NotarizedArchives"];
|
|
||||||
return notarizedArchivesPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)notarizedArchive {
|
|
||||||
NSString *bundleVersion =
|
|
||||||
[[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey];
|
|
||||||
NSString *notarizedArchiveBasename =
|
|
||||||
[NSString stringWithFormat:@"%@-r%@.zip", _appName, bundleVersion];
|
|
||||||
NSString *notarizedArchive =
|
|
||||||
[[self notarizedArchivesPath] stringByAppendingPathComponent:notarizedArchiveBasename];
|
|
||||||
return notarizedArchive;
|
|
||||||
}
|
|
||||||
@end
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* ArchiveUtil.swift
|
||||||
|
*
|
||||||
|
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
||||||
|
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
||||||
|
* Some rights reserved. See "LICENSE.TXT" for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
struct ArchiveUtil {
|
||||||
|
var appName: String
|
||||||
|
var targetAppBundleName: String
|
||||||
|
|
||||||
|
init(appName: String, targetAppBundleName: String) {
|
||||||
|
self.appName = appName
|
||||||
|
self.targetAppBundleName = targetAppBundleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns YES if (1) a zip file under
|
||||||
|
// Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if
|
||||||
|
// Resources/$_invalidAppBundleName does not exist.
|
||||||
|
func validateIfNotarizedArchiveExists() -> Bool {
|
||||||
|
guard let resourePath = Bundle.main.resourcePath,
|
||||||
|
let notarizedArchivesPath = notarizedArchivesPath,
|
||||||
|
let notarizedArchive = notarizedArchive,
|
||||||
|
let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory(atPath: notarizedArchivesPath)
|
||||||
|
else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName)
|
||||||
|
let count = notarizedArchivesContent.count
|
||||||
|
let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive)
|
||||||
|
let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath)
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
if count != 1 || !notarizedArchiveExists || devModeAppBundleExists {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.alertStyle = .informational
|
||||||
|
alert.messageText = "Internal Error"
|
||||||
|
alert.informativeText = "devMode installer, expected archive name: \(notarizedArchive), " +
|
||||||
|
"archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)"
|
||||||
|
alert.addButton(withTitle: "Terminate")
|
||||||
|
alert.runModal()
|
||||||
|
NSApp.terminate(nil)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !devModeAppBundleExists {
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.alertStyle = .informational
|
||||||
|
alert.messageText = "Internal Error"
|
||||||
|
alert.informativeText = "Dev target bundle does not exist: \(devModeAppBundlePath)"
|
||||||
|
alert.addButton(withTitle: "Terminate")
|
||||||
|
alert.runModal()
|
||||||
|
NSApp.terminate(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func unzipNotarizedArchive() -> String? {
|
||||||
|
if !self.validateIfNotarizedArchiveExists() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let notarizedArchive = notarizedArchive,
|
||||||
|
let resourcePath = Bundle.main.resourcePath else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString)
|
||||||
|
let arguments: [String] = [notarizedArchive, "-d", tempFilePath]
|
||||||
|
let unzipTask = Process()
|
||||||
|
unzipTask.launchPath = "/usr/bin/unzip"
|
||||||
|
unzipTask.currentDirectoryPath = resourcePath
|
||||||
|
unzipTask.arguments = arguments
|
||||||
|
unzipTask.launch()
|
||||||
|
unzipTask.waitUntilExit()
|
||||||
|
|
||||||
|
assert(unzipTask.terminationStatus == 0, "Must successfully unzipped")
|
||||||
|
guard let result = (tempFilePath as NSString).appendingPathExtension(targetAppBundleName) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
assert(FileManager.default.fileExists(atPath: result), "App bundle must be unzipped at \(resourcePath).")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private var notarizedArchivesPath: String? {
|
||||||
|
let resourePath = Bundle.main.resourcePath
|
||||||
|
let notarizedArchivesPath = resourePath?.appending("NotarizedArchives")
|
||||||
|
return notarizedArchivesPath
|
||||||
|
}
|
||||||
|
|
||||||
|
private var notarizedArchive: String? {
|
||||||
|
guard let notarizedArchivesPath = notarizedArchivesPath,
|
||||||
|
let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip"
|
||||||
|
let notarizedArchive = notarizedArchivesPath.appending(notarizedArchiveBasename)
|
||||||
|
return notarizedArchive
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -271,6 +271,7 @@ Gw
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="appCopyrightLabel" destination="03l-rN-zf9" id="XS5-cZ-k9H"/>
|
<outlet property="appCopyrightLabel" destination="03l-rN-zf9" id="XS5-cZ-k9H"/>
|
||||||
<outlet property="appEULAContent" destination="47J-tO-8TZ" id="kRU-X2-8kX"/>
|
<outlet property="appEULAContent" destination="47J-tO-8TZ" id="kRU-X2-8kX"/>
|
||||||
|
<outlet property="appNameLabel" destination="bzR-Oa-BZa" id="swF-1l-dhS"/>
|
||||||
<outlet property="appVersionLabel" destination="z1m-8k-Z63" id="75X-uy-0Iz"/>
|
<outlet property="appVersionLabel" destination="z1m-8k-Z63" id="75X-uy-0Iz"/>
|
||||||
<outlet property="cancelButton" destination="592" id="710"/>
|
<outlet property="cancelButton" destination="592" id="710"/>
|
||||||
<outlet property="installButton" destination="575" id="709"/>
|
<outlet property="installButton" destination="575" id="709"/>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Chronosphere.h
|
||||||
|
*
|
||||||
|
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
||||||
|
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
||||||
|
* Some rights reserved. See "LICENSE.TXT" for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import Cocoa;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
// 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/
|
||||||
|
BOOL appBundleChronoshiftedToARandomizedPath(NSString *bundle);
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Chronosphere.m
|
||||||
|
*
|
||||||
|
* Copyright 2021-2022 vChewing Project (3-Clause BSD License).
|
||||||
|
* Derived from 2011-2022 OpenVanilla Project (MIT License).
|
||||||
|
* Some rights reserved. See "LICENSE.TXT" for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "Chronosphere.h"
|
||||||
|
#import <sys/mount.h>
|
||||||
|
|
||||||
|
BOOL appBundleChronoshiftedToARandomizedPath(NSString *bundle)
|
||||||
|
{
|
||||||
|
const char *bundleAbsPath = [[bundle stringByExpandingTildeInPath] UTF8String];
|
||||||
|
int entryCount = getfsstat(NULL, 0, 0);
|
||||||
|
int entrySize = sizeof(struct statfs);
|
||||||
|
struct statfs *bufs = (struct statfs *)calloc(entryCount, entrySize);
|
||||||
|
entryCount = getfsstat(bufs, entryCount * entrySize, MNT_NOWAIT);
|
||||||
|
for (int i = 0; i < entryCount; i++) {
|
||||||
|
if (!strcmp(bundleAbsPath, bufs[i].f_mntfromname)) {
|
||||||
|
free(bufs);
|
||||||
|
|
||||||
|
// getfsstat() may return us a cached result, and so we need to get the stat of the mounted fs.
|
||||||
|
// If statfs() returns an error, the mounted fs is already gone.
|
||||||
|
struct statfs stat;
|
||||||
|
int checkResult = statfs(bundleAbsPath, &stat);
|
||||||
|
if (checkResult != 0) {
|
||||||
|
// Meaning the app's bundle is not mounted, that is it's not translocated.
|
||||||
|
// It also means that the app is not loaded.
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(bufs);
|
||||||
|
return NO;
|
||||||
|
}
|
|
@ -10,3 +10,4 @@
|
||||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#import "Chronosphere.h"
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
5B000FC3278495AD004F02AC /* SimpBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC1278495AD004F02AC /* SimpBopomofo.tiff */; };
|
5B000FC3278495AD004F02AC /* SimpBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC1278495AD004F02AC /* SimpBopomofo.tiff */; };
|
||||||
5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC2278495AD004F02AC /* SimpBopomofo@2x.tiff */; };
|
5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC2278495AD004F02AC /* SimpBopomofo@2x.tiff */; };
|
||||||
5B1958522788A2BF00FAEB14 /* BSDLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* BSDLicense.txt */; };
|
5B1958522788A2BF00FAEB14 /* BSDLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* BSDLicense.txt */; };
|
||||||
|
5B21711B279B998C00F91A2B /* Chronosphere.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B217119279B998800F91A2B /* Chronosphere.m */; };
|
||||||
|
5B21711E279B9AD900F91A2B /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21711C279B9AD700F91A2B /* ArchiveUtil.swift */; };
|
||||||
|
5B217126279BA37500F91A2B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B217124279BA37300F91A2B /* AppDelegate.swift */; };
|
||||||
5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; };
|
5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; };
|
||||||
5B58E87F278413E7003EA2AD /* BSDLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* BSDLicense.txt */; };
|
5B58E87F278413E7003EA2AD /* BSDLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* BSDLicense.txt */; };
|
||||||
5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; };
|
5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; };
|
||||||
|
@ -50,13 +53,11 @@
|
||||||
6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4A15FC0EE100ABF4B3 /* Localizable.strings */; };
|
6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4A15FC0EE100ABF4B3 /* Localizable.strings */; };
|
||||||
6A187E2616004C5900466B2E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A187E2816004C5900466B2E /* MainMenu.xib */; };
|
6A187E2616004C5900466B2E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A187E2816004C5900466B2E /* MainMenu.xib */; };
|
||||||
6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */ = {isa = PBXBuildFile; fileRef = 6A225A1E23679F2600F685C6 /* NotarizedArchives */; };
|
6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */ = {isa = PBXBuildFile; fileRef = 6A225A1E23679F2600F685C6 /* NotarizedArchives */; };
|
||||||
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A225A222367A1D700F685C6 /* ArchiveUtil.m */; };
|
|
||||||
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
||||||
6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
||||||
6A38BC1515FC117A00A8A51F /* data-cht.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data-cht.txt */; };
|
6A38BC1515FC117A00A8A51F /* data-cht.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data-cht.txt */; };
|
||||||
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
|
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
|
||||||
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
|
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
|
||||||
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; };
|
|
||||||
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; };
|
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; };
|
||||||
6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; };
|
6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; };
|
||||||
6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; };
|
6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; };
|
||||||
|
@ -90,6 +91,10 @@
|
||||||
5B19584E27888F5D00FAEB14 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
5B19584E27888F5D00FAEB14 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
||||||
5B19584F27888F5F00FAEB14 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
5B19584F27888F5F00FAEB14 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
||||||
5B19585127888F6B00FAEB14 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = "<group>"; };
|
5B19585127888F6B00FAEB14 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = "<group>"; };
|
||||||
|
5B217118279B998800F91A2B /* Chronosphere.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Chronosphere.h; sourceTree = "<group>"; };
|
||||||
|
5B217119279B998800F91A2B /* Chronosphere.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Chronosphere.m; sourceTree = "<group>"; };
|
||||||
|
5B21711C279B9AD700F91A2B /* ArchiveUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveUtil.swift; sourceTree = "<group>"; };
|
||||||
|
5B217124279BA37300F91A2B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = "<group>"; };
|
5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = "<group>"; };
|
||||||
5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
||||||
5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = "<group>"; };
|
5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = "<group>"; };
|
||||||
|
@ -210,15 +215,11 @@
|
||||||
6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
6A15B32721A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/Base.lproj/preferences.xib; sourceTree = "<group>"; };
|
6A15B32721A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/Base.lproj/preferences.xib; sourceTree = "<group>"; };
|
||||||
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
|
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
|
||||||
6A225A212367A1D700F685C6 /* ArchiveUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveUtil.h; sourceTree = "<group>"; };
|
|
||||||
6A225A222367A1D700F685C6 /* ArchiveUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArchiveUtil.m; sourceTree = "<group>"; };
|
|
||||||
6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||||
6A38BBF615FC117A00A8A51F /* data-cht.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-cht.txt"; sourceTree = "<group>"; };
|
6A38BBF615FC117A00A8A51F /* data-cht.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-cht.txt"; sourceTree = "<group>"; };
|
||||||
6A38BBFA15FC117A00A8A51F /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
|
6A38BBFA15FC117A00A8A51F /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
|
||||||
6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; };
|
6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
6ACA41CB15FC1D7500935EF6 /* vChewingInstaller.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingInstaller.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
6ACA41CB15FC1D7500935EF6 /* vChewingInstaller.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingInstaller.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
6ACA41E815FC1D9000935EF6 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Source/Installer/AppDelegate.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
6ACA41E915FC1D9000935EF6 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Source/Installer/AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
6ACA41EB15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
6ACA41EB15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
6ACA41EF15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
6ACA41EF15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Installer-Info.plist"; path = "Source/Installer/Installer-Info.plist"; sourceTree = SOURCE_ROOT; };
|
6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Installer-Info.plist"; path = "Source/Installer/Installer-Info.plist"; sourceTree = SOURCE_ROOT; };
|
||||||
|
@ -513,11 +514,11 @@
|
||||||
6ACA41E715FC1D9000935EF6 /* Installer */ = {
|
6ACA41E715FC1D9000935EF6 /* Installer */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6A225A212367A1D700F685C6 /* ArchiveUtil.h */,
|
5B217118279B998800F91A2B /* Chronosphere.h */,
|
||||||
6A225A222367A1D700F685C6 /* ArchiveUtil.m */,
|
5B217119279B998800F91A2B /* Chronosphere.m */,
|
||||||
|
5B21711C279B9AD700F91A2B /* ArchiveUtil.swift */,
|
||||||
|
5B217124279BA37300F91A2B /* AppDelegate.swift */,
|
||||||
6A225A1E23679F2600F685C6 /* NotarizedArchives */,
|
6A225A1E23679F2600F685C6 /* NotarizedArchives */,
|
||||||
6ACA41E815FC1D9000935EF6 /* AppDelegate.h */,
|
|
||||||
6ACA41E915FC1D9000935EF6 /* AppDelegate.m */,
|
|
||||||
6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */,
|
6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */,
|
||||||
6ACA41EE15FC1D9000935EF6 /* Localizable.strings */,
|
6ACA41EE15FC1D9000935EF6 /* Localizable.strings */,
|
||||||
6ACA41F015FC1D9000935EF6 /* MainMenu.xib */,
|
6ACA41F015FC1D9000935EF6 /* MainMenu.xib */,
|
||||||
|
@ -729,9 +730,10 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */,
|
5B21711E279B9AD900F91A2B /* ArchiveUtil.swift in Sources */,
|
||||||
6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */,
|
5B21711B279B998C00F91A2B /* Chronosphere.m in Sources */,
|
||||||
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
|
6ACA41FF15FC1D9000935EF6 /* main.m in Sources */,
|
||||||
|
5B217126279BA37500F91A2B /* AppDelegate.swift in Sources */,
|
||||||
5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */,
|
5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
Loading…
Reference in New Issue