/ CoreGraphics / CGDirectDisplay.m
CGDirectDisplay.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  
 20  #import <AppKit/NSDisplay.h>
 21  #import <AppKit/NSScreen.h>
 22  #import <CoreGraphics/CGDirectDisplay.h>
 23  #import <CoreGraphics/CGError.h>
 24  #import <IOKit/graphics/IOGraphicsLib.h>
 25  #import <IOKit/graphics/IOGraphicsTypes.h>
 26  #include <dlfcn.h>
 27  
 28  // not sure what type or value this has
 29  unsigned int kCGDisplayPixelHeight = kCGDisplayHeight;
 30  unsigned int kCGDisplayPixelWidth = kCGDisplayWidth;
 31  
 32  const CFStringRef kCGDisplayProductNameKey = CFSTR("kCGDisplayProductNameKey");
 33  
 34  CGError CGCaptureAllDisplays(void) {
 35      return kCGErrorSuccess;
 36  }
 37  
 38  CGError CGReleaseAllDisplays(void) {
 39      return kCGErrorSuccess;
 40  }
 41  
 42  // Our platform abstraction is in AppKit
 43  static NSDisplay *currentDisplay(void) {
 44      Class cls = NSClassFromString(@"NSDisplay");
 45  
 46      if (!cls) {
 47          if (dlopen("/System/Library/Frameworks/AppKit.framework/Versions/C/"
 48                     "AppKit",
 49                     RTLD_LAZY | RTLD_GLOBAL) != NULL)
 50              cls = NSClassFromString(@"NSDisplay");
 51      }
 52  
 53      return [cls currentDisplay];
 54  }
 55  
 56  CGDirectDisplayID CGMainDisplayID(void) {
 57      NSDisplay *display = currentDisplay();
 58      if (!display)
 59          return kCGErrorInvalidConnection;
 60  
 61      NSArray<NSScreen *> *screens = [display screens];
 62  
 63      for (int i = 0; i < [screens count]; i++) {
 64          if (!NSIsEmptyRect([[screens objectAtIndex: i] frame])) {
 65              return i + 1;
 66          }
 67      }
 68  
 69      return kCGNullDirectDisplay;
 70  }
 71  
 72  CGError CGGetOnlineDisplayList(uint32_t maxDisplays,
 73                                 CGDirectDisplayID *onlineDisplays,
 74                                 uint32_t *displayCount)
 75  {
 76      NSDisplay *display = currentDisplay();
 77      if (!display)
 78          return kCGErrorInvalidConnection;
 79  
 80      NSArray<NSScreen *> *screens = [display screens];
 81      const CGDirectDisplayID mainDisplay = CGMainDisplayID();
 82  
 83      *displayCount = 0;
 84  
 85      // Main display should be the first returned
 86      if (mainDisplay != kCGNullDirectDisplay) {
 87          (*displayCount)++;
 88          if (maxDisplays > 0)
 89              onlineDisplays[0] = mainDisplay;
 90      }
 91  
 92      for (int i = 0; i < [screens count]; i++) {
 93          if ((i + 1) != mainDisplay) {
 94              if (*displayCount < maxDisplays)
 95                  onlineDisplays[*displayCount] = i + 1;
 96              (*displayCount)++;
 97          }
 98      }
 99  
100      return kCGErrorSuccess;
101  }
102  
103  size_t CGDisplayPixelsHigh(CGDirectDisplayID displayIndex) {
104      NSDisplay *display = currentDisplay();
105      if (!display)
106          return kCGErrorInvalidConnection;
107  
108      NSArray<NSScreen *> *screens = [display screens];
109      if (displayIndex > [screens count] || displayIndex <= 0)
110          return 0;
111  
112      return NSHeight([[screens objectAtIndex: displayIndex - 1] frame]);
113  }
114  
115  size_t CGDisplayPixelsWide(CGDirectDisplayID displayIndex) {
116      NSDisplay *display = currentDisplay();
117      if (!display)
118          return kCGErrorInvalidConnection;
119  
120      NSArray<NSScreen *> *screens = [display screens];
121      if (displayIndex > [screens count] || displayIndex <= 0)
122          return 0;
123  
124      return NSWidth([[screens objectAtIndex: displayIndex - 1] frame]);
125  }
126  
127  CGError CGGetActiveDisplayList(uint32_t maxDisplays,
128                                 CGDirectDisplayID *activeDisplays,
129                                 uint32_t *displayCount)
130  {
131      NSDisplay *display = currentDisplay();
132      if (!display)
133          return kCGErrorInvalidConnection;
134  
135      NSArray<NSScreen *> *screens = [display screens];
136  
137      *displayCount = 0;
138      for (int i = 0; i < [screens count]; i++) {
139          if (!NSIsEmptyRect([[screens objectAtIndex: i] frame])) {
140              if (*displayCount < maxDisplays)
141                  activeDisplays[*displayCount] = i + 1;
142              (*displayCount)++;
143          }
144      }
145  
146      return kCGErrorSuccess;
147  }
148  
149  CGError CGGetDisplaysWithOpenGLDisplayMask(CGOpenGLDisplayMask mask,
150                                             uint32_t maxDisplays,
151                                             CGDirectDisplayID *displays,
152                                             uint32_t *matchingDisplayCount)
153  {
154      return CGGetOnlineDisplayList(maxDisplays, displays, matchingDisplayCount);
155  }
156  
157  CGDirectDisplayID CGOpenGLDisplayMaskToDisplayID(CGOpenGLDisplayMask mask) {
158      return CGMainDisplayID();
159  }
160  
161  CGError CGGetDisplaysWithPoint(CGPoint point, uint32_t maxDisplays,
162                                 CGDirectDisplayID *displays,
163                                 uint32_t *matchingDisplayCount)
164  {
165      NSDisplay *display = currentDisplay();
166      if (!display)
167          return kCGErrorInvalidConnection;
168  
169      NSArray<NSScreen *> *screens = [display screens];
170      *matchingDisplayCount = 0;
171  
172      for (int i = 0; i < [screens count]; i++) {
173          NSRect rect = [[screens objectAtIndex: i] frame];
174          if (NSPointInRect(point, rect)) {
175              if (*matchingDisplayCount < maxDisplays)
176                  displays[*matchingDisplayCount] = i + 1;
177              (*matchingDisplayCount)++;
178          }
179      }
180  
181      return kCGErrorSuccess;
182  }
183  
184  CGError CGGetDisplaysWithRect(CGRect rect, uint32_t maxDisplays,
185                                CGDirectDisplayID *displays,
186                                uint32_t *matchingDisplayCount)
187  {
188      NSDisplay *display = currentDisplay();
189      if (!display)
190          return kCGErrorInvalidConnection;
191  
192      NSArray<NSScreen *> *screens = [display screens];
193      *matchingDisplayCount = 0;
194  
195      for (int i = 0; i < [screens count]; i++) {
196          NSRect screenRect = [[screens objectAtIndex: i] frame];
197          if (NSIntersectsRect(rect, screenRect)) {
198              if (*matchingDisplayCount < maxDisplays)
199                  displays[*matchingDisplayCount] = i + 1;
200              (*matchingDisplayCount)++;
201          }
202      }
203  
204      return kCGErrorSuccess;
205  }
206  
207  CGError CGDisplayCapture(CGDirectDisplayID display) {
208      return kCGErrorSuccess;
209  }
210  
211  CGError CGDisplayRelease(CGDirectDisplayID display) {
212      return kCGErrorSuccess;
213  }
214  
215  CGRect CGDisplayBounds(CGDirectDisplayID displayIndex) {
216      NSDisplay *display = currentDisplay();
217      if (!display)
218          return NSZeroRect;
219  
220      NSArray<NSScreen *> *screens = [display screens];
221      if (displayIndex > [screens count] || displayIndex <= 0)
222          return NSZeroRect;
223  
224      return [[screens objectAtIndex: displayIndex - 1] frame];
225  }
226  
227  CGError CGDisplayHideCursor(CGDirectDisplayID displayIndex) {
228      NSDisplay *display = currentDisplay();
229      if (!display)
230          return kCGErrorInvalidConnection;
231  
232      [display hideCursor];
233      return kCGErrorSuccess;
234  }
235  
236  CGError CGDisplayShowCursor(CGDirectDisplayID displayIndex) {
237      NSDisplay *display = currentDisplay();
238      if (!display)
239          return kCGErrorInvalidConnection;
240  
241      [display unhideCursor];
242      return kCGErrorSuccess;
243  }
244  
245  CFArrayRef CGDisplayAvailableModes(CGDirectDisplayID displayIndex) {
246      NSDisplay *display = currentDisplay();
247      if (!display)
248          return NULL;
249      return (CFArrayRef) [display modesForScreen: displayIndex - 1];
250  }
251  
252  Boolean CGDisplayIsMain(CGDirectDisplayID display) {
253      return display == 1;
254  }
255  
256  CGDirectDisplayID CGDisplayMirrorsDisplay(CGDirectDisplayID display) {
257      // STUB!
258      // TODO: Get this from XRandR
259      return kCGNullDirectDisplay;
260  }
261  
262  CGDisplayModeRef CGDisplayCopyDisplayMode(CGDirectDisplayID displayId) {
263      NSDisplay *display = currentDisplay();
264      if (!display)
265          return NULL;
266  
267      NSDictionary *dict = [[display currentModeForScreen: displayId - 1] retain];
268  
269      return (CGDisplayModeRef) dict;
270  }
271  
272  void CGDisplayModeRelease(CGDisplayModeRef mode) {
273      if (mode != NULL)
274          CFRelease(mode);
275  }
276  
277  CGDisplayModeRef CGDisplayModeRetain(CGDisplayModeRef mode) {
278      if (mode)
279          CFRetain(mode);
280      return mode;
281  }
282  
283  size_t CGDisplayModeGetHeight(CGDisplayModeRef mode) {
284      NSDictionary *dict = (NSDictionary *) mode;
285      return [[dict valueForKey: @"Height"] unsignedIntValue];
286  }
287  
288  size_t CGDisplayModeGetWidth(CGDisplayModeRef mode) {
289      NSDictionary *dict = (NSDictionary *) mode;
290      return [[dict valueForKey: @"Width"] unsignedIntValue];
291  }
292  
293  double CGDisplayModeGetRefreshRate(CGDisplayModeRef mode) {
294      NSDictionary *dict = (NSDictionary *) mode;
295      return [[dict valueForKey: @"RefreshRate"] doubleValue];
296  }
297  
298  CFStringRef CGDisplayModeCopyPixelEncoding(CGDisplayModeRef mode) {
299      NSDictionary *dict = (NSDictionary *) mode;
300      unsigned depth = [[dict valueForKey: @"Depth"] unsignedIntValue];
301  
302      switch (depth) {
303      case 24:
304      case 32:
305          return CFSTR(IO32BitDirectPixels);
306      case 16:
307          return CFSTR(IO16BitDirectPixels);
308      default:
309          return CFSTR("");
310      }
311  }
312  
313  CFArrayRef CGDisplayCopyAllDisplayModes(CGDirectDisplayID displayIndex,
314                                          CFDictionaryRef options)
315  {
316      NSDisplay *display = currentDisplay();
317      if (!display)
318          return NULL;
319      return (CFArrayRef)[[display modesForScreen: displayIndex - 1] retain];
320  }
321  
322  CGError CGDisplaySetDisplayMode(CGDirectDisplayID displayId,
323                                  CGDisplayModeRef mode, CFDictionaryRef options)
324  {
325      NSDisplay *display = currentDisplay();
326      if (!display)
327          return kCGErrorInvalidConnection;
328      BOOL result = [display setMode: mode forScreen: displayId - 1];
329  
330      return result ? kCGErrorSuccess : kCGErrorFailure;
331  }
332  
333  static NSData *edidForDisplay(CGDirectDisplayID displayId) {
334      NSDisplay *display = currentDisplay();
335      if (!display)
336          return nil;
337  
338      NSArray<NSScreen *> *screens = [display screens];
339      if (displayId <= 0 || displayId > [screens count])
340          return nil;
341  
342      NSScreen *screen = [screens objectAtIndex: displayId - 1];
343      NSData *edid = [screen edid];
344      if (edid && [edid length] >= 16)
345          return edid;
346  
347      return nil;
348  }
349  
350  uint32_t CGDisplaySerialNumber(CGDirectDisplayID displayId) {
351      NSData *edid = edidForDisplay(displayId);
352      if (!edid)
353          return displayId;
354  
355      return CFSwapInt32LittleToHost(*(uint32_t *) (&[edid bytes][12]));
356  }
357  
358  uint32_t CGDisplayModelNumber(CGDirectDisplayID displayId) {
359      NSData *edid = edidForDisplay(displayId);
360      if (!edid)
361          return kDisplayProductIDGeneric;
362  
363      return CFSwapInt16LittleToHost(*(uint16_t *) (&[edid bytes][10]));
364  }
365  
366  uint32_t CGDisplayVendorNumber(CGDirectDisplayID displayId) {
367      NSData *edid = edidForDisplay(displayId);
368      if (!edid)
369          return kDisplayVendorIDUnknown;
370  
371      return CFSwapInt16BigToHost(*(uint16_t *) (&[edid bytes][8]));
372  }
373  
374  io_service_t CGDisplayIOServicePort(CGDirectDisplayID displayID) {
375      // The code in this function is:
376      // Copyright (c) 2002-2006 Marcus Geelnard
377      // Copyright (c) 2006-2010 Camilla Berglund <elmindreda@elmindreda.org>
378      // Taken from
379      // https://github.com/glfw/glfw/blob/e0a6772e5e4c672179fc69a90bcda3369792ed1f/src/cocoa_monitor.m
380  
381      io_iterator_t iter;
382      io_service_t serv, servicePort = 0;
383  
384      CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect");
385  
386      // releases matching for us
387      kern_return_t err =
388              IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iter);
389      if (err)
390          return 0;
391  
392      while ((serv = IOIteratorNext(iter)) != 0) {
393          CFDictionaryRef info;
394          CFIndex vendorID, productID, serialNumber;
395          CFNumberRef vendorIDRef, productIDRef, serialNumberRef;
396          Boolean success;
397  
398          info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName);
399  
400          vendorIDRef = CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
401          productIDRef = CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
402          serialNumberRef =
403                  CFDictionaryGetValue(info, CFSTR(kDisplaySerialNumber));
404  
405          if (!vendorIDRef || !productIDRef || !serialNumberRef) {
406              CFRelease(info);
407              continue;
408          }
409  
410          success =
411                  CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID);
412          success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType,
413                                      &productID);
414          success &= CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType,
415                                      &serialNumber);
416  
417          if (!success) {
418              CFRelease(info);
419              continue;
420          }
421  
422          // If the vendor and product id along with the serial don't match
423          // then we are not looking at the correct monitor.
424          // NOTE: The serial number is important in cases where two monitors
425          //       are the exact same.
426          if (CGDisplayVendorNumber(displayID) != vendorID ||
427              CGDisplayModelNumber(displayID) != productID ||
428              CGDisplaySerialNumber(displayID) != serialNumber) {
429              CFRelease(info);
430              continue;
431          }
432  
433          // The VendorID, Product ID, and the Serial Number all Match Up!
434          // Therefore we have found the appropriate display io_service
435          servicePort = serv;
436          CFRelease(info);
437          break;
438      }
439  
440      IOObjectRelease(iter);
441      return servicePort;
442  }
443  
444  CGError CGWarpMouseCursorPosition(CGPoint newCursorPosition) {
445      NSDisplay *display = currentDisplay();
446      if (!display)
447          return kCGErrorInvalidConnection;
448      [display warpMouse: newCursorPosition];
449      return kCGErrorSuccess;
450  }
451  
452  CGError CGAssociateMouseAndMouseCursorPosition(boolean_t connected) {
453      NSDisplay *display = currentDisplay();
454      if (!display)
455          return kCGErrorInvalidConnection;
456  
457      [display grabMouse: connected];
458      return kCGErrorSuccess;
459  }
460  
461  CFDictionaryRef CGDisplayBestModeForParametersAndRefreshRate(
462          CGDirectDisplayID display, size_t bitsPerPixel, size_t width,
463          size_t height, CGRefreshRate refreshRate, boolean_t *exactMatch)
464  {
465      return nil;
466  }
467  
468  boolean_t CGDisplayIsCaptured(CGDirectDisplayID display) {
469      return FALSE;
470  }
471  
472  CGError CGDisplaySwitchToMode(CGDirectDisplayID display, CFDictionaryRef mode) {
473      return kCGErrorSuccess;
474  }
475  
476  CFDictionaryRef CGDisplayCurrentMode(CGDirectDisplayID display) {
477      return nil;
478  }
479  
480  size_t CGDisplayModeGetPixelWidth(CGDisplayModeRef mode) {
481      return 0;
482  }