1 // Copyright (C) 2008  Tim Moore
2 //
3 // This program is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU General Public License as
5 // published by the Free Software Foundation; either version 2 of the
6 // License, or (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 
17 #include "config.h"
18 
19 #include "WindowBuilder.hxx"
20 #include "WindowSystemAdapter.hxx"
21 #include <Main/fg_props.hxx>
22 #include <osg/Version>
23 
24 #include <GUI/MessageBox.hxx>
25 
26 #include <sstream>
27 #include <limits>
28 
29 #if defined(SG_MAC)
30     #include <osgViewer/api/Cocoa/GraphicsWindowCocoa>
31 #endif
32 
33 #if defined(HAVE_QT)
34     #include "GraphicsWindowQt5.hxx"
35 #endif
36 
37 
38 using namespace std;
39 using namespace osg;
40 
41 namespace flightgear
42 {
makeName(const string & prefix,int num)43 string makeName(const string& prefix, int num)
44 {
45     stringstream stream;
46     stream << prefix << num;
47     return stream.str();
48 }
49 
50 ref_ptr<WindowBuilder> WindowBuilder::windowBuilder;
51 
52 const string WindowBuilder::defaultWindowName("FlightGear");
53 
54 // default to true (historical behaviour), we will clear the flag if
55 // we run another GUI.
56 bool WindowBuilder::poseAsStandaloneApp = true;
57 
initWindowBuilder(bool stencil)58 void WindowBuilder::initWindowBuilder(bool stencil)
59 {
60     windowBuilder = new WindowBuilder(stencil);
61 }
62 
WindowBuilder(bool stencil)63 WindowBuilder::WindowBuilder(bool stencil) : defaultCounter(0)
64 {
65 #if defined (HAVE_QT)
66     usingQtGraphicsWindow = fgGetBool("/sim/rendering/graphics-window-qt", false);
67 #endif
68     makeDefaultTraits(stencil);
69 }
70 
makeDefaultTraits(bool stencil)71 void WindowBuilder::makeDefaultTraits(bool stencil)
72 {
73     GraphicsContext::WindowingSystemInterface* wsi
74         = osg::GraphicsContext::getWindowingSystemInterface();
75 #if defined(HAVE_QT) && OSG_VERSION_GREATER_THAN(3, 5, 9)
76     if (usingQtGraphicsWindow) {
77         // use the correct WSI for OpenSceneGraph >= 3.6
78         wsi = osg::GraphicsContext::getWindowingSystemInterface("FlightGearQt5");
79     }
80 #endif
81 
82 
83     defaultTraits = new osg::GraphicsContext::Traits;
84     auto traits = defaultTraits.get();
85 
86     traits->readDISPLAY();
87     if (traits->displayNum < 0)
88         traits->displayNum = 0;
89     if (traits->screenNum < 0)
90         traits->screenNum = 0;
91 
92     int bpp = fgGetInt("/sim/rendering/bits-per-pixel");
93     int cbits = (bpp <= 16) ?  5 :  8;
94     int zbits = (bpp <= 16) ? 16 : 24;
95     traits->red = traits->green = traits->blue = cbits;
96     traits->depth = zbits;
97 
98     if (stencil)
99         traits->stencil = 8;
100 
101     traits->doubleBuffer = true;
102     traits->mipMapGeneration = true;
103     traits->windowName = "FlightGear";
104     // XXX should check per window too.
105     traits->sampleBuffers = fgGetInt("/sim/rendering/multi-sample-buffers", traits->sampleBuffers);
106     traits->samples = fgGetInt("/sim/rendering/multi-samples", traits->samples);
107     traits->vsync = fgGetBool("/sim/rendering/vsync-enable", traits->vsync);
108 
109     const bool wantFullscreen = fgGetBool("/sim/startup/fullscreen");
110     if (usingQtGraphicsWindow) {
111 #if defined(HAVE_QT)
112         // fullscreen is handled by Qt natively
113         // we will check and set fullscreen mode when building
114         // the window instance
115         auto data = new GraphicsWindowQt5::WindowData;
116         data->createFullscreen = wantFullscreen;
117         data->isPrimaryWindow = true;
118 
119 #if OSG_VERSION_GREATER_THAN(3, 5, 9)
120         traits->windowingSystemPreference = "FlightGearQt5";
121 #endif
122         traits->inheritedWindowData = data;
123         traits->windowDecoration = true;
124         traits->supportsResize = true;
125         traits->width = fgGetInt("/sim/startup/xsize");
126         traits->height = fgGetInt("/sim/startup/ysize");
127 
128         // these are marker values to tell GraphicsWindowQt5 to use default x/y
129         traits->x = std::numeric_limits<int>::max();
130         traits->y = std::numeric_limits<int>::max();
131 #else
132         SG_LOG(SG_VIEW,SG_ALERT,"requested Qt GraphicsWindow in non-Qt build");
133 #endif
134     } else {
135         unsigned screenwidth = 0;
136         unsigned screenheight = 0;
137         // this is a deprecated method, should be screen-aware.
138         wsi->getScreenResolution(*traits, screenwidth, screenheight);
139 
140         // handle fullscreen manually
141         traits->windowDecoration = !wantFullscreen;
142         if (!traits->windowDecoration) {
143             // fullscreen
144             traits->supportsResize = false;
145             traits->width = screenwidth;
146             traits->height = screenheight;
147             SG_LOG(SG_VIEW,SG_DEBUG,"Using full screen size for window: " << screenwidth << " x " << screenheight);
148         } else {
149             // window
150             int w = fgGetInt("/sim/startup/xsize");
151             int h = fgGetInt("/sim/startup/ysize");
152             traits->supportsResize = true;
153             traits->width = w;
154             traits->height = h;
155             if ((w>0)&&(h>0))
156             {
157                 traits->x = ((unsigned)w>screenwidth) ? 0 : (screenwidth-w)/3;
158                 traits->y = ((unsigned)h>screenheight) ? 0 : (screenheight-h)/3;
159             }
160             SG_LOG(SG_VIEW,SG_DEBUG,"Using initial window size: " << w << " x " << h);
161         }
162     }
163 }
164 
165 } // of namespace flightgear
166 
167 namespace
168 {
169 // Helper functions that set a value based on a property if it exists,
170 // returning 1 if the value was set.
171 
setFromProperty(string & place,const SGPropertyNode * node,const char * name)172 inline int setFromProperty(string& place, const SGPropertyNode* node,
173                             const char* name)
174 {
175     const SGPropertyNode* valNode = node->getNode(name);
176     if (valNode) {
177         place = valNode->getStringValue();
178         return 1;
179     }
180     return 0;
181 }
182 
setFromProperty(int & place,const SGPropertyNode * node,const char * name)183 inline int setFromProperty(int& place, const SGPropertyNode* node,
184                             const char* name)
185 {
186     const SGPropertyNode* valNode = node->getNode(name);
187     if (valNode) {
188         place = valNode->getIntValue();
189         return 1;
190     }
191     return 0;
192 }
193 
setFromProperty(bool & place,const SGPropertyNode * node,const char * name)194 inline int setFromProperty(bool& place, const SGPropertyNode* node,
195                             const char* name)
196 {
197     const SGPropertyNode* valNode = node->getNode(name);
198     if (valNode) {
199         place = valNode->getBoolValue();
200         return 1;
201     }
202     return 0;
203 }
204 }
205 
206 namespace flightgear
207 {
208 
setFullscreenTraits(const SGPropertyNode * winNode,GraphicsContext::Traits * traits)209 void WindowBuilder::setFullscreenTraits(const SGPropertyNode* winNode, GraphicsContext::Traits* traits)
210 {
211     const SGPropertyNode* orrNode = winNode->getNode("overrideRedirect");
212     bool overrideRedirect = orrNode && orrNode->getBoolValue();
213     traits->overrideRedirect = overrideRedirect;
214 
215 #if defined(HAVE_QT)
216     if (usingQtGraphicsWindow) {
217         auto data = new GraphicsWindowQt5::WindowData;
218         data->createFullscreen = true;
219         traits->inheritedWindowData = data;
220         traits->windowDecoration = winNode->getBoolValue("decoration");
221     } else
222 #endif
223     // this codepath is mandatory on non-Qt builds
224     {
225         traits->windowDecoration = false;
226 
227         unsigned int width = 0;
228         unsigned int height = 0;
229         auto wsi = osg::GraphicsContext::getWindowingSystemInterface();
230         wsi->getScreenResolution(*traits, width, height);
231         traits->width = width;
232         traits->height = height;
233         traits->supportsResize = false;
234         traits->x = 0;
235         traits->y = 0;
236     }
237 }
238 
setWindowedTraits(const SGPropertyNode * winNode,GraphicsContext::Traits * traits)239 bool WindowBuilder::setWindowedTraits(const SGPropertyNode* winNode, GraphicsContext::Traits* traits)
240 {
241     bool customTraits = false;
242 #if defined(HAVE_QT)
243     if (usingQtGraphicsWindow) {
244         if (winNode->hasValue("fullscreen")) {
245             auto data = new GraphicsWindowQt5::WindowData;
246             data->createFullscreen = false;
247             traits->inheritedWindowData = data;
248             customTraits = true;
249         }
250         customTraits |= setFromProperty(traits->windowDecoration, winNode, "decoration");
251         customTraits |= setFromProperty(traits->width, winNode, "width");
252         customTraits |= setFromProperty(traits->height, winNode, "height");
253     } else
254 #endif
255     {
256         int resizable = 0;
257         const SGPropertyNode* fullscreenNode = winNode->getNode("fullscreen");
258         if (fullscreenNode && !fullscreenNode->getBoolValue())
259         {
260             traits->windowDecoration = true;
261             resizable = 1;
262         }
263         resizable |= setFromProperty(traits->windowDecoration, winNode, "decoration");
264         resizable |= setFromProperty(traits->width, winNode, "width");
265         resizable |= setFromProperty(traits->height, winNode, "height");
266         if (resizable) {
267             traits->supportsResize = true;
268             customTraits = true;
269         }
270     }
271 
272     return customTraits;
273 }
274 
setMacPoseAsStandaloneApp(GraphicsContext::Traits * traits)275 void WindowBuilder::setMacPoseAsStandaloneApp(GraphicsContext::Traits* traits)
276 {
277 #if defined(SG_MAC)
278     if (!usingQtGraphicsWindow) {
279         // this logic is unecessary if using a Qt window, since everything
280         // plays together nicely
281         int flags = osgViewer::GraphicsWindowCocoa::WindowData::CheckForEvents;
282 
283         // avoid both QApplication and OSG::CocoaViewer doing single-application
284         // init (Apple menu, making front process, etc)
285         if (poseAsStandaloneApp) {
286             flags |= osgViewer::GraphicsWindowCocoa::WindowData::PoseAsStandaloneApp;
287         }
288         traits->inheritedWindowData = new osgViewer::GraphicsWindowCocoa::WindowData(flags);
289     }
290 #endif
291 }
292 
buildWindow(const SGPropertyNode * winNode)293 GraphicsWindow* WindowBuilder::buildWindow(const SGPropertyNode* winNode)
294 {
295     WindowSystemAdapter* wsa = WindowSystemAdapter::getWSA();
296     string windowName;
297     if (winNode->hasChild("window-name"))
298         windowName = winNode->getStringValue("window-name");
299     else if (winNode->hasChild("name"))
300         windowName = winNode->getStringValue("name");
301     GraphicsWindow* result = 0;
302     if (!windowName.empty()) {
303         // look for an existing window and return that
304         result = wsa->findWindow(windowName);
305         if (result)
306             return result;
307     }
308     auto traits = new GraphicsContext::Traits(*defaultTraits);
309     int traitsSet = setFromProperty(traits->hostName, winNode, "host-name");
310     traitsSet |= setFromProperty(traits->displayNum, winNode, "display");
311     traitsSet |= setFromProperty(traits->screenNum, winNode, "screen");
312 
313     const SGPropertyNode* fullscreenNode = winNode->getNode("fullscreen");
314     if (fullscreenNode && fullscreenNode->getBoolValue()) {
315         setFullscreenTraits(winNode, traits);
316         traitsSet = 1;
317     } else {
318         traitsSet |= setWindowedTraits(winNode, traits);
319     }
320     traitsSet |= setFromProperty(traits->x, winNode, "x");
321     traitsSet |= setFromProperty(traits->y, winNode, "y");
322     if (!windowName.empty() && windowName != traits->windowName) {
323         traits->windowName = windowName;
324         traitsSet = 1;
325     } else if (traitsSet) {
326         traits->windowName = makeName("FlightGear", defaultCounter++);
327     }
328 
329     setMacPoseAsStandaloneApp(traits);
330 
331     bool drawGUI = false;
332     traitsSet |= setFromProperty(drawGUI, winNode, "gui");
333     if (traitsSet) {
334 #if defined (HAVE_QT)
335         if (usingQtGraphicsWindow) {
336             // this assumes the user only sets the 'gui' flag on one window, not ideal
337             auto data = static_cast<GraphicsWindowQt5::WindowData*>(traits->inheritedWindowData.get());
338             data->isPrimaryWindow = drawGUI;
339         }
340 #endif
341 
342         GraphicsContext* gc = GraphicsContext::createGraphicsContext(traits);
343         if (gc) {
344             GraphicsWindow* window = WindowSystemAdapter::getWSA()
345                 ->registerWindow(gc, traits->windowName);
346             if (drawGUI)
347                 window->flags |= GraphicsWindow::GUI;
348             return window;
349         } else {
350             return 0;
351         }
352     } else {
353         // XXX What if the window has no traits, but does have a name?
354         // We should create a "default window" registered with that name.
355         return getDefaultWindow();
356     }
357 }
358 
getDefaultWindow()359 GraphicsWindow* WindowBuilder::getDefaultWindow()
360 {
361     GraphicsWindow* defaultWindow
362         = WindowSystemAdapter::getWSA()->findWindow(defaultWindowName);
363     if (defaultWindow)
364         return defaultWindow;
365 
366     // create if it, if necessary
367     GraphicsContext::Traits* traits
368         = new GraphicsContext::Traits(*defaultTraits);
369     traits->windowName = "FlightGear";
370 
371     setMacPoseAsStandaloneApp(traits);
372 
373     // this may be the point, where we discover OpenGL is broken on the
374     // system.
375     GraphicsContext* gc = GraphicsContext::createGraphicsContext(traits);
376     if (!gc) {
377         flightgear::fatalMessageBoxThenExit("Unable to create window",
378                                             "FlightGear was unable to create a window supporting 3D rendering (OpenGL). "
379                                             "This is normally due to outdated graphics drivers, please check if updates are available. ",
380                                             "Depending on your OS and graphics chipset, updates might come from AMD, nVidia or Intel.");
381         return nullptr; // unreachable anyway
382     }
383 
384     defaultWindow = WindowSystemAdapter::getWSA()->registerWindow(gc, defaultWindowName);
385     return defaultWindow;
386 
387 }
388 
setPoseAsStandaloneApp(bool b)389 void WindowBuilder::setPoseAsStandaloneApp(bool b)
390 {
391     poseAsStandaloneApp = b;
392 }
393 
394 } // of namespace flightgear
395