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