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