Skip to content
2 changes: 2 additions & 0 deletions include/SDL3/SDL_hints.h
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,8 @@ extern "C" {
* - "2": The indicator bar is dim and the first swipe makes it visible and
* the second swipe performs the "home" action. (default for fullscreen
* applications)
* - "3": The indicator bar is dim and only the bottom edge defers system
* gestures, allowing other screen edges to respond normally.
*
* This hint can be set anytime.
*
Expand Down
12 changes: 9 additions & 3 deletions src/video/cocoa/SDL_cocoawindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -1140,9 +1140,8 @@ - (void)windowDidChangeOcclusionState:(NSNotification *)aNotification

- (void)windowWillStartLiveResize:(NSNotification *)aNotification
{
// We'll try to maintain 60 FPS during live resizing
const NSTimeInterval interval = 1.0 / 60.0;
liveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:interval
// Let macOS schedule updates as fast as it can during live resize
liveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.0
repeats:TRUE
block:^(NSTimer *unusedTimer)
{
Expand Down Expand Up @@ -1299,6 +1298,11 @@ - (void)windowDidMiniaturize:(NSNotification *)aNotification
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
}

- (void)windowWillDeminiaturize:(NSNotification *)aNotification
{
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
}

- (void)windowDidDeminiaturize:(NSNotification *)aNotification
{
// Always send restored before maximized.
Expand All @@ -1308,6 +1312,8 @@ - (void)windowDidDeminiaturize:(NSNotification *)aNotification
SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0);
}

SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);

if ([self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) {
SDL_UpdateFullscreenMode(_data.window, true, true);
}
Expand Down
44 changes: 44 additions & 0 deletions src/video/uikit/SDL_uikitappdelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#undef main
#endif

#define SDL_IOS_UIApplicationLaunchOptionsURLKey "SDL_IOS_UIApplicationLaunchOptionsURLKey"

static SDL_main_func forward_main;
static int forward_argc;
static char **forward_argv;
Expand Down Expand Up @@ -457,6 +459,8 @@ - (void)sceneDidEnterBackground:(UIScene *)scene

- (void)handleURL:(NSURL *)url
{
SDL_SetHint(SDL_IOS_UIApplicationLaunchOptionsURLKey, [[url absoluteString] UTF8String]);

const char *sourceApplicationCString = NULL;
NSURL *fileURL = url.filePathURL;
if (fileURL != nil) {
Expand Down Expand Up @@ -646,6 +650,42 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
[[NSFileManager defaultManager] changeCurrentDirectoryPath:[bundle resourcePath]];

SDL_SetMainReady();

// Kill me
NSArray<NSString *> *launchOptionKeys = @[
UIApplicationLaunchOptionsURLKey,
UIApplicationLaunchOptionsSourceApplicationKey,
UIApplicationLaunchOptionsAnnotationKey,
UIApplicationLaunchOptionsRemoteNotificationKey,
UIApplicationLaunchOptionsLocationKey,
UIApplicationLaunchOptionsBluetoothCentralsKey,
UIApplicationLaunchOptionsBluetoothPeripheralsKey,
UIApplicationLaunchOptionsNewsstandDownloadsKey,
UIApplicationLaunchOptionsShortcutItemKey,
UIApplicationLaunchOptionsUserActivityDictionaryKey
];

for (NSString *key in launchOptionKeys) {
id value = launchOptions[key];

NSString *hintKey = [NSString stringWithFormat:@"SDL_IOS_%@", [key description]];
NSString *hintValue;

SDL_SetHint([hintKey UTF8String], nil);

if ([value isKindOfClass:[NSURL class]]) {
hintValue = [(NSURL *)value absoluteString];
} else if ([value isKindOfClass:[NSString class]]) {
hintValue = (NSString *)value;
} else {
hintValue = [value description];
}

if (hintKey && hintValue) {
SDL_SetHint([hintKey UTF8String], [hintValue UTF8String]);
}
}

[self performSelector:@selector(postFinishLaunch) withObject:nil afterDelay:0.0];

return YES;
Expand Down Expand Up @@ -685,6 +725,10 @@ - (void)sendDropFileForURL:(NSURL *)url fromSourceApplication:(NSString *)source

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
if (url) {
SDL_SetHint(SDL_IOS_UIApplicationLaunchOptionsURLKey, [[url absoluteString] UTF8String]);
}

// TODO: Handle options
[self sendDropFileForURL:url fromSourceApplication:NULL];
return YES;
Expand Down
19 changes: 19 additions & 0 deletions src/video/uikit/SDL_uikitmessagebox.m
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,24 @@ static void SDLCALL UIKit_ShowMessageBoxMainThreadCallback(void *userdata)
}
}

#ifdef SDL_PLATFORM_IOS
bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
{
// Message boxes dont seem to work on iOS 18.5 or lower
// Only iOS 26 seems to work just fine
// This is to be fixed later
if (@available(iOS 26.0, *)) {
UIKit_ShowMessageBoxData data = { messageboxdata, buttonID, false };
if (!SDL_RunOnMainThread(UIKit_ShowMessageBoxMainThreadCallback, &data, true)) {
return false;
} else if (!data.result) {
return SDL_SetError("Could not show message box.");
}
return true;
}
return false;
}
#else
bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
{
UIKit_ShowMessageBoxData data = { messageboxdata, buttonID, false };
Expand All @@ -157,5 +175,6 @@ bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonI
}
return true;
}
#endif

#endif // SDL_VIDEO_DRIVER_UIKIT
34 changes: 27 additions & 7 deletions src/video/uikit/SDL_uikitviewcontroller.m
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,31 @@ - (void)startAnimation
{
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];

#ifdef SDL_PLATFORM_VISIONOS
displayLink.preferredFramesPerSecond = 90 / animationInterval; //TODO: Get frame max frame rate on visionOS
#else
SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());

displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
#endif
if (mode) {

int frame_rate = (int)(mode->refresh_rate / animationInterval);

if (@available(iOS 15.0, tvOS 15.0, *)) {

if (frame_rate > 60) {

displayLink.preferredFrameRateRange = CAFrameRateRangeMake((frame_rate * 2) / 3, frame_rate, frame_rate);

} else {

displayLink.preferredFrameRateRange = CAFrameRateRangeMake(frame_rate, frame_rate, frame_rate);

}

} else {

displayLink.preferredFramesPerSecond = frame_rate;

}

}

[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
Expand Down Expand Up @@ -262,7 +280,9 @@ - (BOOL)prefersHomeIndicatorAutoHidden
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
if (self.homeIndicatorHidden >= 0) {
if (self.homeIndicatorHidden == 2) {
if (self.homeIndicatorHidden == 3) {
return UIRectEdgeBottom;
} else if (self.homeIndicatorHidden == 2) {
return UIRectEdgeAll;
} else {
return UIRectEdgeNone;
Expand Down
133 changes: 112 additions & 21 deletions src/video/windows/SDL_windowswindow.c
Original file line number Diff line number Diff line change
Expand Up @@ -593,8 +593,11 @@ static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window)
if (data->keyboard_hook) {
UnhookWindowsHookEx(data->keyboard_hook);
}
if (data->hicon) {
DestroyIcon(data->hicon);
if (data->hicon_small) {
DestroyIcon(data->hicon_small);
}
if (data->hicon_large) {
DestroyIcon(data->hicon_large);
}
ReleaseDC(data->hwnd, data->hdc);
RemoveProp(data->hwnd, TEXT("SDL_WindowData"));
Expand Down Expand Up @@ -839,26 +842,34 @@ void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
#endif
}

bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
SDL_WindowData *data = window->internal;
HWND hwnd = data->hwnd;
static HICON create_hicon(SDL_Surface *surface)
{
SDL_Surface *icon = surface;
HICON hicon = NULL;
BYTE *icon_bmp;
int icon_len, mask_len, row_len, y;
BITMAPINFOHEADER *bmi;
Uint8 *dst;
bool isstack;
bool result = true;

if (icon->format != SDL_PIXELFORMAT_ARGB8888) {
icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB8888);
if (!icon) {
SDL_SetError("SetWindowIcon() failed, failed to convert icon to ARGB8888 format");
}
}

// Create temporary buffer for ICONIMAGE structure
SDL_COMPILE_TIME_ASSERT(WIN_SetWindowIcon_uses_BITMAPINFOHEADER_to_prepare_an_ICONIMAGE, sizeof(BITMAPINFOHEADER) == 40);
mask_len = (icon->h * (icon->w + 7) / 8);
icon_len = sizeof(BITMAPINFOHEADER) + icon->h * icon->w * sizeof(Uint32) + mask_len;
icon_bmp = SDL_small_alloc(BYTE, icon_len, &isstack);
if (!icon_bmp) {
return false;
if (icon != surface) {
SDL_DestroySurface(icon);
}

return NULL;
}

// Write the BITMAPINFO header
Expand All @@ -876,7 +887,6 @@ bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *
bmi->biClrImportant = SDL_Swap32LE(0);

// Write the pixels upside down into the bitmap buffer
SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888);
dst = &icon_bmp[sizeof(BITMAPINFOHEADER)];
row_len = icon->w * sizeof(Uint32);
y = icon->h;
Expand All @@ -893,21 +903,102 @@ bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *

SDL_small_free(icon_bmp, isstack);

if (icon != surface) {
SDL_DestroySurface(icon);
}

if (!hicon) {
return SDL_SetError("SetWindowIcon() failed, error %08X", (unsigned int)GetLastError());
SDL_SetError("SetWindowIcon() failed, error %08X", (unsigned int)GetLastError());
return NULL;
}

if (data->hicon) {
DestroyIcon(data->hicon);
return hicon;
}
#endif

bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
SDL_WindowData *data = window->internal;
HWND hwnd = data->hwnd;

int icon_count = 0;
SDL_Surface **icons = SDL_GetSurfaceImages(icon, &icon_count);
if (!icons || !icon_count) {
return false;
}
data->hicon = hicon;

// Set the icon for the window
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon);
// Dispose the old icons, if available
if (data->hicon_small) {
DestroyIcon(data->hicon_small);
data->hicon_small = NULL;
}
if (data->hicon_large) {
DestroyIcon(data->hicon_large);
data->hicon_large = NULL;
}

// Set the icon in the task manager (should we do this?)
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hicon);
return result;
SDL_Surface* icon_small;
SDL_Surface* icon_large;

int small_icon_w = GetSystemMetrics(SM_CXSMICON);
int small_icon_h = GetSystemMetrics(SM_CYSMICON);
int small_closest_distance = -1;

int large_icon_w = GetSystemMetrics(SM_CXICON);
int large_icon_h = GetSystemMetrics(SM_CYICON);
int large_closest_distance = -1;

for (int i = 0; i < icon_count; ++i) {
SDL_Surface *candidate = icons[i];

int small_delta_w = candidate->w - small_icon_w;
int small_delta_h = candidate->h - small_icon_h;
int small_distance = (small_delta_w * small_delta_w) + (small_delta_h * small_delta_h);

if (small_delta_w < 0 || small_delta_h < 0) {
small_distance += 1000000;
}

if (small_closest_distance < 0 || small_distance < small_closest_distance) {
icon_small = candidate;
small_closest_distance = small_distance;
}

int large_delta_w = candidate->w - large_icon_w;
int large_delta_h = candidate->h - large_icon_h;
int large_distance = (large_delta_w * large_delta_w) + (large_delta_h * large_delta_h);

if (large_delta_w < 0 || large_delta_h < 0) {
large_distance += 1000000;
}

if (large_closest_distance < 0 || large_distance < large_closest_distance) {
icon_large = candidate;
large_closest_distance = large_distance;
}
}

SDL_free(icons);

if (!icon_small || !icon_large) {
return false;
}

HICON hicon_small = create_hicon(icon_small);
HICON hicon_large = create_hicon(icon_large);
if (!hicon_small || !hicon_large) {
return false;
}

data->hicon_small = hicon_small;
data->hicon_large = hicon_large;

// Set the icons for the window
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon_small);
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hicon_large);

return true;
#else
return SDL_Unsupported();
#endif
Expand Down Expand Up @@ -1375,10 +1466,10 @@ SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window
WIN_MaximizeWindow(_this, window);
}

if (!fullscreen && data && data->hicon) {
if (!fullscreen && data && data->hicon_small) {
// Reset the icon for the window when returning from fullscreen mode
SendMessage(hwnd, WM_SETICON, ICON_SMALL, 0);
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)data->hicon);
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)data->hicon_small);
}

#ifdef HIGHDPI_DEBUG
Expand Down
3 changes: 2 additions & 1 deletion src/video/windows/SDL_windowswindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ struct SDL_WindowData
HDC mdc;
HINSTANCE hinstance;
HBITMAP hbm;
HICON hicon;
HICON hicon_small;
HICON hicon_large;
WNDPROC wndproc;
HHOOK keyboard_hook;
WPARAM mouse_button_flags;
Expand Down