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