10 #include "../stdafx.h"
11 #include "../openttd.h"
12 #include "../gfx_func.h"
13 #include "../blitter/factory.hpp"
14 #include "../thread.h"
15 #include "../progress.h"
16 #include "../core/random_func.hpp"
17 #include "../core/math_func.hpp"
18 #include "../core/mem_func.hpp"
19 #include "../core/geometry_func.hpp"
20 #include "../fileio_func.h"
21 #include "../framerate_type.h"
22 #include "../window_func.h"
26 # include <emscripten.h>
27 # include <emscripten/html5.h>
30 #include "../safeguards.h"
34 Rect r = {left, top, left + width, top + height};
41 this->
MakeDirty(0, 0, _screen.width, _screen.height);
44 static const Dimension default_resolutions[] = {
58 static void FindResolutions()
62 for (
int display = 0; display < SDL_GetNumVideoDisplays(); display++) {
63 for (
int i = 0; i < SDL_GetNumDisplayModes(display); i++) {
65 SDL_GetDisplayMode(display, i, &mode);
67 if (mode.w < 640 || mode.h < 480)
continue;
75 _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
81 static void GetAvailableVideoMode(uint *w, uint *h)
94 if (newdelta < delta) {
103 static uint FindStartupDisplay(uint startup_display)
105 int num_displays = SDL_GetNumVideoDisplays();
108 if (
IsInsideBS(startup_display, 0, num_displays))
return startup_display;
112 SDL_GetGlobalMouseState(&mx, &my);
113 for (
int display = 0; display < num_displays; ++display) {
115 if (SDL_GetDisplayBounds(display, &r) == 0 &&
IsInsideBS(mx, r.x, r.w) &&
IsInsideBS(my, r.y, r.h)) {
116 Debug(driver, 1,
"SDL2: Mouse is at ({}, {}), use display {} ({}, {}, {}, {})", mx, my, display, r.x, r.y, r.w, r.h);
140 flags |= SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
143 flags |= SDL_WINDOW_FULLSCREEN;
146 int x = SDL_WINDOWPOS_UNDEFINED, y = SDL_WINDOWPOS_UNDEFINED;
148 if (SDL_GetDisplayBounds(this->startup_display, &r) == 0) {
149 x = r.x + std::max(0, r.w -
static_cast<int>(w)) / 2;
150 y = r.y + std::max(0, r.h -
static_cast<int>(h)) / 4;
161 Debug(driver, 0,
"SDL2: Couldn't allocate a window to draw on: {}", SDL_GetError());
166 if (!icon_path.empty()) {
168 SDL_Surface *icon = SDL_LoadBMP(icon_path.c_str());
169 if (icon !=
nullptr) {
171 uint32_t rgbmap = SDL_MapRGB(icon->format, 255, 0, 255);
173 SDL_SetColorKey(icon, SDL_TRUE, rgbmap);
175 SDL_FreeSurface(icon);
182 bool VideoDriver_SDL_Base::CreateMainSurface(uint w, uint h,
bool resize)
184 GetAvailableVideoMode(&w, &h);
185 Debug(driver, 1,
"SDL2: using mode {}x{}", w, h);
188 if (resize) SDL_SetWindowSize(this->
sdl_window, w, h);
194 if (_fullscreen) _cursor.
in_window =
true;
199 bool VideoDriver_SDL_Base::ClaimMousePointer()
202 #ifndef __EMSCRIPTEN__
214 SDL_StartTextInput();
232 std::vector<int> rates = {};
233 for (
int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
234 SDL_DisplayMode mode = {};
235 if (SDL_GetDisplayMode(i, 0, &mode) != 0)
continue;
236 if (mode.refresh_rate != 0) rates.push_back(mode.refresh_rate);
243 const SDL_Keycode vk_from;
244 const uint8_t vk_count;
245 const uint8_t map_to;
246 const bool unprintable;
248 constexpr
SDLVkMapping(SDL_Keycode vk_first, SDL_Keycode vk_last, uint8_t map_first, [[maybe_unused]] uint8_t map_last,
bool unprintable)
249 : vk_from(vk_first), vk_count(vk_last - vk_first + 1), map_to(map_first), unprintable(unprintable)
251 assert((vk_last - vk_first) == (map_last - map_first));
255 #define AS(x, z) {x, x, z, z, false}
256 #define AM(x, y, z, w) {x, y, z, w, false}
257 #define AS_UP(x, z) {x, x, z, z, true}
258 #define AM_UP(x, y, z, w) {x, y, z, w, true}
262 AS_UP(SDLK_PAGEUP, WKC_PAGEUP),
263 AS_UP(SDLK_PAGEDOWN, WKC_PAGEDOWN),
264 AS_UP(SDLK_UP, WKC_UP),
265 AS_UP(SDLK_DOWN, WKC_DOWN),
266 AS_UP(SDLK_LEFT, WKC_LEFT),
267 AS_UP(SDLK_RIGHT, WKC_RIGHT),
269 AS_UP(SDLK_HOME, WKC_HOME),
270 AS_UP(SDLK_END, WKC_END),
272 AS_UP(SDLK_INSERT, WKC_INSERT),
273 AS_UP(SDLK_DELETE, WKC_DELETE),
276 AM(SDLK_a, SDLK_z,
'A',
'Z'),
277 AM(SDLK_0, SDLK_9,
'0',
'9'),
279 AS_UP(SDLK_ESCAPE, WKC_ESC),
280 AS_UP(SDLK_PAUSE, WKC_PAUSE),
281 AS_UP(SDLK_BACKSPACE, WKC_BACKSPACE),
283 AS(SDLK_SPACE, WKC_SPACE),
284 AS(SDLK_RETURN, WKC_RETURN),
285 AS(SDLK_TAB, WKC_TAB),
288 AM_UP(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12),
301 AS(SDLK_KP_DIVIDE, WKC_NUM_DIV),
302 AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL),
303 AS(SDLK_KP_MINUS, WKC_NUM_MINUS),
304 AS(SDLK_KP_PLUS, WKC_NUM_PLUS),
305 AS(SDLK_KP_ENTER, WKC_NUM_ENTER),
306 AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL),
322 static uint ConvertSdlKeyIntoMy(SDL_Keysym *sym, char32_t *character)
325 bool unprintable =
false;
327 for (
const auto &map : _vk_mapping) {
328 if (
IsInsideBS(sym->sym, map.vk_from, map.vk_count)) {
329 key = sym->sym - map.vk_from + map.map_to;
330 unprintable = map.unprintable;
336 if (sym->scancode == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
339 if (sym->mod & KMOD_GUI) key |= WKC_META;
340 if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT;
341 if (sym->mod & KMOD_CTRL) key |= WKC_CTRL;
342 if (sym->mod & KMOD_ALT) key |= WKC_ALT;
345 if (sym->mod & KMOD_GUI ||
346 sym->mod & KMOD_CTRL ||
347 sym->mod & KMOD_ALT ||
349 *character = WKC_NONE;
351 *character = sym->sym;
365 for (
const auto &map : _vk_mapping) {
366 if (
IsInsideBS(kc, map.vk_from, map.vk_count)) {
367 key = kc - map.vk_from + map.map_to;
374 SDL_Scancode sc = SDL_GetScancodeFromKey(kc);
375 if (sc == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
384 if (!SDL_PollEvent(&ev))
return false;
387 case SDL_MOUSEMOTION: {
388 int32_t x = ev.motion.x;
389 int32_t y = ev.motion.y;
394 while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION)) {
408 if (ev.wheel.y > 0) {
410 }
else if (ev.wheel.y < 0) {
415 case SDL_MOUSEBUTTONDOWN:
417 ev.button.button = SDL_BUTTON_RIGHT;
420 switch (ev.button.button) {
421 case SDL_BUTTON_LEFT:
425 case SDL_BUTTON_RIGHT:
435 case SDL_MOUSEBUTTONUP:
440 }
else if (ev.button.button == SDL_BUTTON_LEFT) {
443 }
else if (ev.button.button == SDL_BUTTON_RIGHT) {
450 HandleExitGameRequest();
454 if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_GUI)) &&
455 (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) {
456 if (ev.key.repeat == 0) ToggleFullScreen(!_fullscreen);
460 uint keycode = ConvertSdlKeyIntoMy(&ev.key.keysym, &character);
464 keycode == WKC_DELETE ||
465 keycode == WKC_NUM_ENTER ||
466 keycode == WKC_LEFT ||
467 keycode == WKC_RIGHT ||
469 keycode == WKC_DOWN ||
470 keycode == WKC_HOME ||
471 keycode == WKC_END ||
472 keycode & WKC_META ||
473 keycode & WKC_CTRL ||
475 (keycode >= WKC_F1 && keycode <= WKC_F12) ||
482 case SDL_TEXTINPUT: {
484 SDL_Keycode kc = SDL_GetKeyFromName(ev.text.text);
496 case SDL_WINDOWEVENT: {
497 if (ev.window.event == SDL_WINDOWEVENT_EXPOSED) {
499 this->
MakeDirty(0, 0, _screen.width, _screen.height);
500 }
else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
501 int w = std::max(ev.window.data1, 64);
502 int h = std::max(ev.window.data2, 64);
503 CreateMainSurface(w, h, w != ev.window.data1 || h != ev.window.data2);
504 }
else if (ev.window.event == SDL_WINDOWEVENT_ENTER) {
508 SDL_SetRelativeMouseMode(SDL_FALSE);
509 }
else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) {
521 static std::optional<std::string_view> InitializeSDL()
524 if (SDL_WasInit(SDL_INIT_VIDEO) != 0)
return std::nullopt;
526 if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
return SDL_GetError();
530 std::optional<std::string_view> VideoDriver_SDL_Base::Initialize()
534 auto error = InitializeSDL();
535 if (error)
return error;
547 auto error = this->Initialize();
548 if (error)
return error;
550 #ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
555 if (!SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE,
"0"))
return SDL_GetError();
559 this->startup_display = FindStartupDisplay(
GetDriverParamInt(param,
"display", -1));
562 return SDL_GetError();
565 const char *dname = SDL_GetCurrentVideoDriver();
566 Debug(driver, 1,
"SDL2: using driver '{}'", dname);
578 #ifdef __EMSCRIPTEN__
579 this->is_game_threaded =
false;
589 SDL_QuitSubSystem(SDL_INIT_VIDEO);
590 if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
597 uint32_t mod = SDL_GetModState();
598 const Uint8 *keys = SDL_GetKeyboardState(
nullptr);
611 (keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
612 (keys[SDL_SCANCODE_UP] ? 2 : 0) |
613 (keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
614 (keys[SDL_SCANCODE_DOWN] ? 8 : 0);
619 void VideoDriver_SDL_Base::LoopOnce()
622 #ifdef __EMSCRIPTEN__
629 extern void PostMainLoop();
632 emscripten_cancel_main_loop();
633 emscripten_exit_pointerlock();
637 if (_game_mode == GM_BOOTSTRAP) {
638 EM_ASM(
if (window[
"openttd_bootstrap_reload"]) openttd_bootstrap_reload());
640 EM_ASM(
if (window[
"openttd_exit"]) openttd_exit());
650 #ifndef __EMSCRIPTEN__
657 #ifdef __EMSCRIPTEN__
659 emscripten_set_main_loop_arg(&this->EmscriptenLoop,
this, 0, 1);
663 while (!_exit_game) {
673 return CreateMainSurface(w, h,
true);
685 if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(this->
sdl_window), &dm) < 0) {
686 Debug(driver, 0,
"SDL_GetCurrentDisplayMode() failed: {}", SDL_GetError());
688 SDL_SetWindowSize(this->
sdl_window, dm.w, dm.h);
692 Debug(driver, 1,
"SDL2: Setting {}", fullscreen ?
"fullscreen" :
"windowed");
693 int ret = SDL_SetWindowFullscreen(this->
sdl_window, fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
696 _fullscreen = fullscreen;
697 if (!fullscreen) SDL_SetWindowSize(this->
sdl_window, w, h);
699 Debug(driver, 0,
"SDL_SetWindowFullscreen() failed: {}", SDL_GetError());
711 return CreateMainSurface(w, h,
false);
716 SDL_DisplayMode mode;
719 return {
static_cast<uint
>(mode.w),
static_cast<uint
>(mode.h) };
728 assert(_screen.dst_ptr !=
nullptr);
735 if (_screen.dst_ptr !=
nullptr) {
738 _screen.dst_ptr =
nullptr;