- fallback to non-sRGB visuals if the context creation failed (GLX-only)
[freeglut] / src / x11 / fg_window_x11.c
1 /*
2  * fg_window_x11.c
3  *
4  * Window management methods for X11
5  *
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>
8  * Copied for Platform code by Evan Felix <karcaw at gmail.com>
9  * Creation date: Thur Feb 2 2012
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
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
24  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27  */
28
29 #define FREEGLUT_BUILDING_LIB
30 #include <GL/freeglut.h>
31 #include <limits.h>     /* LONG_MAX */
32 #include <unistd.h>     /* usleep, gethostname, getpid */
33 #include <sys/types.h>  /* pid_t */
34 #include "../fg_internal.h"
35
36 #ifdef EGL_VERSION_1_0
37 #include "egl/fg_window_egl.h"
38 #define fghCreateNewContext fghCreateNewContextEGL
39 #else
40 #include "x11/fg_window_x11_glx.h"
41 #endif
42
43 #ifndef HOST_NAME_MAX
44 #define HOST_NAME_MAX   255
45 #endif
46
47 /* Motif window hints, only define needed ones */
48 typedef struct
49 {
50     unsigned long flags;
51     unsigned long functions;
52     unsigned long decorations;
53     long input_mode;
54     unsigned long status;
55 } MotifWmHints;
56 #define MWM_HINTS_DECORATIONS         (1L << 1)
57 #define MWM_DECOR_BORDER              (1L << 1)
58
59 static int fghResizeFullscrToggle(void)
60 {
61     XWindowAttributes attributes;
62     SFG_Window *win = fgStructure.CurrentWindow;
63
64     if(glutGet(GLUT_FULL_SCREEN)) {
65         /* restore original window size */
66         fgStructure.CurrentWindow->State.WorkMask = GLUT_SIZE_WORK;
67         fgStructure.CurrentWindow->State.DesiredWidth  = win->State.pWState.OldWidth;
68         fgStructure.CurrentWindow->State.DesiredHeight = win->State.pWState.OldHeight;
69
70     } else {
71         fgStructure.CurrentWindow->State.pWState.OldWidth  = win->State.Width;
72         fgStructure.CurrentWindow->State.pWState.OldHeight = win->State.Height;
73
74         /* resize the window to cover the entire screen */
75         XGetWindowAttributes(fgDisplay.pDisplay.Display,
76                 fgStructure.CurrentWindow->Window.Handle,
77                 &attributes);
78         
79         /*
80          * The "x" and "y" members of "attributes" are the window's coordinates
81          * relative to its parent, i.e. to the decoration window.
82          */
83         XMoveResizeWindow(fgDisplay.pDisplay.Display,
84                 fgStructure.CurrentWindow->Window.Handle,
85                 -attributes.x,
86                 -attributes.y,
87                 fgDisplay.ScreenWidth,
88                 fgDisplay.ScreenHeight);
89     }
90     return 0;
91 }
92
93 #define _NET_WM_STATE_TOGGLE    2
94 static int fghEwmhFullscrToggle(void)
95 {
96     XEvent xev;
97     long evmask = SubstructureRedirectMask | SubstructureNotifyMask;
98
99     if(!fgDisplay.pDisplay.State || !fgDisplay.pDisplay.StateFullScreen) {
100         return -1;
101     }
102
103     xev.type = ClientMessage;
104     xev.xclient.window = fgStructure.CurrentWindow->Window.Handle;
105     xev.xclient.message_type = fgDisplay.pDisplay.State;
106     xev.xclient.format = 32;
107     xev.xclient.data.l[0] = _NET_WM_STATE_TOGGLE;
108     xev.xclient.data.l[1] = fgDisplay.pDisplay.StateFullScreen;
109     xev.xclient.data.l[2] = 0;  /* no second property to toggle */
110     xev.xclient.data.l[3] = 1;  /* source indication: application */
111     xev.xclient.data.l[4] = 0;  /* unused */
112
113     if(!XSendEvent(fgDisplay.pDisplay.Display, fgDisplay.pDisplay.RootWindow, 0, evmask, &xev)) {
114         return -1;
115     }
116     return 0;
117 }
118
119 static int fghToggleFullscreen(void)
120 {
121     /* first try the EWMH (_NET_WM_STATE) method ... */
122     if(fghEwmhFullscrToggle() != -1) {
123         return 0;
124     }
125
126     /* fall back to resizing the window */
127     if(fghResizeFullscrToggle() != -1) {
128         return 0;
129     }
130     return -1;
131 }
132
133 static Bool fghWindowIsVisible( Display *display, XEvent *event, XPointer arg)
134 {
135     Window window = (Window)arg;
136     return (event->type == MapNotify) && (event->xmap.window == window);
137 }
138
139 /*
140  * Opens a window. Requires a SFG_Window object created and attached
141  * to the freeglut structure. OpenGL context is created here.
142  */
143 void fgPlatformOpenWindow( SFG_Window* window, const char* title,
144                            GLboolean positionUse, int x, int y,
145                            GLboolean sizeUse, int w, int h,
146                            GLboolean gameMode, GLboolean isSubWindow )
147 {
148     XVisualInfo * visualInfo = NULL;
149     XSetWindowAttributes winAttr;
150     XTextProperty textProperty;
151     XSizeHints sizeHints;
152     XWMHints wmHints;
153     XEvent eventReturnBuffer; /* return buffer required for a call */
154     unsigned long mask;
155     unsigned int current_DisplayMode = fgState.DisplayMode ;
156     XEvent fakeEvent = {0};
157
158     /* Save the display mode if we are creating a menu window */
159     if( window->IsMenu && ( ! fgStructure.MenuContext ) )
160         fgState.DisplayMode = GLUT_DOUBLE | GLUT_RGB ;
161
162 #ifdef EGL_VERSION_1_0
163 #define WINDOW_CONFIG window->Window.pContext.egl.Config
164 #else
165 #define WINDOW_CONFIG window->Window.pContext.FBConfig
166 #endif
167     fghChooseConfig(&WINDOW_CONFIG);
168
169     if( window->IsMenu && ( ! fgStructure.MenuContext ) )
170         fgState.DisplayMode = current_DisplayMode ;
171
172     if( ! WINDOW_CONFIG )
173     {
174         /*
175          * The "fghChooseConfig" returned a null meaning that the visual
176          * context is not available.
177          * Try a couple of variations to see if they will work.
178          */
179 #ifndef EGL_VERSION_1_0
180         if( !( fgState.DisplayMode & GLUT_DOUBLE ) )
181         {
182             fgState.DisplayMode |= GLUT_DOUBLE ;
183             fghChooseConfig(&WINDOW_CONFIG);
184             fgState.DisplayMode &= ~GLUT_DOUBLE;
185
186             if( WINDOW_CONFIG ) goto done_retry;
187         }
188 #endif
189
190         if( fgState.DisplayMode & GLUT_MULTISAMPLE )
191         {
192             fgState.DisplayMode &= ~GLUT_MULTISAMPLE ;
193             fghChooseConfig(&WINDOW_CONFIG);
194             fgState.DisplayMode |= GLUT_MULTISAMPLE;
195
196             if( WINDOW_CONFIG ) goto done_retry;
197         }
198
199         if( fgState.DisplayMode & GLUT_SRGB )
200         {
201             fgState.DisplayMode &= ~GLUT_SRGB ;
202             fghChooseConfig(&WINDOW_CONFIG);
203             fgState.DisplayMode |= GLUT_SRGB;
204
205             if( WINDOW_CONFIG ) goto done_retry;
206         }
207     }
208 done_retry:
209
210     FREEGLUT_INTERNAL_ERROR_EXIT( WINDOW_CONFIG != NULL,
211                                   "FBConfig with necessary capabilities not found", "fgOpenWindow" );
212
213     /*  Get the X visual.  */
214 #ifdef EGL_VERSION_1_0
215     EGLint vid = 0;
216     XVisualInfo visualTemplate;
217     int num_visuals;
218     if (!eglGetConfigAttrib(fgDisplay.pDisplay.egl.Display, window->Window.pContext.egl.Config, EGL_NATIVE_VISUAL_ID, &vid))
219       fgError("eglGetConfigAttrib(EGL_NATIVE_VISUAL_ID) failed");
220     visualTemplate.visualid = vid;
221     visualInfo = XGetVisualInfo(fgDisplay.pDisplay.Display, VisualIDMask, &visualTemplate, &num_visuals);
222 #else
223     visualInfo = glXGetVisualFromFBConfig( fgDisplay.pDisplay.Display,
224                                            window->Window.pContext.FBConfig );
225 #endif
226
227     FREEGLUT_INTERNAL_ERROR_EXIT( visualInfo != NULL,
228                                   "visualInfo could not be retrieved from FBConfig", "fgOpenWindow" );
229
230     /*
231      * XXX HINT: the masks should be updated when adding/removing callbacks.
232      * XXX       This might speed up message processing. Is that true?
233      * XXX
234      * XXX A: Not appreciably, but it WILL make it easier to debug.
235      * XXX    Try tracing old GLUT and try tracing freeglut.  Old GLUT
236      * XXX    turns off events that it doesn't need and is a whole lot
237      * XXX    more pleasant to trace.  (Think mouse-motion!  Tons of
238      * XXX    ``bonus'' GUI events stream in.)
239      */
240     winAttr.event_mask        =
241         StructureNotifyMask | SubstructureNotifyMask | ExposureMask |
242         ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask |
243         VisibilityChangeMask | EnterWindowMask | LeaveWindowMask |
244         PointerMotionMask | ButtonMotionMask;
245     winAttr.background_pixmap = None;
246     winAttr.background_pixel  = 0;
247     winAttr.border_pixel      = 0;
248
249     winAttr.colormap = XCreateColormap(
250         fgDisplay.pDisplay.Display, fgDisplay.pDisplay.RootWindow,
251         visualInfo->visual, AllocNone
252     );
253
254     mask = CWBackPixmap | CWBorderPixel | CWColormap | CWEventMask;
255
256     if( window->IsMenu || ( gameMode == GL_TRUE ) )
257     {
258         winAttr.override_redirect = True;
259         mask |= CWOverrideRedirect;
260     }
261
262     if( ! positionUse )
263         x = y = -1; /* default window position */
264     if( ! sizeUse )
265         w = h = 300; /* default window size */
266
267     window->Window.Handle = XCreateWindow(
268         fgDisplay.pDisplay.Display,
269         window->Parent == NULL ? fgDisplay.pDisplay.RootWindow :
270         window->Parent->Window.Handle,
271         x, y, w, h, 0,
272         visualInfo->depth, InputOutput,
273         visualInfo->visual, mask,
274         &winAttr
275     );
276
277     /* Fake configure event to force viewport setup
278      * even with no window manager.
279      */
280     fakeEvent.xconfigure.type = ConfigureNotify;
281     fakeEvent.xconfigure.display = fgDisplay.pDisplay.Display;
282     fakeEvent.xconfigure.window = window->Window.Handle;
283     fakeEvent.xconfigure.x = x;
284     fakeEvent.xconfigure.y = y;
285     fakeEvent.xconfigure.width = w;
286     fakeEvent.xconfigure.height = h;
287     XPutBackEvent(fgDisplay.pDisplay.Display, &fakeEvent);
288
289     /*
290      * The GLX context creation, possibly trying the direct context rendering
291      *  or else use the current context if the user has so specified
292      */
293
294     if( window->IsMenu )
295     {
296         /*
297          * If there isn't already an OpenGL rendering context for menu
298          * windows, make one
299          */
300         if( !fgStructure.MenuContext )
301         {
302             fgStructure.MenuContext =
303                 (SFG_MenuContext *)malloc( sizeof(SFG_MenuContext) );
304             fgStructure.MenuContext->MContext = fghCreateNewContext( window );
305         }
306
307         /* window->Window.Context = fgStructure.MenuContext->MContext; */
308         window->Window.Context = fghCreateNewContext( window );
309     }
310     else if( fgState.UseCurrentContext )
311     {
312
313 #ifdef EGL_VERSION_1_0
314         window->Window.Context = eglGetCurrentContext( );
315 #else
316         window->Window.Context = glXGetCurrentContext( );
317 #endif
318
319         if( ! window->Window.Context )
320             window->Window.Context = fghCreateNewContext( window );
321     }
322     else
323         window->Window.Context = fghCreateNewContext( window );
324
325 #if !defined( __FreeBSD__ ) && !defined( __NetBSD__ ) && !defined(EGL_VERSION_1_0)
326     if(  !glXIsDirect( fgDisplay.pDisplay.Display, window->Window.Context ) )
327     {
328       if( fgState.DirectContext == GLUT_FORCE_DIRECT_CONTEXT )
329         fgError( "Unable to force direct context rendering for window '%s'",
330                  title );
331     }
332 #endif
333
334     sizeHints.flags = 0;
335     if ( positionUse )
336         sizeHints.flags |= USPosition;
337     if ( sizeUse )
338         sizeHints.flags |= USSize;
339
340     /*
341      * Fill in the size hints values now (the x, y, width and height
342      * settings are obsolete, are there any more WMs that support them?)
343      * Unless the X servers actually stop supporting these, we should
344      * continue to fill them in.  It is *not* our place to tell the user
345      * that they should replace a window manager that they like, and which
346      * works, just because *we* think that it's not "modern" enough.
347      */
348     sizeHints.x      = x;
349     sizeHints.y      = y;
350     sizeHints.width  = w;
351     sizeHints.height = h;
352
353     wmHints.flags = StateHint;
354     wmHints.initial_state = fgState.ForceIconic ? IconicState : NormalState;
355     /* Prepare the window and iconified window names... */
356     XStringListToTextProperty( (char **) &title, 1, &textProperty );
357
358     XSetWMProperties(
359         fgDisplay.pDisplay.Display,
360         window->Window.Handle,
361         &textProperty,
362         &textProperty,
363         0,
364         0,
365         &sizeHints,
366         &wmHints,
367         NULL
368     );
369     XFree( textProperty.value );
370
371     XSetWMProtocols( fgDisplay.pDisplay.Display, window->Window.Handle,
372                      &fgDisplay.pDisplay.DeleteWindow, 1 );
373
374     if (!isSubWindow && !window->IsMenu &&
375         ((fgState.DisplayMode & GLUT_BORDERLESS) || (fgState.DisplayMode & GLUT_CAPTIONLESS)))
376     {
377         /* _MOTIF_WM_HINTS is replaced by _NET_WM_WINDOW_TYPE, but that property does not allow precise
378          * control over the visual style of the window, which is what we are trying to achieve here.
379          * Stick with Motif and hope for the best... */
380         MotifWmHints hints = {0};
381         hints.flags = MWM_HINTS_DECORATIONS;
382         hints.decorations = (fgState.DisplayMode & GLUT_CAPTIONLESS) ? MWM_DECOR_BORDER:0;
383
384         XChangeProperty(fgDisplay.pDisplay.Display, window->Window.Handle,
385                         XInternAtom( fgDisplay.pDisplay.Display, "_MOTIF_WM_HINTS", False ),
386                         XInternAtom( fgDisplay.pDisplay.Display, "_MOTIF_WM_HINTS", False ), 32,
387                         PropModeReplace,
388                         (unsigned char*) &hints,
389                         sizeof(MotifWmHints) / sizeof(long));
390     }
391
392
393     if (fgDisplay.pDisplay.NetWMSupported
394         && fgDisplay.pDisplay.NetWMPid != None
395         && fgDisplay.pDisplay.ClientMachine != None)
396     {
397       char hostname[HOST_NAME_MAX];
398       pid_t pid = getpid();
399
400       if (pid > 0 && gethostname(hostname, sizeof(hostname)) > -1)
401       {
402         hostname[sizeof(hostname) - 1] = '\0';
403
404         XChangeProperty(
405             fgDisplay.pDisplay.Display,
406             window->Window.Handle,
407             fgDisplay.pDisplay.NetWMPid,
408             XA_CARDINAL,
409             32,
410             PropModeReplace,
411             (unsigned char *) &pid,
412             1
413         );
414
415         XChangeProperty(
416             fgDisplay.pDisplay.Display,
417             window->Window.Handle,
418             fgDisplay.pDisplay.ClientMachine,
419             XA_STRING,
420             8,
421             PropModeReplace,
422             (unsigned char *) hostname,
423             strlen(hostname)
424         );
425       }
426     }
427
428 #ifdef EGL_VERSION_1_0
429     fghPlatformOpenWindowEGL(window);
430 #else
431     glXMakeContextCurrent(
432         fgDisplay.pDisplay.Display,
433         window->Window.Handle,
434         window->Window.Handle,
435         window->Window.Context
436     );
437 #endif
438
439     /* register extension events _before_ window is mapped */
440     #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H
441        fgRegisterDevices( fgDisplay.pDisplay.Display, &(window->Window.Handle) );
442     #endif
443
444     if (!window->IsMenu)    /* Don't show window after creation if its a menu */
445     {
446         XMapWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
447         window->State.Visible = GL_TRUE;
448     }
449
450     XFree(visualInfo);
451
452     /* wait till window visible */
453     if( !isSubWindow && !window->IsMenu)
454         XPeekIfEvent( fgDisplay.pDisplay.Display, &eventReturnBuffer, &fghWindowIsVisible, (XPointer)(window->Window.Handle) );
455 #undef WINDOW_CONFIG
456 }
457
458
459 /*
460  * Request a window resize
461  */
462 void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
463 {
464     XResizeWindow( fgDisplay.pDisplay.Display, window->Window.Handle,
465                    width, height );
466     XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
467 }
468
469
470 /*
471  * Closes a window, destroying the frame and OpenGL context
472  */
473 void fgPlatformCloseWindow( SFG_Window* window )
474 {
475 #ifdef EGL_VERSION_1_0
476     fghPlatformCloseWindowEGL(window);
477 #else
478     if( window->Window.Context )
479         glXDestroyContext( fgDisplay.pDisplay.Display, window->Window.Context );
480     window->Window.pContext.FBConfig = NULL;
481 #endif
482
483     if( window->Window.Handle ) {
484         XDestroyWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
485     }
486     /* XFlush( fgDisplay.pDisplay.Display ); */ /* XXX Shouldn't need this */
487 }
488
489
490 /*
491  * This function makes the specified window visible
492  */
493 void fgPlatformShowWindow( SFG_Window *window )
494 {
495     XMapWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
496     XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
497 }
498
499 /*
500  * This function hides the specified window
501  */
502 void fgPlatformHideWindow( SFG_Window *window )
503 {
504     if( window->Parent == NULL )
505         XWithdrawWindow( fgDisplay.pDisplay.Display,
506                          window->Window.Handle,
507                          fgDisplay.pDisplay.Screen );
508     else
509         XUnmapWindow( fgDisplay.pDisplay.Display,
510                       window->Window.Handle );
511     XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
512 }
513
514 /*
515  * Iconify the specified window (top-level windows only)
516  */
517 void fgPlatformIconifyWindow( SFG_Window *window )
518 {
519     XIconifyWindow( fgDisplay.pDisplay.Display, window->Window.Handle,
520                     fgDisplay.pDisplay.Screen );
521     XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
522
523     fgStructure.CurrentWindow->State.Visible   = GL_FALSE;
524 }
525
526 /*
527  * Set the current window's title
528  */
529 void fgPlatformGlutSetWindowTitle( const char* title )
530 {
531     XTextProperty text;
532
533     text.value = (unsigned char *) title;
534     text.encoding = XA_STRING;
535     text.format = 8;
536     text.nitems = strlen( title );
537
538     XSetWMName(
539         fgDisplay.pDisplay.Display,
540         fgStructure.CurrentWindow->Window.Handle,
541         &text
542     );
543
544     XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
545 }
546
547 /*
548  * Set the current window's iconified title
549  */
550 void fgPlatformGlutSetIconTitle( const char* title )
551 {
552     XTextProperty text;
553
554     text.value = (unsigned char *) title;
555     text.encoding = XA_STRING;
556     text.format = 8;
557     text.nitems = strlen( title );
558
559     XSetWMIconName(
560         fgDisplay.pDisplay.Display,
561         fgStructure.CurrentWindow->Window.Handle,
562         &text
563     );
564
565     XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
566 }
567
568 /*
569  * Change the specified window's position
570  */
571 void fgPlatformPositionWindow( SFG_Window *window, int x, int y )
572 {
573     XMoveWindow( fgDisplay.pDisplay.Display, window->Window.Handle,
574                  x, y );
575     XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
576 }
577
578 /*
579  * Lowers the specified window (by Z order change)
580  */
581 void fgPlatformPushWindow( SFG_Window *window )
582 {
583     XLowerWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
584 }
585
586 /*
587  * Raises the specified window (by Z order change)
588  */
589 void fgPlatformPopWindow( SFG_Window *window )
590 {
591     XRaiseWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
592 }
593
594 /*
595  * Toggle the window's full screen state.
596  */
597 void fgPlatformFullScreenToggle( SFG_Window *win )
598 {
599     if(fghToggleFullscreen() != -1) {
600         win->State.IsFullscreen = !win->State.IsFullscreen;
601     }
602 }
603