/ 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  }