1 /*
2 * Quartz-specific support for the XRandR extension
3 *
4 * Copyright (c) 2001-2004 Greg Parker and Torrey T. Lyons,
5 * 2010 Jan Hauffa.
6 * 2010-2012 Apple Inc.
7 * All Rights Reserved.
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a
10 * copy of this software and associated documentation files (the "Software"),
11 * to deal in the Software without restriction, including without limitation
12 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 * and/or sell copies of the Software, and to permit persons to whom the
14 * Software is furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22 * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
23 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
24 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 * DEALINGS IN THE SOFTWARE.
26 *
27 * Except as contained in this notice, the name(s) of the above copyright
28 * holders shall not be used in advertising or otherwise to promote the sale,
29 * use or other dealings in this Software without prior written authorization.
30 */
31
32 #include "sanitizedCarbon.h"
33
34 #ifdef HAVE_DIX_CONFIG_H
35 #include <dix-config.h>
36 #endif
37
38 #include "quartzRandR.h"
39 #include "quartz.h"
40 #include "darwin.h"
41
42 #include "X11Application.h"
43
44 #include <X11/extensions/randr.h>
45 #include <randrstr.h>
46 #include <IOKit/graphics/IOGraphicsTypes.h>
47
48 /* TODO: UGLY, find a better way!
49 * We want to ignore kXquartzDisplayChanged which are generated by us
50 */
51 static Bool ignore_next_fake_mode_update = FALSE;
52
53 #define FAKE_REFRESH_ROOTLESS 1
54 #define FAKE_REFRESH_FULLSCREEN 2
55
56 #define DEFAULT_REFRESH 60
57 #define kDisplayModeUsableFlags (kDisplayModeValidFlag | kDisplayModeSafeFlag)
58
59 #define CALLBACK_SUCCESS 0
60 #define CALLBACK_CONTINUE 1
61 #define CALLBACK_ERROR -1
62
63 typedef int (*QuartzModeCallback)
64 (ScreenPtr, QuartzModeInfoPtr, void *);
65
66 static void
QuartzRandRGetModeInfo(CGDisplayModeRef modeRef,QuartzModeInfoPtr pMode)67 QuartzRandRGetModeInfo(CGDisplayModeRef modeRef,
68 QuartzModeInfoPtr pMode)
69 {
70 pMode->width = CGDisplayModeGetWidth(modeRef);
71 pMode->height = CGDisplayModeGetHeight(modeRef);
72 pMode->refresh = (int)(CGDisplayModeGetRefreshRate(modeRef) + 0.5);
73 if (pMode->refresh == 0)
74 pMode->refresh = DEFAULT_REFRESH;
75 pMode->ref = NULL;
76 pMode->pSize = NULL;
77 }
78
79 static Bool
QuartzRandRCopyCurrentModeInfo(CGDirectDisplayID screenId,QuartzModeInfoPtr pMode)80 QuartzRandRCopyCurrentModeInfo(CGDirectDisplayID screenId,
81 QuartzModeInfoPtr pMode)
82 {
83 CGDisplayModeRef curModeRef = CGDisplayCopyDisplayMode(screenId);
84 if (!curModeRef)
85 return FALSE;
86
87 QuartzRandRGetModeInfo(curModeRef, pMode);
88 pMode->ref = curModeRef;
89 return TRUE;
90 }
91
92 static Bool
QuartzRandRSetCGMode(CGDirectDisplayID screenId,QuartzModeInfoPtr pMode)93 QuartzRandRSetCGMode(CGDirectDisplayID screenId,
94 QuartzModeInfoPtr pMode)
95 {
96 CGDisplayModeRef modeRef = (CGDisplayModeRef)pMode->ref;
97 if (!modeRef)
98 return FALSE;
99
100 return (CGDisplaySetDisplayMode(screenId, modeRef,
101 NULL) == kCGErrorSuccess);
102 }
103
104 static Bool
QuartzRandREnumerateModes(ScreenPtr pScreen,QuartzModeCallback callback,void * data)105 QuartzRandREnumerateModes(ScreenPtr pScreen,
106 QuartzModeCallback callback,
107 void *data)
108 {
109 Bool retval = FALSE;
110 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
111
112 /* Just an 800x600 fallback if we have no attached heads */
113 if (pQuartzScreen->displayIDs) {
114 CGDisplayModeRef curModeRef, modeRef;
115 CFStringRef curPixelEnc, pixelEnc;
116 CFComparisonResult pixelEncEqual;
117 CFArrayRef modes;
118 QuartzModeInfo modeInfo;
119 int i;
120 CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0];
121
122 curModeRef = CGDisplayCopyDisplayMode(screenId);
123 if (!curModeRef)
124 return FALSE;
125 curPixelEnc = CGDisplayModeCopyPixelEncoding(curModeRef);
126 CGDisplayModeRelease(curModeRef);
127
128 modes = CGDisplayCopyAllDisplayModes(screenId, NULL);
129 if (!modes) {
130 CFRelease(curPixelEnc);
131 return FALSE;
132 }
133 for (i = 0; i < CFArrayGetCount(modes); i++) {
134 int cb;
135 modeRef = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i);
136
137 /* Skip modes that are not usable on the current display or have a
138 different pixel encoding than the current mode. */
139 if ((CGDisplayModeGetIOFlags(modeRef) &
140 kDisplayModeUsableFlags) !=
141 kDisplayModeUsableFlags)
142 continue;
143 pixelEnc = CGDisplayModeCopyPixelEncoding(modeRef);
144 pixelEncEqual = CFStringCompare(pixelEnc, curPixelEnc, 0);
145 CFRelease(pixelEnc);
146 if (pixelEncEqual != kCFCompareEqualTo)
147 continue;
148
149 QuartzRandRGetModeInfo(modeRef, &modeInfo);
150 modeInfo.ref = modeRef;
151 cb = callback(pScreen, &modeInfo, data);
152 if (cb == CALLBACK_CONTINUE) {
153 retval = TRUE;
154 }
155 else if (cb == CALLBACK_SUCCESS) {
156 CFRelease(modes);
157 CFRelease(curPixelEnc);
158 return TRUE;
159 }
160 else if (cb == CALLBACK_ERROR) {
161 CFRelease(modes);
162 CFRelease(curPixelEnc);
163 return FALSE;
164 }
165 }
166
167 CFRelease(modes);
168 CFRelease(curPixelEnc);
169 }
170
171 switch (callback(pScreen, &pQuartzScreen->rootlessMode, data)) {
172 case CALLBACK_SUCCESS:
173 return TRUE;
174
175 case CALLBACK_ERROR:
176 return FALSE;
177
178 case CALLBACK_CONTINUE:
179 retval = TRUE;
180
181 default:
182 break;
183 }
184
185 switch (callback(pScreen, &pQuartzScreen->fullscreenMode, data)) {
186 case CALLBACK_SUCCESS:
187 return TRUE;
188
189 case CALLBACK_ERROR:
190 return FALSE;
191
192 case CALLBACK_CONTINUE:
193 retval = TRUE;
194
195 default:
196 break;
197 }
198
199 return retval;
200 }
201
202 static Bool
QuartzRandRModesEqual(QuartzModeInfoPtr pMode1,QuartzModeInfoPtr pMode2)203 QuartzRandRModesEqual(QuartzModeInfoPtr pMode1,
204 QuartzModeInfoPtr pMode2)
205 {
206 return (pMode1->width == pMode2->width) &&
207 (pMode1->height == pMode2->height) &&
208 (pMode1->refresh == pMode2->refresh);
209 }
210
211 static Bool
QuartzRandRRegisterMode(ScreenPtr pScreen,QuartzModeInfoPtr pMode)212 QuartzRandRRegisterMode(ScreenPtr pScreen,
213 QuartzModeInfoPtr pMode)
214 {
215 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
216 Bool isCurrentMode = QuartzRandRModesEqual(&pQuartzScreen->currentMode,
217 pMode);
218
219 /* TODO: DPI */
220 pMode->pSize =
221 RRRegisterSize(pScreen, pMode->width, pMode->height, pScreen->mmWidth,
222 pScreen->mmHeight);
223 if (pMode->pSize) {
224 //DEBUG_LOG("registering: %d x %d @ %d %s\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh, isCurrentMode ? "*" : "");
225 RRRegisterRate(pScreen, pMode->pSize, pMode->refresh);
226
227 if (isCurrentMode)
228 RRSetCurrentConfig(pScreen, RR_Rotate_0, pMode->refresh,
229 pMode->pSize);
230
231 return TRUE;
232 }
233 return FALSE;
234 }
235
236 static int
QuartzRandRRegisterModeCallback(ScreenPtr pScreen,QuartzModeInfoPtr pMode,void * data __unused)237 QuartzRandRRegisterModeCallback(ScreenPtr pScreen,
238 QuartzModeInfoPtr pMode,
239 void *data __unused)
240 {
241 if (QuartzRandRRegisterMode(pScreen, pMode)) {
242 return CALLBACK_CONTINUE;
243 }
244 else {
245 return CALLBACK_ERROR;
246 }
247 }
248
249 static Bool
QuartzRandRSetMode(ScreenPtr pScreen,QuartzModeInfoPtr pMode,BOOL doRegister)250 QuartzRandRSetMode(ScreenPtr pScreen, QuartzModeInfoPtr pMode,
251 BOOL doRegister)
252 {
253 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
254 Bool captureDisplay =
255 (pMode->refresh != FAKE_REFRESH_FULLSCREEN && pMode->refresh !=
256 FAKE_REFRESH_ROOTLESS);
257 CGDirectDisplayID screenId;
258
259 if (pQuartzScreen->displayIDs == NULL)
260 return FALSE;
261
262 screenId = pQuartzScreen->displayIDs[0];
263 if (XQuartzShieldingWindowLevel == 0 && captureDisplay) {
264 if (!X11ApplicationCanEnterRandR())
265 return FALSE;
266 CGCaptureAllDisplays();
267 XQuartzShieldingWindowLevel = CGShieldingWindowLevel(); // 2147483630
268 DEBUG_LOG("Display captured. ShieldWindowID: %u, Shield level: %d\n",
269 CGShieldingWindowID(screenId), XQuartzShieldingWindowLevel);
270 }
271
272 if (pQuartzScreen->currentMode.ref &&
273 CFEqual(pMode->ref, pQuartzScreen->currentMode.ref)) {
274 DEBUG_LOG("Requested RandR resolution matches current CG mode\n");
275 }
276 if (QuartzRandRSetCGMode(screenId, pMode)) {
277 ignore_next_fake_mode_update = TRUE;
278 }
279 else {
280 DEBUG_LOG("Error while requesting CG resolution change.\n");
281 return FALSE;
282 }
283
284 /* If the client requested the fake rootless mode, switch to rootless.
285 * Otherwise, force fullscreen mode.
286 */
287 QuartzSetRootless(pMode->refresh == FAKE_REFRESH_ROOTLESS);
288 if (pMode->refresh != FAKE_REFRESH_ROOTLESS) {
289 QuartzShowFullscreen(TRUE);
290 }
291
292 if (pQuartzScreen->currentMode.ref)
293 CFRelease(pQuartzScreen->currentMode.ref);
294 pQuartzScreen->currentMode = *pMode;
295 if (pQuartzScreen->currentMode.ref)
296 CFRetain(pQuartzScreen->currentMode.ref);
297
298 if (XQuartzShieldingWindowLevel != 0 && !captureDisplay) {
299 CGReleaseAllDisplays();
300 XQuartzShieldingWindowLevel = 0;
301 }
302
303 return TRUE;
304 }
305
306 static int
QuartzRandRSetModeCallback(ScreenPtr pScreen,QuartzModeInfoPtr pMode,void * data)307 QuartzRandRSetModeCallback(ScreenPtr pScreen,
308 QuartzModeInfoPtr pMode,
309 void *data)
310 {
311 QuartzModeInfoPtr pReqMode = (QuartzModeInfoPtr)data;
312
313 if (!QuartzRandRModesEqual(pMode, pReqMode))
314 return CALLBACK_CONTINUE; /* continue enumeration */
315
316 DEBUG_LOG("Found a match for requested RandR resolution (%dx%d@%d).\n",
317 (int)pMode->width, (int)pMode->height, (int)pMode->refresh);
318
319 if (QuartzRandRSetMode(pScreen, pMode, FALSE))
320 return CALLBACK_SUCCESS;
321 else
322 return CALLBACK_ERROR;
323 }
324
325 static Bool
QuartzRandRGetInfo(ScreenPtr pScreen,Rotation * rotations)326 QuartzRandRGetInfo(ScreenPtr pScreen, Rotation *rotations)
327 {
328 *rotations = RR_Rotate_0; /* TODO: support rotation */
329
330 return QuartzRandREnumerateModes(pScreen, QuartzRandRRegisterModeCallback,
331 NULL);
332 }
333
334 static Bool
QuartzRandRSetConfig(ScreenPtr pScreen,Rotation randr,int rate,RRScreenSizePtr pSize)335 QuartzRandRSetConfig(ScreenPtr pScreen,
336 Rotation randr,
337 int rate,
338 RRScreenSizePtr pSize)
339 {
340 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
341 QuartzModeInfo reqMode;
342
343 reqMode.width = pSize->width;
344 reqMode.height = pSize->height;
345 reqMode.refresh = rate;
346
347 /* Do not switch modes if requested mode is equal to current mode. */
348 if (QuartzRandRModesEqual(&reqMode, &pQuartzScreen->currentMode))
349 return TRUE;
350
351 if (QuartzRandREnumerateModes(pScreen, QuartzRandRSetModeCallback,
352 &reqMode)) {
353 return TRUE;
354 }
355
356 DEBUG_LOG("Unable to find a matching config: %d x %d @ %d\n",
357 (int)reqMode.width, (int)reqMode.height,
358 (int)reqMode.refresh);
359 return FALSE;
360 }
361
362 static Bool
_QuartzRandRUpdateFakeModes(ScreenPtr pScreen)363 _QuartzRandRUpdateFakeModes(ScreenPtr pScreen)
364 {
365 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
366 QuartzModeInfo activeMode;
367
368 if (pQuartzScreen->displayCount > 0) {
369 if (!QuartzRandRCopyCurrentModeInfo(pQuartzScreen->displayIDs[0],
370 &activeMode)) {
371 ErrorF("Unable to determine current display mode.\n");
372 return FALSE;
373 }
374 }
375 else {
376 memset(&activeMode, 0, sizeof(activeMode));
377 activeMode.width = 800;
378 activeMode.height = 600;
379 activeMode.refresh = 60;
380 }
381
382 if (pQuartzScreen->fullscreenMode.ref)
383 CFRelease(pQuartzScreen->fullscreenMode.ref);
384 if (pQuartzScreen->currentMode.ref)
385 CFRelease(pQuartzScreen->currentMode.ref);
386
387 if (pQuartzScreen->displayCount > 1) {
388 activeMode.width = pScreen->width;
389 activeMode.height = pScreen->height;
390 if (XQuartzIsRootless)
391 activeMode.height += aquaMenuBarHeight;
392 }
393
394 pQuartzScreen->fullscreenMode = activeMode;
395 pQuartzScreen->fullscreenMode.refresh = FAKE_REFRESH_FULLSCREEN;
396
397 pQuartzScreen->rootlessMode = activeMode;
398 pQuartzScreen->rootlessMode.refresh = FAKE_REFRESH_ROOTLESS;
399 pQuartzScreen->rootlessMode.height -= aquaMenuBarHeight;
400
401 if (XQuartzIsRootless) {
402 pQuartzScreen->currentMode = pQuartzScreen->rootlessMode;
403 }
404 else {
405 pQuartzScreen->currentMode = pQuartzScreen->fullscreenMode;
406 }
407
408 /* This extra retain is for currentMode's copy.
409 * fullscreen and rootless share a retain.
410 */
411 if (pQuartzScreen->currentMode.ref)
412 CFRetain(pQuartzScreen->currentMode.ref);
413
414 DEBUG_LOG("rootlessMode: %d x %d\n",
415 (int)pQuartzScreen->rootlessMode.width,
416 (int)pQuartzScreen->rootlessMode.height);
417 DEBUG_LOG("fullscreenMode: %d x %d\n",
418 (int)pQuartzScreen->fullscreenMode.width,
419 (int)pQuartzScreen->fullscreenMode.height);
420 DEBUG_LOG("currentMode: %d x %d\n", (int)pQuartzScreen->currentMode.width,
421 (int)pQuartzScreen->currentMode.height);
422
423 return TRUE;
424 }
425
426 Bool
QuartzRandRUpdateFakeModes(BOOL force_update)427 QuartzRandRUpdateFakeModes(BOOL force_update)
428 {
429 ScreenPtr pScreen = screenInfo.screens[0];
430
431 if (ignore_next_fake_mode_update) {
432 DEBUG_LOG(
433 "Ignoring update request caused by RandR resolution change.\n");
434 ignore_next_fake_mode_update = FALSE;
435 return TRUE;
436 }
437
438 if (!_QuartzRandRUpdateFakeModes(pScreen))
439 return FALSE;
440
441 if (force_update)
442 RRGetInfo(pScreen, TRUE);
443
444 return TRUE;
445 }
446
447 Bool
QuartzRandRInit(ScreenPtr pScreen)448 QuartzRandRInit(ScreenPtr pScreen)
449 {
450 rrScrPrivPtr pScrPriv;
451
452 if (!RRScreenInit(pScreen)) return FALSE;
453 if (!_QuartzRandRUpdateFakeModes(pScreen)) return FALSE;
454
455 pScrPriv = rrGetScrPriv(pScreen);
456 pScrPriv->rrGetInfo = QuartzRandRGetInfo;
457 pScrPriv->rrSetConfig = QuartzRandRSetConfig;
458 return TRUE;
459 }
460
461 void
QuartzRandRSetFakeRootless(void)462 QuartzRandRSetFakeRootless(void)
463 {
464 int i;
465
466 DEBUG_LOG("QuartzRandRSetFakeRootless called.\n");
467
468 for (i = 0; i < screenInfo.numScreens; i++) {
469 ScreenPtr pScreen = screenInfo.screens[i];
470 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
471
472 QuartzRandRSetMode(pScreen, &pQuartzScreen->rootlessMode, TRUE);
473 }
474 }
475
476 void
QuartzRandRSetFakeFullscreen(BOOL state)477 QuartzRandRSetFakeFullscreen(BOOL state)
478 {
479 int i;
480
481 DEBUG_LOG("QuartzRandRSetFakeFullscreen called.\n");
482
483 for (i = 0; i < screenInfo.numScreens; i++) {
484 ScreenPtr pScreen = screenInfo.screens[i];
485 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
486
487 QuartzRandRSetMode(pScreen, &pQuartzScreen->fullscreenMode, TRUE);
488 }
489
490 QuartzShowFullscreen(state);
491 }
492
493 /* Toggle fullscreen mode. If "fake" fullscreen is the current mode,
494 * this will just show/hide the X11 windows. If we are in a RandR fullscreen
495 * mode, this will toggles us to the default fake mode and hide windows if
496 * it is fullscreen
497 */
498 void
QuartzRandRToggleFullscreen(void)499 QuartzRandRToggleFullscreen(void)
500 {
501 ScreenPtr pScreen = screenInfo.screens[0];
502 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
503
504 if (pQuartzScreen->currentMode.ref == NULL) {
505 ErrorF(
506 "Ignoring QuartzRandRToggleFullscreen because don't have a current mode set.\n");
507 }
508 else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_ROOTLESS) {
509 ErrorF(
510 "Ignoring QuartzRandRToggleFullscreen because we are in rootless mode.\n");
511 }
512 else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_FULLSCREEN) {
513 /* Legacy fullscreen mode. Hide/Show */
514 QuartzShowFullscreen(!XQuartzFullscreenVisible);
515 }
516 else {
517 /* RandR fullscreen mode. Return to default mode and hide if it is fullscreen. */
518 if (XQuartzRootlessDefault) {
519 QuartzRandRSetFakeRootless();
520 }
521 else {
522 QuartzRandRSetFakeFullscreen(FALSE);
523 }
524 }
525 }
526