Merge branch 'genete_master'
[synfig.git] / synfig-osx / launcher / quartz.c
1 /**************************************************************
2  *
3  * Quartz-specific support for the Darwin X Server
4  *
5  **************************************************************/
6 /*
7  * Copyright (c) 2001 Greg Parker and Torrey T. Lyons.
8  * Copyright (c) 2002 Apple Computer, Inc.
9  *                 All Rights Reserved.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
25  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  *
29  * Except as contained in this notice, the name(s) of the above copyright
30  * holders shall not be used in advertising or otherwise to promote the sale,
31  * use or other dealings in this Software without prior written authorization.
32  */
33 /* $XFree86: xc/programs/Xserver/hw/darwin/quartz/quartz.c,v 1.1 2002/03/28 02:21:18 torrey Exp $ */
34
35 #include "quartz.h"
36 #include "darwin.h"
37 #include "quartz-audio.h"
38 #include "quartz-cursor.h"
39 #include "rootless.h"
40 #include "rootless-window.h"
41 #include "pseudoramiX.h"
42 #include "globals.h"
43 #include "dri.h"
44 #define _APPLEWM_SERVER_
45 #include "applewmstr.h"
46 #include "X11Application.h"
47
48 #include "scrnintstr.h"
49 #include "colormapst.h"
50
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <fcntl.h>
54 #include <unistd.h>
55 #include <sys/wait.h>
56
57 #include <AvailabilityMacros.h>
58 #include <CoreGraphics/CoreGraphics.h>
59
60 /* Shared global variables for Quartz modes */
61 int quartzEventWriteFD = -1;
62 int quartzUseSysBeep = 1;
63 int quartzServerVisible = FALSE;
64 int quartzDesiredDepth = -1;
65 int quartzHasRoot = FALSE, quartzEnableRootless = TRUE;
66 int quartzFullscreenDisableHotkeys = TRUE;
67 int noPseudoramiXExtension = FALSE;
68 int quartzXpluginOptions = 0;
69
70 extern char *display;
71
72 static CGDirectDisplayID
73 display_at_index (int index)
74 {
75     CGError err;
76     CGDisplayCount cnt;
77     CGDirectDisplayID dpy[index+1];
78
79     err = CGGetActiveDisplayList (index + 1, dpy, &cnt);
80     if (err == kCGErrorSuccess && (int) cnt == index + 1)
81         return dpy[index];
82     else
83         return kCGNullDirectDisplay;
84 }
85
86 static CGRect
87 display_screen_bounds (CGDirectDisplayID id, Bool remove_menubar)
88 {
89     CGRect frame;
90
91     frame = CGDisplayBounds (id);
92
93     if (remove_menubar && !quartzHasRoot
94         && frame.origin.x == 0 && frame.origin.y == 0)
95     {
96         /* Remove Aqua menubar from display bounds. */
97
98         frame.origin.y += 22;
99         frame.size.height -= 22;
100     }
101
102     return frame;
103 }
104
105 static void
106 addPseudoramiXScreens (int *x, int *y, int *width, int *height)
107 {
108     CGDisplayCount i, total = 16;       /* FIXME: hardcoded maximum */
109     CGRect unionRect = CGRectNull, frame;
110     CGDirectDisplayID screens[total];
111         
112     CGGetActiveDisplayList (total, screens, &total);
113
114     /* Get the union of all screens */
115     for (i = 0; i < total; i++)
116     {
117         CGDirectDisplayID dpy = screens[i];
118
119         /* we can't remove the menubar from the screen - doing so
120            would constrain the pointer to the screen, not allowing it
121            to reach the menubar.. */
122
123         frame = display_screen_bounds (dpy, FALSE);
124         unionRect = CGRectUnion (unionRect, frame);
125     }
126
127     /* Use unionRect as the screen size for the X server. */
128     *x = unionRect.origin.x;
129     *y = unionRect.origin.y;
130     *width = unionRect.size.width;
131     *height = unionRect.size.height;
132
133     /* Tell PseudoramiX about the real screens. */
134     for (i = 0; i < total; i++)
135     {
136         CGDirectDisplayID dpy = screens[i];
137
138         frame = display_screen_bounds (dpy, TRUE);
139
140 #ifdef DEBUG
141         ErrorF("PseudoramiX screen %d added: %dx%d @ (%d,%d).\n", i,
142                (int)frame.size.width, (int)frame.size.height,
143                (int)frame.origin.x, (int)frame.origin.y);
144 #endif
145
146         frame.origin.x -= unionRect.origin.x;
147         frame.origin.y -= unionRect.origin.y;
148
149 #ifdef DEBUG
150         ErrorF("PseudoramiX screen %d placed at X11 coordinate (%d,%d).\n",
151                i, (int)frame.origin.x, (int)frame.origin.y);
152 #endif
153
154         PseudoramiXAddScreen(frame.origin.x, frame.origin.y,
155                              frame.size.width, frame.size.height);
156     }
157 }
158
159 /* Do mode dependent initialization of each screen for Quartz. */
160 Bool
161 QuartzAddScreen (int index, ScreenPtr pScreen)
162 {
163     DarwinFramebufferPtr dfb = SCREEN_PRIV(pScreen);
164
165     /* If no specific depth chosen, look for the depth of the main display.
166        Else if 16bpp specified, use that. Else use 32bpp. */
167
168     dfb->componentCount = 3;
169     dfb->bitsPerComponent = 8;
170     dfb->bitsPerPixel = 32;
171
172     if (quartzDesiredDepth == -1)
173     {
174         dfb->bitsPerComponent = CGDisplayBitsPerSample (kCGDirectMainDisplay);
175         dfb->bitsPerPixel = CGDisplayBitsPerPixel (kCGDirectMainDisplay);
176     }
177     else if (quartzDesiredDepth == 15)
178     {
179         dfb->bitsPerComponent = 5;
180         dfb->bitsPerPixel = 16;
181     }
182     else if (quartzDesiredDepth == 8)
183     {
184         dfb->bitsPerComponent = 8;
185         dfb->bitsPerPixel = 8;
186         dfb->componentCount = 1;
187     }
188
189     if (noPseudoramiXExtension)
190     {
191         CGDirectDisplayID dpy;
192         CGRect frame;
193
194         dpy = display_at_index (index);
195
196         frame = display_screen_bounds (dpy, TRUE);
197
198         dfb->x = frame.origin.x;
199         dfb->y = frame.origin.y;
200         dfb->width =  frame.size.width;
201         dfb->height = frame.size.height;
202     }
203     else
204     {
205         addPseudoramiXScreens (&dfb->x, &dfb->y, &dfb->width, &dfb->height);
206     }
207
208     dfb->colorBitsPerPixel = dfb->bitsPerComponent * dfb->componentCount;
209
210     /* Passing zero width (pitch) makes miCreateScreenResources set the
211        screen pixmap to the framebuffer pointer, i.e. null. We'll take
212        it from there.. */
213     dfb->pitch = 0;
214     dfb->framebuffer = NULL;
215
216     DRIScreenInit (pScreen);
217
218     return TRUE;
219 }
220
221 /* Finalize mode specific setup of each screen. */
222 Bool
223 QuartzSetupScreen (int index, ScreenPtr pScreen)
224 {
225     // do full screen or rootless specific setup
226     if (! RootlessSetupScreen(index, pScreen))
227         return FALSE;
228
229     // setup cursor support
230     if (! QuartzInitCursor(pScreen))
231         return FALSE;
232
233     DRIFinishScreenInit (pScreen);
234
235     return TRUE;
236 }
237
238
239 /* Quartz display initialization. */
240 void
241 QuartzInitOutput (int argc, char **argv)
242 {
243     static int orig_noPanoramiXExtension;
244     int total;
245
246     if (serverGeneration == 1) {
247         orig_noPanoramiXExtension = noPanoramiXExtension;
248         QuartzAudioInit();
249     }
250
251     /* +xinerama option sets noPanoramiXExtension variable */
252     noPseudoramiXExtension = orig_noPanoramiXExtension;
253
254     total = 16;                         /* FIXME: hardcoded maximum */
255     if (total > 0)
256     {
257         CGDirectDisplayID screens[total];
258         CGGetActiveDisplayList (total, screens, &total);
259     }
260
261     if (noPseudoramiXExtension)
262         darwinScreensFound = total;
263     else
264         darwinScreensFound =  1; // only PseudoramiX knows about the rest
265
266     if (!quartzEnableRootless)
267         RootlessHideAllWindows ();
268 }
269
270 /* This function from randr.c */
271 extern char     *ConnectionInfo;
272 static int padlength[4] = {0, 3, 2, 1};
273 static void
274 RREditConnectionInfo (ScreenPtr pScreen)
275 {
276     xConnSetup      *connSetup;
277     char            *vendor;
278     xPixmapFormat   *formats;
279     xWindowRoot     *root;
280     xDepth          *depth;
281     xVisualType     *visual;
282     int             screen = 0;
283     int             d;
284
285     connSetup = (xConnSetup *) ConnectionInfo;
286     vendor = (char *) connSetup + sizeof (xConnSetup);
287     formats = (xPixmapFormat *) ((char *) vendor +
288                                  connSetup->nbytesVendor +
289                                  padlength[connSetup->nbytesVendor & 3]);
290     root = (xWindowRoot *) ((char *) formats +
291                             sizeof (xPixmapFormat) * screenInfo.numPixmapFormats);
292     while (screen != pScreen->myNum)
293     {
294         depth = (xDepth *) ((char *) root + 
295                             sizeof (xWindowRoot));
296         for (d = 0; d < root->nDepths; d++)
297         {
298             visual = (xVisualType *) ((char *) depth +
299                                       sizeof (xDepth));
300             depth = (xDepth *) ((char *) visual +
301                                 depth->nVisuals * sizeof (xVisualType));
302         }
303         root = (xWindowRoot *) ((char *) depth);
304         screen++;
305     }
306     root->pixWidth = pScreen->width;
307     root->pixHeight = pScreen->height;
308     root->mmWidth = pScreen->mmWidth;
309     root->mmHeight = pScreen->mmHeight;
310 }
311
312 static void
313 QuartzUpdateScreens (void)
314 {
315     ScreenPtr pScreen;
316     WindowPtr pRoot;
317     int x, y, width, height, sx, sy;
318     xEvent e;
319
320     if (noPseudoramiXExtension || screenInfo.numScreens != 1)
321     {
322         /* FIXME: if not using Xinerama, we have multiple screens, and
323            to do this properly may need to add or remove screens. Which
324            isn't possible. So don't do anything. Another reason why
325            we default to running with Xinerama. */
326
327         return;
328     }
329
330     pScreen = screenInfo.screens[0];
331
332     PseudoramiXResetScreens ();
333     addPseudoramiXScreens (&x, &y, &width, &height);
334
335     dixScreenOrigins[pScreen->myNum].x = x;
336     dixScreenOrigins[pScreen->myNum].y = y;
337     pScreen->mmWidth = pScreen->mmWidth * ((double) width / pScreen->width);
338     pScreen->mmHeight = pScreen->mmHeight * ((double) height / pScreen->height);
339     pScreen->width = width;
340     pScreen->height = height;
341
342     /* FIXME: should probably do something with RandR here. */
343
344     DarwinAdjustScreenOrigins (&screenInfo);
345     RootlessRepositionWindows (screenInfo.screens[0]);
346     RootlessUpdateScreenPixmap (screenInfo.screens[0]);
347
348     sx = dixScreenOrigins[pScreen->myNum].x + darwinMainScreenX;
349     sy = dixScreenOrigins[pScreen->myNum].y + darwinMainScreenY;
350
351     /* Adjust the root window. */
352
353     pRoot = WindowTable[pScreen->myNum];
354     pScreen->ResizeWindow (pRoot, x - sx, y - sy, width, height, NULL);
355     pScreen->PaintWindowBackground (pRoot, &pRoot->borderClip,  PW_BACKGROUND);
356     QuartzIgnoreNextWarpCursor ();
357     DefineInitialRootWindow (pRoot);
358
359     /* Send an event for the root reconfigure */
360
361     e.u.u.type = ConfigureNotify;
362     e.u.configureNotify.window = pRoot->drawable.id;
363     e.u.configureNotify.aboveSibling = None;
364     e.u.configureNotify.x = x - sx;
365     e.u.configureNotify.y = y - sy;
366     e.u.configureNotify.width = width;
367     e.u.configureNotify.height = height;
368     e.u.configureNotify.borderWidth = wBorderWidth (pRoot);
369     e.u.configureNotify.override = pRoot->overrideRedirect;
370     DeliverEvents (pRoot, &e, 1, NullWindow);
371
372     /* FIXME: what does this do? */
373     RREditConnectionInfo (pScreen);
374 }
375
376 static void
377 do_exec (void (*callback) (void *data), void *data)
378 {
379     /* Do the fork-twice trick to avoid needing to reap zombies */
380
381     int child1, child2 = 0;
382     int status;
383
384     /* we should really try to report errors here.. */
385
386     child1 = fork ();
387
388     switch (child1)
389     {
390     case -1:                            /* error */
391         break;
392
393     case 0:                             /* child1 */
394         child2 = fork ();
395
396         switch (child2)
397         {
398             int max_files, i;
399             char buf[1024], *tem;
400
401         case -1:                        /* error */
402             _exit (1);
403
404         case 0:                         /* child2 */
405             /* close all open files except for standard streams */
406             max_files = sysconf (_SC_OPEN_MAX);
407             for (i = 3; i < max_files; i++)
408                 close (i);
409
410             /* ensure stdin is on /dev/null */
411             close (0);
412             open ("/dev/null", O_RDONLY);
413
414             /* cd $HOME */
415             tem = getenv ("HOME");
416             if (tem != NULL)
417                 chdir (tem);
418
419             /* Setup environment */
420             snprintf (buf, sizeof (buf), ":%s", display);
421             setenv ("DISPLAY", buf, TRUE);
422             tem = getenv ("PATH");
423             if (tem != NULL && tem[0] != NULL)
424                 snprintf (buf, sizeof (buf), "%s:/usr/X11R6/bin", tem);
425             else
426                 snprintf (buf, sizeof (buf), "/bin:/usr/bin:/usr/X11R6/bin");
427             setenv ("PATH", buf, TRUE);
428
429             (*callback) (data);
430
431             _exit (2);
432
433         default:                        /* parent (child1) */
434             _exit (0);
435         }
436         break;
437
438     default:                            /* parent */
439         waitpid (child1, &status, 0);
440     }
441 }
442
443 static void
444 run_client_callback (void *data)
445 {
446     char **argv = data;
447     execvp (argv[0], argv);
448 }
449
450 /* Note that this function is called from both X server and appkit threads */
451 void
452 QuartzRunClient (const char *command)
453 {
454     const char *shell;
455     const char *argv[5];
456
457     shell = getenv ("SHELL");
458     if (shell == NULL)
459         shell = "/bin/bash";
460
461     /* At least [ba]sh, [t]csh and zsh all work with this syntax. We
462        need to use an interactive shell to force it to load the user's
463        environment. Otherwise things like fink don't work at all well.. */
464
465     argv[0] = shell;
466     argv[1] = "-i";
467     argv[2] = "-c";
468     argv[3] = command;
469     argv[4] = NULL;
470
471     do_exec (run_client_callback, argv);
472 }
473
474 static void
475 QuartzSetFullscreen (Bool state)
476 {
477     if (quartzHasRoot == state)
478         return;
479
480     quartzHasRoot = state;
481
482     xp_disable_update ();
483
484     if (!quartzHasRoot && !quartzEnableRootless)
485         RootlessHideAllWindows ();
486
487     RootlessUpdateRooted (quartzHasRoot);
488
489     if (quartzHasRoot && !quartzEnableRootless)
490         RootlessShowAllWindows ();
491
492     /* Only update screen info when something is visible. Avoids the wm
493        moving the windows out from under the menubar when it shouldn't */
494
495     if (quartzHasRoot || quartzEnableRootless)
496         QuartzUpdateScreens ();
497
498     /* Somehow the menubar manages to interfere with our event stream
499        in fullscreen mode, even though it's not visible. */
500
501     X11ApplicationShowHideMenubar (!quartzHasRoot);
502
503     xp_reenable_update ();
504
505 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1030
506     if (quartzFullscreenDisableHotkeys)
507         xp_disable_hot_keys (quartzHasRoot);
508 #endif
509 }
510
511 static void
512 QuartzSetRootless (Bool state)
513 {
514     if (quartzEnableRootless == state)
515         return;
516
517     quartzEnableRootless = state;
518
519     if (!quartzEnableRootless && !quartzHasRoot)
520     {
521         xp_disable_update ();
522         RootlessHideAllWindows ();
523         xp_reenable_update ();
524     }
525     else if (quartzEnableRootless && !quartzHasRoot)
526     {
527         xp_disable_update ();
528         RootlessShowAllWindows ();
529         QuartzUpdateScreens ();
530         xp_reenable_update ();
531     }
532 }
533
534 /* Show the X server on screen. Does nothing if already shown.  Restore the
535    X clip regions the X server cursor state. */
536 static void
537 QuartzShow (void)
538 {
539     int i;
540
541     if (quartzServerVisible)
542         return;
543
544     quartzServerVisible = TRUE;
545
546     for (i = 0; i < screenInfo.numScreens; i++)
547     {
548         if (screenInfo.screens[i])
549             QuartzResumeXCursor(screenInfo.screens[i]);
550     }
551
552     /* FIXME: not sure about this, it may need to have a preference like
553        in XDarwin..? */
554
555     if (!quartzEnableRootless)
556         QuartzSetFullscreen (TRUE);
557 }
558
559 /* Remove the X server display from the screen. Does nothing if already
560    hidden. Set X clip regions to prevent drawing, and restore the Aqua
561    cursor. */
562 static void
563 QuartzHide (void)
564 {
565     int i;
566
567     if (!quartzServerVisible)
568         return;
569
570     for (i = 0; i < screenInfo.numScreens; i++)
571     {
572         if (screenInfo.screens[i])
573             QuartzSuspendXCursor(screenInfo.screens[i]);
574     }
575
576     QuartzSetFullscreen (FALSE);
577
578     quartzServerVisible = FALSE;
579 }
580
581 /* Cleanup before X server shutdown. Release the screen and restore the
582    Aqua cursor. */
583 void
584 QuartzGiveUp (void)
585 {
586     int i;
587
588     for (i = 0; i < screenInfo.numScreens; i++) {
589         if (screenInfo.screens[i]) {
590             QuartzSuspendXCursor(screenInfo.screens[i]);
591         }
592     }
593 }
594
595 int
596 QuartzProcessArgument( int argc, char *argv[], int i )
597 {
598     /* This arg is passed when launched from the Aqua GUI. */
599     if (strncmp (argv[i], "-psn_", 5) == 0)
600     {
601         return 1;
602     }
603
604     if (strcmp (argv[i], "-depth") == 0)
605     {
606         int arg;
607
608         if (i == argc - 1)
609             FatalError ("-depth requires an argument\n");
610
611         arg = atoi (argv[i + 1]);
612         if (arg == 8 || arg == 15 || arg == 24)
613             quartzDesiredDepth = arg;
614         else
615             FatalError ("Only 8, 15 and 24 bit color depths are supported.\n");
616
617         return 2;
618     }
619
620     return 0;
621 }
622
623 void
624 QuartzClientMessage (const xEvent *xe)
625 {
626     switch (xe->u.clientMessage.u.l.type)
627     {
628     case kXquartzControllerNotify:
629         AppleWMSendEvent (AppleWMControllerNotify,
630                           AppleWMControllerNotifyMask,
631                           xe->u.clientMessage.u.l.longs0,
632                           xe->u.clientMessage.u.l.longs1);
633         break;
634
635     case kXquartzPasteboardNotify:
636         AppleWMSendEvent (AppleWMPasteboardNotify,
637                           AppleWMPasteboardNotifyMask,
638                           xe->u.clientMessage.u.l.longs0,
639                           xe->u.clientMessage.u.l.longs1);
640         break;
641
642     case kXquartzActivate:
643         QuartzShow ();
644         AppleWMSendEvent (AppleWMActivationNotify,
645                           AppleWMActivationNotifyMask,
646                           AppleWMIsActive, 0);
647         break;
648
649     case kXquartzDeactivate:
650         AppleWMSendEvent (AppleWMActivationNotify,
651                           AppleWMActivationNotifyMask,
652                           AppleWMIsInactive, 0);
653         QuartzHide ();
654         break;
655
656     case kXquartzDisplayChanged:
657         QuartzUpdateScreens ();
658         break;
659
660     case kXquartzWindowState:
661         RootlessNativeWindowStateChanged (xe->u.clientMessage.u.l.longs0,
662                                           xe->u.clientMessage.u.l.longs1);
663         break;
664
665     case kXquartzWindowMoved:
666         RootlessNativeWindowMoved (xe->u.clientMessage.u.l.longs0);
667         break;
668
669     case kXquartzToggleFullscreen:
670         if (quartzEnableRootless)
671             QuartzSetFullscreen (!quartzHasRoot);
672         else if (quartzHasRoot)
673             QuartzHide ();
674         else
675             QuartzShow ();
676         break;
677
678     case kXquartzSetRootless:
679         QuartzSetRootless (xe->u.clientMessage.u.l.longs0);
680         if (!quartzEnableRootless && !quartzHasRoot)
681             QuartzHide ();
682     }
683 }