1 /* bzflag
2 * Copyright (c) 1993-2021 Tim Riker
3 *
4 * This package is free software; you can redistribute it and/or
5 * modify it under the terms of the license found in the file
6 * named COPYING that should have accompanied this file.
7 *
8 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12
13 // Own include
14 #include "SDL2Window.h"
15
16 // Common includes
17 #include "OpenGLGState.h"
18 #include "TimeKeeper.h"
19
20 #ifdef _WIN32
21 HWND SDLWindow::hwnd = NULL;
22 #endif
23
SDLWindow(const SDLDisplay * _display,SDLVisual *)24 SDLWindow::SDLWindow(const SDLDisplay* _display, SDLVisual*)
25 : BzfWindow(_display), hasGamma(true), origGamma(-1.0f), lastGamma(1.0f),
26 windowId(NULL), glContext(NULL), canGrabMouse(true), fullScreen(false),
27 vsync(false), base_width(640), base_height(480), min_width(-1), min_height(-1)
28 {
29 }
30
~SDLWindow()31 SDLWindow::~SDLWindow()
32 {
33 // Restore the original gamma when we exit the client
34 setGamma(origGamma);
35
36 if (windowId != NULL)
37 SDL_DestroyWindow(windowId);
38 }
39
setTitle(const char * _title)40 void SDLWindow::setTitle(const char *_title)
41 {
42 title = _title;
43 if (windowId)
44 SDL_SetWindowTitle(windowId, title.c_str());
45 }
46
setFullscreen(bool on)47 void SDLWindow::setFullscreen(bool on)
48 {
49 fullScreen = on;
50 }
51
iconify(void)52 void SDLWindow::iconify(void)
53 {
54 SDL_MinimizeWindow(windowId);
55 }
56
57
disableConfineToMotionbox()58 void SDLWindow::disableConfineToMotionbox()
59 {
60 #ifndef _WIN32
61 SDL_SetWindowGrab(windowId, SDL_FALSE);
62 #else
63 ClipCursor(NULL);
64 #endif
65 }
66
67
confineToMotionbox(int x1,int y1,int x2,int y2)68 void SDLWindow::confineToMotionbox(int x1, int y1, int x2, int y2)
69 {
70 #ifdef __APPLE__
71 // Work around an issue in macOS that caused mouse confinement to persist
72 // after minimizing. It seems that mouse/keyboard events are sent to the
73 // game even when minimized, until another other application is clicked.
74 // Moving the mouse during the 5 second pause countdown would trigger a
75 // call to confineToMotionbox and would continue to trap the mouse.
76 if (SDL_GetWindowFlags(windowId) & SDL_WINDOW_MINIMIZED)
77 return;
78 #endif
79
80 #ifndef _WIN32
81 if (! SDL_GetWindowGrab(windowId))
82 SDL_SetWindowGrab(windowId, SDL_TRUE);
83
84 BzfWindow::confineToMotionbox(x1, y1, x2, y2);
85 #else
86 int posx, posy;
87 SDL_GetWindowPosition(windowId, &posx, &posy);
88
89 // Store the boundary positions as rectangle
90 RECT rect;
91 rect.top = y1 + posy;
92 rect.left = x1 + posx;
93 rect.bottom = y2 + posy;
94 rect.right = x2 + posx;
95
96 // Restrict cursor to that rectangle
97 ClipCursor(&rect);
98 #endif
99 }
100
101
warpMouse(int _x,int _y)102 void SDLWindow::warpMouse(int _x, int _y)
103 {
104 SDL_WarpMouseInWindow(windowId, _x, _y);
105 }
106
getMouse(int & _x,int & _y) const107 void SDLWindow::getMouse(int& _x, int& _y) const
108 {
109 SDL_GetMouseState(&_x, &_y);
110 }
111
setSize(int _width,int _height)112 void SDLWindow::setSize(int _width, int _height)
113 {
114 // workaround for two issues on Linux, where resizing by dragging the window corner causes glitching, and where
115 // iconifying or switching applications while using a scaled fullscreen resolution causes the non-fullscreen
116 // window resolution to assume the fullscreen resolution
117 #ifdef __linux__
118 if(!fullScreen)
119 {
120 base_width = _width;
121 base_height = _height;
122 }
123 #else
124 base_width = _width;
125 base_height = _height;
126 if (!fullScreen && windowId)
127 SDL_SetWindowSize(windowId, base_width, base_height);
128 #endif // __linux__
129 }
130
getSize(int & width,int & height) const131 void SDLWindow::getSize(int& width, int& height) const
132 {
133 if (fullScreen)
134 const_cast<SDLDisplay *>(static_cast<const SDLDisplay *>(getDisplay()))->getWindowSize(width, height);
135 else
136 {
137 width = base_width;
138 height = base_height;
139 }
140 }
141
setGamma(float gamma)142 void SDLWindow::setGamma(float gamma)
143 {
144 lastGamma = gamma;
145 int result = SDL_SetWindowBrightness(windowId, gamma);
146 if (result == -1)
147 {
148 printf("Could not set Gamma: %s.\n", SDL_GetError());
149 hasGamma = false;
150 }
151 }
152
getGamma() const153 float SDLWindow::getGamma() const
154 {
155 return SDL_GetWindowBrightness(windowId);
156 }
157
hasGammaControl() const158 bool SDLWindow::hasGammaControl() const
159 {
160 return hasGamma;
161 }
162
swapBuffers()163 void SDLWindow::swapBuffers()
164 {
165 SDL_GL_SwapWindow(windowId);
166
167 // workaround for SDL 2 bug on mac where an application window obstructed
168 // by another window will not honor a vsync restriction
169 // bug report: https://bugzilla.libsdl.org/show_bug.cgi?id=2998
170 // TODO: Remove this workaround when/if SDL2 includes their own workaround.
171 #ifdef __APPLE__
172 if (! SDL_GL_GetSwapInterval())
173 return;
174
175 int maxRunawayFPS = 65;
176 SDL_DisplayMode desktopDisplayMode;
177 if (SDL_GetDesktopDisplayMode(0, &desktopDisplayMode) == 0)
178 maxRunawayFPS = desktopDisplayMode.refresh_rate + 5;
179
180 static TimeKeeper lastFrame = TimeKeeper::getSunGenesisTime();
181 const TimeKeeper now = TimeKeeper::getCurrent();
182
183 const double remaining = 1.0 / (double) maxRunawayFPS - (now - lastFrame);
184
185 // this doesn't create our exact desired FPS, since our handling is
186 // frame-to-frame and some frames will be late already and will not be
187 // delayed, but it's close enough for the purposes of this workaround
188 if (remaining > 0.0)
189 TimeKeeper::sleep(remaining);
190
191 lastFrame = now;
192 #endif //__APPLE__
193 }
194
195 // For some reason, when creating a new fullscreen window on Linux with a different resolution than before, SDL throws
196 // a resize event with the old window resolution, which is not what we want. This function is called to filter SDL
197 // window resize events right after the resolution change and adjust the resolution to the correct one.
198 #ifdef __linux__
SDLWindowEventFilter(void * resolution,SDL_Event * event)199 int SDLWindowEventFilter(void *resolution, SDL_Event *event)
200 {
201 if(event->type == SDL_WINDOWEVENT && (event->window.event == SDL_WINDOWEVENT_RESIZED ||
202 event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
203 {
204 // adjust the window resolution to match the values passed to us
205 event->window.data1 = static_cast<int *>(resolution)[0];
206 event->window.data2 = static_cast<int *>(resolution)[1];
207 }
208
209 return 1; // allow the event
210 }
211 #endif // __linux__
212
create(void)213 bool SDLWindow::create(void)
214 {
215 int targetWidth, targetHeight;
216 getSize(targetWidth, targetHeight);
217 SDL_bool windowWasGrabbed = SDL_FALSE;
218 if (windowId != NULL)
219 windowWasGrabbed = SDL_GetWindowGrab(windowId);
220
221 // if we have an existing identical window, go no further
222 if (windowId != NULL)
223 {
224 int currentWidth, currentHeight;
225 SDL_GetWindowSize(windowId, ¤tWidth, ¤tHeight);
226
227 Uint32 priorWindowFlags = SDL_GetWindowFlags(windowId);
228 if (fullScreen == (priorWindowFlags & SDL_WINDOW_FULLSCREEN) &&
229 targetWidth == currentWidth && targetHeight == currentHeight)
230 return true;
231 }
232
233 // destroy the pre-existing window if it exists
234 if (windowId != NULL)
235 {
236 if (glContext)
237 SDL_GL_DeleteContext(glContext);
238 glContext = NULL;
239
240 SDL_DestroyWindow(windowId);
241 }
242
243 // (re)create the window
244
245 // workaround for an SDL 2 bug on Linux with the GNOME Window List extension enabled, where attempting to create a
246 // fullscreen window on a lower-resolution primary display while a higher-resolution secondary display is plugged in
247 // causes an infinite loop of window creation on the secondary display
248 // bug report: https://bugzilla.libsdl.org/show_bug.cgi?id=4990
249 #ifdef __linux__
250 if(! fullScreen || SDL_GetNumVideoDisplays() < 2) // create the window with the standard logic
251 {
252 #endif // __linux__
253 windowId = SDL_CreateWindow(
254 title.c_str(),
255 SDL_WINDOWPOS_UNDEFINED,
256 SDL_WINDOWPOS_UNDEFINED,
257 targetWidth,
258 targetHeight,
259 SDL_WINDOW_OPENGL |
260 (fullScreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_RESIZABLE) |
261 (windowWasGrabbed ? SDL_WINDOW_INPUT_GRABBED : 0));
262
263 // continuation of above workaround
264 #ifdef __linux__
265 }
266 else // create the window in windowed mode first and then switch to fullscreen
267 {
268 windowId = SDL_CreateWindow(
269 title.c_str(),
270 SDL_WINDOWPOS_UNDEFINED,
271 SDL_WINDOWPOS_UNDEFINED,
272 base_width,
273 base_height,
274 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (windowWasGrabbed ? SDL_WINDOW_INPUT_GRABBED : 0));
275
276 SDL_DisplayMode displayMode;
277 if(SDL_GetDesktopDisplayMode(0, &displayMode) < 0)
278 {
279 printf("Unable to get desktop display mode: %s", SDL_GetError());
280 return false;
281 }
282 displayMode.w = targetWidth;
283 displayMode.h = targetHeight;
284 if(SDL_SetWindowDisplayMode(windowId, &displayMode))
285 {
286 printf("Unable to set display mode: %s", SDL_GetError());
287 return false;
288 }
289 if(SDL_SetWindowFullscreen(windowId, SDL_WINDOW_FULLSCREEN) < 0)
290 {
291 printf("Unable to set window to fullscreen mode: %s", SDL_GetError());
292 return false;
293 }
294 }
295
296 // Depending on the distribution (or possibly the window manager), SDL will not recognize the new window resolution
297 // and will keep returning the previous resolution when SDL_GetWindowSize() is called, until a period of time has
298 // passed and events have been pumped. Wait up to two seconds for the correct resolution to start being returned,
299 // checking every quarter second, to avoid repeating the window destruction/re-creation process based on bad data.
300 int currentWidth, currentHeight, resCheckLoops = 0;
301
302 do
303 {
304 SDL_PumpEvents();
305 SDL_GetWindowSize(windowId, ¤tWidth, ¤tHeight);
306
307 if(currentWidth == targetWidth && currentHeight == targetHeight)
308 break;
309
310 TimeKeeper::sleep(0.25f);
311 }
312 while (resCheckLoops++ < 8);
313 #endif // __linux__
314
315 // Apply filters due to resize event issues on Linux (see the explanation above for SDLWindowEventFilter())
316 #ifdef __linux__
317 SDL_PumpEvents();
318 int windowResolution[] = { targetWidth, targetHeight };
319 SDL_FilterEvents(&SDLWindowEventFilter, windowResolution);
320 #endif // __linux__
321
322 // Work around an issue with SDL on macOS where a window that gets resized by the operating system for various
323 // reasons (e.g., creating a window that doesn't fit between the dock and menu bar, or switching from a maximized
324 // window to native fullscreen then back to windowed mode) doesn't always correctly throw a resize event
325 #ifdef __APPLE__
326 SDL_PumpEvents();
327
328 int currentWidth, currentHeight;
329 SDL_GetWindowSize(windowId, ¤tWidth, ¤tHeight);
330
331 if(! fullScreen && (currentWidth != targetWidth || currentHeight != targetHeight))
332 {
333 SDL_Event fakeResizeEvent;
334 SDL_zero(fakeResizeEvent);
335
336 fakeResizeEvent.window.type = SDL_WINDOWEVENT;
337 fakeResizeEvent.window.windowID = 0; // deliberately not matching SDL_GetWindowID() so SDL doesn't purge event
338 fakeResizeEvent.window.event = SDL_WINDOWEVENT_RESIZED;
339 fakeResizeEvent.window.data1 = currentWidth;
340 fakeResizeEvent.window.data2 = currentHeight;
341
342 SDL_PushEvent(&fakeResizeEvent);
343 }
344 #endif // __APPLE__
345
346 // Store the gamma immediately after creating the first window
347 if (origGamma < 0)
348 origGamma = getGamma();
349
350 // At least on Windows, recreating the window resets the gamma, so set it
351 setGamma(lastGamma);
352
353 #ifdef _WIN32
354 SDL_VERSION(&info.version);
355 if (SDL_GetWindowWMInfo(windowId,&info))
356 {
357 if (info.subsystem == SDL_SYSWM_WINDOWS)
358 hwnd = info.info.win.window;
359 }
360 #endif
361
362 if (!windowId)
363 {
364 printf("Could not set Video Mode: %s.\n", SDL_GetError());
365 return false;
366 }
367
368 if (min_width >= 0)
369 setMinSize(min_width, min_height);
370
371 makeContext();
372 makeCurrent();
373
374 if(SDL_GL_SetSwapInterval(vsync ? -1 : 0) == -1 && vsync)
375 // no adaptive vsync; set regular vsync
376 SDL_GL_SetSwapInterval(1);
377
378 // init opengl context
379 OpenGLGState::initContext();
380
381 return true;
382 }
383
enableGrabMouse(bool on)384 void SDLWindow::enableGrabMouse(bool on)
385 {
386 canGrabMouse = on;
387 if (canGrabMouse)
388 SDL_SetWindowGrab(windowId, SDL_TRUE);
389 else
390 SDL_SetWindowGrab(windowId, SDL_FALSE);
391 }
392
makeContext()393 void SDLWindow::makeContext()
394 {
395 glContext = SDL_GL_CreateContext(windowId);
396 if (!glContext)
397 printf("Could not Create GL Context: %s.\n", SDL_GetError());
398 }
399
setVerticalSync(bool setting)400 void SDLWindow::setVerticalSync(bool setting)
401 {
402 vsync = setting;
403
404 if (windowId != NULL)
405 if (glContext != NULL)
406 if(SDL_GL_SetSwapInterval(vsync ? -1 : 0) == -1 && vsync)
407 // no adaptive vsync; set regular vsync
408 SDL_GL_SetSwapInterval(1);
409 }
410
setMinSize(int width,int height)411 void SDLWindow::setMinSize(int width, int height)
412 {
413 min_width = width;
414 min_height = height;
415 if (!windowId)
416 return;
417 SDL_SetWindowMinimumSize (windowId, width, height);
418 }
419
makeCurrent()420 void SDLWindow::makeCurrent()
421 {
422 if (!windowId)
423 return;
424 if (!glContext)
425 return;
426 int result = SDL_GL_MakeCurrent(windowId, glContext);
427 if (result < 0)
428 {
429 printf("Could not Make GL Context Current: %s.\n", SDL_GetError());
430 abort();
431 }
432 }
433
freeContext()434 void SDLWindow::freeContext()
435 {
436 SDL_GL_DeleteContext(glContext);
437 }
438
439 // Local Variables: ***
440 // mode: C++ ***
441 // tab-width: 4 ***
442 // c-basic-offset: 4 ***
443 // indent-tabs-mode: nil ***
444 // End: ***
445 // ex: shiftwidth=4 tabstop=4
446