Vuo  2.0.3
VuoScreenCommon.mm
Go to the documentation of this file.
1 
10 #include "module.h"
11 #include "VuoScreenCommon.h"
12 #include "VuoTriggerSet.hh"
13 #include "VuoOsStatus.h"
14 #include "VuoPnpId.h"
15 #include "VuoApp.h"
16 
17 #include <IOKit/graphics/IOGraphicsLib.h>
20 #ifndef NS_RETURNS_INNER_POINTER
21 #define NS_RETURNS_INNER_POINTER
22 #endif
23 #include <AppKit/AppKit.h>
24 
25 #ifdef VUO_COMPILER
27  "title" : "VuoScreenCommon",
28  "dependencies" : [
29  "VuoApp",
30  "VuoOsStatus",
31  "VuoPnpId",
32  "VuoScreen",
33  "VuoList_VuoScreen",
34  "IOKit.framework",
35  "AppKit.framework"
36  ]
37  });
38 #endif
39 
41 unsigned int VuoScreen_useCount = 0;
42 
47 - (NSInteger)deviceId;
48 @end
49 
53 static VuoText VuoScreen_getName(CGDirectDisplayID displayID)
54 {
55  // Deprecated on 10.9, but there's no alternative.
56  #pragma clang diagnostic push
57  #pragma clang diagnostic ignored "-Wdeprecated-declarations"
58  io_service_t port = CGDisplayIOServicePort(displayID);
59  #pragma clang diagnostic pop
60 
61  NSDictionary *deviceInfo = (NSDictionary *)IODisplayCreateInfoDictionary(port, kIODisplayOnlyPreferredName);
62 
63  NSData *edid = [deviceInfo objectForKey:@kIODisplayEDIDKey];
64  NSString *displayLocation = [deviceInfo objectForKey:@kIODisplayLocationKey];
65  NSString *manufacturerName = nil;
66  uint32_t serialNumber = 0;
67  uint8_t manufacturedWeek = 0;
68  uint16_t manufacturedYear = 0;
69  if (edid)
70  {
71  const unsigned char *bytes = (const unsigned char *)[edid bytes];
72 
73  uint16_t manufacturerId = (bytes[8] << 8) + bytes[9];
74  char *manufacturer = VuoPnpId_getString(manufacturerId);
75  manufacturerName = [NSString stringWithUTF8String:manufacturer];
76  free(manufacturer);
77 
78  serialNumber = (bytes[15] << 24) + (bytes[14] << 16) + (bytes[13] << 8) + bytes[12];
79 
80  manufacturedWeek = bytes[16];
81  manufacturedYear = bytes[17] + 1990;
82  }
83  else
84  {
85  // If no EDID manufacturer is provided, inspect the device path for some known unique keys.
86  if ([displayLocation rangeOfString:@"SRXDisplayCard" options:0].location != NSNotFound)
87  manufacturerName = @"Splashtop XDisplay";
88  else if ([displayLocation rangeOfString:@"info_ennowelbers_proxyframebuffer_fbuffer" options:0].location != NSNotFound)
89  manufacturerName = @"GoodDual Display";
90  }
91 
92  // Yam Display reports "DIS" as its EDID manufacturer.
93  // I assume they meant that as short for "Display"
94  // but it's actually the apparently-unrelated company "Diseda S.A.", so ignore it.
95  if ([displayLocation rangeOfString:@"com_yamstu_YamDisplayDriver" options:0].location != NSNotFound)
96  manufacturerName = nil;
97 
98 
99  [deviceInfo autorelease];
100  NSDictionary *localizedNames = [deviceInfo objectForKey:@kDisplayProductName];
101  NSString *name = nil;
102  if ([localizedNames count] > 0)
103  {
104  NSString *modelName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
105  if ([modelName length] > 0)
106  {
107  if (manufacturerName)
108  name = [NSString stringWithFormat:@"%@: %@", manufacturerName, modelName];
109  else
110  name = modelName;
111  }
112  }
113 
114  if (!name)
115  {
116  if (manufacturerName)
117  name = manufacturerName;
118  else
119  name = @"";
120  }
121 
122  if (serialNumber && manufacturedYear)
123  return VuoText_make([[NSString stringWithFormat:@"%@ (%d, %d-W%02d)", name, serialNumber, manufacturedYear, manufacturedWeek] UTF8String]);
124  else if (serialNumber)
125  return VuoText_make([[NSString stringWithFormat:@"%@ (%d)", name, serialNumber] UTF8String]);
126  else if (manufacturedYear)
127  return VuoText_make([[NSString stringWithFormat:@"%@ (%d-W%02d)", name, manufacturedYear, manufacturedWeek] UTF8String]);
128  else
129  return VuoText_make([name UTF8String]);
130 }
131 
132 @implementation NSScreen (VuoAdditions)
136 - (NSInteger)deviceId
137 {
138  NSDictionary *deviceDescription = [self deviceDescription];
139  return [[deviceDescription objectForKey:@"NSScreenNumber"] integerValue];
140 }
141 @end
142 
147 {
148  VuoScreen realizedScreen;
149  bool realized = VuoScreen_realize(screen, &realizedScreen);
150  if (!realized)
151  return NULL;
152  else
153  {
154  VuoScreen_retain(realizedScreen);
155  for (NSScreen *nsscreen in [NSScreen screens])
156  if ([nsscreen deviceId] == realizedScreen.id)
157  {
158  VuoScreen_release(realizedScreen);
159  return nsscreen;
160  }
161  VuoScreen_release(realizedScreen);
162  }
163 
164  return NULL;
165 }
166 
171 {
172  CGRect bounds = CGDisplayBounds(display);
173  CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
174  int backingScaleFactor = CGDisplayModeGetPixelWidth(mode) / bounds.size.width;
175  int fakeDPI = backingScaleFactor * 72;
176  CGDisplayModeRelease(mode);
177 
178  return (VuoScreen){
179  VuoScreenType_MatchId,
180  display,
181  CGDisplayIDToOpenGLDisplayMask(display),
182  VuoScreen_getName(display),
183  true,
184  VuoPoint2d_make(bounds.origin.x, bounds.origin.y),
185  (VuoInteger)bounds.size.width,
186  (VuoInteger)bounds.size.height,
187  fakeDPI,
188  fakeDPI
189  };
190 }
191 
196 {
197  NSScreen *screen = (NSScreen *)vscreen;
198  return VuoScreen_makeFromCGDirectDisplay(screen.deviceId);
199 }
200 
205 {
207 
208  // +[NSScreen screens] causes formerly-headless apps to begin bouncing endlessly in the dock,
209  // so use the CoreGraphics functions instead.
210  CGDirectDisplayID displays[256];
211  uint32_t displayCount = 0;
212  CGError e = CGGetOnlineDisplayList(256, displays, &displayCount);
213  if (e != kCGErrorSuccess)
214  {
215  char *errorStr = VuoOsStatus_getText(e);
216  VUserLog("Error: Couldn't get display info: %s", errorStr);
217  free(errorStr);
218  return screens;
219  }
220 
221  for (int i = 0; i < displayCount; ++i)
223 
224  return screens;
225 }
226 
231 {
232  // +[NSScreen mainScreen] causes formerly-headless apps to begin bouncing endlessly in the dock.
233  // There's no CoreGraphics equivalent (CGMainDisplayID() returns the screen with the menu bar, not the focused screen).
234  VuoApp_init(false);
235 
236  return VuoScreen_makeFromNSScreen([NSScreen mainScreen]);
237 }
238 
245 {
246  return VuoScreen_makeFromCGDirectDisplay(CGMainDisplayID());
247 }
248 
255 {
256  CGDirectDisplayID displays[256];
257  uint32_t displayCount = 0;
258  CGError e = CGGetOnlineDisplayList(256, displays, &displayCount);
259  if (e != kCGErrorSuccess)
260  {
261  char *errorStr = VuoOsStatus_getText(e);
262  VUserLog("Error: Couldn't get display info: %s", errorStr);
263  free(errorStr);
264  return VuoScreen_getPrimary();
265  }
266 
267  if (displayCount > 1)
268  return VuoScreen_makeFromCGDirectDisplay(displays[1]);
269 
270  return VuoScreen_makeFromName(nullptr);
271 }
272 
274 #define VuoScreen_reconfigurationCallback_showFlag(a) \
275  if (flags & a) VUserLog(" %s", #a);
276 
280 void VuoScreen_reconfigurationCallback(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo)
281 {
282  // Ignore the "something's about to happen" notification.
283  if (flags == kCGDisplayBeginConfigurationFlag)
284  return;
285 
286  if (VuoIsDebugEnabled())
287  {
288  VUserLog("display %x, flags %x", display, flags);
289  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayBeginConfigurationFlag );
290  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayMovedFlag );
291  VuoScreen_reconfigurationCallback_showFlag(kCGDisplaySetMainFlag );
292  VuoScreen_reconfigurationCallback_showFlag(kCGDisplaySetModeFlag );
293  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayAddFlag );
294  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayRemoveFlag );
295  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayEnabledFlag );
296  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayDisabledFlag );
297  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayMirrorFlag );
298  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayUnMirrorFlag );
299  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayDesktopShapeChangedFlag);
300  }
301 
302  // If multiple displays are attached, this callback is invoked multiple times.
303  // Coalesce into a single Vuo event.
304  const double notificationDelaySeconds = 0.25;
305  static dispatch_source_t timer = 0;
306  if (timer)
307  // Push the timer back.
308  dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, notificationDelaySeconds*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, notificationDelaySeconds*NSEC_PER_SEC/10);
309  else
310  {
311  timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
312  dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, notificationDelaySeconds*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, notificationDelaySeconds*NSEC_PER_SEC/10);
313  dispatch_source_set_event_handler(timer, ^{
315  dispatch_source_cancel(timer);
316  });
317  dispatch_source_set_cancel_handler(timer, ^{
318  dispatch_release(timer);
319  timer = 0;
320  });
321  dispatch_resume(timer);
322  }
323 }
324 
331 void VuoScreen_use(void)
332 {
333  if (__sync_add_and_fetch(&VuoScreen_useCount, 1) == 1)
334  {
335  // Our process only receives CGDisplay notifications while running in app mode.
336  VuoApp_init(false);
337 
338  CGDisplayRegisterReconfigurationCallback(VuoScreen_reconfigurationCallback, NULL);
339  }
340 }
341 
349 {
350  if (VuoScreen_useCount <= 0)
351  {
352  VUserLog("Error: Unbalanced VuoScreen_use() / _disuse() calls.");
353  return;
354  }
355 
356  if (__sync_sub_and_fetch(&VuoScreen_useCount, 1) == 0)
357  CGDisplayRemoveReconfigurationCallback(VuoScreen_reconfigurationCallback, NULL);
358 }
359 
369 {
370  VuoScreen_callbacks.addTrigger(screens);
371  screens(VuoScreen_getList());
372 }
373 
381 {
382  VuoScreen_callbacks.removeTrigger(screens);
383 }