/ CoreGraphics / CGS.m
CGS.m
1 /* 2 This file is part of Darling. 3 4 Copyright (C) 2020 Lubos Dolezel 5 6 Darling is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 Darling is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with Darling. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 #include <CoreGraphics/CoreGraphicsPrivate.h> 20 #import <Foundation/Foundation.h> 21 #include <dispatch/dispatch.h> 22 #include <stdatomic.h> 23 #import <CoreGraphics/CGSConnection.h> 24 #import <CoreGraphics/CGSWindow.h> 25 #import <CoreGraphics/CGSSurface.h> 26 #include <pthread.h> 27 28 static NSMutableDictionary<NSNumber*, CGSConnection*>* g_connections = nil; 29 static Boolean g_denyConnections = FALSE; 30 31 static CGSConnectionID g_defaultConnection = -1; 32 static pthread_mutex_t g_defaultConnectionMutex = PTHREAD_MUTEX_INITIALIZER; 33 34 static _Atomic CGSConnectionID g_nextConnectionID = 1; 35 static Class g_backendClass = nil; 36 37 __attribute__((visibility("hidden"))) CFMutableArrayRef g_cgsNotifyProc; 38 __attribute__((visibility("hidden"))) pthread_mutex_t g_cgsNotifyProcMutex = PTHREAD_MUTEX_INITIALIZER; 39 40 typedef struct 41 { 42 CGSNotifyProcPtr proc; 43 CGSNotificationType notificationType; 44 void* private; 45 } NotifyProcEntry; 46 47 CGError CGSSetDenyWindowServerConnections(Boolean deny) 48 { 49 NSUInteger connectionCount; 50 @synchronized(g_connections) 51 { 52 connectionCount = g_connections.count; 53 } 54 55 if (deny && connectionCount > 0) 56 { 57 return kCGErrorFailure; 58 } 59 60 g_denyConnections = deny; 61 return kCGSErrorSuccess; 62 } 63 64 void CGSShutdownServerConnections(void) 65 { 66 @synchronized(g_connections) 67 { 68 [g_connections removeAllObjects]; 69 } 70 g_defaultConnection = -1; 71 } 72 73 __attribute__((constructor)) 74 void CGSInitialize(void) 75 { 76 static dispatch_once_t once; 77 dispatch_once(&once, ^{ 78 g_connections = [[NSMutableDictionary alloc] initWithCapacity: 1]; 79 }); 80 } 81 82 static void _CGSLoadBackend(void) 83 { 84 NSBundle* cgBundle = [NSBundle bundleForClass: [CGSConnection class]]; 85 NSMutableArray<NSBundle*>* backends = [NSMutableArray new]; 86 87 for (NSString *path in [cgBundle pathsForResourcesOfType: @"backend" inDirectory: @"Backends"]) 88 { 89 NSBundle* backendBundle = [NSBundle bundleWithPath: path]; 90 if ([backendBundle load]) 91 [backends addObject: backendBundle]; 92 } 93 94 // Sort them according to the NSPriority key in their Info.plist files. 95 [backends sortUsingComparator: ^(NSBundle *b1, NSBundle *b2) { 96 NSNumber *p1 = [b1 objectForInfoDictionaryKey: @"NSPriority"]; 97 NSNumber *p2 = [b2 objectForInfoDictionaryKey: @"NSPriority"]; 98 return [p2 compare: p1]; 99 }]; 100 101 // Try to instantiate them in that order. 102 for (NSBundle *backendBundle in backends) 103 { 104 Class cls = [backendBundle principalClass]; 105 if ([cls isAvailable]) 106 { 107 g_backendClass = cls; 108 break; 109 } 110 } 111 [backends release]; 112 } 113 114 CGError CGSNewConnection(CGSDictionaryObj attribs, CGSConnectionID* connId) 115 { 116 *connId = -1; 117 118 if (g_denyConnections) 119 return kCGErrorCannotComplete; 120 121 static dispatch_once_t once; 122 dispatch_once(&once, ^{ 123 _CGSLoadBackend(); 124 }); 125 126 if (!g_backendClass) 127 return kCGErrorCannotComplete; 128 129 CGSConnectionID newConnID = g_nextConnectionID++; 130 CGSConnection* conn = [[g_backendClass alloc] initWithConnectionID: newConnID]; 131 132 if (conn != nil) 133 { 134 *connId = newConnID; 135 @synchronized(g_connections) 136 { 137 [g_connections setObject: conn forKey: [NSNumber numberWithInt: newConnID]]; 138 } 139 [conn release]; 140 return kCGSErrorSuccess; 141 } 142 else 143 return kCGErrorFailure; 144 } 145 146 CGSConnection* _CGSConnectionForID(CGSConnectionID connId) 147 { 148 @synchronized(g_connections) 149 { 150 return [g_connections objectForKey:[NSNumber numberWithInt: connId]]; 151 } 152 } 153 154 void* _CGSNativeDisplay(CGSConnectionID connId) 155 { 156 return [_CGSConnectionForID(connId) nativeDisplay]; 157 } 158 159 void* _CGSNativeWindowForID(CGSConnectionID connId, CGSWindowID winId) 160 { 161 CGSConnection* conn = _CGSConnectionForID(connId); 162 return [[conn windowForId: winId] nativeWindow]; 163 } 164 165 void* _CGSNativeWindowForSurfaceID(CGSConnectionID connId, CGSWindowID winId, CGSSurfaceID surfaceId) 166 { 167 CGSConnection* conn = _CGSConnectionForID(connId); 168 return [[[conn windowForId: winId] surfaceForId: surfaceId] nativeWindow]; 169 } 170 171 CGError CGSReleaseConnection(CGSConnectionID connId) 172 { 173 NSNumber* num = [NSNumber numberWithInt:connId]; 174 175 @synchronized(g_connections) 176 { 177 if (![g_connections objectForKey: num]) 178 return kCGErrorInvalidConnection; 179 [g_connections removeObjectForKey: num]; 180 } 181 return kCGSErrorSuccess; 182 } 183 184 CGSConnectionID _CGSDefaultConnection(void) 185 { 186 return CGSMainConnectionID(); 187 } 188 189 CGSConnectionID CGSMainConnectionID(void) 190 { 191 if (g_defaultConnection == -1) 192 { 193 pthread_mutex_lock(&g_defaultConnectionMutex); 194 if (g_defaultConnection == -1) 195 CGSNewConnection(NULL, &g_defaultConnection); 196 pthread_mutex_unlock(&g_defaultConnectionMutex); 197 } 198 return g_defaultConnection; 199 } 200 201 static CGError getWindow(CGSConnectionID cid, CGSWindowID wid, CGSWindow** out) 202 { 203 CGSConnection* c = _CGSConnectionForID(cid); 204 if (!c) 205 return kCGErrorInvalidConnection; 206 207 CGSWindow* window = [c windowForId: wid]; 208 if (!window) 209 return kCGErrorIllegalArgument; 210 211 *out = window; 212 return kCGSErrorSuccess; 213 } 214 215 CGError CGSNewWindow(CGSConnectionID conn, CFIndex flags, float x_offset, float y_offset, const CGSRegionRef region, CGSWindowID* windowID) 216 { 217 CGSConnection* c = _CGSConnectionForID(conn); 218 if (!c) 219 return kCGErrorInvalidConnection; 220 221 CGSWindow* window = [c newWindow: region]; 222 if (!window) 223 return kCGErrorIllegalArgument; 224 225 *windowID = window.windowId; 226 return kCGSErrorSuccess; 227 } 228 229 CGError CGSReleaseWindow(CGSConnectionID cid, CGSWindowID wid) 230 { 231 CGSConnection* c = _CGSConnectionForID(cid); 232 if (!c) 233 return kCGErrorInvalidConnection; 234 return [c destroyWindow: wid]; 235 } 236 237 CGError CGSSetWindowShape(CGSConnectionID cid, CGSWindowID wid, float x_offset, float y_offset, const CGSRegionRef shape) 238 { 239 CGSWindow* window; 240 CGError err = getWindow(cid, wid, &window); 241 242 if (err != kCGSErrorSuccess) 243 return err; 244 245 return [window setRegion: shape]; 246 } 247 248 OSStatus CGSOrderWindow(CGSConnectionID cid, CGSWindowID wid, CGSWindowOrderingMode place, CGSWindowID relativeToWindow) 249 { 250 CGSConnection* c = _CGSConnectionForID(cid); 251 if (!c) 252 return kCGErrorInvalidConnection; 253 254 CGSWindow* window = [c windowForId: wid]; 255 if (!window) 256 return kCGErrorIllegalArgument; 257 258 CGSWindow* relativeTo = [c windowForId: relativeToWindow]; 259 return [window orderWindow: place relativeTo: relativeTo]; 260 } 261 262 CGError CGSMoveWindow(CGSConnectionID cid, CGSWindowID wid, const CGPoint *window_pos) 263 { 264 CGSWindow* window; 265 CGError err = getWindow(cid, wid, &window); 266 267 if (err != kCGSErrorSuccess) 268 return err; 269 270 return [window moveTo: window_pos]; 271 } 272 273 extern CGError CGSSetWindowOpacity(CGSConnectionID cid, CGSWindowID wid, bool isOpaque); 274 extern CGError CGSSetWindowAlpha(CGSConnectionID cid, CGSWindowID wid, float alpha); 275 extern CGError CGSSetWindowLevel(CGSConnectionID cid, CGSWindowID wid, CGWindowLevel level); 276 277 CGError CGSGetWindowProperty(CGSConnectionID cid, CGSWindowID wid, CFStringRef key, CFTypeRef *outValue) 278 { 279 CGSWindow* window; 280 CGError err = getWindow(cid, wid, &window); 281 282 if (err != kCGSErrorSuccess) 283 return err; 284 285 return [window getProperty: key value: outValue]; 286 } 287 288 CGError CGSSetWindowProperty(CGSConnectionID cid, CGSWindowID wid, CFStringRef key, CFTypeRef value) 289 { 290 CGSWindow* window; 291 CGError err = getWindow(cid, wid, &window); 292 293 if (err != kCGSErrorSuccess) 294 return err; 295 296 return [window setProperty: key value: value]; 297 } 298 299 CGError getSurface(CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID sid, CGSSurface** surface) 300 { 301 CGSWindow* window; 302 303 CGError err = getWindow(cid, wid, &window); 304 if (err != kCGSErrorSuccess) 305 return err; 306 307 *surface = [window surfaceForId: sid]; 308 return (*surface) ? kCGSErrorSuccess : kCGErrorIllegalArgument; 309 } 310 311 CGError CGSAddSurface(CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID *sid) 312 { 313 CGSWindow* window; 314 315 CGError err = getWindow(cid, wid, &window); 316 if (err != kCGSErrorSuccess) 317 return err; 318 319 CGSSurface* surface = [window createSurface]; 320 if (!surface) 321 return kCGErrorFailure; 322 323 *sid = surface.surfaceId; 324 return kCGSErrorSuccess; 325 } 326 327 CGError CGSRemoveSurface(CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID sid) 328 { 329 CGSSurface* surface; 330 331 CGError err = getSurface(cid, wid, sid, &surface); 332 if (err != kCGSErrorSuccess) 333 return err; 334 335 [surface invalidate]; 336 return kCGSErrorSuccess; 337 } 338 339 CGError CGSSetSurfaceBounds(CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID sid, CGRect rect) 340 { 341 CGSSurface* surface; 342 343 CGError err = getSurface(cid, wid, sid, &surface); 344 if (err != kCGSErrorSuccess) 345 return err; 346 347 return [surface setBounds: rect]; 348 } 349 350 CGSConnection* _CGSConnectionFromEventRecord(const CGSEventRecordPtr record) 351 { 352 if (!record) 353 return nil; 354 return _CGSConnectionForID(record->connection); 355 } 356 357 static void simplyFree(CFAllocatorRef allocator, const void* mem) 358 { 359 free((void*) mem); 360 } 361 362 CGError CGSRegisterNotifyProc(CGSNotifyProcPtr proc, CGSNotificationType notificationType, void* private) 363 { 364 static dispatch_once_t once; 365 366 dispatch_once(&once, ^{ 367 CFArrayCallBacks cb = { 368 .release = simplyFree, 369 .version = 0, 370 }; 371 g_cgsNotifyProc = CFArrayCreateMutable(NULL, 0, &cb); 372 }); 373 374 NotifyProcEntry* e = (NotifyProcEntry*) malloc(sizeof(NotifyProcEntry)); 375 e->proc = proc; 376 e->notificationType = notificationType; 377 e->private = private; 378 379 pthread_mutex_lock(&g_cgsNotifyProcMutex); 380 CFArrayAppendValue(g_cgsNotifyProc, e); 381 pthread_mutex_unlock(&g_cgsNotifyProcMutex); 382 383 return kCGSErrorSuccess; 384 } 385 386 CGError CGSRemoveNotifyProc(CGSNotifyProcPtr proc, CGSNotificationType notificationType) 387 { 388 if (!g_cgsNotifyProc) 389 return kCGSErrorSuccess; 390 391 pthread_mutex_lock(&g_cgsNotifyProcMutex); 392 393 // This code is not too efficient, but it is called so rarely (if ever), it doesn't matter. 394 for (CFIndex i = 0; i < CFArrayGetCount(g_cgsNotifyProc); i++) 395 { 396 NotifyProcEntry* e = (NotifyProcEntry*) CFArrayGetValueAtIndex(g_cgsNotifyProc, i); 397 if (e->proc == proc && e->notificationType == notificationType) 398 { 399 CFArrayRemoveValueAtIndex(g_cgsNotifyProc, i); 400 break; 401 } 402 } 403 404 pthread_mutex_unlock(&g_cgsNotifyProcMutex); 405 406 return kCGSErrorSuccess; 407 }