diff --git a/gfx/display_servers/dispserv_apple.m b/gfx/display_servers/dispserv_apple.m
new file mode 100644
index 0000000000..240f226e50
--- /dev/null
+++ b/gfx/display_servers/dispserv_apple.m
@@ -0,0 +1,193 @@
+/* RetroArch - A frontend for libretro.
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#import
+#ifdef IOS
+#import
+#else
+#import
+#endif
+#include
+#include "../verbosity.h"
+#include "../video_display_server.h"
+#include "../video_driver.h"
+#include "../../ui/drivers/cocoa/apple_platform.h"
+#include "../../ui/drivers/cocoa/cocoa_common.h"
+
+#ifdef OSX
+#import
+#import
+#endif
+
+#ifdef OSX
+static bool apple_display_server_set_window_opacity(void *data, unsigned opacity)
+{
+ settings_t *settings = config_get_ptr();
+ bool windowed_full = settings->bools.video_windowed_fullscreen;
+ NSWindow *window = ((RetroArch_OSX*)[[NSApplication sharedApplication] delegate]).window;
+ if (windowed_full || !window.keyWindow)
+ return false;
+ window.alphaValue = (CGFloat)opacity / (CGFloat)100.0f;
+ return true;
+}
+
+static bool apple_display_server_set_window_progress(void *data, int progress, bool finished)
+{
+ static NSProgressIndicator *indicator;
+ static dispatch_once_t once;
+ dispatch_once(&once, ^{
+ NSDockTile *dockTile = [NSApp dockTile];
+ NSImageView *iv = [[NSImageView alloc] init];
+ [iv setImage:[[NSApplication sharedApplication] applicationIconImage]];
+ [dockTile setContentView:iv];
+
+ indicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, dockTile.size.width, 20)];
+ indicator.indeterminate = NO;
+ indicator.minValue = 0;
+ indicator.maxValue = 100;
+ indicator.doubleValue = 0;
+
+ // Create a custom view for the dock tile
+ [iv addSubview:indicator];
+ });
+ if (finished)
+ indicator.doubleValue = (double)-1;
+ else
+ indicator.doubleValue = (double)progress;
+ indicator.hidden = finished;
+ [[NSApp dockTile] display];
+ return true;
+}
+
+static bool apple_display_server_set_window_decorations(void *data, bool on)
+{
+ settings_t *settings = config_get_ptr();
+ bool windowed_full = settings->bools.video_windowed_fullscreen;
+ NSWindow *window = ((RetroArch_OSX*)[[NSApplication sharedApplication] delegate]).window;
+ if (windowed_full)
+ return false;
+ if (on)
+ window.styleMask |= NSWindowStyleMaskTitled;
+ else
+ window.styleMask &= ~NSWindowStyleMaskTitled;
+ return true;
+}
+#endif
+
+static bool apple_display_server_set_resolution(void *data,
+ unsigned width, unsigned height, int int_hz, float hz,
+ int center, int monitor_index, int xoffset, int padjust)
+{
+#if (defined(OSX) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000)
+ if (@available(macOS 14, *))
+ [CocoaView get].displayLink.preferredFrameRateRange = CAFrameRateRangeMake(hz * 0.9, hz * 1.2, hz);
+#elif defined(IOS)
+#if (TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000) || (TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED >= 150000)
+ if (@available(iOS 15, tvOS 15, *))
+ [CocoaView get].displayLink.preferredFrameRateRange = CAFrameRateRangeMake(hz * 0.9, hz * 1.2, hz);
+ else
+#endif
+ [CocoaView get].displayLink.preferredFramesPerSecond = hz;
+#endif
+ return true;
+}
+
+static void *apple_display_server_get_resolution_list(
+ void *data, unsigned *len)
+{
+ unsigned j = 0;
+ struct video_display_config *conf = NULL;
+
+ unsigned width, height;
+ NSMutableSet *rates = [NSMutableSet set];
+ double currentRate;
+
+#ifdef OSX
+ NSRect bounds = [CocoaView get].bounds;
+ float scale = cocoa_screen_get_backing_scale_factor();
+ width = bounds.size.width * scale;
+ height = bounds.size.height * scale;
+
+ CGDirectDisplayID mainDisplayID = CGMainDisplayID();
+ CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(mainDisplayID);
+ currentRate = CGDisplayModeGetRefreshRate(currentMode);
+ CFRelease(currentMode);
+ CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(mainDisplayID, NULL);
+ for (CFIndex i = 0; i < CFArrayGetCount(displayModes); i++)
+ {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
+ double refreshRate = CGDisplayModeGetRefreshRate(mode);
+ if (refreshRate > 0)
+ [rates addObject:@(refreshRate)];
+ }
+ CFRelease(displayModes);
+#else
+ CGRect bounds = [CocoaView get].view.bounds;
+ float scale = cocoa_screen_get_native_scale();
+ width = bounds.size.width * scale;
+ height = bounds.size.height * scale;
+
+ UIScreen *mainScreen = [UIScreen mainScreen];
+ currentRate = mainScreen.maximumFramesPerSecond;
+#if !TARGET_OS_TV
+ if (@available(iOS 15, *))
+ [rates addObjectsFromArray:@[@(24), @(30), @(40), @(48), @(60), @(120)]];
+ else
+#endif
+ [rates addObject:@(mainScreen.maximumFramesPerSecond)];
+#endif
+
+ NSArray *sorted = [[rates allObjects] sortedArrayUsingSelector:@selector(compare:)];
+ *len = (unsigned)[sorted count];
+ RARCH_LOG("Available screen refresh rates: %s\n", [[NSString stringWithFormat:@"%@", sorted] UTF8String]);
+
+ if (!(conf = (struct video_display_config*)calloc(*len, sizeof(struct video_display_config))))
+ return NULL;
+
+ for (j = 0; j < *len; j++)
+ {
+ NSNumber *rate = sorted[j];
+ conf[j].width = width;
+ conf[j].height = height;
+ conf[j].bpp = 32;
+ conf[j].refreshrate = [rate unsignedIntValue];
+ conf[j].refreshrate_float = [rate floatValue];
+ conf[j].interlaced = false;
+ conf[j].dblscan = false;
+ conf[j].idx = j;
+ conf[j].current = ([rate doubleValue] == currentRate);
+ }
+ return conf;
+}
+
+const video_display_server_t dispserv_apple = {
+ NULL, /* init */
+ NULL, /* destroy */
+#ifdef OSX
+ apple_display_server_set_window_opacity,
+ apple_display_server_set_window_progress,
+ apple_display_server_set_window_decorations,
+#else
+ NULL, /* set_window_opacity */
+ NULL, /* set_window_progress */
+ NULL, /* set_window_decorations */
+#endif
+ apple_display_server_set_resolution,
+ apple_display_server_get_resolution_list,
+ NULL, /* get_output_options */
+ NULL, /* set_screen_orientation */
+ NULL, /* get_screen_orientation */
+ NULL, /* get_flags */
+ "apple"
+};
diff --git a/gfx/drivers_context/cocoa_gl_ctx.m b/gfx/drivers_context/cocoa_gl_ctx.m
index 16f62b0f70..8875bd8fb0 100644
--- a/gfx/drivers_context/cocoa_gl_ctx.m
+++ b/gfx/drivers_context/cocoa_gl_ctx.m
@@ -232,6 +232,19 @@ static void cocoa_gl_gfx_ctx_get_video_size(void *data,
}
#endif
+static float cocoa_gl_gfx_ctx_get_refresh_rate(void *data)
+{
+#ifdef OSX
+ CGDirectDisplayID mainDisplayID = CGMainDisplayID();
+ CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(mainDisplayID);
+ float currentRate = CGDisplayModeGetRefreshRate(currentMode);
+ CFRelease(currentMode);
+ return currentRate;
+#else
+ return [UIScreen mainScreen].maximumFramesPerSecond;
+#endif
+}
+
static gfx_ctx_proc_t cocoa_gl_gfx_ctx_get_proc_address(const char *symbol_name)
{
return (gfx_ctx_proc_t)CFBundleGetFunctionPointerForName(
@@ -532,7 +545,7 @@ const gfx_ctx_driver_t gfx_ctx_cocoagl = {
#else
cocoa_gl_gfx_ctx_get_video_size,
#endif
- NULL, /* get_refresh_rate */
+ cocoa_gl_gfx_ctx_get_refresh_rate,
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */
diff --git a/gfx/drivers_context/cocoa_vk_ctx.m b/gfx/drivers_context/cocoa_vk_ctx.m
index 7738d22aa5..9e732dc319 100755
--- a/gfx/drivers_context/cocoa_vk_ctx.m
+++ b/gfx/drivers_context/cocoa_vk_ctx.m
@@ -138,6 +138,19 @@ static void cocoa_vk_gfx_ctx_get_video_size(void *data,
}
#endif
+static float cocoa_vk_gfx_ctx_get_refresh_rate(void *data)
+{
+#ifdef OSX
+ CGDirectDisplayID mainDisplayID = CGMainDisplayID();
+ CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(mainDisplayID);
+ float currentRate = CGDisplayModeGetRefreshRate(currentMode);
+ CFRelease(currentMode);
+ return currentRate;
+#else
+ return [UIScreen mainScreen].maximumFramesPerSecond;
+#endif
+}
+
static gfx_ctx_proc_t cocoa_vk_gfx_ctx_get_proc_address(const char *symbol_name)
{
return NULL;
@@ -353,7 +366,7 @@ const gfx_ctx_driver_t gfx_ctx_cocoavk = {
#else
cocoa_vk_gfx_ctx_get_video_size,
#endif
- NULL, /* get_refresh_rate */
+ cocoa_vk_gfx_ctx_get_refresh_rate,
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */
diff --git a/gfx/video_display_server.h b/gfx/video_display_server.h
index be6a7088cc..cff4b6eebf 100644
--- a/gfx/video_display_server.h
+++ b/gfx/video_display_server.h
@@ -104,6 +104,7 @@ extern const video_display_server_t dispserv_win32;
extern const video_display_server_t dispserv_x11;
extern const video_display_server_t dispserv_kms;
extern const video_display_server_t dispserv_android;
+extern const video_display_server_t dispserv_apple;
RETRO_END_DECLS
diff --git a/gfx/video_driver.c b/gfx/video_driver.c
index ad2735deba..4ffa368676 100644
--- a/gfx/video_driver.c
+++ b/gfx/video_driver.c
@@ -1103,6 +1103,8 @@ void* video_display_server_init(enum rarch_display_type type)
default:
#if defined(ANDROID)
current_display_server = &dispserv_android;
+#elif defined(__APPLE__)
+ current_display_server = &dispserv_apple;
#else
current_display_server = &dispserv_null;
#endif
diff --git a/griffin/griffin_objc.m b/griffin/griffin_objc.m
index c37b9b5630..68c6ac6e47 100644
--- a/griffin/griffin_objc.m
+++ b/griffin/griffin_objc.m
@@ -27,6 +27,8 @@
#define HAVE_COMPRESSION 1
#endif
+#include "../gfx/display_servers/dispserv_apple.m"
+
#if defined(HAVE_COCOATOUCH) || defined(HAVE_COCOA) || defined(HAVE_COCOA_METAL)
#include "../ui/drivers/cocoa/cocoa_common.m"
diff --git a/menu/menu_setting.c b/menu/menu_setting.c
index 4c1fa82562..d9ff8b2a9c 100644
--- a/menu/menu_setting.c
+++ b/menu/menu_setting.c
@@ -13461,7 +13461,9 @@ static bool setting_append_list(
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
+#ifndef OSX
MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_REINIT);
+#endif
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
CONFIG_BOOL(
@@ -14170,7 +14172,7 @@ static bool setting_append_list(
SD_FLAG_NONE
);
-#if !defined(RARCH_MOBILE)
+#if !defined(RARCH_MOBILE) || defined(IOS)
{
#if defined(HAVE_STEAM) && defined(HAVE_MIST)
bool on_deck = false;
diff --git a/pkg/apple/iOS/Info.plist b/pkg/apple/iOS/Info.plist
index 238c84fb73..40df489f54 100644
--- a/pkg/apple/iOS/Info.plist
+++ b/pkg/apple/iOS/Info.plist
@@ -6,6 +6,8 @@
$(PRODUCT_BUNDLE_IDENTIFIER)
ALTDeviceID
$(TARGET_DEVICE_IDENTIFIER)
+ CADisableMinimumFrameDurationOnPhone
+
CFBundleDevelopmentRegion
en
CFBundleDisplayName
diff --git a/ui/drivers/cocoa/apple_platform.h b/ui/drivers/cocoa/apple_platform.h
index 9e9514f37a..66013794af 100644
--- a/ui/drivers/cocoa/apple_platform.h
+++ b/ui/drivers/cocoa/apple_platform.h
@@ -16,6 +16,8 @@ extern void ios_show_file_sheet(void);
#ifdef __OBJC__
+#import
+
#ifdef HAVE_METAL
#import
#import
@@ -53,9 +55,6 @@ typedef enum apple_view_type
* the displays should not sleep.
*/
- (bool)setDisableDisplaySleep:(bool)disable;
-#if defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)
-- (void)updateWindowedMode;
-#endif
@end
#endif
@@ -76,6 +75,8 @@ void rarch_stop_draw_observer(void);
@end
#endif
+#import
+
@interface RetroArch_iOS : UINavigationController {
UIView *_renderView;
@@ -94,7 +95,11 @@ UINavigationControllerDelegate> {
- (void)refreshSystemConfig;
@end
+
#else
+
+#import
+
#if defined(HAVE_COCOA_METAL)
@interface RetroArch_OSX : NSObject {
#elif (defined(__MACH__) && defined(MAC_OS_X_VERSION_MAX_ALLOWED) && (MAC_OS_X_VERSION_MAX_ALLOWED < 101200))
diff --git a/ui/drivers/cocoa/cocoa_common.h b/ui/drivers/cocoa/cocoa_common.h
index 13cb0d5c59..15d88430b1 100644
--- a/ui/drivers/cocoa/cocoa_common.h
+++ b/ui/drivers/cocoa/cocoa_common.h
@@ -18,6 +18,7 @@
#define __COCOA_COMMON_SHARED_H
#include
+#include
#if defined(HAVE_COCOATOUCH)
#include
@@ -73,6 +74,8 @@
@property(readwrite) UIInterfaceOrientation lockInterfaceOrientation;
#endif
+@property(nonatomic,readwrite) CADisplayLink *displayLink;
+
+ (CocoaView*)get;
@end
@@ -87,6 +90,10 @@ void get_ios_version(int *major, int *minor);
- (void)display;
#endif
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
+@property(nonatomic,readwrite) CADisplayLink *displayLink API_AVAILABLE(macos(14.0));
+#endif
+
@end
#endif
diff --git a/ui/drivers/cocoa/cocoa_common.m b/ui/drivers/cocoa/cocoa_common.m
index fd90d0f310..8fed836beb 100644
--- a/ui/drivers/cocoa/cocoa_common.m
+++ b/ui/drivers/cocoa/cocoa_common.m
@@ -104,6 +104,12 @@ void cocoa_file_load_with_detect_core(const char *filename);
- (void)scrollWheel:(NSEvent *)theEvent { }
#endif
+#if !defined(OSX) || __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
+-(void)step:(CADisplayLink*)target API_AVAILABLE(macos(14.0), ios(3.1), tvos(3.1))
+{
+}
+#endif
+
+ (CocoaView*)get
{
CocoaView *view = (BRIDGE CocoaView*)nsview_get_ptr();
@@ -111,6 +117,21 @@ void cocoa_file_load_with_detect_core(const char *filename);
{
view = [CocoaView new];
nsview_set_ptr(view);
+#if defined(IOS)
+ view.displayLink = [CADisplayLink displayLinkWithTarget:view selector:@selector(step:)];
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 || __TV_OS_VERSION_MAX_ALLOWED >= 150000
+ if (@available(iOS 15.0, tvOS 15.0, *))
+ [view.displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(60, 120, 120)];
+#endif
+ [view.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+#elif defined(OSX) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
+ if (@available(macOS 14.0, *))
+ {
+ view.displayLink = [view displayLinkWithTarget:view selector:@selector(step:)];
+ view.displayLink.preferredFrameRateRange = CAFrameRateRangeMake(60, 120, 120);
+ [view.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ }
+#endif
}
return view;
}
diff --git a/ui/drivers/ui_cocoa.m b/ui/drivers/ui_cocoa.m
index 4bcbe7d81a..0c2d405554 100644
--- a/ui/drivers/ui_cocoa.m
+++ b/ui/drivers/ui_cocoa.m
@@ -533,7 +533,9 @@ static ui_application_t ui_application_cocoa = {
- (void)windowDidBecomeKey:(NSNotification *)notification
{
- [apple_platform updateWindowedMode];
+ settings_t *settings = config_get_ptr();
+ video_display_server_set_window_opacity(settings->uints.video_window_opacity);
+ video_display_server_set_window_decorations(settings->bools.video_window_show_decorations);
}
- (void)windowDidMove:(NSNotification *)notification
@@ -720,7 +722,6 @@ static ui_application_t ui_application_cocoa = {
if (is_fullscreen)
[self.window toggleFullScreen:self];
[self updateWindowedSize:mode];
- [self updateWindowedMode];
}
/* HACK(sgc): ensure MTKView posts a drawable resize event */
@@ -754,24 +755,6 @@ static ui_application_t ui_application_cocoa = {
[self.window setContentSize:NSMakeSize(mode.width, mode.height)];
}
-- (void)updateWindowedMode
-{
- settings_t *settings = config_get_ptr();
- bool windowed_full = settings->bools.video_windowed_fullscreen;
- bool show_decorations = settings->bools.video_window_show_decorations;
- CGFloat opacity = (CGFloat)settings->uints.video_window_opacity / (CGFloat)100.0;
-
- if (windowed_full || !self.window.keyWindow)
- return;
-
- if (show_decorations)
- self.window.styleMask |= NSWindowStyleMaskTitled;
- else
- self.window.styleMask &= ~NSWindowStyleMaskTitled;
-
- self.window.alphaValue = opacity;
-}
-
- (void)setCursorVisible:(bool)v
{
if (v)