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 */
48typedef 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
59static 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
94static 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
119static 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
133static 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 */
143void 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 long event_mask;
158
159 /* Save the display mode if we are creating a menu window */
160 if( window->IsMenu && ( ! fgStructure.MenuContext ) )
161 fgState.DisplayMode = GLUT_DOUBLE | GLUT_RGB ;
162
163#ifdef EGL_VERSION_1_0
164#define WINDOW_CONFIG window->Window.pContext.egl.Config
165#else
166#define WINDOW_CONFIG window->Window.pContext.FBConfig
167#endif
168 fghChooseConfig(&WINDOW_CONFIG);
169
170 if( window->IsMenu && ( ! fgStructure.MenuContext ) )
171 fgState.DisplayMode = current_DisplayMode ;
172
173 if( ! WINDOW_CONFIG )
174 {
175 /*
176 * The "fghChooseConfig" returned a null meaning that the visual
177 * context is not available.
178 * Try a couple of variations to see if they will work.
179 */
180#ifndef EGL_VERSION_1_0
181 if( !( fgState.DisplayMode & GLUT_DOUBLE ) )
182 {
183 fgState.DisplayMode |= GLUT_DOUBLE ;
184 fghChooseConfig(&WINDOW_CONFIG);
185 fgState.DisplayMode &= ~GLUT_DOUBLE;
186 }
187#endif
188
189 if( fgState.DisplayMode & GLUT_MULTISAMPLE )
190 {
191 fgState.DisplayMode &= ~GLUT_MULTISAMPLE ;
192 fghChooseConfig(&WINDOW_CONFIG);
193 fgState.DisplayMode |= GLUT_MULTISAMPLE;
194 }
195 }
196
197 FREEGLUT_INTERNAL_ERROR_EXIT( WINDOW_CONFIG != NULL,
198 "FBConfig with necessary capabilities not found", "fgOpenWindow" );
199
200 /* Get the X visual. */
201#ifdef EGL_VERSION_1_0
202 EGLint vid = 0;
203 XVisualInfo visualTemplate;
204 int num_visuals;
205 if (!eglGetConfigAttrib(fgDisplay.pDisplay.egl.Display, window->Window.pContext.egl.Config, EGL_NATIVE_VISUAL_ID, &vid))
206 fgError("eglGetConfigAttrib(EGL_NATIVE_VISUAL_ID) failed");
207 visualTemplate.visualid = vid;
208 visualInfo = XGetVisualInfo(fgDisplay.pDisplay.Display, VisualIDMask, &visualTemplate, &num_visuals);
209#else
210 visualInfo = glXGetVisualFromFBConfig( fgDisplay.pDisplay.Display,
211 window->Window.pContext.FBConfig );
212#endif
213
214 FREEGLUT_INTERNAL_ERROR_EXIT( visualInfo != NULL,
215 "visualInfo could not be retrieved from FBConfig", "fgOpenWindow" );
216
217 /*
218 * XXX HINT: the masks should be updated when adding/removing callbacks.
219 * XXX This might speed up message processing. Is that true?
220 * XXX
221 * XXX A: Not appreciably, but it WILL make it easier to debug.
222 * XXX Try tracing old GLUT and try tracing freeglut. Old GLUT
223 * XXX turns off events that it doesn't need and is a whole lot
224 * XXX more pleasant to trace. (Think mouse-motion! Tons of
225 * XXX ``bonus'' GUI events stream in.)
226 */
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;
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 event_mask =
260 StructureNotifyMask | SubstructureNotifyMask | ExposureMask |
261 ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask |
262 VisibilityChangeMask | EnterWindowMask | LeaveWindowMask |
263 PointerMotionMask | ButtonMotionMask;
264
265 /* Create input context */
266 window->Window.pContext.IC = NULL;
267 if (fgDisplay.pDisplay.IM)
268 {
269 long im_event_mask;
270 window->Window.pContext.IC =
271 XCreateIC(fgDisplay.pDisplay.IM,
272 XNInputStyle, fgDisplay.pDisplay.InputStyle,
273 XNClientWindow, window->Window.Handle,
274 XNFocusWindow, window->Window.Handle,
275 NULL);
276 XGetICValues(window->Window.pContext.IC, XNFilterEvents, &im_event_mask, NULL);
277 event_mask |= im_event_mask;
278 XSetICFocus(window->Window.pContext.IC);
279 }
280 XSelectInput(fgDisplay.pDisplay.Display, window->Window.Handle, event_mask);
281
282 /* Fake configure event to force viewport setup
283 * even with no window manager.
284 */
285 fakeEvent.xconfigure.type = ConfigureNotify;
286 fakeEvent.xconfigure.display = fgDisplay.pDisplay.Display;
287 fakeEvent.xconfigure.window = window->Window.Handle;
288 fakeEvent.xconfigure.x = x;
289 fakeEvent.xconfigure.y = y;
290 fakeEvent.xconfigure.width = w;
291 fakeEvent.xconfigure.height = h;
292 XPutBackEvent(fgDisplay.pDisplay.Display, &fakeEvent);
293
294 /*
295 * The GLX context creation, possibly trying the direct context rendering
296 * or else use the current context if the user has so specified
297 */
298
299 if( window->IsMenu )
300 {
301 /*
302 * If there isn't already an OpenGL rendering context for menu
303 * windows, make one
304 */
305 if( !fgStructure.MenuContext )
306 {
307 fgStructure.MenuContext =
308 (SFG_MenuContext *)malloc( sizeof(SFG_MenuContext) );
309 fgStructure.MenuContext->MContext = fghCreateNewContext( window );
310 }
311
312 /* window->Window.Context = fgStructure.MenuContext->MContext; */
313 window->Window.Context = fghCreateNewContext( window );
314 }
315 else if( fgState.UseCurrentContext )
316 {
317
318#ifdef EGL_VERSION_1_0
319 window->Window.Context = eglGetCurrentContext( );
320#else
321 window->Window.Context = glXGetCurrentContext( );
322#endif
323
324 if( ! window->Window.Context )
325 window->Window.Context = fghCreateNewContext( window );
326 }
327 else
328 window->Window.Context = fghCreateNewContext( window );
329
330#if !defined( __FreeBSD__ ) && !defined( __NetBSD__ ) && !defined(EGL_VERSION_1_0)
331 if( !glXIsDirect( fgDisplay.pDisplay.Display, window->Window.Context ) )
332 {
333 if( fgState.DirectContext == GLUT_FORCE_DIRECT_CONTEXT )
334 fgError( "Unable to force direct context rendering for window '%s'",
335 title );
336 }
337#endif
338
339 sizeHints.flags = 0;
340 if ( positionUse )
341 sizeHints.flags |= USPosition;
342 if ( sizeUse )
343 sizeHints.flags |= USSize;
344
345 /*
346 * Fill in the size hints values now (the x, y, width and height
347 * settings are obsolete, are there any more WMs that support them?)
348 * Unless the X servers actually stop supporting these, we should
349 * continue to fill them in. It is *not* our place to tell the user
350 * that they should replace a window manager that they like, and which
351 * works, just because *we* think that it's not "modern" enough.
352 */
353 sizeHints.x = x;
354 sizeHints.y = y;
355 sizeHints.width = w;
356 sizeHints.height = h;
357
358 wmHints.flags = StateHint;
359 wmHints.initial_state = fgState.ForceIconic ? IconicState : NormalState;
360 /* Prepare the window and iconified window names... */
361 XStringListToTextProperty( (char **) &title, 1, &textProperty );
362
363 XSetWMProperties(
364 fgDisplay.pDisplay.Display,
365 window->Window.Handle,
366 &textProperty,
367 &textProperty,
368 0,
369 0,
370 &sizeHints,
371 &wmHints,
372 NULL
373 );
374 XFree( textProperty.value );
375
376 XSetWMProtocols( fgDisplay.pDisplay.Display, window->Window.Handle,
377 &fgDisplay.pDisplay.DeleteWindow, 1 );
378
379 if (!isSubWindow && !window->IsMenu &&
380 ((fgState.DisplayMode & GLUT_BORDERLESS) || (fgState.DisplayMode & GLUT_CAPTIONLESS)))
381 {
382 /* _MOTIF_WM_HINTS is replaced by _NET_WM_WINDOW_TYPE, but that property does not allow precise
383 * control over the visual style of the window, which is what we are trying to achieve here.
384 * Stick with Motif and hope for the best... */
385 MotifWmHints hints = {0};
386 hints.flags = MWM_HINTS_DECORATIONS;
387 hints.decorations = (fgState.DisplayMode & GLUT_CAPTIONLESS) ? MWM_DECOR_BORDER:0;
388
389 XChangeProperty(fgDisplay.pDisplay.Display, window->Window.Handle,
390 XInternAtom( fgDisplay.pDisplay.Display, "_MOTIF_WM_HINTS", False ),
391 XInternAtom( fgDisplay.pDisplay.Display, "_MOTIF_WM_HINTS", False ), 32,
392 PropModeReplace,
393 (unsigned char*) &hints,
394 sizeof(MotifWmHints) / sizeof(long));
395 }
396
397
398 if (fgDisplay.pDisplay.NetWMSupported
399 && fgDisplay.pDisplay.NetWMPid != None
400 && fgDisplay.pDisplay.ClientMachine != None)
401 {
402 char hostname[HOST_NAME_MAX];
403 pid_t pid = getpid();
404
405 if (pid > 0 && gethostname(hostname, sizeof(hostname)) > -1)
406 {
407 hostname[sizeof(hostname) - 1] = '\0';
408
409 XChangeProperty(
410 fgDisplay.pDisplay.Display,
411 window->Window.Handle,
412 fgDisplay.pDisplay.NetWMPid,
413 XA_CARDINAL,
414 32,
415 PropModeReplace,
416 (unsigned char *) &pid,
417 1
418 );
419
420 XChangeProperty(
421 fgDisplay.pDisplay.Display,
422 window->Window.Handle,
423 fgDisplay.pDisplay.ClientMachine,
424 XA_STRING,
425 8,
426 PropModeReplace,
427 (unsigned char *) hostname,
428 strlen(hostname)
429 );
430 }
431 }
432
433#ifdef EGL_VERSION_1_0
434 fghPlatformOpenWindowEGL(window);
435#else
436 glXMakeContextCurrent(
437 fgDisplay.pDisplay.Display,
438 window->Window.Handle,
439 window->Window.Handle,
440 window->Window.Context
441 );
442#endif
443
444 /* register extension events _before_ window is mapped */
445 #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H
446 fgRegisterDevices( fgDisplay.pDisplay.Display, &(window->Window.Handle) );
447 #endif
448
449 if (!window->IsMenu) /* Don't show window after creation if its a menu */
450 {
451 XMapWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
452 window->State.Visible = GL_TRUE;
453 }
454
455 XFree(visualInfo);
456
457 /* wait till window visible */
458 if( !isSubWindow && !window->IsMenu)
459 XPeekIfEvent( fgDisplay.pDisplay.Display, &eventReturnBuffer, &fghWindowIsVisible, (XPointer)(window->Window.Handle) );
460#undef WINDOW_CONFIG
461}
462
463
464/*
465 * Request a window resize
466 */
467void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height )
468{
469 XResizeWindow( fgDisplay.pDisplay.Display, window->Window.Handle,
470 width, height );
471 XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
472}
473
474
475/*
476 * Closes a window, destroying the frame and OpenGL context
477 */
478void fgPlatformCloseWindow( SFG_Window* window )
479{
480#ifdef EGL_VERSION_1_0
481 fghPlatformCloseWindowEGL(window);
482#else
483 if( window->Window.Context )
484 glXDestroyContext( fgDisplay.pDisplay.Display, window->Window.Context );
485 window->Window.pContext.FBConfig = NULL;
486#endif
487
488 if (window->Window.pContext.IC) {
489 XDestroyIC(window->Window.pContext.IC);
490 }
491
492 if( window->Window.Handle ) {
493 XDestroyWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
494 }
495 /* XFlush( fgDisplay.pDisplay.Display ); */ /* XXX Shouldn't need this */
496}
497
498
499/*
500 * This function makes the specified window visible
501 */
502void fgPlatformShowWindow( SFG_Window *window )
503{
504 XMapWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
505 XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
506}
507
508/*
509 * This function hides the specified window
510 */
511void fgPlatformHideWindow( SFG_Window *window )
512{
513 if( window->Parent == NULL )
514 XWithdrawWindow( fgDisplay.pDisplay.Display,
515 window->Window.Handle,
516 fgDisplay.pDisplay.Screen );
517 else
518 XUnmapWindow( fgDisplay.pDisplay.Display,
519 window->Window.Handle );
520 XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
521}
522
523/*
524 * Iconify the specified window (top-level windows only)
525 */
526void fgPlatformIconifyWindow( SFG_Window *window )
527{
528 XIconifyWindow( fgDisplay.pDisplay.Display, window->Window.Handle,
529 fgDisplay.pDisplay.Screen );
530 XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
531
532 fgStructure.CurrentWindow->State.Visible = GL_FALSE;
533}
534
535/*
536 * Set the current window's title
537 */
538void fgPlatformGlutSetWindowTitle( const char* title )
539{
540 XTextProperty text;
541
542 text.value = (unsigned char *) title;
543 text.encoding = XA_STRING;
544 text.format = 8;
545 text.nitems = strlen( title );
546
547 XSetWMName(
548 fgDisplay.pDisplay.Display,
549 fgStructure.CurrentWindow->Window.Handle,
550 &text
551 );
552
553 XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
554}
555
556/*
557 * Set the current window's iconified title
558 */
559void fgPlatformGlutSetIconTitle( const char* title )
560{
561 XTextProperty text;
562
563 text.value = (unsigned char *) title;
564 text.encoding = XA_STRING;
565 text.format = 8;
566 text.nitems = strlen( title );
567
568 XSetWMIconName(
569 fgDisplay.pDisplay.Display,
570 fgStructure.CurrentWindow->Window.Handle,
571 &text
572 );
573
574 XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
575}
576
577/*
578 * Change the specified window's position
579 */
580void fgPlatformPositionWindow( SFG_Window *window, int x, int y )
581{
582 XMoveWindow( fgDisplay.pDisplay.Display, window->Window.Handle,
583 x, y );
584 XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */
585}
586
587/*
588 * Lowers the specified window (by Z order change)
589 */
590void fgPlatformPushWindow( SFG_Window *window )
591{
592 XLowerWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
593}
594
595/*
596 * Raises the specified window (by Z order change)
597 */
598void fgPlatformPopWindow( SFG_Window *window )
599{
600 XRaiseWindow( fgDisplay.pDisplay.Display, window->Window.Handle );
601}
602
603/*
604 * Toggle the window's full screen state.
605 */
606void fgPlatformFullScreenToggle( SFG_Window *win )
607{
608 if(fghToggleFullscreen() != -1) {
609 win->State.IsFullscreen = !win->State.IsFullscreen;
610 }
611}
612
613