platform_misc_mac.mm (4277B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 // Normally, system includes come last. But apparently some of our macro names are redefined... 5 #include <Cocoa/Cocoa.h> 6 #include <IOKit/pwr_mgt/IOPMLib.h> 7 #include <QuartzCore/QuartzCore.h> 8 #include <cinttypes> 9 #include <optional> 10 #include <sys/sysctl.h> 11 #include <vector> 12 13 #include "metal_layer.h" 14 #include "platform_misc.h" 15 #include "window_info.h" 16 17 #include "common/log.h" 18 #include "common/small_string.h" 19 20 Log_SetChannel(PlatformMisc); 21 22 #if __has_feature(objc_arc) 23 #error ARC should not be enabled. 24 #endif 25 26 static IOPMAssertionID s_prevent_idle_assertion = kIOPMNullAssertionID; 27 28 bool PlatformMisc::InitializeSocketSupport(Error* error) 29 { 30 return true; 31 } 32 33 static bool SetScreensaverInhibitMacOS(bool inhibit) 34 { 35 if (inhibit) 36 { 37 const CFStringRef reason = CFSTR("System Running"); 38 if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, reason, 39 &s_prevent_idle_assertion) != kIOReturnSuccess) 40 { 41 ERROR_LOG("IOPMAssertionCreateWithName() failed"); 42 return false; 43 } 44 45 return true; 46 } 47 else 48 { 49 IOPMAssertionRelease(s_prevent_idle_assertion); 50 s_prevent_idle_assertion = kIOPMNullAssertionID; 51 return true; 52 } 53 } 54 55 static bool s_screensaver_suspended; 56 57 void PlatformMisc::SuspendScreensaver() 58 { 59 if (s_screensaver_suspended) 60 61 if (!SetScreensaverInhibitMacOS(true)) 62 { 63 ERROR_LOG("Failed to suspend screensaver."); 64 return; 65 } 66 67 s_screensaver_suspended = true; 68 } 69 70 void PlatformMisc::ResumeScreensaver() 71 { 72 if (!s_screensaver_suspended) 73 return; 74 75 if (!SetScreensaverInhibitMacOS(false)) 76 ERROR_LOG("Failed to resume screensaver."); 77 78 s_screensaver_suspended = false; 79 } 80 81 template<typename T> 82 static std::optional<T> sysctlbyname(const char* name) 83 { 84 T output = 0; 85 size_t output_size = sizeof(output); 86 if (sysctlbyname(name, &output, &output_size, nullptr, 0) != 0) 87 return std::nullopt; 88 89 return output; 90 } 91 92 size_t PlatformMisc::GetRuntimePageSize() 93 { 94 return sysctlbyname<u32>("hw.pagesize").value_or(0); 95 } 96 97 bool PlatformMisc::PlaySoundAsync(const char* path) 98 { 99 NSString* nspath = [[NSString alloc] initWithUTF8String:path]; 100 NSSound* sound = [[NSSound alloc] initWithContentsOfFile:nspath byReference:YES]; 101 const bool result = [sound play]; 102 [sound release]; 103 [nspath release]; 104 return result; 105 } 106 107 bool CocoaTools::CreateMetalLayer(WindowInfo* wi) 108 { 109 // Punt off to main thread if we're not calling from it already. 110 if (![NSThread isMainThread]) 111 { 112 bool ret; 113 dispatch_sync(dispatch_get_main_queue(), [&ret, wi]() { ret = CreateMetalLayer(wi); }); 114 return ret; 115 } 116 117 CAMetalLayer* layer = [CAMetalLayer layer]; 118 if (layer == nil) 119 { 120 ERROR_LOG("Failed to create CAMetalLayer"); 121 return false; 122 } 123 124 NSView* view = (__bridge NSView*)wi->window_handle; 125 [view setWantsLayer:TRUE]; 126 [view setLayer:layer]; 127 [layer setContentsScale:[[[view window] screen] backingScaleFactor]]; 128 129 wi->surface_handle = (__bridge void*)layer; 130 return true; 131 } 132 133 void CocoaTools::DestroyMetalLayer(WindowInfo* wi) 134 { 135 if (!wi->surface_handle) 136 return; 137 138 // Punt off to main thread if we're not calling from it already. 139 if (![NSThread isMainThread]) 140 { 141 dispatch_sync(dispatch_get_main_queue(), [wi]() { DestroyMetalLayer(wi); }); 142 return; 143 } 144 145 NSView* view = (__bridge NSView*)wi->window_handle; 146 CAMetalLayer* layer = (__bridge CAMetalLayer*)wi->surface_handle; 147 [view setLayer:nil]; 148 [view setWantsLayer:NO]; 149 [layer release]; 150 } 151 152 std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi) 153 { 154 if (![NSThread isMainThread]) 155 { 156 std::optional<float> ret; 157 dispatch_sync(dispatch_get_main_queue(), [&ret, wi]{ ret = GetViewRefreshRate(wi); }); 158 return ret; 159 } 160 161 std::optional<float> ret; 162 NSView* const view = (__bridge NSView*)wi.window_handle; 163 const u32 did = [[[[[view window] screen] deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue]; 164 if (CGDisplayModeRef mode = CGDisplayCopyDisplayMode(did)) 165 { 166 ret = CGDisplayModeGetRefreshRate(mode); 167 CGDisplayModeRelease(mode); 168 } 169 170 return ret; 171 }