SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 1d0366052dbd26261265dfbbcd708e74aa2809d7 parent ea05a0c765b84e1674609a1cef21bab166bca64c Author: Lior Halphon <LIJI32@gmail.com> Date: Sun, 25 Apr 2021 22:28:24 +0300 Updater support Diffstat:
| M | Cocoa/AppDelegate.h | 13 | +++++++++++-- |
| M | Cocoa/AppDelegate.m | 309 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | Cocoa/Document.m | 2 | +- |
| M | Cocoa/GBPreferencesWindow.h | 1 | + |
| M | Cocoa/GBPreferencesWindow.m | 19 | +++++++++++++++++++ |
| M | Cocoa/Preferences.xib | 58 | +++++++++++++++++++++++++++++++++++++++++++++++++++------- |
| A | Cocoa/UpdateWindow.xib | 139 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | Makefile | 3 | +++ |
8 files changed, 533 insertions(+), 11 deletions(-)
diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h @@ -1,16 +1,25 @@ #import <Cocoa/Cocoa.h> +#import <WebKit/WebKit.h> -@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate> +@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate, WebUIDelegate, WebPolicyDelegate, WebFrameLoadDelegate> @property (nonatomic, strong) IBOutlet NSWindow *preferencesWindow; @property (nonatomic, strong) IBOutlet NSView *graphicsTab; @property (nonatomic, strong) IBOutlet NSView *emulationTab; @property (nonatomic, strong) IBOutlet NSView *audioTab; @property (nonatomic, strong) IBOutlet NSView *controlsTab; +@property (nonatomic, strong) IBOutlet NSView *updatesTab; - (IBAction)showPreferences: (id) sender; - (IBAction)toggleDeveloperMode:(id)sender; - (IBAction)switchPreferencesTab:(id)sender; @property (nonatomic, weak) IBOutlet NSMenuItem *linkCableMenuItem; - +@property (nonatomic, strong) IBOutlet NSWindow *updateWindow; +@property (nonatomic, strong) IBOutlet WebView *updateChanges; +@property (nonatomic, strong) IBOutlet NSProgressIndicator *updatesSpinner; +@property (strong) IBOutlet NSButton *updatesButton; +@property (strong) IBOutlet NSTextField *updateProgressLabel; +@property (strong) IBOutlet NSButton *updateProgressButton; +@property (strong) IBOutlet NSWindow *updateProgressWindow; +@property (strong) IBOutlet NSProgressIndicator *updateProgressSpinner; @end diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m @@ -4,11 +4,35 @@ #include <Core/gb.h> #import <Carbon/Carbon.h> #import <JoyKit/JoyKit.h> +#import <WebKit/WebKit.h> + +#define UPDATE_SERVER "https://sameboy.github.io" +#define str(x) #x +#define xstr(x) str(x) + +static uint32_t color_to_int(NSColor *color) +{ + color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + return (((unsigned)(color.redComponent * 0xFF)) << 16) | + (((unsigned)(color.greenComponent * 0xFF)) << 8) | + ((unsigned)(color.blueComponent * 0xFF)); +} @implementation AppDelegate { NSWindow *preferences_window; NSArray<NSView *> *preferences_tabs; + NSString *_lastVersion; + NSString *_updateURL; + NSURLSessionDownloadTask *_updateTask; + enum { + UPDATE_DOWNLOADING, + UPDATE_EXTRACTING, + UPDATE_WAIT_INSTALL, + UPDATE_INSTALLING, + UPDATE_FAILED, + } _updateState; + NSString *_downloadDirectory; } - (void) applicationDidFinishLaunching:(NSNotification *)notification @@ -54,6 +78,16 @@ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; } + + [self askAutoUpdates]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]) { + [self checkForUpdates]; + } + + if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) { + [NSApp activateIgnoringOtherApps:YES]; + } } - (IBAction)toggleDeveloperMode:(id)sender @@ -111,15 +145,19 @@ [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects]; NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject]; _preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier]; - preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab]; + preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab, self.updatesTab]; [self switchPreferencesTab:first_toolbar_item]; [_preferencesWindow center]; +#ifndef UPDATE_SUPPORT + [_preferencesWindow.toolbar removeItemAtIndex:4]; +#endif } [_preferencesWindow makeKeyAndOrderFront:self]; } - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender { + [self askAutoUpdates]; /* Bring an existing panel to the foreground */ for (NSWindow *window in [[NSApplication sharedApplication] windows]) { if ([window isKindOfClass:[NSOpenPanel class]]) { @@ -136,6 +174,275 @@ [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; } +- (void)updateFound +{ + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/raw_changes"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + NSColor *linkColor = [NSColor colorWithRed:0.125 green:0.325 blue:1.0 alpha:1.0]; + if (@available(macOS 10.10, *)) { + linkColor = [NSColor linkColor]; + } + + NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSRange cutoffRange = [changes rangeOfString:@"<!--(" xstr(VERSION) ")-->"]; + if (cutoffRange.location != NSNotFound) { + changes = [changes substringToIndex:cutoffRange.location]; + } + + NSString *html = [NSString stringWithFormat:@"<!DOCTYPE html><html><head><title></title>" + "<style>html {background-color:transparent; color: #%06x; line-height:1.5} a:link, a:visited{color:#%06x; text-decoration:none}</style>" + "</head><body>%@</body></html>", + color_to_int([NSColor textColor]), + color_to_int(linkColor), + changes]; + + if ([(NSHTTPURLResponse *)response statusCode] == 200) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSArray *objects; + [[NSBundle mainBundle] loadNibNamed:@"UpdateWindow" owner:self topLevelObjects:&objects]; + self.updateChanges.preferences.standardFontFamily = [NSFont systemFontOfSize:0].familyName; + self.updateChanges.preferences.fixedFontFamily = @"Menlo"; + self.updateChanges.drawsBackground = false; + [self.updateChanges.mainFrame loadHTMLString:html baseURL:nil]; + }); + } + }] resume]; +} + +- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems +{ + // Disable reload context menu + if ([defaultMenuItems count] <= 2) { + return nil; + } + return defaultMenuItems; +} + +- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sender.mainFrame.frameView.documentView.enclosingScrollView.drawsBackground = true; + sender.mainFrame.frameView.documentView.enclosingScrollView.backgroundColor = [NSColor textBackgroundColor]; + sender.policyDelegate = self; + [self.updateWindow center]; + [self.updateWindow makeKeyAndOrderFront:nil]; + }); +} + +- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener +{ + [listener ignore]; + [[NSWorkspace sharedWorkspace] openURL:[request URL]]; +} + +- (void)checkForUpdates +{ +#ifdef UPDATE_SUPPORT + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.updatesSpinner stopAnimation:nil]; + [self.updatesButton setEnabled:YES]; + }); + if ([(NSHTTPURLResponse *)response statusCode] == 200) { + NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSArray <NSString *> *components = [string componentsSeparatedByString:@"|"]; + if (components.count != 2) return; + _lastVersion = components[0]; + _updateURL = components[1]; + if (![@xstr(VERSION) isEqualToString:_lastVersion] && + ![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) { + [self updateFound]; + } + } + }] resume]; +#endif +} + +- (IBAction)userCheckForUpdates:(id)sender +{ + if (self.updateWindow) { + [self.updateWindow makeKeyAndOrderFront:sender]; + } + else { + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"GBSkippedVersion"]; + [self checkForUpdates]; + [sender setEnabled:false]; + [self.updatesSpinner startAnimation:sender]; + } +} + +- (void)askAutoUpdates +{ +#ifdef UPDATE_SUPPORT + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAskedAutoUpdates"]) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"Should SameBoy check for updates when launched?"; + alert.informativeText = @"SameBoy is frequently updated with new features, accuracy improvements, and bug fixes. This setting can always be changed in the preferences window."; + [alert addButtonWithTitle:@"Check on Launch"]; + [alert addButtonWithTitle:@"Don't Check on Launch"]; + + [[NSUserDefaults standardUserDefaults] setBool:[alert runModal] == NSAlertFirstButtonReturn forKey:@"GBAutoUpdatesEnabled"]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBAskedAutoUpdates"]; + } +#endif +} + +- (IBAction)skipVersion:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:_lastVersion forKey:@"GBSkippedVersion"]; + [self.updateWindow performClose:sender]; +} + +- (IBAction)installUpdate:(id)sender +{ + [self.updateProgressSpinner startAnimation:nil]; + self.updateProgressButton.title = @"Cancel"; + self.updateProgressButton.enabled = true; + self.updateProgressLabel.stringValue = @"Downloading update..."; + _updateState = UPDATE_DOWNLOADING; + _updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { + _updateTask = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Extracting update..."; + _updateState = UPDATE_EXTRACTING; + }); + + _downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:[[NSBundle mainBundle] bundleURL] + create:YES + error:nil] path]; + NSTask *unzipTask; + if (!_downloadDirectory) { + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to extract update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + } + + unzipTask = [[NSTask alloc] init]; + unzipTask.launchPath = @"/usr/bin/unzip"; + unzipTask.arguments = @[location.path, @"-d", _downloadDirectory]; + [unzipTask launch]; + [unzipTask waitUntilExit]; + if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to extract update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + return; + } + + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Update ready, save your game progress and click Install."; + _updateState = UPDATE_WAIT_INSTALL; + self.updateProgressButton.title = @"Install"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + }]; + [_updateTask resume]; + + self.updateProgressWindow.preventsApplicationTerminationWhenModal = false; + [self.updateWindow beginSheet:self.updateProgressWindow completionHandler:^(NSModalResponse returnCode) { + [self.updateWindow close]; + }]; +} + +- (void)performUpgrade +{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Instaling update..."; + _updateState = UPDATE_INSTALLING; + self.updateProgressButton.enabled = false; + [self.updateProgressSpinner startAnimation:nil]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString *executablePath = [[NSBundle mainBundle] executablePath]; + NSString *contentsPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents"]; + NSString *contentsTempPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"TempContents"]; + NSString *updateContentsPath = [_downloadDirectory stringByAppendingPathComponent:@"SameBoy.app/Contents"]; + NSError *error = nil; + [[NSFileManager defaultManager] moveItemAtPath:contentsPath toPath:contentsTempPath error:&error]; + if (error) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + _downloadDirectory = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to install update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + return; + } + [[NSFileManager defaultManager] moveItemAtPath:updateContentsPath toPath:contentsPath error:&error]; + if (error) { + [[NSFileManager defaultManager] moveItemAtPath:contentsTempPath toPath:contentsPath error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + _downloadDirectory = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to install update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + return; + } + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:contentsTempPath error:nil]; + _downloadDirectory = nil; + atexit_b(^{ + execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL); + }); + + dispatch_async(dispatch_get_main_queue(), ^{ + [NSApp terminate:nil]; + }); + }); +} + +- (IBAction)updateAction:(id)sender +{ + switch (_updateState) { + case UPDATE_DOWNLOADING: + [_updateTask cancelByProducingResumeData:nil]; + _updateTask = nil; + [self.updateProgressWindow close]; + break; + case UPDATE_WAIT_INSTALL: + [self performUpgrade]; + break; + case UPDATE_EXTRACTING: + case UPDATE_INSTALLING: + break; + case UPDATE_FAILED: + [self.updateProgressWindow close]; + break; + } +} + +- (void)dealloc +{ + if (_downloadDirectory) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + } +} + - (IBAction)nop:(id)sender { } diff --git a/Cocoa/Document.m b/Cocoa/Document.m @@ -1586,7 +1586,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void)cameraRequestUpdate { - dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @try { if (!cameraSession) { if (@available(macOS 10.14, *)) { diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h @@ -25,5 +25,6 @@ @property (nonatomic, weak) IBOutlet NSPopUpButton *cgbPopupButton; @property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton; @property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton; +@property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m @@ -29,6 +29,8 @@ NSPopUpButton *_rumbleModePopupButton; NSSlider *_temperatureSlider; NSSlider *_interferenceSlider; + NSButton *_autoUpdatesCheckbox; + } + (NSArray *)filterList @@ -381,6 +383,12 @@ } +- (IBAction)changeAutoUpdates:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAutoUpdatesEnabled"]; +} + - (IBAction) configureJoypad:(id)sender { [self.configureJoypadButton setEnabled:NO]; @@ -705,4 +713,15 @@ } [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"]; } + +- (NSButton *)autoUpdatesCheckbox +{ + return _autoUpdatesCheckbox; +} + +- (void)setAutoUpdatesCheckbox:(NSButton *)autoUpdatesCheckbox +{ + _autoUpdatesCheckbox = autoUpdatesCheckbox; + [_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]]; +} @end diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib @@ -13,6 +13,9 @@ <outlet property="emulationTab" destination="ymk-46-SX7" id="ofG-ca-a5C"/> <outlet property="graphicsTab" destination="sRK-wO-K6R" id="pfP-Di-i0Q"/> <outlet property="preferencesWindow" destination="QvC-M9-y7g" id="kBg-fq-rZh"/> + <outlet property="updatesButton" destination="KnI-UA-Nlj" id="9Lw-d7-Qoj"/> + <outlet property="updatesSpinner" destination="fB8-sd-zrh" id="HbX-JT-8Su"/> + <outlet property="updatesTab" destination="ffn-ie-9C3" id="rti-eV-pIV"/> </connections> </customObject> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> @@ -49,17 +52,24 @@ <action selector="switchPreferencesTab:" target="-2" id="Tio-D7-PaA"/> </connections> </toolbarItem> + <toolbarItem implicitItemIdentifier="EB7AB5BC-57B0-4639-A037-E28226866C80" label="Updates" paletteLabel="Updates" tag="4" image="AppIcon" autovalidates="NO" selectable="YES" id="lMV-68-ntz"> + <connections> + <action selector="switchPreferencesTab:" target="-2" id="bfU-hc-FnN"/> + </connections> + </toolbarItem> </allowedToolbarItems> <defaultToolbarItems> <toolbarItem reference="zdp-Z7-yZM"/> <toolbarItem reference="fCd-4a-SKR"/> <toolbarItem reference="EMp-g1-eKu"/> <toolbarItem reference="uNZ-j1-Dfx"/> + <toolbarItem reference="lMV-68-ntz"/> </defaultToolbarItems> </toolbar> <connections> <outlet property="analogControlsCheckbox" destination="RuW-Db-dzW" id="FRE-hI-mnU"/> <outlet property="aspectRatioCheckbox" destination="Vfj-tg-7OP" id="Yw0-xS-DBr"/> + <outlet property="autoUpdatesCheckbox" destination="ZVh-ob-6wl" id="qPS-53-3aW"/> <outlet property="bootROMsButton" destination="T3Y-Ln-Onl" id="tdL-Yv-E2K"/> <outlet property="bootROMsFolderItem" destination="Dzv-Gc-zoL" id="yhV-ZI-avD"/> <outlet property="cgbPopupButton" destination="dlD-sk-SHO" id="4tg-SR-e17"/> @@ -694,10 +704,21 @@ <action selector="changeAnalogControls:" target="QvC-M9-y7g" id="1xR-gY-WKo"/> </connections> </button> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb"> + <rect key="frame" x="195" y="13" width="72" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="push" title="Clear" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/> + </connections> + </button> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO"> - <rect key="frame" x="26" y="13" width="169" height="32"/> + <rect key="frame" x="27" y="13" width="172" height="32"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <buttonCell key="cell" type="push" title="Configure a Controller" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim"> + <buttonCell key="cell" type="push" title="Configure a controller" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <font key="font" metaFont="system"/> </buttonCell> @@ -705,22 +726,45 @@ <action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/> </connections> </button> - <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb"> - <rect key="frame" x="195" y="13" width="72" height="32"/> + </subviews> + <point key="canvasLocation" x="-159" y="1161.5"/> + </customView> + <customView id="ffn-ie-9C3"> + <rect key="frame" x="0.0" y="0.0" width="292" height="95"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZVh-ob-6wl"> + <rect key="frame" x="18" y="59" width="256" height="18"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <buttonCell key="cell" type="push" title="Clear" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw"> + <buttonCell key="cell" type="check" title="Check for updates on launch" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="euw-4z-Urd"> + <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="changeAutoUpdates:" target="QvC-M9-y7g" id="MrL-Zh-0Cc"/> + </connections> + </button> + <progressIndicator wantsLayer="YES" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fB8-sd-zrh"> + <rect key="frame" x="257" y="23" width="16" height="16"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + </progressIndicator> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KnI-UA-Nlj"> + <rect key="frame" x="14" y="13" width="240" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="push" title="Check for updates now" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="j8a-EZ-Ef5"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <font key="font" metaFont="system"/> </buttonCell> <connections> - <action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/> + <action selector="userCheckForUpdates:" target="-2" id="ACZ-SI-yTQ"/> </connections> </button> </subviews> - <point key="canvasLocation" x="-159" y="1161.5"/> + <point key="canvasLocation" x="-825" y="699.5"/> </customView> </objects> <resources> + <image name="AppIcon" width="128" height="128"/> <image name="CPU" width="32" height="32"/> <image name="Display" width="32" height="32"/> <image name="Joypad" width="32" height="32"/> diff --git a/Cocoa/UpdateWindow.xib b/Cocoa/UpdateWindow.xib @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/> + <plugIn identifier="com.apple.WebKitIBPlugin" version="14868"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="AppDelegate"> + <connections> + <outlet property="updateChanges" destination="ATt-VM-cb5" id="hJj-Nd-FBv"/> + <outlet property="updateProgressButton" destination="7nO-AA-WmG" id="wTa-9l-cOG"/> + <outlet property="updateProgressLabel" destination="wIm-GX-c6B" id="URp-JG-6wR"/> + <outlet property="updateProgressSpinner" destination="fqq-Nb-THz" id="4vC-m5-ysO"/> + <outlet property="updateProgressWindow" destination="2Gy-QG-FoA" id="RXw-50-DQh"/> + <outlet property="updateWindow" destination="QvC-M9-y7g" id="iwP-kC-tmG"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <window title="Update Available" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g"> + <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/> + <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> + <rect key="contentRect" x="196" y="240" width="480" height="360"/> + <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/> + <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> + <rect key="frame" x="0.0" y="0.0" width="480" height="360"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xZt-UI-Wl2"> + <rect key="frame" x="338" y="13" width="128" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="push" title="Install Update" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Mav-rY-sJo"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent" base64-UTF8="YES"> +DQ +</string> + </buttonCell> + <connections> + <action selector="installUpdate:" target="-2" id="jJc-CY-4vz"/> + </connections> + </button> + <button verticalHuggingPriority="750" id="b3S-YN-iDZ"> + <rect key="frame" x="242" y="13" width="96" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="push" title="Not Now" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="QsJ-cv-hwF"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="performClose:" target="QvC-M9-y7g" id="8qO-ac-k7U"/> + </connections> + </button> + <button verticalHuggingPriority="750" id="uOb-4Q-S8o"> + <rect key="frame" x="14" y="13" width="128" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="push" title="Skip Version" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ai5-6n-dvH"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="skipVersion:" target="-2" id="Jf8-Qe-6X0"/> + </connections> + </button> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Swh-S6-6XA"> + <rect key="frame" x="18" y="313" width="444" height="27"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" lineBreakMode="clipping" title="A new version of SameBoy is available with the following changes:" id="WsO-pC-VO7"> + <font key="font" usesAppearanceFont="YES"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="9Tu-Q3-l40"> + <rect key="frame" x="0.0" y="302" width="480" height="5"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + </box> + <box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="dva-Ay-nnl"> + <rect key="frame" x="0.0" y="58" width="480" height="4"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> + </box> + <webView maintainsBackForwardList="NO" id="ATt-VM-cb5"> + <rect key="frame" x="0.0" y="61" width="480" height="243"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <webPreferences key="preferences" defaultFontSize="13" defaultFixedFontSize="13" minimumFontSize="0" plugInsEnabled="NO" javaEnabled="NO" javaScriptEnabled="NO" javaScriptCanOpenWindowsAutomatically="NO" loadsImagesAutomatically="NO" allowsAnimatedImages="NO" allowsAnimatedImageLooping="NO"> + <nil key="identifier"/> + </webPreferences> + <connections> + <outlet property="UIDelegate" destination="-2" id="xQ1-eY-1hu"/> + <outlet property="frameLoadDelegate" destination="-2" id="BOf-df-5LR"/> + </connections> + </webView> + </subviews> + </view> + <point key="canvasLocation" x="217" y="267"/> + </window> + <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="2Gy-QG-FoA"> + <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> + <rect key="contentRect" x="283" y="305" width="512" height="61"/> + <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/> + <view key="contentView" id="jjT-Z5-15Q"> + <rect key="frame" x="0.0" y="0.0" width="512" height="61"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <progressIndicator wantsLayer="YES" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fqq-Nb-THz"> + <rect key="frame" x="20" y="15" width="32" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + </progressIndicator> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7nO-AA-WmG"> + <rect key="frame" x="417" y="13" width="82" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6l6-qX-gsr"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent" base64-UTF8="YES"> +Gw +</string> + </buttonCell> + <connections> + <action selector="updateAction:" target="-2" id="geO-Gk-xrs"/> + </connections> + </button> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wIm-GX-c6B"> + <rect key="frame" x="58" y="15" width="359" height="24"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" lineBreakMode="clipping" title="Downloading update..." id="qmF-X1-v5B"> + <font key="font" usesAppearanceFont="YES"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + </view> + <point key="canvasLocation" x="11" y="-203.5"/> + </window> + </objects> +</document> diff --git a/Makefile b/Makefile @@ -122,6 +122,9 @@ endif CFLAGS += $(WARNINGS) CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +ifneq (,$(UPDATE_SUPPORT)) +CFLAGS += -DUPDATE_SUPPORT +endif ifeq (,$(PKG_CONFIG)) SDL_CFLAGS := $(shell sdl2-config --cflags)
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.