25# include <emscripten.h>
26# include <emscripten/html5.h>
33 Rect r = {left, top, left + width, top + height};
40 this->
MakeDirty(0, 0, _screen.width, _screen.height);
43static const Dimension default_resolutions[] = {
57static void FindResolutions()
61 for (
int display = 0; display < SDL_GetNumVideoDisplays(); display++) {
62 for (
int i = 0; i < SDL_GetNumDisplayModes(display); i++) {
64 SDL_GetDisplayMode(display, i, &mode);
66 if (mode.w < 640 || mode.h < 480)
continue;
74 _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
80static void GetAvailableVideoMode(uint *w, uint *h)
93 if (newdelta < delta) {
102static uint FindStartupDisplay(uint startup_display)
104 int num_displays = SDL_GetNumVideoDisplays();
107 if (
IsInsideBS(startup_display, 0, num_displays))
return startup_display;
111 SDL_GetGlobalMouseState(&mx, &my);
112 for (
int display = 0; display < num_displays; ++display) {
114 if (SDL_GetDisplayBounds(display, &r) == 0 &&
IsInsideBS(mx, r.x, r.w) &&
IsInsideBS(my, r.y, r.h)) {
115 Debug(driver, 1,
"SDL2: Mouse is at ({}, {}), use display {} ({}, {}, {}, {})", mx, my, display, r.x, r.y, r.w, r.h);
145 flags |= SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
148 flags |= SDL_WINDOW_FULLSCREEN;
151 int x = SDL_WINDOWPOS_UNDEFINED, y = SDL_WINDOWPOS_UNDEFINED;
153 if (SDL_GetDisplayBounds(this->startup_display, &r) == 0) {
154 x = r.x + std::max(0, r.w -
static_cast<int>(w)) / 2;
155 y = r.y + std::max(0, r.h -
static_cast<int>(h)) / 4;
166 Debug(driver, 0,
"SDL2: Couldn't allocate a window to draw on: {}", SDL_GetError());
171 if (!icon_path.empty()) {
173 SDL_Surface *icon = SDL_LoadBMP(icon_path.c_str());
174 if (icon !=
nullptr) {
176 uint32_t rgbmap = SDL_MapRGB(icon->format, 255, 0, 255);
178 SDL_SetColorKey(icon, SDL_TRUE, rgbmap);
180 SDL_FreeSurface(icon);
187bool VideoDriver_SDL_Base::CreateMainSurface(uint w, uint h,
bool resize)
189 GetAvailableVideoMode(&w, &h);
190 Debug(driver, 1,
"SDL2: using mode {}x{}", w, h);
193 if (resize) SDL_SetWindowSize(this->
sdl_window, w, h);
199 if (_fullscreen) _cursor.in_window =
true;
207#ifndef __EMSCRIPTEN__
218 SDL_StartTextInput();
236 std::vector<int> rates = {};
237 for (
int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
238 SDL_DisplayMode mode = {};
239 if (SDL_GetDisplayMode(i, 0, &mode) != 0)
continue;
240 if (mode.refresh_rate != 0) rates.push_back(mode.refresh_rate);
247 const SDL_Keycode vk_from;
248 const uint8_t vk_count;
249 const uint8_t map_to;
250 const bool unprintable;
252 constexpr SDLVkMapping(SDL_Keycode vk_first, SDL_Keycode vk_last, uint8_t map_first, [[maybe_unused]] uint8_t map_last,
bool unprintable)
253 : vk_from(vk_first), vk_count(vk_last - vk_first + 1), map_to(map_first), unprintable(unprintable)
255 assert((vk_last - vk_first) == (map_last - map_first));
259#define AS(x, z) {x, x, z, z, false}
260#define AM(x, y, z, w) {x, y, z, w, false}
261#define AS_UP(x, z) {x, x, z, z, true}
262#define AM_UP(x, y, z, w) {x, y, z, w, true}
266 AS_UP(SDLK_PAGEUP, WKC_PAGEUP),
267 AS_UP(SDLK_PAGEDOWN, WKC_PAGEDOWN),
268 AS_UP(SDLK_UP, WKC_UP),
269 AS_UP(SDLK_DOWN, WKC_DOWN),
270 AS_UP(SDLK_LEFT, WKC_LEFT),
271 AS_UP(SDLK_RIGHT, WKC_RIGHT),
273 AS_UP(SDLK_HOME, WKC_HOME),
274 AS_UP(SDLK_END, WKC_END),
276 AS_UP(SDLK_INSERT, WKC_INSERT),
277 AS_UP(SDLK_DELETE, WKC_DELETE),
280 AM(SDLK_a, SDLK_z,
'A',
'Z'),
281 AM(SDLK_0, SDLK_9,
'0',
'9'),
283 AS_UP(SDLK_ESCAPE, WKC_ESC),
284 AS_UP(SDLK_PAUSE, WKC_PAUSE),
285 AS_UP(SDLK_BACKSPACE, WKC_BACKSPACE),
287 AS(SDLK_SPACE, WKC_SPACE),
288 AS(SDLK_RETURN, WKC_RETURN),
289 AS(SDLK_TAB, WKC_TAB),
292 AM_UP(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12),
305 AS(SDLK_KP_DIVIDE, WKC_NUM_DIV),
306 AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL),
307 AS(SDLK_KP_MINUS, WKC_NUM_MINUS),
308 AS(SDLK_KP_PLUS, WKC_NUM_PLUS),
309 AS(SDLK_KP_ENTER, WKC_NUM_ENTER),
310 AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL),
326static uint ConvertSdlKeyIntoMy(SDL_Keysym *sym,
char32_t *character)
329 bool unprintable =
false;
331 for (
const auto &map : _vk_mapping) {
332 if (
IsInsideBS(sym->sym, map.vk_from, map.vk_count)) {
333 key = sym->sym - map.vk_from + map.map_to;
334 unprintable = map.unprintable;
340 if (sym->scancode == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
343 if (sym->mod & KMOD_GUI) key |= WKC_META;
344 if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT;
345 if (sym->mod & KMOD_CTRL) key |= WKC_CTRL;
346 if (sym->mod & KMOD_ALT) key |= WKC_ALT;
349 if (sym->mod & KMOD_GUI ||
350 sym->mod & KMOD_CTRL ||
351 sym->mod & KMOD_ALT ||
353 *character = WKC_NONE;
355 *character = sym->sym;
370 for (
const auto &map : _vk_mapping) {
371 if (
IsInsideBS(kc, map.vk_from, map.vk_count)) {
372 key = kc - map.vk_from + map.map_to;
379 SDL_Scancode sc = SDL_GetScancodeFromKey(kc);
380 if (sc == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
389 if (!SDL_PollEvent(&ev))
return false;
392 case SDL_MOUSEMOTION: {
393 int32_t x = ev.motion.x;
394 int32_t y = ev.motion.y;
396 if (_cursor.fix_at) {
399 while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION)) {
405 if (_cursor.UpdateCursorPosition(x, y)) {
406 SDL_WarpMouseInWindow(this->
sdl_window, _cursor.pos.x, _cursor.pos.y);
412 case SDL_MOUSEWHEEL: {
413 if (ev.wheel.y > 0) {
415 }
else if (ev.wheel.y < 0) {
420 const float SCROLL_BUILTIN_MULTIPLIER = 14.0f;
421#if SDL_VERSION_ATLEAST(2, 18, 0)
422 _cursor.v_wheel -= ev.wheel.preciseY * SCROLL_BUILTIN_MULTIPLIER *
_settings_client.gui.scrollwheel_multiplier;
423 _cursor.h_wheel += ev.wheel.preciseX * SCROLL_BUILTIN_MULTIPLIER *
_settings_client.gui.scrollwheel_multiplier;
425 _cursor.v_wheel -=
static_cast<float>(ev.wheel.y * SCROLL_BUILTIN_MULTIPLIER *
_settings_client.gui.scrollwheel_multiplier);
426 _cursor.h_wheel +=
static_cast<float>(ev.wheel.x * SCROLL_BUILTIN_MULTIPLIER *
_settings_client.gui.scrollwheel_multiplier);
428 _cursor.wheel_moved =
true;
432 case SDL_MOUSEBUTTONDOWN:
434 ev.button.button = SDL_BUTTON_RIGHT;
437 switch (ev.button.button) {
438 case SDL_BUTTON_LEFT:
442 case SDL_BUTTON_RIGHT:
452 case SDL_MOUSEBUTTONUP:
457 }
else if (ev.button.button == SDL_BUTTON_LEFT) {
460 }
else if (ev.button.button == SDL_BUTTON_RIGHT) {
467 HandleExitGameRequest();
471 if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_GUI)) &&
472 (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) {
473 if (ev.key.repeat == 0) ToggleFullScreen(!_fullscreen);
477 uint keycode = ConvertSdlKeyIntoMy(&ev.key.keysym, &character);
481 keycode == WKC_DELETE ||
482 keycode == WKC_NUM_ENTER ||
483 keycode == WKC_LEFT ||
484 keycode == WKC_RIGHT ||
486 keycode == WKC_DOWN ||
487 keycode == WKC_HOME ||
488 keycode == WKC_END ||
489 keycode & WKC_META ||
490 keycode & WKC_CTRL ||
492 (keycode >= WKC_F1 && keycode <= WKC_F12) ||
499 case SDL_TEXTINPUT: {
501 SDL_Keycode kc = SDL_GetKeyFromName(ev.text.text);
512 case SDL_WINDOWEVENT: {
513 if (ev.window.event == SDL_WINDOWEVENT_EXPOSED) {
515 this->
MakeDirty(0, 0, _screen.width, _screen.height);
516 }
else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
517 int w = std::max(ev.window.data1, 64);
518 int h = std::max(ev.window.data2, 64);
519 CreateMainSurface(w, h, w != ev.window.data1 || h != ev.window.data2);
520 }
else if (ev.window.event == SDL_WINDOWEVENT_ENTER) {
522 _cursor.in_window =
true;
524 SDL_SetRelativeMouseMode(SDL_FALSE);
525 }
else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) {
528 _cursor.in_window =
false;
537static std::optional<std::string_view> InitializeSDL()
540 if (SDL_WasInit(SDL_INIT_VIDEO) != 0)
return std::nullopt;
542#ifdef SDL_HINT_APP_NAME
543 SDL_SetHint(SDL_HINT_APP_NAME,
"OpenTTD");
546 if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
return SDL_GetError();
550std::optional<std::string_view> VideoDriver_SDL_Base::Initialize()
554 auto error = InitializeSDL();
555 if (error)
return error;
567 auto error = this->Initialize();
568 if (error)
return error;
570#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
575 if (!SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE,
"0"))
return SDL_GetError();
579 this->startup_display = FindStartupDisplay(
GetDriverParamInt(param,
"display", -1));
582 return SDL_GetError();
585 const char *dname = SDL_GetCurrentVideoDriver();
586 Debug(driver, 1,
"SDL2: using driver '{}'", dname);
599 this->is_game_threaded =
false;
609 SDL_QuitSubSystem(SDL_INIT_VIDEO);
610 if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
617 uint32_t mod = SDL_GetModState();
618 const Uint8 *keys = SDL_GetKeyboardState(
nullptr);
631 (keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
632 (keys[SDL_SCANCODE_UP] ? 2 : 0) |
633 (keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
634 (keys[SDL_SCANCODE_DOWN] ? 8 : 0);
639void VideoDriver_SDL_Base::LoopOnce()
649 extern void PostMainLoop();
652 emscripten_cancel_main_loop();
653 emscripten_exit_pointerlock();
657 if (_game_mode == GM_BOOTSTRAP) {
658 EM_ASM(
if (window[
"openttd_bootstrap_reload"]) openttd_bootstrap_reload());
660 EM_ASM(
if (window[
"openttd_exit"]) openttd_exit());
670#ifndef __EMSCRIPTEN__
679 emscripten_set_main_loop_arg(&this->EmscriptenLoop,
this, 0, 1);
683 while (!_exit_game) {
693 return CreateMainSurface(w, h,
true);
705 if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(this->
sdl_window), &dm) < 0) {
706 Debug(driver, 0,
"SDL_GetCurrentDisplayMode() failed: {}", SDL_GetError());
708 SDL_SetWindowSize(this->
sdl_window, dm.w, dm.h);
712 Debug(driver, 1,
"SDL2: Setting {}", fullscreen ?
"fullscreen" :
"windowed");
713 int ret = SDL_SetWindowFullscreen(this->
sdl_window, fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
716 _fullscreen = fullscreen;
717 if (!fullscreen) SDL_SetWindowSize(this->
sdl_window, w, h);
719 Debug(driver, 0,
"SDL_SetWindowFullscreen() failed: {}", SDL_GetError());
731 return CreateMainSurface(w, h,
false);
736 SDL_DisplayMode mode;
739 return {
static_cast<uint
>(mode.w),
static_cast<uint
>(mode.h) };
748 assert(_screen.dst_ptr !=
nullptr);
755 if (_screen.dst_ptr !=
nullptr) {
758 _screen.dst_ptr =
nullptr;
767 SDL_DisableScreenSaver();
769 SDL_EnableScreenSaver();
#define AS(ap_name, size_x, size_y, min_year, max_year, catchment, noise, maint_cost, ttdpatch_type, class_id, name, preview)
AirportSpec definition for airports with at least one depot.
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
virtual void PostResize()
Post resize event.
virtual std::string_view GetName() const =0
Get the name of this driver.
std::optional< std::string_view > Start(const StringList ¶m) override
Start this driver.
bool buffer_locked
Video buffer was locked by the main thread.
Dimension GetScreenSize() const override
Get the resolution of the main screen.
bool PollEvent() override
Process a single system event.
void EditBoxLostFocus() override
This is called to indicate that an edit box has lost focus, text input mode should be disabled.
virtual void ReleaseVideoPointer()=0
Hand video buffer back to the painting backend.
void ClaimMousePointer() override
Claim the exclusive rights for the mouse pointer.
virtual void * GetVideoPointer()=0
Get a pointer to the video buffer.
bool AfterBlitterChange() override
Callback invoked after the blitter was changed.
virtual bool AllocateBackingStore(int w, int h, bool force=false)=0
(Re-)create the backing store.
void InputLoop() override
Handle input logic, is CTRL pressed, should we fast-forward, etc.
Palette local_palette
Current palette to use for drawing.
void MainLoop() override
Perform the actual drawing.
std::string driver_info
Information string about selected driver.
void UnlockVideoBuffer() override
Unlock a previously locked video buffer.
void ClientSizeChanged(int w, int h, bool force)
Indicate to the driver the client-size might have changed.
void MakeDirty(int left, int top, int width, int height) override
Mark a particular area dirty.
virtual bool CreateMainWindow(uint w, uint h, uint flags=0)
Create the main window.
std::vector< int > GetListOfMonitorRefreshRates() override
Get a list of refresh rates of each available monitor.
void SetScreensaverInhibited(bool inhibited) override
Prevents the system from going to sleep.
bool edit_box_focused
This is true to indicate that keyboard input is in text input mode, and SDL_TEXTINPUT events are enab...
bool ChangeResolution(int w, int h) override
Change the resolution of the window.
void Stop() override
Stop this driver.
bool LockVideoBuffer() override
Make sure the video buffer is ready for drawing.
void EditBoxGainedFocus() override
This is called to indicate that an edit box has gained focus, text input mode should be enabled.
Rect dirty_rect
Rectangle encompassing the dirty area of the video buffer.
void CheckPaletteAnim() override
Process any pending palette animation.
struct SDL_Window * sdl_window
Main SDL window.
bool ToggleFullscreen(bool fullscreen) override
Change the full screen setting.
virtual Dimension GetScreenSize() const
Get the resolution of the main screen.
bool fast_forward_key_pressed
The fast-forward key is being pressed.
void Tick()
Give the video-driver a tick.
void SleepTillNextTick()
Sleep till the next tick is about to happen.
void StartGameThread()
Start the loop for game-tick.
static std::string GetCaption()
Get the caption to use for the game's title bar.
void StopGameThread()
Stop the loop for the game-tick.
void UpdateAutoResolution()
Apply resolution auto-detection and clamp to sensible defaults.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
int GetDriverParamInt(const StringList &parm, std::string_view name, int def)
Get an integer parameter the list of parameters.
bool GetDriverParamBool(const StringList &parm, std::string_view name)
Get a boolean parameter the list of parameters.
std::vector< Dimension > _resolutions
List of resolutions.
Dimension _cur_resolution
The current resolution.
bool _rightclick_emulate
Whether right clicking is emulated.
Factory to 'query' all available blitters.
std::string FioFindFullPath(Subdirectory subdir, std::string_view filename)
Find a path to the filename in one of the search directories.
Functions for standard in/out file operations.
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game).
Types for recording game performance data.
Rect BoundingRect(const Rect &r1, const Rect &r2)
Compute the bounding rectangle around two rectangles.
bool _shift_pressed
Is Shift pressed?
bool _left_button_down
Is left mouse button pressed?
bool _ctrl_pressed
Is Ctrl pressed?
uint8_t _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
bool _left_button_clicked
Is left mouse button clicked?
bool _right_button_clicked
Is right mouse button clicked?
bool _right_button_down
Is right mouse button pressed?
Functions related to the gfx engine.
void HandleCtrlChanged()
State of CONTROL key has changed.
void GameSizeChanged()
Size of the application screen changed.
void HandleMouseEvents()
Handle a mouse event from the video driver.
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
void HandleTextInput(std::string_view str, bool marked=false, std::optional< size_t > caret=std::nullopt, std::optional< size_t > insert_location=std::nullopt, std::optional< size_t > replacement_end=std::nullopt)
Handle text input.
@ WKC_BACKSLASH
\ Backslash
@ WKC_SLASH
/ Forward slash
@ WKC_SINGLEQUOTE
' Single quote
@ WKC_R_BRACKET
] Right square bracket
@ WKC_L_BRACKET
[ Left square bracket
@ WKC_SEMICOLON
; Semicolon
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr T Delta(const T a, const T b)
Returns the (absolute) difference between two (scalar) variables.
bool CopyPalette(Palette &local_palette, bool force_copy)
Copy the current palette if the palette was updated.
Functions related to modal progress.
A number of safeguards to prevent using unsafe methods.
static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc)
Convert a SDL_Keycode into our own key codes.
Base of the SDL2 video driver.
ClientSettings _settings_client
The current settings for this game.
Definition of base types and functions in a cross-platform compatible way.
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
std::vector< std::string > StringList
Type for a list of strings.
Dimensions (a width and height) of a rectangle in 2D.
Specification of a rectangle with absolute coordinates of all edges.
std::pair< size_t, char32_t > DecodeUtf8(std::string_view buf)
Decode a character from UTF-8.
Handling of UTF-8 encoded data.
bool FocusedWindowIsConsole()
Check if a console is focused.
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
Window functions not directly related to making/drawing windows.
@ WC_GAME_OPTIONS
Game options window; Window numbers: