Skip to content

Commit

Permalink
cocoa: Implemented display hotplugging support.
Browse files Browse the repository at this point in the history
Fixes #7764.
  • Loading branch information
icculus committed Jan 10, 2025
1 parent 0e2ca93 commit 38176bf
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 61 deletions.
2 changes: 2 additions & 0 deletions src/video/SDL_sysvideo.h
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,8 @@ extern bool SDL_ShouldAllowTopmost(void);

extern void SDL_ToggleDragAndDropSupport(void);

extern void SDL_UpdateDesktopBounds(void);

extern SDL_TextInputType SDL_GetTextInputType(SDL_PropertiesID props);
extern SDL_Capitalization SDL_GetTextInputCapitalization(SDL_PropertiesID props);
extern bool SDL_GetTextInputAutocorrect(SDL_PropertiesID props);
Expand Down
2 changes: 1 addition & 1 deletion src/video/SDL_video.c
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ SDL_SystemTheme SDL_GetSystemTheme(void)
}
}

static void SDL_UpdateDesktopBounds(void)
void SDL_UpdateDesktopBounds(void)
{
SDL_Rect rect;
SDL_zero(rect);
Expand Down
1 change: 1 addition & 0 deletions src/video/cocoa/SDL_cocoamodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ extern bool Cocoa_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDispla
extern bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
extern bool Cocoa_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
extern void Cocoa_QuitModes(SDL_VideoDevice *_this);
extern SDL_VideoDisplay *Cocoa_FindSDLDisplayByCGDirectDisplayID(SDL_VideoDevice *_this, CGDirectDisplayID displayid);

#endif // SDL_cocoamodes_h_
225 changes: 175 additions & 50 deletions src/video/cocoa/SDL_cocoamodes.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#ifdef SDL_VIDEO_DRIVER_COCOA

#include "SDL_cocoavideo.h"
#include "../../events/SDL_events_c.h"

// We need this for IODisplayCreateInfoDictionary and kIODisplayOnlyPreferredName
#include <IOKit/graphics/IOGraphicsLib.h>
Expand Down Expand Up @@ -97,6 +98,17 @@ static bool CG_SetError(const char *prefix, CGDisplayErr result)
return nil;
}

SDL_VideoDisplay *Cocoa_FindSDLDisplayByCGDirectDisplayID(SDL_VideoDevice *_this, CGDirectDisplayID displayid)
{
for (int i = 0; i < _this->num_displays; i++) {
const SDL_DisplayData *displaydata = _this->displays[i]->internal;
if (displaydata && (displaydata->display == displayid)) {
return _this->displays[i];
}
}
return NULL;
}

static float GetDisplayModeRefreshRate(CGDisplayModeRef vidmode, CVDisplayLinkRef link)
{
float refreshRate = (float)CGDisplayModeGetRefreshRate(vidmode);
Expand Down Expand Up @@ -153,7 +165,7 @@ static Uint32 GetDisplayModePixelFormat(CGDisplayModeRef vidmode)
return pixelformat;
}

static bool GetDisplayMode(SDL_VideoDevice *_this, CGDisplayModeRef vidmode, bool vidmodeCurrent, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode)
static bool GetDisplayMode(CGDisplayModeRef vidmode, bool vidmodeCurrent, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode)
{
SDL_DisplayModeData *data;
bool usableForGUI = CGDisplayModeIsUsableForDesktopGUI(vidmode);
Expand Down Expand Up @@ -309,37 +321,182 @@ static void Cocoa_GetHDRProperties(CGDirectDisplayID displayID, SDL_HDROutputPro
#endif
}


bool Cocoa_AddDisplay(CGDirectDisplayID display, bool send_event)
{
CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(display);
if (!moderef) {
return false;
}

SDL_DisplayData *displaydata = (SDL_DisplayData *)SDL_malloc(sizeof(*displaydata));
if (!displaydata) {
CGDisplayModeRelease(moderef);
return false;
}
displaydata->display = display;

CVDisplayLinkRef link = NULL;
CVDisplayLinkCreateWithCGDisplay(display, &link);

SDL_VideoDisplay viddisplay;
SDL_zero(viddisplay);
viddisplay.name = Cocoa_GetDisplayName(display); // this returns a strdup'ed string

SDL_DisplayMode mode;
if (!GetDisplayMode(moderef, true, NULL, link, &mode)) {
CVDisplayLinkRelease(link);
CGDisplayModeRelease(moderef);
SDL_free(viddisplay.name);
SDL_free(displaydata);
return false;
}

CVDisplayLinkRelease(link);
CGDisplayModeRelease(moderef);

Cocoa_GetHDRProperties(displaydata->display, &viddisplay.HDR);

viddisplay.desktop_mode = mode;
viddisplay.internal = displaydata;
const bool retval = SDL_AddVideoDisplay(&viddisplay, send_event);
SDL_free(viddisplay.name);
return retval;
}

static void Cocoa_DisplayReconfigurationCallback(CGDirectDisplayID displayid, CGDisplayChangeSummaryFlags flags, void *userInfo)
{
#if 0
SDL_Log("COCOA DISPLAY RECONFIG CALLBACK! display=%u", (unsigned int) displayid);
#define CHECK_DISPLAY_RECONFIG_FLAG(x) if (flags & x) { SDL_Log(" - " #x); }
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayBeginConfigurationFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMovedFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetMainFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetModeFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayAddFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayRemoveFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayEnabledFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDisabledFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMirrorFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayUnMirrorFlag);
CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDesktopShapeChangedFlag);
#undef CHECK_DISPLAY_RECONFIG_FLAG
#endif

SDL_VideoDevice *_this = (SDL_VideoDevice *) userInfo;
SDL_VideoDisplay *display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid); // will be NULL for newly-added (or newly-unmirrored) displays!

if (flags & kCGDisplayDisabledFlag) {
flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still plugged in.
}

if (flags & kCGDisplayEnabledFlag) {
flags |= kCGDisplayAddFlag; // treat this like a display leaving, even though it's still plugged in.
}

if (flags & kCGDisplayMirrorFlag) {
flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still actually here.
}

if (flags & kCGDisplayUnMirrorFlag) {
flags |= kCGDisplayAddFlag; // treat this like a new display arriving, even though it was here all along.
}

if ((flags & kCGDisplayAddFlag) && (flags & kCGDisplayRemoveFlag)) {
// both adding _and_ removing? Treat it as a remove exclusively. This can happen if a display is unmirroring because it's being disabled, etc.
flags &= ~kCGDisplayAddFlag;
}

if (flags & kCGDisplayAddFlag) {
if (!display) {
if (!Cocoa_AddDisplay(displayid, true)) {
return; // oh well.
}
display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid);
SDL_assert(display != NULL);
}
}

if (flags & kCGDisplayRemoveFlag) {
if (display) {
SDL_DelVideoDisplay(display->id, true);
display = NULL;
}
}

if (flags & kCGDisplaySetModeFlag) {
if (display) {
CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(displayid);
if (moderef) {
CVDisplayLinkRef link = NULL;
CVDisplayLinkCreateWithCGDisplay(displayid, &link);
if (link) {
SDL_DisplayMode mode;
if (GetDisplayMode(moderef, true, NULL, link, &mode)) {
SDL_SetCurrentDisplayMode(display, &mode);
}
CVDisplayLinkRelease(link);
}
CGDisplayModeRelease(moderef);
}
}
}

if (flags & kCGDisplaySetMainFlag) {
if (display) {
for (int i = 0; i < _this->num_displays; i++) {
if (_this->displays[i] == display) {
if (i > 0) {
// move this display to the front of _this->displays so it's treated as primary.
SDL_memmove(&_this->displays[1], &_this->displays[0], sizeof (*_this->displays) * i);
_this->displays[0] = display;
}
flags |= kCGDisplayMovedFlag; // we don't have an SDL event atm for "this display became primary," so at least let everyone know it "moved".
break;
}
}
}
}

if (flags & kCGDisplayMovedFlag) {
if (display) {
SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
}
}

if (flags & kCGDisplayDesktopShapeChangedFlag) {
SDL_UpdateDesktopBounds();
}
}

void Cocoa_InitModes(SDL_VideoDevice *_this)
{
@autoreleasepool {
CGDisplayErr result;
CGDirectDisplayID *displays;
CGDisplayCount numDisplays;
bool isstack;
int pass, i;
CGDisplayCount numDisplays = 0;

result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
if (result != kCGErrorSuccess) {
CG_SetError("CGGetOnlineDisplayList()", result);
return;
}
displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack);

bool isstack;
CGDirectDisplayID *displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack);

result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
if (result != kCGErrorSuccess) {
CG_SetError("CGGetOnlineDisplayList()", result);
SDL_small_free(displays, isstack);
return;
}

// Pick up the primary display in the first pass, then get the rest
for (pass = 0; pass < 2; ++pass) {
for (i = 0; i < numDisplays; ++i) {
SDL_VideoDisplay display;
SDL_DisplayData *displaydata;
SDL_DisplayMode mode;
CGDisplayModeRef moderef = NULL;
CVDisplayLinkRef link = NULL;
// future updates to the display graph will come through this callback.
CGDisplayRegisterReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this);

// Pick up the primary display in the first pass, then get the rest
for (int pass = 0; pass < 2; ++pass) {
for (int i = 0; i < numDisplays; ++i) {
if (pass == 0) {
if (!CGDisplayIsMain(displays[i])) {
continue;
Expand All @@ -354,41 +511,7 @@ void Cocoa_InitModes(SDL_VideoDevice *_this)
continue;
}

moderef = CGDisplayCopyDisplayMode(displays[i]);

if (!moderef) {
continue;
}

displaydata = (SDL_DisplayData *)SDL_malloc(sizeof(*displaydata));
if (!displaydata) {
CGDisplayModeRelease(moderef);
continue;
}
displaydata->display = displays[i];

CVDisplayLinkCreateWithCGDisplay(displays[i], &link);

SDL_zero(display);
// this returns a strdup'ed string
display.name = Cocoa_GetDisplayName(displays[i]);
if (!GetDisplayMode(_this, moderef, true, NULL, link, &mode)) {
CVDisplayLinkRelease(link);
CGDisplayModeRelease(moderef);
SDL_free(display.name);
SDL_free(displaydata);
continue;
}

CVDisplayLinkRelease(link);
CGDisplayModeRelease(moderef);

Cocoa_GetHDRProperties(displaydata->display, &display.HDR);

display.desktop_mode = mode;
display.internal = displaydata;
SDL_AddVideoDisplay(&display, false);
SDL_free(display.name);
Cocoa_AddDisplay(displays[i], false);
}
}
SDL_small_free(displays, isstack);
Expand Down Expand Up @@ -486,7 +609,7 @@ bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i);
SDL_DisplayMode mode;

if (GetDisplayMode(_this, moderef, false, modes, link, &mode)) {
if (GetDisplayMode(moderef, false, modes, link, &mode)) {
if (!SDL_AddFullscreenDisplayMode(display, &mode)) {
CFRelease(mode.internal->modes);
SDL_free(mode.internal);
Expand Down Expand Up @@ -559,6 +682,8 @@ void Cocoa_QuitModes(SDL_VideoDevice *_this)
{
int i, j;

CGDisplayRemoveReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this);

for (i = 0; i < _this->num_displays; ++i) {
SDL_VideoDisplay *display = _this->displays[i];
SDL_DisplayModeData *mode;
Expand Down
14 changes: 4 additions & 10 deletions src/video/cocoa/SDL_cocoawindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -2934,17 +2934,11 @@ SDL_DisplayID Cocoa_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *wind
screen = data.nswindow.screen;

if (screen != nil) {
CGDirectDisplayID displayid;
int i;

// https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc
displayid = [[screen.deviceDescription objectForKey:@"NSScreenNumber"] unsignedIntValue];

for (i = 0; i < _this->num_displays; i++) {
SDL_DisplayData *displaydata = _this->displays[i]->internal;
if (displaydata != NULL && displaydata->display == displayid) {
return _this->displays[i]->id;
}
CGDirectDisplayID displayid = [[screen.deviceDescription objectForKey:@"NSScreenNumber"] unsignedIntValue];
SDL_VideoDisplay *display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid);
if (display) {
return display->id;
}
}

Expand Down

0 comments on commit 38176bf

Please sign in to comment.