Vuo  2.0.0
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 
253 {
254  CGDirectDisplayID displays[256];
255  uint32_t displayCount = 0;
256  CGError e = CGGetOnlineDisplayList(256, displays, &displayCount);
257  if (e != kCGErrorSuccess)
258  {
259  char *errorStr = VuoOsStatus_getText(e);
260  VUserLog("Error: Couldn't get display info: %s", errorStr);
261  free(errorStr);
262  return VuoScreen_getPrimary();
263  }
264 
265  if (displayCount > 1)
266  return VuoScreen_makeFromCGDirectDisplay(displays[1]);
267 
268  return VuoScreen_getPrimary();
269 }
270 
272 #define VuoScreen_reconfigurationCallback_showFlag(a) \
273  if (flags & a) VUserLog(" %s", #a);
274 
278 void VuoScreen_reconfigurationCallback(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo)
279 {
280  // Ignore the "something's about to happen" notification.
281  if (flags == kCGDisplayBeginConfigurationFlag)
282  return;
283 
284  if (VuoIsDebugEnabled())
285  {
286  VUserLog("display %x, flags %x", display, flags);
287  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayBeginConfigurationFlag );
288  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayMovedFlag );
289  VuoScreen_reconfigurationCallback_showFlag(kCGDisplaySetMainFlag );
290  VuoScreen_reconfigurationCallback_showFlag(kCGDisplaySetModeFlag );
291  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayAddFlag );
292  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayRemoveFlag );
293  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayEnabledFlag );
294  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayDisabledFlag );
295  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayMirrorFlag );
296  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayUnMirrorFlag );
297  VuoScreen_reconfigurationCallback_showFlag(kCGDisplayDesktopShapeChangedFlag);
298  }
299 
300  // If multiple displays are attached, this callback is invoked multiple times.
301  // Coalesce into a single Vuo event.
302  const double notificationDelaySeconds = 0.25;
303  static dispatch_source_t timer = 0;
304  if (timer)
305  // Push the timer back.
306  dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, notificationDelaySeconds*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, notificationDelaySeconds*NSEC_PER_SEC/10);
307  else
308  {
309  timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
310  dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, notificationDelaySeconds*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, notificationDelaySeconds*NSEC_PER_SEC/10);
311  dispatch_source_set_event_handler(timer, ^{
313  dispatch_source_cancel(timer);
314  });
315  dispatch_source_set_cancel_handler(timer, ^{
316  dispatch_release(timer);
317  timer = 0;
318  });
319  dispatch_resume(timer);
320  }
321 }
322 
329 void VuoScreen_use(void)
330 {
331  if (__sync_add_and_fetch(&VuoScreen_useCount, 1) == 1)
332  {
333  // Our process only receives CGDisplay notifications while running in app mode.
334  VuoApp_init(false);
335 
336  CGDisplayRegisterReconfigurationCallback(VuoScreen_reconfigurationCallback, NULL);
337  }
338 }
339 
347 {
348  if (VuoScreen_useCount <= 0)
349  {
350  VUserLog("Error: Unbalanced VuoScreen_use() / _disuse() calls.");
351  return;
352  }
353 
354  if (__sync_sub_and_fetch(&VuoScreen_useCount, 1) == 0)
355  CGDisplayRemoveReconfigurationCallback(VuoScreen_reconfigurationCallback, NULL);
356 }
357 
367 {
368  VuoScreen_callbacks.addTrigger(screens);
369  screens(VuoScreen_getList());
370 }
371 
379 {
380  VuoScreen_callbacks.removeTrigger(screens);
381 }