1/*
2 *
3 * Quartz-specific support for the Darwin X Server
4 *
5 * Copyright (c) 2002-2012 Apple Inc. All rights reserved.
6 * Copyright (c) 2001-2004 Greg Parker and Torrey T. Lyons.
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 "inputstr.h"
40#include "quartz.h"
41#include "darwin.h"
42#include "darwinEvents.h"
43#include "pseudoramiX.h"
44#include "extension.h"
45#include "nonsdk_extinit.h"
46#include "glx_extinit.h"
47#define _APPLEWM_SERVER_
48#include "applewmExt.h"
49
50#include "X11Application.h"
51
52#include <X11/extensions/applewmconst.h>
53
54// X headers
55#include "scrnintstr.h"
56#include "windowstr.h"
57#include "colormapst.h"
58#include "globals.h"
59#include "mi.h"
60
61// System headers
62#include <stdlib.h>
63#include <string.h>
64#include <sys/types.h>
65#include <sys/stat.h>
66#include <fcntl.h>
67#include <IOKit/pwr_mgt/IOPMLib.h>
68#include <libkern/OSAtomic.h>
69#include <signal.h>
70
71#include <rootlessCommon.h>
72#include <Xplugin.h>
73
74// These are vended by the Objective-C runtime, but they are unfortunately
75// not available as API in the macOS SDK.  We are following suit with swift
76// and clang in declaring them inline here.  They canot be removed or changed
77// in the OS without major bincompat ramifications.
78//
79// These were added in macOS 10.7.
80void * _Nonnull objc_autoreleasePoolPush(void);
81void objc_autoreleasePoolPop(void * _Nonnull context);
82
83DevPrivateKeyRec quartzScreenKeyRec;
84int aquaMenuBarHeight = 0;
85QuartzModeProcsPtr quartzProcs = NULL;
86const char             *quartzOpenGLBundle = NULL;
87
88/* These are initialized by X11Application with default values set in NSUserDefaults+XQuartzDefaults */
89Bool XQuartzFullscreenDisableHotkeys;
90Bool XQuartzOptionSendsAlt;
91Bool XQuartzEnableKeyEquivalents;
92Bool XQuartzFullscreenMenu;
93Bool XQuartzRootlessDefault;
94
95Bool XQuartzFullscreenVisible = FALSE;
96Bool XQuartzIsRootless = TRUE;
97Bool XQuartzServerVisible = FALSE;
98
99int32_t XQuartzShieldingWindowLevel = 0;
100
101/*
102   ===========================================================================
103
104   Screen functions
105
106   ===========================================================================
107 */
108
109/*
110 * QuartzAddScreen
111 *  Do mode dependent initialization of each screen for Quartz.
112 */
113Bool
114QuartzAddScreen(int index,
115                ScreenPtr pScreen)
116{
117    // The clang static analyzer thinks we leak displayInfo here
118#ifndef __clang_analyzer__
119    // allocate space for private per screen Quartz specific storage
120    QuartzScreenPtr displayInfo = calloc(sizeof(QuartzScreenRec), 1);
121
122    // QUARTZ_PRIV(pScreen) = displayInfo;
123    dixSetPrivate(&pScreen->devPrivates, quartzScreenKey, displayInfo);
124#endif /* __clang_analyzer__ */
125
126    // do Quartz mode specific initialization
127    return quartzProcs->AddScreen(index, pScreen);
128}
129
130/*
131 * QuartzSetupScreen
132 *  Finalize mode specific setup of each screen.
133 */
134Bool
135QuartzSetupScreen(int index,
136                  ScreenPtr pScreen)
137{
138    // do Quartz mode specific setup
139    if (!quartzProcs->SetupScreen(index, pScreen))
140        return FALSE;
141
142    // setup cursor support
143    if (!quartzProcs->InitCursor(pScreen))
144        return FALSE;
145
146#if defined(RANDR)
147    if (!QuartzRandRInit(pScreen)) {
148        DEBUG_LOG("Failed to init RandR extension.\n");
149        return FALSE;
150    }
151#endif
152
153    return TRUE;
154}
155
156/*
157 * QuartzBlockHandler
158 *  Clean out any autoreleased objects.
159 */
160static void
161QuartzBlockHandler(void *blockData, void *pTimeout)
162{
163    static void *poolToken = NULL;
164
165    if (poolToken) {
166        objc_autoreleasePoolPop(poolToken);
167    }
168    poolToken = objc_autoreleasePoolPush();
169}
170
171/*
172 * QuartzWakeupHandler
173 */
174static void
175QuartzWakeupHandler(void *blockData, int result)
176{
177    /* nothing here */
178}
179
180/*
181 * QuartzInitOutput
182 *  Quartz display initialization.
183 */
184void
185QuartzInitOutput(int argc,
186                 char **argv)
187{
188    /* For XQuartz, we want to just use the default signal handler to work better with CrashTracer */
189    signal(SIGSEGV, SIG_DFL);
190    signal(SIGABRT, SIG_DFL);
191    signal(SIGILL, SIG_DFL);
192#ifdef SIGEMT
193    signal(SIGEMT, SIG_DFL);
194#endif
195    signal(SIGFPE, SIG_DFL);
196#ifdef SIGBUS
197    signal(SIGBUS, SIG_DFL);
198#endif
199#ifdef SIGSYS
200    signal(SIGSYS, SIG_DFL);
201#endif
202#ifdef SIGXCPU
203    signal(SIGXCPU, SIG_DFL);
204#endif
205#ifdef SIGXFSZ
206    signal(SIGXFSZ, SIG_DFL);
207#endif
208
209    if (!RegisterBlockAndWakeupHandlers(QuartzBlockHandler,
210                                        QuartzWakeupHandler,
211                                        NULL)) {
212        FatalError("Could not register block and wakeup handlers.");
213    }
214
215    if (!dixRegisterPrivateKey(&quartzScreenKeyRec, PRIVATE_SCREEN, 0))
216        FatalError("Failed to alloc quartz screen private.\n");
217
218    // Do display mode specific initialization
219    quartzProcs->DisplayInit();
220}
221
222/*
223 * QuartzInitInput
224 *  Inform the main thread the X server is ready to handle events.
225 */
226void
227QuartzInitInput(int argc,
228                char **argv)
229{
230    X11ApplicationSetCanQuit(0);
231    X11ApplicationServerReady();
232    // Do final display mode specific initialization before handling events
233    if (quartzProcs->InitInput)
234        quartzProcs->InitInput(argc, argv);
235}
236
237void
238QuartzUpdateScreens(void)
239{
240    ScreenPtr pScreen;
241    WindowPtr pRoot;
242    int x, y, width, height, sx, sy;
243    xEvent e;
244    BoxRec bounds;
245
246    if (noPseudoramiXExtension || screenInfo.numScreens != 1) {
247        /* FIXME: if not using Xinerama, we have multiple screens, and
248           to do this properly may need to add or remove screens. Which
249           isn't possible. So don't do anything. Another reason why
250           we default to running with Xinerama. */
251
252        return;
253    }
254
255    pScreen = screenInfo.screens[0];
256
257    PseudoramiXResetScreens();
258    quartzProcs->AddPseudoramiXScreens(&x, &y, &width, &height, pScreen);
259
260    pScreen->x = x;
261    pScreen->y = y;
262    pScreen->mmWidth = pScreen->mmWidth * ((double)width / pScreen->width);
263    pScreen->mmHeight = pScreen->mmHeight * ((double)height / pScreen->height);
264    pScreen->width = width;
265    pScreen->height = height;
266
267    DarwinAdjustScreenOrigins(&screenInfo);
268
269    /* DarwinAdjustScreenOrigins or UpdateScreen may change pScreen->x/y,
270     * so use it rather than x/y
271     */
272    sx = pScreen->x + darwinMainScreenX;
273    sy = pScreen->y + darwinMainScreenY;
274
275    /* Adjust the root window. */
276    pRoot = pScreen->root;
277    AppleWMSetScreenOrigin(pRoot);
278    pScreen->ResizeWindow(pRoot, x - sx, y - sy, width, height, NULL);
279
280    /* <rdar://problem/7770779> pointer events are clipped to old display region after display reconfiguration
281     * http://xquartz.macosforge.org/trac/ticket/346
282     */
283    bounds.x1 = 0;
284    bounds.x2 = width;
285    bounds.y1 = 0;
286    bounds.y2 = height;
287    pScreen->ConstrainCursor(inputInfo.pointer, pScreen, &bounds);
288    inputInfo.pointer->spriteInfo->sprite->physLimits = bounds;
289    inputInfo.pointer->spriteInfo->sprite->hotLimits = bounds;
290
291    DEBUG_LOG(
292        "Root Window: %dx%d @ (%d, %d) darwinMainScreen (%d, %d) xy (%d, %d) dixScreenOrigins (%d, %d)\n",
293        width, height, x - sx, y - sy, darwinMainScreenX, darwinMainScreenY,
294        x, y,
295        pScreen->x, pScreen->y);
296
297    /* Send an event for the root reconfigure */
298    e.u.u.type = ConfigureNotify;
299    e.u.configureNotify.window = pRoot->drawable.id;
300    e.u.configureNotify.aboveSibling = None;
301    e.u.configureNotify.x = x - sx;
302    e.u.configureNotify.y = y - sy;
303    e.u.configureNotify.width = width;
304    e.u.configureNotify.height = height;
305    e.u.configureNotify.borderWidth = wBorderWidth(pRoot);
306    e.u.configureNotify.override = pRoot->overrideRedirect;
307    DeliverEvents(pRoot, &e, 1, NullWindow);
308
309    quartzProcs->UpdateScreen(pScreen);
310
311    /* PaintWindow needs to be called after RootlessUpdateScreenPixmap (from xprUpdateScreen) */
312    pScreen->PaintWindow(pRoot, &pRoot->borderClip, PW_BACKGROUND);
313
314    /* Tell RandR about the new size, so new connections get the correct info */
315    RRScreenSizeNotify(pScreen);
316}
317
318static void
319pokeActivityCallback(CFRunLoopTimerRef timer, void *info)
320{
321    UpdateSystemActivity(OverallAct);
322}
323
324static void
325QuartzScreenSaver(int state)
326{
327    static CFRunLoopTimerRef pokeActivityTimer = NULL;
328    static CFRunLoopTimerContext pokeActivityContext =
329    { 0, NULL, NULL, NULL, NULL };
330    static OSSpinLock pokeActivitySpinLock = OS_SPINLOCK_INIT;
331
332    OSSpinLockLock(&pokeActivitySpinLock);
333
334    if (state) {
335        if (pokeActivityTimer == NULL)
336            goto QuartzScreenSaverEnd;
337
338        CFRunLoopTimerInvalidate(pokeActivityTimer);
339        CFRelease(pokeActivityTimer);
340        pokeActivityTimer = NULL;
341    }
342    else {
343        if (pokeActivityTimer != NULL)
344            goto QuartzScreenSaverEnd;
345
346        pokeActivityTimer = CFRunLoopTimerCreate(NULL,
347                                                 CFAbsoluteTimeGetCurrent(),
348                                                 30, 0, 0,
349                                                 pokeActivityCallback,
350                                                 &pokeActivityContext);
351        if (pokeActivityTimer == NULL) {
352            ErrorF("Unable to create pokeActivityTimer.\n");
353            goto QuartzScreenSaverEnd;
354        }
355
356        CFRunLoopAddTimer(
357            CFRunLoopGetMain(), pokeActivityTimer, kCFRunLoopCommonModes);
358    }
359QuartzScreenSaverEnd:
360    OSSpinLockUnlock(&pokeActivitySpinLock);
361}
362
363void
364QuartzShowFullscreen(int state)
365{
366    int i;
367
368    DEBUG_LOG("QuartzShowFullscreen: state=%d\n", state);
369
370    if (XQuartzIsRootless) {
371        ErrorF("QuartzShowFullscreen called while in rootless mode.\n");
372        return;
373    }
374
375    QuartzScreenSaver(!state);
376
377    if (XQuartzFullscreenVisible == state)
378        return;
379
380    XQuartzFullscreenVisible = state;
381
382    xp_disable_update();
383
384    if (!XQuartzFullscreenVisible)
385        RootlessHideAllWindows();
386
387    RootlessUpdateRooted(XQuartzFullscreenVisible);
388
389    if (XQuartzFullscreenVisible) {
390        RootlessShowAllWindows();
391        for (i = 0; i < screenInfo.numScreens; i++) {
392            ScreenPtr pScreen = screenInfo.screens[i];
393            RootlessRepositionWindows(pScreen);
394            // JH: I don't think this is necessary, but keeping it here as a reminder
395            //RootlessUpdateScreenPixmap(pScreen);
396        }
397    }
398
399    /* Somehow the menubar manages to interfere with our event stream
400     * in fullscreen mode, even though it's not visible.
401     */
402    X11ApplicationShowHideMenubar(!XQuartzFullscreenVisible);
403
404    xp_reenable_update();
405
406    if (XQuartzFullscreenDisableHotkeys)
407        xp_disable_hot_keys(XQuartzFullscreenVisible);
408}
409
410void
411QuartzSetRootless(Bool state)
412{
413    DEBUG_LOG("QuartzSetRootless state=%d\n", state);
414
415    if (XQuartzIsRootless == state)
416        return;
417
418    if (state)
419        QuartzShowFullscreen(FALSE);
420
421    XQuartzIsRootless = state;
422
423    xp_disable_update();
424
425    /* When in rootless, the menubar is not part of the screen, so we need to update our screens on toggle */
426    QuartzUpdateScreens();
427
428    if (XQuartzIsRootless) {
429        RootlessShowAllWindows();
430    }
431    else {
432        RootlessHideAllWindows();
433    }
434
435    X11ApplicationShowHideMenubar(TRUE);
436
437    xp_reenable_update();
438
439    xp_disable_hot_keys(FALSE);
440}
441
442/*
443 * QuartzShow
444 *  Show the X server on screen. Does nothing if already shown.
445 *  Calls mode specific screen resume to restore the X clip regions
446 *  (if needed) and the X server cursor state.
447 */
448void
449QuartzShow(void)
450{
451    int i;
452
453    if (XQuartzServerVisible)
454        return;
455
456    XQuartzServerVisible = TRUE;
457    for (i = 0; i < screenInfo.numScreens; i++) {
458        if (screenInfo.screens[i]) {
459            quartzProcs->ResumeScreen(screenInfo.screens[i]);
460        }
461    }
462
463    if (!XQuartzIsRootless)
464        QuartzShowFullscreen(TRUE);
465}
466
467/*
468 * QuartzHide
469 *  Remove the X server display from the screen. Does nothing if already
470 *  hidden. Calls mode specific screen suspend to set X clip regions to
471 *  prevent drawing (if needed) and restore the Aqua cursor.
472 */
473void
474QuartzHide(void)
475{
476    int i;
477
478    if (XQuartzServerVisible) {
479        for (i = 0; i < screenInfo.numScreens; i++) {
480            if (screenInfo.screens[i]) {
481                quartzProcs->SuspendScreen(screenInfo.screens[i]);
482            }
483        }
484    }
485
486    if (!XQuartzIsRootless)
487        QuartzShowFullscreen(FALSE);
488    XQuartzServerVisible = FALSE;
489}
490
491/*
492 * QuartzSetRootClip
493 *  Enable or disable rendering to the X screen.
494 */
495void
496QuartzSetRootClip(int mode)
497{
498    int i;
499
500    if (!XQuartzServerVisible)
501        return;
502
503    for (i = 0; i < screenInfo.numScreens; i++) {
504        if (screenInfo.screens[i]) {
505            SetRootClip(screenInfo.screens[i], mode);
506        }
507    }
508}
509
510/*
511 * QuartzSpaceChanged
512 *  Unmap offscreen windows, map onscreen windows
513 */
514void
515QuartzSpaceChanged(uint32_t space_id)
516{
517    /* Do something special here, so we don't depend on quartz-wm for spaces to work... */
518    DEBUG_LOG("Space Changed (%u) ... do something interesting...\n",
519              space_id);
520}
521
522/*
523 * QuartzCopyDisplayIDs
524 *  Associate an X11 screen with one or more CoreGraphics display IDs by copying
525 *  the list into a private array. Free the previously copied array, if present.
526 */
527void
528QuartzCopyDisplayIDs(ScreenPtr pScreen,
529                     int displayCount, CGDirectDisplayID *displayIDs)
530{
531    QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen);
532
533    free(pQuartzScreen->displayIDs);
534    if (displayCount) {
535        size_t size = displayCount * sizeof(CGDirectDisplayID);
536        pQuartzScreen->displayIDs = malloc(size);
537        memcpy(pQuartzScreen->displayIDs, displayIDs, size);
538    }
539    else {
540        pQuartzScreen->displayIDs = NULL;
541    }
542    pQuartzScreen->displayCount = displayCount;
543}
544
545void
546NSBeep(void);
547void
548DDXRingBell(int volume,              // volume is % of max
549            int pitch,               // pitch is Hz
550            int duration)            // duration is milliseconds
551{
552    if (volume)
553        NSBeep();
554}
555