• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 19579900656

21 Nov 2025 06:29PM UTC coverage: 50.493% (+0.01%) from 50.483%
19579900656

Pull #1831

github

web-flow
Merge 112b5cacc into 0713466f4
Pull Request #1831: Fix shortcuts using SDL2 and ALT+Q using WinAPI

11 of 37 new or added lines in 4 files covered. (29.73%)

2 existing lines in 1 file now uncovered.

22523 of 44606 relevant lines covered (50.49%)

34881.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.28
/extras/videoDrivers/SDL2/VideoSDL2.cpp
1
// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "VideoSDL2.h"
6
#include "driver/Interface.h"
7
#include "driver/VideoDriverLoaderInterface.h"
8
#include "driver/VideoInterface.h"
9
#include "enum_cast.hpp"
10
#include "helpers/LSANUtils.h"
11
#include "helpers/containerUtils.h"
12
#include "icon.h"
13
#include "openglCfg.hpp"
14
#include <s25util/utf8.h>
15
#include <boost/nowide/iostream.hpp>
16
#include <SDL.h>
17
#include <algorithm>
18
#include <memory>
19

20
#ifdef _WIN32
21
#    include <boost/nowide/convert.hpp>
22
#    ifndef WIN32_LEAN_AND_MEAN
23
#        define WIN32_LEAN_AND_MEAN
24
#    endif
25
#    include <windows.h> // Avoid "Windows headers require the default packing option" due to SDL2
26
#    include <SDL_syswm.h>
27
#endif // _WIN32
28

29
#define CHECK_SDL(call)                 \
30
    do                                  \
31
    {                                   \
32
        if((call) == -1)                \
33
            PrintError(SDL_GetError()); \
34
    } while(false)
35

36
namespace {
37
template<typename T>
38
struct SDLMemoryDeleter
39
{
40
    void operator()(T* p) const { SDL_free(p); }
×
41
};
42

43
template<typename T>
44
using SDL_memory = std::unique_ptr<T, SDLMemoryDeleter<T>>;
45

NEW
46
void setSpecialKeys(KeyEvent& ke, const SDL_Keymod mod)
×
47
{
NEW
48
    ke.ctrl = (mod & KMOD_CTRL);
×
NEW
49
    ke.shift = (mod & KMOD_SHIFT);
×
NEW
50
    ke.alt = (mod & KMOD_ALT);
×
NEW
51
}
×
NEW
52
void setSpecialKeys(KeyEvent& ke)
×
53
{
NEW
54
    setSpecialKeys(ke, SDL_GetModState());
×
NEW
55
}
×
56
} // namespace
57

58
IVideoDriver* CreateVideoInstance(VideoDriverLoaderInterface* CallBack)
1✔
59
{
60
    return new VideoSDL2(CallBack);
1✔
61
}
62

63
void FreeVideoInstance(IVideoDriver* driver)
1✔
64
{
65
    delete driver;
1✔
66
}
1✔
67

68
const char* GetDriverName()
5✔
69
{
70
    return "(SDL2) OpenGL via SDL2-Library";
5✔
71
}
72

73
VideoSDL2::VideoSDL2(VideoDriverLoaderInterface* CallBack) : VideoDriver(CallBack), window(nullptr), context(nullptr) {}
1✔
74

75
VideoSDL2::~VideoSDL2()
2✔
76
{
77
    CleanUp();
1✔
78
}
2✔
79

80
const char* VideoSDL2::GetName() const
2✔
81
{
82
    return GetDriverName();
2✔
83
}
84

85
bool VideoSDL2::Initialize()
1✔
86
{
87
    initialized = false;
1✔
88
    rttr::ScopedLeakDisabler _;
1✔
89
    if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
1✔
90
    {
91
        PrintError(SDL_GetError());
×
92
        return false;
×
93
    }
94

95
    initialized = true;
1✔
96
    return initialized;
1✔
97
}
98

99
void VideoSDL2::CleanUp()
1✔
100
{
101
    if(!initialized)
1✔
102
        return;
×
103

104
    if(context)
1✔
105
        SDL_GL_DeleteContext(context);
×
106
    if(window)
1✔
107
        SDL_DestroyWindow(window);
×
108
    SDL_QuitSubSystem(SDL_INIT_VIDEO);
1✔
109
    SDL_Quit();
1✔
110
    initialized = false;
1✔
111
}
112

113
void VideoSDL2::UpdateCurrentSizes()
×
114
{
115
    int w, h, w2, h2;
116
    SDL_GetWindowSize(window, &w, &h);
×
117
    SDL_GL_GetDrawableSize(window, &w2, &h2);
×
118
    SetNewSize(VideoMode(w, h), Extent(w2, h2));
×
119
}
×
120

121
bool VideoSDL2::CreateScreen(const std::string& title, const VideoMode& size, bool fullscreen)
×
122
{
123
    if(!initialized)
×
124
        return false;
×
125

126
    // GL-Attributes
127
    CHECK_SDL(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RTTR_OGL_MAJOR));
×
128
    CHECK_SDL(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RTTR_OGL_MINOR));
×
129
    SDL_GLprofile profile;
130
    if((RTTR_OGL_ES))
131
        profile = SDL_GL_CONTEXT_PROFILE_ES;
132
    else if((RTTR_OGL_COMPAT))
133
        profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
×
134
    else
135
        profile = SDL_GL_CONTEXT_PROFILE_CORE;
136
    CHECK_SDL(SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile));
×
137

138
    CHECK_SDL(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8));
×
139
    CHECK_SDL(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8));
×
140
    CHECK_SDL(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8));
×
141
    CHECK_SDL(SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1));
×
142

143
    int wndPos = SDL_WINDOWPOS_CENTERED;
×
144

145
    const auto requestedSize = fullscreen ? FindClosestVideoMode(size) : size;
×
146
    unsigned commonFlags = SDL_WINDOW_OPENGL;
×
147
    // TODO: Fix GUI scaling with High DPI support enabled.
148
    // See https://github.com/Return-To-The-Roots/s25client/issues/1621
149
    // commonFlags |= SDL_WINDOW_ALLOW_HIGHDPI;
150

151
    window = SDL_CreateWindow(title.c_str(), wndPos, wndPos, requestedSize.width, requestedSize.height,
×
152
                              commonFlags | (fullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_RESIZABLE));
×
153

154
    // Fallback to non-fullscreen
155
    if(!window && fullscreen)
×
156
    {
157
        window = SDL_CreateWindow(title.c_str(), wndPos, wndPos, requestedSize.width, requestedSize.height,
×
158
                                  commonFlags | SDL_WINDOW_RESIZABLE);
159
    }
160

161
    if(!window)
×
162
    {
163
        PrintError(SDL_GetError());
×
164
        return false;
×
165
    }
166

167
    isFullscreen_ = (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0;
×
168
    UpdateCurrentSizes();
×
169

170
    if(!isFullscreen_)
×
171
        MoveWindowToCenter();
×
172

173
    SDL_Surface* iconSurf =
174
      SDL_CreateRGBSurfaceFrom(image.data(), 48, 48, 32, 48 * 4, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF);
×
175
    if(iconSurf)
×
176
    {
177
        SDL_SetWindowIcon(window, iconSurf);
×
178
        SDL_FreeSurface(iconSurf);
×
179
    } else
180
        PrintError(SDL_GetError());
×
181

182
    context = SDL_GL_CreateContext(window);
×
183

184
#ifdef _WIN32
185
    SetWindowTextW(GetConsoleWindow(), boost::nowide::widen(title).c_str());
186
#endif
187

188
    std::fill(keyboard.begin(), keyboard.end(), false);
×
189

190
    SDL_ShowCursor(0);
×
191

192
    return true;
×
193
}
194

195
bool VideoSDL2::ResizeScreen(const VideoMode& newSize, bool fullscreen)
×
196
{
197
    if(!initialized)
×
198
        return false;
×
199

200
    if(isFullscreen_ != fullscreen)
×
201
    {
202
        SDL_SetWindowFullscreen(window, fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
×
203
        isFullscreen_ = (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0;
×
204
        if(!isFullscreen_)
×
205
        {
206
            SDL_SetWindowResizable(window, SDL_TRUE);
×
207
            MoveWindowToCenter();
×
208
        }
209
    }
210

211
    if(newSize != GetWindowSize())
×
212
    {
213
        if(isFullscreen_)
×
214
        {
215
            auto const targetMode = FindClosestVideoMode(newSize);
×
216
            SDL_DisplayMode target;
217
            target.w = targetMode.width;
×
218
            target.h = targetMode.height;
×
219
            target.format = 0;           // don't care
×
220
            target.refresh_rate = 0;     // don't care
×
221
            target.driverdata = nullptr; // initialize to 0
×
222
            // Explicitly change the window size to avoid a bug with SDL reporting the wrong size until alt+tab
223
            SDL_SetWindowSize(window, target.w, target.h);
×
224
            if(SDL_SetWindowDisplayMode(window, &target) < 0)
×
225
            {
226
                PrintError(SDL_GetError());
×
227
                return false;
×
228
            }
229
        } else
230
        {
231
            SDL_SetWindowSize(window, newSize.width, newSize.height);
×
232
        }
233
        UpdateCurrentSizes();
×
234
    }
235
    return true;
×
236
}
237

238
void VideoSDL2::PrintError(const std::string& msg) const
×
239
{
240
    boost::nowide::cerr << msg << std::endl;
×
241
}
×
242

243
void VideoSDL2::ShowErrorMessage(const std::string& title, const std::string& message)
×
244
{
245
    // window==nullptr is okay too ("no parent")
246
#ifdef __linux__
247
    // When using window, SDL will try to use a system tool like "zenity" which isn't always installed on every distro
248
    // so rttr will crash. But without a window it will use an x11 backend that should be available on x11 AND wayland
249
    // for compatibility reasons
250
    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.c_str(), message.c_str(), nullptr);
×
251
#else
252
    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.c_str(), message.c_str(), window);
253
#endif
254
}
×
255

256
void VideoSDL2::HandlePaste()
×
257
{
258
    if(!SDL_HasClipboardText())
×
259
        return;
×
260

261
    SDL_memory<char> text(SDL_GetClipboardText());
×
262
    if(!text || *text == '\0') // empty string indicates error
×
263
        PrintError(text ? SDL_GetError() : "Paste failed.");
×
264

265
    for(const char32_t c : s25util::utf8to32(text.get()))
×
NEW
266
        CallBack->Msg_KeyDown(KeyEvent(c));
×
267
}
268

269
void VideoSDL2::DestroyScreen()
×
270
{
271
    CleanUp();
×
272
}
×
273

274
bool VideoSDL2::SwapBuffers()
×
275
{
276
    SDL_GL_SwapWindow(window);
×
277
    return true;
×
278
}
279

280
bool VideoSDL2::MessageLoop()
×
281
{
282
    SDL_Event ev;
283
    while(SDL_PollEvent(&ev))
×
284
    {
285
        switch(ev.type)
×
286
        {
287
            default: break;
×
288

289
            case SDL_QUIT: return false;
×
290
            case SDL_WINDOWEVENT:
×
291
            {
292
                switch(ev.window.event)
×
293
                {
294
                    case SDL_WINDOWEVENT_RESIZED:
×
295
                    {
296
                        isFullscreen_ = (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0;
×
297
                        VideoMode newSize(ev.window.data1, ev.window.data2);
×
298
                        if(newSize != GetWindowSize())
×
299
                        {
300
                            UpdateCurrentSizes();
×
301
                            CallBack->WindowResized();
×
302
                        }
303
                    }
304
                    break;
×
305
                }
306
            }
307
            break;
×
308

309
            case SDL_KEYDOWN:
×
310
            {
NEW
311
                KeyEvent ke;
×
312

313
                switch(ev.key.keysym.sym)
×
314
                {
315
                    default:
×
316
                    {
317
                        // Die 12 F-Tasten
318
                        if(ev.key.keysym.sym >= SDLK_F1 && ev.key.keysym.sym <= SDLK_F12)
×
319
                            ke.kt = static_cast<KeyType>(rttr::enum_cast(KeyType::F1) + ev.key.keysym.sym - SDLK_F1);
×
320
                    }
321
                    break;
×
322
                    case SDLK_RETURN: ke.kt = KeyType::Return; break;
×
323
                    case SDLK_SPACE: ke.kt = KeyType::Space; break;
×
324
                    case SDLK_LEFT: ke.kt = KeyType::Left; break;
×
325
                    case SDLK_RIGHT: ke.kt = KeyType::Right; break;
×
326
                    case SDLK_UP: ke.kt = KeyType::Up; break;
×
327
                    case SDLK_DOWN: ke.kt = KeyType::Down; break;
×
328
                    case SDLK_BACKSPACE: ke.kt = KeyType::Backspace; break;
×
329
                    case SDLK_DELETE: ke.kt = KeyType::Delete; break;
×
330
                    case SDLK_LSHIFT:
×
331
                    case SDLK_RSHIFT: ke.kt = KeyType::Shift; break;
×
332
                    case SDLK_TAB: ke.kt = KeyType::Tab; break;
×
333
                    case SDLK_HOME: ke.kt = KeyType::Home; break;
×
334
                    case SDLK_END: ke.kt = KeyType::End; break;
×
335
                    case SDLK_ESCAPE: ke.kt = KeyType::Escape; break;
×
336
                    case SDLK_PRINTSCREEN: ke.kt = KeyType::Print; break;
×
337
                    // case SDLK_BACKQUOTE: ev.key.keysym.scancode = '^'; break;
338
                    case SDLK_v:
×
339
                        if(SDL_GetModState() & KMOD_CTRL)
×
340
                        {
341
                            HandlePaste();
×
342
                            continue;
×
343
                        }
344
                        break;
×
345
                }
346

NEW
347
                setSpecialKeys(ke, SDL_Keymod(ev.key.keysym.mod));
×
348

NEW
349
                if(ke.kt != KeyType::Invalid)
×
NEW
350
                    CallBack->Msg_KeyDown(ke);
×
NEW
351
                else if(ke.alt || ke.ctrl)
×
352
                {
353
                    // Handle shortcuts (CTRL+x, ALT+y)
354
                    // but not possible combinations (ALT+0054)
NEW
355
                    const SDL_Keycode keycode = ev.key.keysym.sym;
×
NEW
356
                    if(keycode >= 'a' && keycode <= 'z')
×
357
                    {
NEW
358
                        ke.kt = KeyType::Char;
×
NEW
359
                        ke.c = static_cast<char32_t>(keycode);
×
NEW
360
                        CallBack->Msg_KeyDown(ke);
×
361
                    }
362
                }
363
            }
364
            break;
×
365
            case SDL_TEXTINPUT:
×
366
            {
367
                const std::u32string text = s25util::utf8to32(ev.text.text);
×
NEW
368
                KeyEvent ke(0);
×
NEW
369
                setSpecialKeys(ke);
×
UNCOV
370
                for(char32_t c : text)
×
371
                {
NEW
372
                    ke.c = c;
×
373
                    CallBack->Msg_KeyDown(ke);
×
374
                }
375
                break;
×
376
            }
377
            case SDL_MOUSEBUTTONDOWN:
×
378
                mouse_xy.pos = getGuiScale().screenToView(Position(ev.button.x, ev.button.y));
×
379

380
                if(/*!mouse_xy.ldown && */ ev.button.button == SDL_BUTTON_LEFT)
×
381
                {
382
                    mouse_xy.ldown = true;
×
383
                    CallBack->Msg_LeftDown(mouse_xy);
×
384
                }
385
                if(/*!mouse_xy.rdown &&*/ ev.button.button == SDL_BUTTON_RIGHT)
×
386
                {
387
                    mouse_xy.rdown = true;
×
388
                    CallBack->Msg_RightDown(mouse_xy);
×
389
                }
390
                break;
×
391
            case SDL_MOUSEBUTTONUP:
×
392
                mouse_xy.pos = getGuiScale().screenToView(Position(ev.button.x, ev.button.y));
×
393

394
                if(/*mouse_xy.ldown &&*/ ev.button.button == SDL_BUTTON_LEFT)
×
395
                {
396
                    mouse_xy.ldown = false;
×
397
                    CallBack->Msg_LeftUp(mouse_xy);
×
398
                }
399
                if(/*mouse_xy.rdown &&*/ ev.button.button == SDL_BUTTON_RIGHT)
×
400
                {
401
                    mouse_xy.rdown = false;
×
402
                    CallBack->Msg_RightUp(mouse_xy);
×
403
                }
404
                break;
×
405
            case SDL_MOUSEWHEEL:
×
406
            {
407
                int y = ev.wheel.y;
×
408
                if(ev.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
×
409
                    y = -y;
×
410
                if(y > 0)
×
411
                    CallBack->Msg_WheelUp(mouse_xy);
×
412
                else if(y < 0)
×
413
                    CallBack->Msg_WheelDown(mouse_xy);
×
414
            }
415
            break;
×
416
            case SDL_MOUSEMOTION:
×
417
            {
418
                const auto newPos = getGuiScale().screenToView(Position(ev.motion.x, ev.motion.y));
×
419
                // Avoid duplicate events especially when warping the mouse
420
                if(newPos != mouse_xy.pos)
×
421
                {
422
                    mouse_xy.pos = newPos;
×
423
                    CallBack->Msg_MouseMove(mouse_xy);
×
424
                }
425
            }
426
            break;
×
427
        }
428
    }
429

430
    return true;
×
431
}
432

433
unsigned long VideoSDL2::GetTickCount() const
×
434
{
435
    return SDL_GetTicks();
×
436
}
437

438
void VideoSDL2::ListVideoModes(std::vector<VideoMode>& video_modes) const
×
439
{
440
    int display = SDL_GetWindowDisplayIndex(window);
×
441
    if(display < 0)
×
442
        display = 0;
×
443
    for(int i = SDL_GetNumDisplayModes(display) - 1; i >= 0; --i)
×
444
    {
445
        SDL_DisplayMode mode;
446
        if(SDL_GetDisplayMode(display, i, &mode) != 0)
×
447
            PrintError(SDL_GetError());
×
448
        else
449
        {
450
            VideoMode vm(mode.w, mode.h);
×
451
            if(!helpers::contains(video_modes, vm))
×
452
                video_modes.push_back(vm);
×
453
        }
454
    }
455
}
×
456

457
OpenGL_Loader_Proc VideoSDL2::GetLoaderFunction() const
×
458
{
459
    return SDL_GL_GetProcAddress;
×
460
}
461

462
void VideoSDL2::SetMousePos(Position pos)
×
463
{
464
    const auto screenPos = getGuiScale().viewToScreen(pos);
×
465
    mouse_xy.pos = pos;
×
466
    SDL_WarpMouseInWindow(window, screenPos.x, screenPos.y);
×
467
}
×
468

469
KeyEvent VideoSDL2::GetModKeyState() const
×
470
{
NEW
471
    KeyEvent ke;
×
NEW
472
    setSpecialKeys(ke);
×
UNCOV
473
    return ke;
×
474
}
475

476
void* VideoSDL2::GetMapPointer() const
×
477
{
478
#ifdef WIN32
479
    SDL_SysWMinfo wmInfo;
480
    SDL_VERSION(&wmInfo.version);
481
    SDL_GetWindowWMInfo(window, &wmInfo);
482
    // return (void*)wmInfo.info.win.window;
483
    return (void*)wmInfo.info.win.window;
484
#else
485
    return nullptr;
×
486
#endif
487
}
488

489
void VideoSDL2::MoveWindowToCenter()
×
490
{
491
    SDL_Rect usableBounds;
492
    CHECK_SDL(SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(window), &usableBounds));
×
493
    int top, left, bottom, right;
494
    CHECK_SDL(SDL_GetWindowBordersSize(window, &top, &left, &bottom, &right));
×
495
    usableBounds.w -= left + right;
×
496
    usableBounds.h -= top + bottom;
×
497
    if(usableBounds.w < GetWindowSize().width || usableBounds.h < GetWindowSize().height)
×
498
    {
499
        SDL_SetWindowSize(window, usableBounds.w, usableBounds.h);
×
500
        UpdateCurrentSizes();
×
501
    }
502
    SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
×
503
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc