11#include "../openttd.h"
12#include "../gfx_func.h"
13#include "../blitter/factory.hpp"
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 "../core/utf8.hpp"
21#include "../fileio_func.h"
22#include "../framerate_type.h"
23#include "../window_func.h"
27# include <emscripten.h>
28# include <emscripten/html5.h>
31#include "../safeguards.h"
35 Rect r = {left, top, left + width, top + height};
42 this->
MakeDirty(0, 0, _screen.width, _screen.height);
45static const Dimension default_resolutions[] = {
59static void FindResolutions()
63 for (
int display = 0; display < SDL_GetNumVideoDisplays(); display++) {
64 for (
int i = 0; i < SDL_GetNumDisplayModes(display); i++) {
66 SDL_GetDisplayMode(display, i, &mode);
68 if (mode.w < 640 || mode.h < 480)
continue;
76 _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
82static void GetAvailableVideoMode(uint *w, uint *h)
95 if (newdelta < delta) {
104static uint FindStartupDisplay(uint startup_display)
106 int num_displays = SDL_GetNumVideoDisplays();
109 if (
IsInsideBS(startup_display, 0, num_displays))
return startup_display;
113 SDL_GetGlobalMouseState(&mx, &my);
114 for (
int display = 0; display < num_displays; ++display) {
116 if (SDL_GetDisplayBounds(display, &r) == 0 &&
IsInsideBS(mx, r.x, r.w) &&
IsInsideBS(my, r.y, r.h)) {
117 Debug(driver, 1,
"SDL2: Mouse is at ({}, {}), use display {} ({}, {}, {}, {})", mx, my, display, r.x, r.y, r.w, r.h);
141 flags |= SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
144 flags |= SDL_WINDOW_FULLSCREEN;
147 int x = SDL_WINDOWPOS_UNDEFINED, y = SDL_WINDOWPOS_UNDEFINED;
149 if (SDL_GetDisplayBounds(this->startup_display, &r) == 0) {
150 x = r.x + std::max(0, r.w -
static_cast<int>(w)) / 2;
151 y = r.y + std::max(0, r.h -
static_cast<int>(h)) / 4;
162 Debug(driver, 0,
"SDL2: Couldn't allocate a window to draw on: {}", SDL_GetError());
167 if (!icon_path.empty()) {
169 SDL_Surface *icon = SDL_LoadBMP(icon_path.c_str());
170 if (icon !=
nullptr) {
172 uint32_t rgbmap = SDL_MapRGB(icon->format, 255, 0, 255);
174 SDL_SetColorKey(icon, SDL_TRUE, rgbmap);
176 SDL_FreeSurface(icon);
183bool VideoDriver_SDL_Base::CreateMainSurface(uint w, uint h,
bool resize)
185 GetAvailableVideoMode(&w, &h);
186 Debug(driver, 1,
"SDL2: using mode {}x{}", w, h);
189 if (resize) SDL_SetWindowSize(this->
sdl_window, w, h);
195 if (_fullscreen) _cursor.
in_window =
true;
200bool VideoDriver_SDL_Base::ClaimMousePointer()
203#ifndef __EMSCRIPTEN__
215 SDL_StartTextInput();
233 std::vector<int> rates = {};
234 for (
int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
235 SDL_DisplayMode mode = {};
236 if (SDL_GetDisplayMode(i, 0, &mode) != 0)
continue;
237 if (mode.refresh_rate != 0) rates.push_back(mode.refresh_rate);
244 const SDL_Keycode vk_from;
245 const uint8_t vk_count;
246 const uint8_t map_to;
247 const bool unprintable;
249 constexpr SDLVkMapping(SDL_Keycode vk_first, SDL_Keycode vk_last, uint8_t map_first, [[maybe_unused]] uint8_t map_last,
bool unprintable)
250 : vk_from(vk_first), vk_count(vk_last - vk_first + 1), map_to(map_first), unprintable(unprintable)
252 assert((vk_last - vk_first) == (map_last - map_first));
256#define AS(x, z) {x, x, z, z, false}
257#define AM(x, y, z, w) {x, y, z, w, false}
258#define AS_UP(x, z) {x, x, z, z, true}
259#define AM_UP(x, y, z, w) {x, y, z, w, true}
263 AS_UP(SDLK_PAGEUP, WKC_PAGEUP),
264 AS_UP(SDLK_PAGEDOWN, WKC_PAGEDOWN),
265 AS_UP(SDLK_UP, WKC_UP),
266 AS_UP(SDLK_DOWN, WKC_DOWN),
267 AS_UP(SDLK_LEFT, WKC_LEFT),
268 AS_UP(SDLK_RIGHT, WKC_RIGHT),
270 AS_UP(SDLK_HOME, WKC_HOME),
271 AS_UP(SDLK_END, WKC_END),
273 AS_UP(SDLK_INSERT, WKC_INSERT),
274 AS_UP(SDLK_DELETE, WKC_DELETE),
277 AM(SDLK_a, SDLK_z,
'A',
'Z'),
278 AM(SDLK_0, SDLK_9,
'0',
'9'),
280 AS_UP(SDLK_ESCAPE, WKC_ESC),
281 AS_UP(SDLK_PAUSE, WKC_PAUSE),
282 AS_UP(SDLK_BACKSPACE, WKC_BACKSPACE),
284 AS(SDLK_SPACE, WKC_SPACE),
285 AS(SDLK_RETURN, WKC_RETURN),
286 AS(SDLK_TAB, WKC_TAB),
289 AM_UP(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12),
302 AS(SDLK_KP_DIVIDE, WKC_NUM_DIV),
303 AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL),
304 AS(SDLK_KP_MINUS, WKC_NUM_MINUS),
305 AS(SDLK_KP_PLUS, WKC_NUM_PLUS),
306 AS(SDLK_KP_ENTER, WKC_NUM_ENTER),
307 AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL),
323static uint ConvertSdlKeyIntoMy(SDL_Keysym *sym,
char32_t *character)
326 bool unprintable =
false;
328 for (
const auto &map : _vk_mapping) {
329 if (
IsInsideBS(sym->sym, map.vk_from, map.vk_count)) {
330 key = sym->sym - map.vk_from + map.map_to;
331 unprintable = map.unprintable;
337 if (sym->scancode == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
340 if (sym->mod & KMOD_GUI) key |= WKC_META;
341 if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT;
342 if (sym->mod & KMOD_CTRL) key |= WKC_CTRL;
343 if (sym->mod & KMOD_ALT) key |= WKC_ALT;
346 if (sym->mod & KMOD_GUI ||
347 sym->mod & KMOD_CTRL ||
348 sym->mod & KMOD_ALT ||
350 *character = WKC_NONE;
352 *character = sym->sym;
366 for (
const auto &map : _vk_mapping) {
367 if (
IsInsideBS(kc, map.vk_from, map.vk_count)) {
368 key = kc - map.vk_from + map.map_to;
375 SDL_Scancode sc = SDL_GetScancodeFromKey(kc);
376 if (sc == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
385 if (!SDL_PollEvent(&ev))
return false;
388 case SDL_MOUSEMOTION: {
389 int32_t x = ev.motion.x;
390 int32_t y = ev.motion.y;
395 while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION)) {
408 case SDL_MOUSEWHEEL: {
409 if (ev.wheel.y > 0) {
411 }
else if (ev.wheel.y < 0) {
416 const float SCROLL_BUILTIN_MULTIPLIER = 14.0f;
417#if SDL_VERSION_ATLEAST(2, 18, 0)
424 _cursor.wheel_moved =
true;
428 case SDL_MOUSEBUTTONDOWN:
430 ev.button.button = SDL_BUTTON_RIGHT;
433 switch (ev.button.button) {
434 case SDL_BUTTON_LEFT:
438 case SDL_BUTTON_RIGHT:
448 case SDL_MOUSEBUTTONUP:
453 }
else if (ev.button.button == SDL_BUTTON_LEFT) {
456 }
else if (ev.button.button == SDL_BUTTON_RIGHT) {
463 HandleExitGameRequest();
467 if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_GUI)) &&
468 (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) {
469 if (ev.key.repeat == 0) ToggleFullScreen(!_fullscreen);
473 uint keycode = ConvertSdlKeyIntoMy(&ev.key.keysym, &character);
477 keycode == WKC_DELETE ||
478 keycode == WKC_NUM_ENTER ||
479 keycode == WKC_LEFT ||
480 keycode == WKC_RIGHT ||
482 keycode == WKC_DOWN ||
483 keycode == WKC_HOME ||
484 keycode == WKC_END ||
485 keycode & WKC_META ||
486 keycode & WKC_CTRL ||
488 (keycode >= WKC_F1 && keycode <= WKC_F12) ||
495 case SDL_TEXTINPUT: {
497 SDL_Keycode kc = SDL_GetKeyFromName(ev.text.text);
508 case SDL_WINDOWEVENT: {
509 if (ev.window.event == SDL_WINDOWEVENT_EXPOSED) {
511 this->
MakeDirty(0, 0, _screen.width, _screen.height);
512 }
else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
513 int w = std::max(ev.window.data1, 64);
514 int h = std::max(ev.window.data2, 64);
515 CreateMainSurface(w, h, w != ev.window.data1 || h != ev.window.data2);
516 }
else if (ev.window.event == SDL_WINDOWEVENT_ENTER) {
520 SDL_SetRelativeMouseMode(SDL_FALSE);
521 }
else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) {
533static std::optional<std::string_view> InitializeSDL()
536 if (SDL_WasInit(SDL_INIT_VIDEO) != 0)
return std::nullopt;
538#ifdef SDL_HINT_APP_NAME
539 SDL_SetHint(SDL_HINT_APP_NAME,
"OpenTTD");
542 if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
return SDL_GetError();
546std::optional<std::string_view> VideoDriver_SDL_Base::Initialize()
550 auto error = InitializeSDL();
551 if (error)
return error;
563 auto error = this->Initialize();
564 if (error)
return error;
566#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
571 if (!SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE,
"0"))
return SDL_GetError();
575 this->startup_display = FindStartupDisplay(
GetDriverParamInt(param,
"display", -1));
578 return SDL_GetError();
581 const char *dname = SDL_GetCurrentVideoDriver();
582 Debug(driver, 1,
"SDL2: using driver '{}'", dname);
595 this->is_game_threaded =
false;
605 SDL_QuitSubSystem(SDL_INIT_VIDEO);
606 if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
613 uint32_t mod = SDL_GetModState();
614 const Uint8 *keys = SDL_GetKeyboardState(
nullptr);
627 (keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
628 (keys[SDL_SCANCODE_UP] ? 2 : 0) |
629 (keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
630 (keys[SDL_SCANCODE_DOWN] ? 8 : 0);
635void VideoDriver_SDL_Base::LoopOnce()
645 extern void PostMainLoop();
648 emscripten_cancel_main_loop();
649 emscripten_exit_pointerlock();
653 if (_game_mode == GM_BOOTSTRAP) {
654 EM_ASM(
if (window[
"openttd_bootstrap_reload"]) openttd_bootstrap_reload());
656 EM_ASM(
if (window[
"openttd_exit"]) openttd_exit());
666#ifndef __EMSCRIPTEN__
675 emscripten_set_main_loop_arg(&this->EmscriptenLoop,
this, 0, 1);
679 while (!_exit_game) {
689 return CreateMainSurface(w, h,
true);
701 if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(this->
sdl_window), &dm) < 0) {
702 Debug(driver, 0,
"SDL_GetCurrentDisplayMode() failed: {}", SDL_GetError());
704 SDL_SetWindowSize(this->
sdl_window, dm.w, dm.h);
708 Debug(driver, 1,
"SDL2: Setting {}", fullscreen ?
"fullscreen" :
"windowed");
709 int ret = SDL_SetWindowFullscreen(this->
sdl_window, fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
712 _fullscreen = fullscreen;
713 if (!fullscreen) SDL_SetWindowSize(this->
sdl_window, w, h);
715 Debug(driver, 0,
"SDL_SetWindowFullscreen() failed: {}", SDL_GetError());
727 return CreateMainSurface(w, h,
false);
732 SDL_DisplayMode mode;
735 return {
static_cast<uint
>(mode.w),
static_cast<uint
>(mode.h) };
744 assert(_screen.dst_ptr !=
nullptr);
751 if (_screen.dst_ptr !=
nullptr) {
754 _screen.dst_ptr =
nullptr;
#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.
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-side 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.
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.
std::pair< size_t, char32_t > DecodeUtf8(std::string_view buf)
Decode a character from UTF-8.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
int GetDriverParamInt(const StringList &parm, const char *name, int def)
Get an integer 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.
bool GetDriverParamBool(const StringList &parm, const char *name)
Get a boolean parameter the list of parameters.
std::string FioFindFullPath(Subdirectory subdir, const std::string &filename)
Find a path to the filename in one of the search directories.
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
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?
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(const char *str, bool marked=false, const char *caret=nullptr, const char *insert_location=nullptr, const char *replacement_end=nullptr)
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.
static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc)
Like ConvertSdlKeyIntoMy(), but takes an SDL_Keycode as input instead of an SDL_Keysym.
Base of the SDL2 video driver.
ClientSettings _settings_client
The current settings for this game.
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.
GUISettings gui
settings related to the GUI
bool UpdateCursorPosition(int x, int y)
Update cursor position on mouse movement.
bool fix_at
mouse is moving, but cursor is not (used for scrolling)
Point pos
logical mouse position
bool in_window
mouse inside this window, determines drawing logic
int wheel
mouse wheel movement
Dimensions (a width and height) of a rectangle in 2D.
uint8_t scrollwheel_multiplier
how much 'wheel' per incoming event from the OS?
Specification of a rectangle with absolute coordinates of all edges.
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...
@ WC_GAME_OPTIONS
Game options window; Window numbers: