Fix UI scale for X11.

Fix page up and page down keys not working.
Fix mouse wheel not working
This commit is contained in:
Magnus Norddahl 2025-04-22 12:05:09 +02:00
commit fe452faadc
4 changed files with 85 additions and 55 deletions

View file

@ -1,10 +1,65 @@
#include "sdl2_display_backend.h"
#include "sdl2_display_window.h"
#include <stdexcept>
#include <SDL2/SDL_video.h>
#ifndef WIN32
#include <dlfcn.h>
#endif
namespace X11DPI
{
typedef struct _XDisplay Display;
typedef Display *(*XOpenDisplayPtr)(const char*);
typedef char *(*XGetDefaultPtr)(Display*, const char*, const char*);
typedef int (*XCloseDisplayPtr)(Display*);
}
SDL2DisplayBackend::SDL2DisplayBackend()
{
int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
if (result != 0)
throw std::runtime_error(std::string("Unable to initialize SDL:") + SDL_GetError());
SDL2DisplayWindow::PaintEventNumber = SDL_RegisterEvents(1);
// SDL2 doesn't have proper native hidpi support for Linux. Cheat a bit here by asking X11 ourselves.
#if !defined(WIN32) && !defined(__APPLE__)
const char* driver = SDL_GetCurrentVideoDriver();
if (driver && strcmp(driver, "x11") == 0)
{
void* module = dlopen("libX11.so", RTLD_NOW | RTLD_LOCAL);
if (module)
{
using namespace X11DPI;
auto XOpenDisplay = (XOpenDisplayPtr)dlsym(module, "XOpenDisplay");
auto XGetDefault = (XGetDefaultPtr)dlsym(module, "XGetDefault");
auto XCloseDisplay = (XCloseDisplayPtr)dlsym(module, "XCloseDisplay");
if (XOpenDisplay && XGetDefault && XCloseDisplay)
{
auto display = XOpenDisplay(nullptr);
if (display)
{
char* value = XGetDefault(display, "Xft", "dpi");
if (value)
{
int dpi = std::atoi(value);
if (dpi != 0)
{
UIScale = dpi / 96.0;
}
}
XCloseDisplay(display);
}
}
}
}
#endif
}
std::unique_ptr<DisplayWindow> SDL2DisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI)
{
return std::make_unique<SDL2DisplayWindow>(windowHost, popupWindow, static_cast<SDL2DisplayWindow*>(owner), renderAPI);
return std::make_unique<SDL2DisplayWindow>(windowHost, popupWindow, static_cast<SDL2DisplayWindow*>(owner), renderAPI, UIScale);
}
void SDL2DisplayBackend::ProcessEvents()
@ -24,7 +79,12 @@ void SDL2DisplayBackend::ExitLoop()
Size SDL2DisplayBackend::GetScreenSize()
{
return SDL2DisplayWindow::GetScreenSize();
SDL_Rect rect = {};
int result = SDL_GetDisplayBounds(0, &rect);
if (result != 0)
throw std::runtime_error(std::string("Unable to get screen size:") + SDL_GetError());
return Size(rect.w / UIScale, rect.h / UIScale);
}
void* SDL2DisplayBackend::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)

View file

@ -5,6 +5,8 @@
class SDL2DisplayBackend : public DisplayBackend
{
public:
SDL2DisplayBackend();
std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override;
void ProcessEvents() override;
void RunLoop() override;
@ -16,4 +18,7 @@ public:
Size GetScreenSize() override;
bool IsSDL2() override { return true; }
private:
double UIScale = 1.0;
};

View file

@ -7,28 +7,8 @@ Uint32 SDL2DisplayWindow::PaintEventNumber = 0xffffffff;
bool SDL2DisplayWindow::ExitRunLoop;
std::unordered_map<int, SDL2DisplayWindow*> SDL2DisplayWindow::WindowList;
class InitSDL
SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI, double uiscale) : WindowHost(windowHost), UIScale(uiscale)
{
public:
InitSDL()
{
int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
if (result != 0)
throw std::runtime_error(std::string("Unable to initialize SDL:") + SDL_GetError());
SDL2DisplayWindow::PaintEventNumber = SDL_RegisterEvents(1);
}
};
static void CheckInitSDL()
{
static InitSDL initsdl;
}
SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost)
{
CheckInitSDL();
unsigned int flags = SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/;
if (renderAPI == RenderAPI::Vulkan)
flags |= SDL_WINDOW_VULKAN;
@ -121,8 +101,8 @@ void SDL2DisplayWindow::SetClientFrame(const Rect& box)
int w = (int)std::round(box.width * uiscale);
int h = (int)std::round(box.height * uiscale);
SDL_SetWindowPosition(Handle.window, x, y);
SDL_SetWindowSize(Handle.window, w, h);
SDL_SetWindowPosition(Handle.window, x, y);
}
void SDL2DisplayWindow::Show()
@ -288,7 +268,8 @@ int SDL2DisplayWindow::GetPixelHeight() const
double SDL2DisplayWindow::GetDpiScale() const
{
// SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs.
return 1.0;
// We currently cheat by asking X11 directly. See the SDL2DisplayBackend constructor.
return UIScale;
}
void SDL2DisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels)
@ -360,8 +341,6 @@ void SDL2DisplayWindow::SetClipboardText(const std::string& text)
void SDL2DisplayWindow::ProcessEvents()
{
CheckInitSDL();
SDL_Event event;
while (SDL_PollEvent(&event) != 0)
{
@ -371,8 +350,6 @@ void SDL2DisplayWindow::ProcessEvents()
void SDL2DisplayWindow::RunLoop()
{
CheckInitSDL();
ExitRunLoop = false;
while (!ExitRunLoop)
{
@ -385,28 +362,11 @@ void SDL2DisplayWindow::RunLoop()
void SDL2DisplayWindow::ExitLoop()
{
CheckInitSDL();
ExitRunLoop = true;
}
Size SDL2DisplayWindow::GetScreenSize()
{
CheckInitSDL();
SDL_Rect rect = {};
int result = SDL_GetDisplayBounds(0, &rect);
if (result != 0)
throw std::runtime_error(std::string("Unable to get screen size:") + SDL_GetError());
double uiscale = 1.0; // SDL2 doesn't really support this properly. SDL_GetDisplayDPI returns the wrong information according to the docs.
return Size(rect.w / uiscale, rect.h / uiscale);
}
void* SDL2DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
CheckInitSDL();
// To do: implement timers
return nullptr;
@ -414,8 +374,6 @@ void* SDL2DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void(
void SDL2DisplayWindow::StopTimer(void* timerID)
{
CheckInitSDL();
// To do: implement timers
}
@ -523,11 +481,15 @@ void SDL2DisplayWindow::OnMouseButtonDown(const SDL_MouseButtonEvent& event)
void SDL2DisplayWindow::OnMouseWheel(const SDL_MouseWheelEvent& event)
{
InputKey key = (event.y > 0) ? InputKey::MouseWheelUp : (event.y < 0) ? InputKey::MouseWheelDown : InputKey::None;
if (key != InputKey::None)
{
WindowHost->OnWindowMouseWheel(GetMousePos(event), key);
}
int x = 0, y = 0;
SDL_GetMouseState(&x, &y);
double uiscale = GetDpiScale();
Point mousepos(x / uiscale, y / uiscale);
if (event.y > 0)
WindowHost->OnWindowMouseWheel(mousepos, InputKey::MouseWheelUp);
else if (event.y < 0)
WindowHost->OnWindowMouseWheel(mousepos, InputKey::MouseWheelDown);
}
void SDL2DisplayWindow::OnMouseMotion(const SDL_MouseMotionEvent& event)
@ -560,7 +522,9 @@ InputKey SDL2DisplayWindow::ScancodeToInputKey(SDL_Scancode keycode)
case SDL_SCANCODE_ESCAPE: return InputKey::Escape;
case SDL_SCANCODE_SPACE: return InputKey::Space;
case SDL_SCANCODE_END: return InputKey::End;
case SDL_SCANCODE_PAGEDOWN: return InputKey::PageDown;
case SDL_SCANCODE_HOME: return InputKey::Home;
case SDL_SCANCODE_PAGEUP: return InputKey::PageUp;
case SDL_SCANCODE_LEFT: return InputKey::Left;
case SDL_SCANCODE_UP: return InputKey::Up;
case SDL_SCANCODE_RIGHT: return InputKey::Right;

View file

@ -9,7 +9,7 @@
class SDL2DisplayWindow : public DisplayWindow
{
public:
SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI);
SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, SDL2DisplayWindow* owner, RenderAPI renderAPI, double uiscale);
~SDL2DisplayWindow();
void SetWindowTitle(const std::string& text) override;
@ -83,7 +83,6 @@ public:
static void ProcessEvents();
static void RunLoop();
static void ExitLoop();
static Size GetScreenSize();
static void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer);
static void StopTimer(void* timerID);
@ -95,6 +94,8 @@ public:
int BackBufferWidth = 0;
int BackBufferHeight = 0;
double UIScale = 1.0;
bool CursorLocked = false;
bool isFullscreen = false;