1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #define FORBIDDEN_SYMBOL_ALLOW_ALL
24 
25 #include "backends/platform/sdl/sdl-window.h"
26 
27 #include "common/textconsole.h"
28 
29 #include "icons/residualvm.xpm"
30 
31 #if SDL_VERSION_ATLEAST(2, 0, 0)
32 static const uint32 fullscreenMask = SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN;
33 #endif
34 
SdlWindow()35 SdlWindow::SdlWindow()
36 #if SDL_VERSION_ATLEAST(2, 0, 0)
37 	: _window(nullptr), _inputGrabState(false), _windowCaption("ResidualVM"),
38 	_lastFlags(0), _lastX(SDL_WINDOWPOS_UNDEFINED), _lastY(SDL_WINDOWPOS_UNDEFINED)
39 #endif
40 	{
41 }
42 
~SdlWindow()43 SdlWindow::~SdlWindow() {
44 #if SDL_VERSION_ATLEAST(2, 0, 0)
45 	destroyWindow();
46 #endif
47 }
48 
setupIcon()49 void SdlWindow::setupIcon() {
50 	int x, y, w, h, ncols, nbytes, i;
51 	unsigned int rgba[256];
52 	unsigned int *icon;
53 
54 	if (sscanf(scummvm_icon[0], "%d %d %d %d", &w, &h, &ncols, &nbytes) != 4) {
55 		warning("Wrong format of scummvm_icon[0] (%s)", scummvm_icon[0]);
56 
57 		return;
58 	}
59 	if ((w > 512) || (h > 512) || (ncols > 255) || (nbytes > 1)) {
60 		warning("Could not load the built-in icon (%d %d %d %d)", w, h, ncols, nbytes);
61 		return;
62 	}
63 	icon = (unsigned int*)malloc(w*h*sizeof(unsigned int));
64 	if (!icon) {
65 		warning("Could not allocate temp storage for the built-in icon");
66 		return;
67 	}
68 
69 	for (i = 0; i < ncols; i++) {
70 		unsigned char code;
71 		char color[32];
72 		memset(color, 0, sizeof(color));
73 		unsigned int col;
74 		if (sscanf(scummvm_icon[1 + i], "%c c %s", &code, color) != 2) {
75 			warning("Wrong format of scummvm_icon[%d] (%s)", 1 + i, scummvm_icon[1 + i]);
76 		}
77 		if (!strcmp(color, "None"))
78 			col = 0x00000000;
79 		else if (!strcmp(color, "black"))
80 			col = 0xFF000000;
81 		else if (color[0] == '#') {
82 			if (sscanf(color + 1, "%06x", &col) != 1) {
83 				warning("Wrong format of color (%s)", color + 1);
84 			}
85 			col |= 0xFF000000;
86 		} else {
87 			warning("Could not load the built-in icon (%d %s - %s) ", code, color, scummvm_icon[1 + i]);
88 			free(icon);
89 			return;
90 		}
91 
92 		rgba[code] = col;
93 	}
94 	for (y = 0; y < h; y++) {
95 		const char *line = scummvm_icon[1 + ncols + y];
96 		for (x = 0; x < w; x++) {
97 			icon[x + w * y] = rgba[(int)line[x]];
98 		}
99 	}
100 
101 	SDL_Surface *sdl_surf = SDL_CreateRGBSurfaceFrom(icon, w, h, 32, w * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000);
102 	if (!sdl_surf) {
103 		warning("SDL_CreateRGBSurfaceFrom(icon) failed");
104 	}
105 
106 #if SDL_VERSION_ATLEAST(2, 0, 0)
107 	if (_window) {
108 		SDL_SetWindowIcon(_window, sdl_surf);
109 	}
110 #else
111 	SDL_WM_SetIcon(sdl_surf, NULL);
112 #endif
113 
114 	SDL_FreeSurface(sdl_surf);
115 	free(icon);
116 }
117 
setWindowCaption(const Common::String & caption)118 void SdlWindow::setWindowCaption(const Common::String &caption) {
119 #if SDL_VERSION_ATLEAST(2, 0, 0)
120 	_windowCaption = caption;
121 	if (_window) {
122 		SDL_SetWindowTitle(_window, caption.c_str());
123 	}
124 #else
125 	SDL_WM_SetCaption(caption.c_str(), caption.c_str());
126 #endif
127 }
128 
toggleMouseGrab()129 void SdlWindow::toggleMouseGrab() {
130 #if SDL_VERSION_ATLEAST(2, 0, 0)
131 	if (_window) {
132 		_inputGrabState = !(SDL_GetWindowGrab(_window) == SDL_TRUE);
133 		SDL_SetWindowGrab(_window, _inputGrabState ? SDL_TRUE : SDL_FALSE);
134 	}
135 #else
136 	if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF) {
137 		SDL_WM_GrabInput(SDL_GRAB_ON);
138 	} else {
139 		SDL_WM_GrabInput(SDL_GRAB_OFF);
140 	}
141 #endif
142 }
143 
hasMouseFocus() const144 bool SdlWindow::hasMouseFocus() const {
145 #if SDL_VERSION_ATLEAST(2, 0, 0)
146 	if (_window) {
147 		return (SDL_GetWindowFlags(_window) & SDL_WINDOW_MOUSE_FOCUS);
148 	} else {
149 		return false;
150 	}
151 #else
152 	return (SDL_GetAppState() & SDL_APPMOUSEFOCUS);
153 #endif
154 }
155 
warpMouseInWindow(uint x,uint y)156 void SdlWindow::warpMouseInWindow(uint x, uint y) {
157 #if SDL_VERSION_ATLEAST(2, 0, 0)
158 	if (_window && hasMouseFocus()) {
159 		SDL_WarpMouseInWindow(_window, x, y);
160 	}
161 #else
162 	SDL_WarpMouse(x, y);
163 #endif
164 }
165 
iconifyWindow()166 void SdlWindow::iconifyWindow() {
167 #if SDL_VERSION_ATLEAST(2, 0, 0)
168 	if (_window) {
169 		SDL_MinimizeWindow(_window);
170 	}
171 #else
172 	SDL_WM_IconifyWindow();
173 #endif
174 }
175 
getSDLWMInformation(SDL_SysWMinfo * info) const176 bool SdlWindow::getSDLWMInformation(SDL_SysWMinfo *info) const {
177 	SDL_VERSION(&info->version);
178 #if SDL_VERSION_ATLEAST(2, 0, 0)
179 	return SDL_GetWindowWMInfo(_window, info);
180 #else
181 	return SDL_GetWMInfo(info);
182 #endif
183 }
184 
185 #if SDL_VERSION_ATLEAST(2, 0, 0)
copySDLSurface(SDL_Surface * src)186 SDL_Surface *copySDLSurface(SDL_Surface *src) {
187 	const bool locked = SDL_MUSTLOCK(src) == SDL_TRUE;
188 
189 	if (locked) {
190 		if (SDL_LockSurface(src) != 0) {
191 			return nullptr;
192 		}
193 	}
194 
195 	SDL_Surface *res = SDL_CreateRGBSurfaceFrom(src->pixels,
196 	                       src->w, src->h, src->format->BitsPerPixel,
197 	                       src->pitch, src->format->Rmask, src->format->Gmask,
198 	                       src->format->Bmask, src->format->Amask);
199 
200 	if (locked) {
201 		SDL_UnlockSurface(src);
202 	}
203 
204 	return res;
205 }
206 
createOrUpdateWindow(int width,int height,uint32 flags)207 bool SdlWindow::createOrUpdateWindow(int width, int height, uint32 flags) {
208 	if (_inputGrabState) {
209 		flags |= SDL_WINDOW_INPUT_GRABBED;
210 	}
211 
212 	// SDL_WINDOW_RESIZABLE can also be updated without recreating the window
213 	// starting with SDL 2.0.5, but it is not treated as updateable here
214 	// because:
215 	// 1. It is currently only changed in conjunction with the SDL_WINDOW_OPENGL
216 	//    flag, so the window will always be recreated anyway when changing
217 	//    resizability; and
218 	// 2. Users (particularly on Windows) will sometimes swap older SDL DLLs
219 	//    to avoid bugs, which would be impossible if the feature was enabled
220 	//    at compile time using SDL_VERSION_ATLEAST.
221 	const uint32 updateableFlagsMask = fullscreenMask | SDL_WINDOW_INPUT_GRABBED;
222 
223 	const uint32 oldNonUpdateableFlags = _lastFlags & ~updateableFlagsMask;
224 	const uint32 newNonUpdateableFlags = flags & ~updateableFlagsMask;
225 
226 	if (!_window || oldNonUpdateableFlags != newNonUpdateableFlags) {
227 		destroyWindow();
228 		_window = SDL_CreateWindow(_windowCaption.c_str(), _lastX,
229 								   _lastY, width, height, flags);
230 		if (_window) {
231 			setupIcon();
232 		}
233 	} else {
234 		const uint32 fullscreenFlags = flags & fullscreenMask;
235 
236 		if (fullscreenFlags) {
237 			SDL_DisplayMode fullscreenMode;
238 			fullscreenMode.w = width;
239 			fullscreenMode.h = height;
240 			fullscreenMode.driverdata = nullptr;
241 			fullscreenMode.format = 0;
242 			fullscreenMode.refresh_rate = 0;
243 			SDL_SetWindowDisplayMode(_window, &fullscreenMode);
244 		} else {
245 			SDL_SetWindowSize(_window, width, height);
246 		}
247 
248 		SDL_SetWindowFullscreen(_window, fullscreenFlags);
249 		SDL_SetWindowGrab(_window, (flags & SDL_WINDOW_INPUT_GRABBED) ? SDL_TRUE : SDL_FALSE);
250 	}
251 
252 	if (!_window) {
253 		return false;
254 	}
255 
256 #if defined(MACOSX)
257 	// macOS windows with the flag SDL_WINDOW_FULLSCREEN_DESKTOP exiting their fullscreen space
258 	// ignore the size set by SDL_SetWindowSize while they were in fullscreen mode.
259 	// Instead, they revert back to their previous windowed mode size.
260 	// This is a bug in SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3719.
261 	// TODO: Remove the call to SDL_SetWindowSize below once the SDL bug is fixed.
262 
263 	// In some cases at this point there may be a pending SDL resize event with the old size.
264 	// This happens for example if we destroyed the window, or when switching between windowed
265 	// and fullscreen modes. If we changed the window size here, this pending event will have the
266 	// old (and incorrect) size. To avoid any issue we call SDL_SetWindowSize() to generate another
267 	// resize event (SDL_WINDOWEVENT_SIZE_CHANGED) so that the last resize event we receive has
268 	// the correct size. This fixes for exmample bug #9971: SDL2: Fullscreen to RTL launcher resolution
269 	SDL_SetWindowSize(_window, width, height);
270 #endif
271 
272 	_lastFlags = flags;
273 
274 	return true;
275 }
276 
destroyWindow()277 void SdlWindow::destroyWindow() {
278 	if (_window) {
279 		if (!(_lastFlags & fullscreenMask)) {
280 			SDL_GetWindowPosition(_window, &_lastX, &_lastY);
281 		}
282 		SDL_DestroyWindow(_window);
283 		_window = nullptr;
284 	}
285 }
286 #endif
287