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