From 56d6836cebcf775b2246ee3def1d39a29c8d28dd Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 13:21:52 +0100
Subject: [PATCH 1/9] [client,sdl] update mouse pointer when mouse enters

---
 client/SDL/SDL3/sdl_context.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp
index 75df27721b29..9655c54c29cd 100644
--- a/client/SDL/SDL3/sdl_context.cpp
+++ b/client/SDL/SDL3/sdl_context.cpp
@@ -1150,6 +1150,8 @@ bool SdlContext::handleEvent(const SDL_Event& ev)
 		case SDL_EVENT_RENDER_DEVICE_RESET:
 		case SDL_EVENT_WILL_ENTER_FOREGROUND:
 			return redraw();
+		case SDL_EVENT_WINDOW_MOUSE_ENTER:
+			return sdl_Pointer_Set_Process(this);
 		default:
 			return true;
 	}

From 3eb660ace5796e26dee51b22206f9bc8693e9c43 Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 13:02:02 +0100
Subject: [PATCH 2/9] [client,sdl] remove display id from title

---
 client/SDL/SDL3/sdl_context.cpp | 3 ---
 client/SDL/SDL3/sdl_window.cpp  | 7 ++-----
 2 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp
index 9655c54c29cd..9351c7adfb5f 100644
--- a/client/SDL/SDL3/sdl_context.cpp
+++ b/client/SDL/SDL3/sdl_context.cpp
@@ -411,9 +411,6 @@ bool SdlContext::createWindows()
 		if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
 			flags |= SDL_WINDOW_BORDERLESS;
 
-		std::stringstream ss;
-		ss << title << ":" << x;
-
 		auto did = WINPR_ASSERTING_INT_CAST(SDL_DisplayID, id);
 		auto window = SdlWindow::create(did, title, flags, w, h);
 
diff --git a/client/SDL/SDL3/sdl_window.cpp b/client/SDL/SDL3/sdl_window.cpp
index be1241d3ea27..b5e2dd90c042 100644
--- a/client/SDL/SDL3/sdl_window.cpp
+++ b/client/SDL/SDL3/sdl_window.cpp
@@ -328,11 +328,8 @@ SdlWindow SdlWindow::create(SDL_DisplayID id, const std::string& title, Uint32 f
 		height = static_cast<Uint32>(rect.h);
 	}
 
-	std::stringstream ss;
-	ss << title << ":" << id;
-	SdlWindow window{
-		ss.str(), startupX, startupY, static_cast<int>(width), static_cast<int>(height), flags
-	};
+	SdlWindow window{ title, startupX, startupY, static_cast<int>(width), static_cast<int>(height),
+		              flags };
 
 	if ((flags & (SDL_WINDOW_FULLSCREEN)) != 0)
 	{

From 1be664218c7dc622d2fa6d7ed028e9855cf03cd5 Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 13:08:28 +0100
Subject: [PATCH 3/9] [client,sdl] simplify window creation

---
 client/SDL/SDL3/sdl_window.cpp | 33 +++++++++++++--------------------
 client/SDL/SDL3/sdl_window.hpp |  3 +--
 2 files changed, 14 insertions(+), 22 deletions(-)

diff --git a/client/SDL/SDL3/sdl_window.cpp b/client/SDL/SDL3/sdl_window.cpp
index b5e2dd90c042..0a1aa0fcf756 100644
--- a/client/SDL/SDL3/sdl_window.cpp
+++ b/client/SDL/SDL3/sdl_window.cpp
@@ -23,15 +23,14 @@
 #include "sdl_window.hpp"
 #include "sdl_utils.hpp"
 
-SdlWindow::SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
-                     Sint32 height, [[maybe_unused]] Uint32 flags)
+SdlWindow::SdlWindow(const std::string& title, const SDL_Rect& rect, [[maybe_unused]] Uint32 flags)
 {
 	auto props = SDL_CreateProperties();
 	SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str());
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, startupX);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, startupY);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
-	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, rect.x);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, rect.y);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, rect.w);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, rect.h);
 
 	if (flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)
 		SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
@@ -47,8 +46,8 @@ SdlWindow::SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY,
 
 	auto sc = scale();
 	const int iscale = static_cast<int>(sc * 100.0f);
-	auto w = 100 * width / iscale;
-	auto h = 100 * height / iscale;
+	auto w = 100 * rect.w / iscale;
+	auto h = 100 * rect.h / iscale;
 	std::ignore = resize({ w, h });
 	SDL_SetHint(SDL_HINT_APP_NAME, "");
 	std::ignore = SDL_SyncWindow(_window);
@@ -315,26 +314,20 @@ SdlWindow SdlWindow::create(SDL_DisplayID id, const std::string& title, Uint32 f
                             Uint32 height)
 {
 	flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
-	auto startupX = static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id));
-	auto startupY = static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id));
+
+	SDL_Rect rect = { static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)),
+		              static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)), static_cast<int>(width),
+		              static_cast<int>(height) };
 
 	if ((flags & SDL_WINDOW_FULLSCREEN) != 0)
 	{
-		SDL_Rect rect = {};
-		SDL_GetDisplayBounds(id, &rect);
-		startupX = rect.x;
-		startupY = rect.y;
-		width = static_cast<Uint32>(rect.w);
-		height = static_cast<Uint32>(rect.h);
+		std::ignore = SDL_GetDisplayBounds(id, &rect);
 	}
 
-	SdlWindow window{ title, startupX, startupY, static_cast<int>(width), static_cast<int>(height),
-		              flags };
+	SdlWindow window{ title, rect, flags };
 
 	if ((flags & (SDL_WINDOW_FULLSCREEN)) != 0)
 	{
-		SDL_Rect rect = {};
-		SDL_GetDisplayBounds(id, &rect);
 		window.setOffsetX(rect.x);
 		window.setOffsetY(rect.y);
 	}
diff --git a/client/SDL/SDL3/sdl_window.hpp b/client/SDL/SDL3/sdl_window.hpp
index c2a8e59732b6..ae6a72486d7a 100644
--- a/client/SDL/SDL3/sdl_window.hpp
+++ b/client/SDL/SDL3/sdl_window.hpp
@@ -80,8 +80,7 @@ class SdlWindow
 	void updateSurface();
 
   protected:
-	SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
-	          Sint32 height, Uint32 flags);
+	SdlWindow(const std::string& title, const SDL_Rect& rect, Uint32 flags);
 
   private:
 	SDL_Window* _window = nullptr;

From 2bec6913a8775ca241090c8f70e3e152aa8b45d8 Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 18:53:37 +0100
Subject: [PATCH 4/9] [client,sdl] fix multimonitor mouse coordinates

apply monitor offset to window coordinates.
---
 client/SDL/SDL3/sdl_context.cpp | 13 +++++++++++++
 client/SDL/SDL3/sdl_context.hpp |  2 ++
 2 files changed, 15 insertions(+)

diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp
index 9351c7adfb5f..798e1bfd3b20 100644
--- a/client/SDL/SDL3/sdl_context.cpp
+++ b/client/SDL/SDL3/sdl_context.cpp
@@ -760,6 +760,16 @@ int SdlContext::error_info_to_error(DWORD* pcode, char** msg, size_t* len) const
 	return exit_code;
 }
 
+void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y)
+{
+	if (!freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
+		return;
+
+	auto w = getWindowForId(window);
+	x -= static_cast<float>(w->offsetX());
+	y -= static_cast<float>(w->offsetY());
+}
+
 bool SdlContext::drawToWindow(SdlWindow& window, const std::vector<SDL_Rect>& rects)
 {
 	if (!isConnected())
@@ -897,6 +907,7 @@ bool SdlContext::handleEvent(const SDL_MouseMotionEvent& ev)
 		return false;
 	removeLocalScaling(copy.motion.x, copy.motion.y);
 	removeLocalScaling(copy.motion.xrel, copy.motion.yrel);
+	applyMonitorOffset(copy.motion.windowID, copy.motion.x, copy.motion.y);
 
 	return SdlTouch::handleEvent(this, copy.motion);
 }
@@ -1013,6 +1024,7 @@ bool SdlContext::handleEvent(const SDL_MouseButtonEvent& ev)
 	if (!eventToPixelCoordinates(ev.windowID, copy))
 		return false;
 	removeLocalScaling(copy.button.x, copy.button.y);
+	applyMonitorOffset(copy.button.windowID, copy.button.x, copy.button.y);
 	return SdlTouch::handleEvent(this, copy.button);
 }
 
@@ -1024,6 +1036,7 @@ bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev)
 		return false;
 	removeLocalScaling(copy.tfinger.dx, copy.tfinger.dy);
 	removeLocalScaling(copy.tfinger.x, copy.tfinger.y);
+	applyMonitorOffset(copy.tfinger.windowID, copy.tfinger.x, copy.tfinger.y);
 	return SdlTouch::handleEvent(this, copy.tfinger);
 }
 
diff --git a/client/SDL/SDL3/sdl_context.hpp b/client/SDL/SDL3/sdl_context.hpp
index e83b1b0b2a98..fab4373f80bb 100644
--- a/client/SDL/SDL3/sdl_context.hpp
+++ b/client/SDL/SDL3/sdl_context.hpp
@@ -162,6 +162,8 @@ class SdlContext
 
 	[[nodiscard]] int error_info_to_error(DWORD* pcode, char** msg, size_t* len) const;
 
+	void applyMonitorOffset(SDL_WindowID window, float& x, float& y);
+
 	rdpContext* _context = nullptr;
 	wLog* _log = nullptr;
 

From fe9a1b0eedcebc1d8722662c54df4a5d3993444b Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 19:05:58 +0100
Subject: [PATCH 5/9] [client,sdl] add error checks and logs

---
 client/SDL/SDL3/sdl_context.cpp | 3 +++
 client/SDL/SDL3/sdl_input.cpp   | 8 +++-----
 client/common/client.c          | 4 ++--
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp
index 798e1bfd3b20..573c375678be 100644
--- a/client/SDL/SDL3/sdl_context.cpp
+++ b/client/SDL/SDL3/sdl_context.cpp
@@ -664,7 +664,10 @@ int SdlContext::sdl_client_thread_run(std::string& error_msg)
 		const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
 
 		if (status == WAIT_FAILED)
+		{
+			WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects WAIT_FAILED");
 			break;
+		}
 
 		if (!freerdp_check_event_handles(context()))
 		{
diff --git a/client/SDL/SDL3/sdl_input.cpp b/client/SDL/SDL3/sdl_input.cpp
index 1d9ef0f5b9c8..6e7c3e7b3220 100644
--- a/client/SDL/SDL3/sdl_input.cpp
+++ b/client/SDL/SDL3/sdl_input.cpp
@@ -309,16 +309,14 @@ bool sdlInput::keyboard_focus_in()
 	WINPR_ASSERT(input);
 
 	auto syncFlags = sdl_get_kbd_flags();
-	freerdp_input_send_focus_in_event(input, WINPR_ASSERTING_INT_CAST(uint16_t, syncFlags));
+	if (!freerdp_input_send_focus_in_event(input, WINPR_ASSERTING_INT_CAST(uint16_t, syncFlags)))
+		return false;
 
 	/* finish with a mouse pointer position like mstsc.exe if required */
 	// TODO: fullscreen/remote app
 	float fx = 0.0f;
 	float fy = 0.0f;
-	if (_sdl->fullscreen())
-		SDL_GetGlobalMouseState(&fx, &fy);
-	else
-		SDL_GetMouseState(&fx, &fy);
+	SDL_GetMouseState(&fx, &fy);
 
 	auto w = SDL_GetMouseFocus();
 	const auto& pos = _sdl->screenToPixel(SDL_GetWindowID(w), SDL_FPoint{ fx, fy });
diff --git a/client/common/client.c b/client/common/client.c
index 487554dd9192..e4f99a7f0565 100644
--- a/client/common/client.c
+++ b/client/common/client.c
@@ -1771,8 +1771,8 @@ BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative, UIN
 			cctx->lastX = x;
 			cctx->lastY = y;
 		}
-		freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
-		                               (UINT16)cctx->lastY);
+		return freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
+		                                      (UINT16)cctx->lastY);
 	}
 	return TRUE;
 }

From 241f338ad1f31004be4579ffc0264848931da935 Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 20:51:03 +0100
Subject: [PATCH 6/9] [client,sdl] log all sdl_run errors

---
 client/SDL/SDL3/sdl_freerdp.cpp | 65 ++++++++++++++++++++-------------
 1 file changed, 40 insertions(+), 25 deletions(-)

diff --git a/client/SDL/SDL3/sdl_freerdp.cpp b/client/SDL/SDL3/sdl_freerdp.cpp
index 81d86a1c36a8..9c1316abe77c 100644
--- a/client/SDL/SDL3/sdl_freerdp.cpp
+++ b/client/SDL/SDL3/sdl_freerdp.cpp
@@ -81,21 +81,30 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 	int rc = -1;
 	WINPR_ASSERT(sdl);
 
-	while (!sdl->shallAbort())
+	struct ErrorMsg
 	{
-		SDL_Event windowEvent = {};
-		while (!sdl->shallAbort() && SDL_WaitEventTimeout(nullptr, 1000))
+		int rc;
+		Uint32 type;
+		std::string msg;
+	};
+
+	try
+	{
+		while (!sdl->shallAbort())
 		{
-			/* Only poll standard SDL events and SDL_EVENT_USERS meant to create
-			 * dialogs. do not process the dialog return value events here.
-			 */
-			const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
-			                               SDL_EVENT_USER_RETRY_DIALOG);
-			if (prc < 0)
+			SDL_Event windowEvent = {};
+			while (!sdl->shallAbort() && SDL_WaitEventTimeout(nullptr, 1000))
 			{
-				if (sdl_log_error(prc, sdl->getWLog(), "SDL_PeepEvents"))
-					continue;
-			}
+				/* Only poll standard SDL events and SDL_EVENT_USERS meant to create
+				 * dialogs. do not process the dialog return value events here.
+				 */
+				const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
+				                               SDL_EVENT_USER_RETRY_DIALOG);
+				if (prc < 0)
+				{
+					if (sdl_log_error(prc, sdl->getWLog(), "SDL_PeepEvents"))
+						continue;
+				}
 
 #if defined(WITH_DEBUG_SDL_EVENTS)
 			SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "got event %s [0x%08" PRIx32 "]",
@@ -108,12 +117,12 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 				continue;
 
 			if (!sdl->handleEvent(windowEvent))
-				return -1;
+				throw ErrorMsg{ -1, windowEvent.type, "sdl->handleEvent" };
 
 			switch (windowEvent.type)
 			{
 				case SDL_EVENT_QUIT:
-					freerdp_abort_connect_context(sdl->context());
+					std::ignore = freerdp_abort_connect_context(sdl->context());
 					break;
 				case SDL_EVENT_USER_CERT_DIALOG:
 				{
@@ -121,7 +130,7 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 					auto title = static_cast<const char*>(windowEvent.user.data1);
 					auto msg = static_cast<const char*>(windowEvent.user.data2);
 					if (!sdl_cert_dialog_show(title, msg))
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl_cert_dialog_show" };
 				}
 				break;
 				case SDL_EVENT_USER_SHOW_DIALOG:
@@ -130,7 +139,7 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 					auto title = static_cast<const char*>(windowEvent.user.data1);
 					auto msg = static_cast<const char*>(windowEvent.user.data2);
 					if (!sdl_message_dialog_show(title, msg, windowEvent.user.code))
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl_message_dialog_show" };
 				}
 				break;
 				case SDL_EVENT_USER_SCARD_DIALOG:
@@ -139,7 +148,7 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 					auto title = static_cast<const char*>(windowEvent.user.data1);
 					auto msg = static_cast<const char**>(windowEvent.user.data2);
 					if (!sdl_scard_dialog_show(title, windowEvent.user.code, msg))
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl_scard_dialog_show" };
 				}
 				break;
 				case SDL_EVENT_USER_AUTH_DIALOG:
@@ -147,7 +156,7 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 					SDLConnectionDialogHider hider(sdl);
 					if (!sdl_auth_dialog_show(
 					        reinterpret_cast<const SDL_UserAuthArg*>(windowEvent.padding)))
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl_auth_dialog_show" };
 				}
 				break;
 				case SDL_EVENT_USER_UPDATE:
@@ -157,7 +166,7 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 					{
 						rectangles = sdl->pop();
 						if (!sdl->drawToWindows(rectangles))
-							return -1;
+							throw ErrorMsg{ -1, windowEvent.type, "sdl->drawToWindows" };
 					} while (!rectangles.empty());
 				}
 				break;
@@ -165,7 +174,7 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 				{
 					auto ctx = static_cast<SdlContext*>(windowEvent.user.data1);
 					if (!ctx->createWindows())
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl->createWindows" };
 				}
 				break;
 				case SDL_EVENT_USER_WINDOW_RESIZEABLE:
@@ -186,7 +195,7 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 				break;
 				case SDL_EVENT_USER_WINDOW_MINIMIZE:
 					if (!sdl->minimizeAllWindows())
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl->minimizeAllWindows" };
 					break;
 				case SDL_EVENT_USER_POINTER_NULL:
 					SDL_HideCursor();
@@ -210,22 +219,28 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 					    static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data2));
 					if (!sdl->moveMouseTo(
 					        { static_cast<float>(x) * 1.0f, static_cast<float>(y) * 1.0f }))
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl->moveMouseTo" };
 				}
 				break;
 				case SDL_EVENT_USER_POINTER_SET:
 					sdl->setCursor(static_cast<rdpPointer*>(windowEvent.user.data1));
 					if (!sdl_Pointer_Set_Process(sdl))
-						return -1;
+						throw ErrorMsg{ -1, windowEvent.type, "sdl_Pointer_Set_Process" };
 					break;
 				case SDL_EVENT_USER_QUIT:
 				default:
 					break;
 			}
+			}
 		}
-	}
-
 	rc = 1;
+	}
+	catch (ErrorMsg& msg)
+	{
+		WLog_Print(sdl->getWLog(), WLOG_ERROR, "[exception] %s {%s}", msg.msg.c_str(),
+		           sdl::utils::toString(msg.type).c_str());
+		rc = msg.rc;
+	}
 
 	return rc;
 }

From 7e220eee98ed1ab9c340faed28db3a56610cd501 Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 21:05:22 +0100
Subject: [PATCH 7/9] [client,sdl] ignore missing renderer

When trying to map screen to pixel coordinates ignore a NULL renderer.
This was sometimes observed with X11 backend.
---
 client/SDL/SDL3/sdl_context.cpp | 22 ++++++++++++++++++----
 client/SDL/SDL3/sdl_context.hpp |  3 ++-
 2 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp
index 573c375678be..f8041cfe28d6 100644
--- a/client/SDL/SDL3/sdl_context.cpp
+++ b/client/SDL/SDL3/sdl_context.cpp
@@ -763,7 +763,7 @@ int SdlContext::error_info_to_error(DWORD* pcode, char** msg, size_t* len) const
 	return exit_code;
 }
 
-void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y)
+void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y) const
 {
 	if (!freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
 		return;
@@ -850,6 +850,14 @@ bool SdlContext::removeDisplay(SDL_DisplayID id)
 	return true;
 }
 
+const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const
+{
+	auto it = _windows.find(id);
+	if (it == _windows.end())
+		return nullptr;
+	return &it->second;
+}
+
 SdlWindow* SdlContext::getWindowForId(SDL_WindowID id)
 {
 	auto it = _windows.find(id);
@@ -1048,9 +1056,11 @@ bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev)
 	auto w = getWindowForId(id);
 	if (!w)
 		return false;
+
+	/* Ignore errors here, sometimes SDL has no renderer */
 	auto renderer = SDL_GetRenderer(w->window());
 	if (!renderer)
-		return false;
+		return true;
 	return SDL_ConvertEventToRenderCoordinates(renderer, &ev);
 }
 
@@ -1078,9 +1088,11 @@ SDL_FPoint SdlContext::screenToPixel(SDL_WindowID id, const SDL_FPoint& pos)
 	auto w = getWindowForId(id);
 	if (!w)
 		return {};
+
+	/* Ignore errors here, sometimes SDL has no renderer */
 	auto renderer = SDL_GetRenderer(w->window());
 	if (!renderer)
-		return {};
+		return pos;
 
 	SDL_FPoint rpos{};
 	if (!SDL_RenderCoordinatesFromWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
@@ -1094,9 +1106,11 @@ SDL_FPoint SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos)
 	auto w = getWindowForId(id);
 	if (!w)
 		return {};
+
+	/* Ignore errors here, sometimes SDL has no renderer */
 	auto renderer = SDL_GetRenderer(w->window());
 	if (!renderer)
-		return {};
+		return pos;
 
 	SDL_FPoint rpos{};
 	if (!SDL_RenderCoordinatesToWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
diff --git a/client/SDL/SDL3/sdl_context.hpp b/client/SDL/SDL3/sdl_context.hpp
index fab4373f80bb..5cc17f814a9a 100644
--- a/client/SDL/SDL3/sdl_context.hpp
+++ b/client/SDL/SDL3/sdl_context.hpp
@@ -106,6 +106,7 @@ class SdlContext
 	[[nodiscard]] int exitCode() const;
 	[[nodiscard]] SDL_PixelFormat pixelFormat() const;
 
+	[[nodiscard]] const SdlWindow* getWindowForId(SDL_WindowID id) const;
 	[[nodiscard]] SdlWindow* getWindowForId(SDL_WindowID id);
 	[[nodiscard]] SdlWindow* getFirstWindow();
 
@@ -162,7 +163,7 @@ class SdlContext
 
 	[[nodiscard]] int error_info_to_error(DWORD* pcode, char** msg, size_t* len) const;
 
-	void applyMonitorOffset(SDL_WindowID window, float& x, float& y);
+	void applyMonitorOffset(SDL_WindowID window, float& x, float& y) const;
 
 	rdpContext* _context = nullptr;
 	wLog* _log = nullptr;

From a9e3954076ad4b5f870f2448e54dc08c2e27ba39 Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 20:35:06 +0100
Subject: [PATCH 8/9] [client,sdl] simplify mouse cursor update and restore

---
 client/SDL/SDL3/sdl_context.cpp | 47 +++++++++++++++++++++++++++++----
 client/SDL/SDL3/sdl_context.hpp | 18 ++++++++++---
 client/SDL/SDL3/sdl_freerdp.cpp | 21 +++++----------
 client/SDL/SDL3/sdl_pointer.cpp | 38 +++++++++++++++-----------
 client/SDL/SDL3/sdl_pointer.hpp |  4 +--
 5 files changed, 87 insertions(+), 41 deletions(-)

diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp
index f8041cfe28d6..0dbf299fba6a 100644
--- a/client/SDL/SDL3/sdl_context.cpp
+++ b/client/SDL/SDL3/sdl_context.cpp
@@ -956,6 +956,8 @@ bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
 
 	switch (ev.type)
 	{
+		case SDL_EVENT_WINDOW_MOUSE_ENTER:
+			return restoreCursor();
 		case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
 			if (isConnected())
 			{
@@ -963,7 +965,7 @@ bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
 					return false;
 				if (!drawToWindow(*window))
 					return false;
-				if (!sdl_Pointer_Set_Process(this))
+				if (!restoreCursor())
 					return false;
 			}
 			break;
@@ -972,7 +974,7 @@ bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
 				return false;
 			if (!drawToWindow(*window))
 				return false;
-			if (!sdl_Pointer_Set_Process(this))
+			if (!restoreCursor())
 				return false;
 			break;
 		case SDL_EVENT_WINDOW_MOVED:
@@ -1177,8 +1179,6 @@ bool SdlContext::handleEvent(const SDL_Event& ev)
 		case SDL_EVENT_RENDER_DEVICE_RESET:
 		case SDL_EVENT_WILL_ENTER_FOREGROUND:
 			return redraw();
-		case SDL_EVENT_WINDOW_MOUSE_ENTER:
-			return sdl_Pointer_Set_Process(this);
 		default:
 			return true;
 	}
@@ -1276,9 +1276,16 @@ rdpClientContext* SdlContext::common() const
 	return reinterpret_cast<rdpClientContext*>(context());
 }
 
-void SdlContext::setCursor(rdpPointer* cursor)
+bool SdlContext::setCursor(CursorType type)
+{
+	_cursorType = type;
+	return restoreCursor();
+}
+
+bool SdlContext::setCursor(rdpPointer* cursor)
 {
 	_cursor = cursor;
+	return setCursor(CURSOR_IMAGE);
 }
 
 rdpPointer* SdlContext::cursor() const
@@ -1286,6 +1293,36 @@ rdpPointer* SdlContext::cursor() const
 	return _cursor;
 }
 
+bool SdlContext::restoreCursor()
+{
+	WLog_Print(getWLog(), WLOG_DEBUG, "restore cursor: %d", _cursorType);
+	switch (_cursorType)
+	{
+		case CURSOR_NULL:
+			if (!SDL_HideCursor())
+				return false;
+
+			setHasCursor(false);
+			return true;
+
+		case CURSOR_DEFAULT:
+		{
+			auto def = SDL_GetDefaultCursor();
+			if (!SDL_SetCursor(def))
+				return false;
+			if (!SDL_ShowCursor())
+				return false;
+			setHasCursor(true);
+			return true;
+		}
+		case CURSOR_IMAGE:
+			setHasCursor(true);
+			return sdl_Pointer_Set_Process(this);
+		default:
+			return false;
+	}
+}
+
 void SdlContext::setMonitorIds(const std::vector<SDL_DisplayID>& ids)
 {
 	_monitorIds.clear();
diff --git a/client/SDL/SDL3/sdl_context.hpp b/client/SDL/SDL3/sdl_context.hpp
index 5cc17f814a9a..ef6b59fb7cdc 100644
--- a/client/SDL/SDL3/sdl_context.hpp
+++ b/client/SDL/SDL3/sdl_context.hpp
@@ -43,6 +43,13 @@
 class SdlContext
 {
   public:
+	enum CursorType
+	{
+		CURSOR_NULL,
+		CURSOR_DEFAULT,
+		CURSOR_IMAGE
+	};
+
 	explicit SdlContext(rdpContext* context);
 	SdlContext(const SdlContext& other) = delete;
 	SdlContext(SdlContext&& other) = delete;
@@ -78,15 +85,17 @@ class SdlContext
 	[[nodiscard]] rdpContext* context() const;
 	[[nodiscard]] rdpClientContext* common() const;
 
-	void setCursor(rdpPointer* cursor);
+	[[nodiscard]] bool setCursor(CursorType type);
+	[[nodiscard]] bool setCursor(rdpPointer* cursor);
 	[[nodiscard]] rdpPointer* cursor() const;
+	[[nodiscard]] bool restoreCursor();
 
 	void setMonitorIds(const std::vector<SDL_DisplayID>& ids);
-	const std::vector<SDL_DisplayID>& monitorIds() const;
-	int64_t monitorId(uint32_t index) const;
+	[[nodiscard]] const std::vector<SDL_DisplayID>& monitorIds() const;
+	[[nodiscard]] int64_t monitorId(uint32_t index) const;
 
 	void push(std::vector<SDL_Rect>&& rects);
-	std::vector<SDL_Rect> pop();
+	[[nodiscard]] std::vector<SDL_Rect> pop();
 
 	void setHasCursor(bool val);
 	[[nodiscard]] bool hasCursor() const;
@@ -171,6 +180,7 @@ class SdlContext
 	std::atomic<bool> _connected = false;
 	bool _cursor_visible = true;
 	rdpPointer* _cursor = nullptr;
+	CursorType _cursorType = CURSOR_NULL;
 	std::vector<SDL_DisplayID> _monitorIds;
 	std::mutex _queue_mux;
 	std::queue<std::vector<SDL_Rect>> _queue;
diff --git a/client/SDL/SDL3/sdl_freerdp.cpp b/client/SDL/SDL3/sdl_freerdp.cpp
index 9c1316abe77c..678547d88be7 100644
--- a/client/SDL/SDL3/sdl_freerdp.cpp
+++ b/client/SDL/SDL3/sdl_freerdp.cpp
@@ -198,19 +198,13 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 						throw ErrorMsg{ -1, windowEvent.type, "sdl->minimizeAllWindows" };
 					break;
 				case SDL_EVENT_USER_POINTER_NULL:
-					SDL_HideCursor();
-					sdl->setCursor(nullptr);
-					sdl->setHasCursor(false);
+					if (!sdl->setCursor(SdlContext::CURSOR_NULL))
+						throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
 					break;
 				case SDL_EVENT_USER_POINTER_DEFAULT:
-				{
-					SDL_Cursor* def = SDL_GetDefaultCursor();
-					SDL_SetCursor(def);
-					SDL_ShowCursor();
-					sdl->setCursor(nullptr);
-					sdl->setHasCursor(true);
-				}
-				break;
+					if (!sdl->setCursor(SdlContext::CURSOR_DEFAULT))
+						throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
+					break;
 				case SDL_EVENT_USER_POINTER_POSITION:
 				{
 					const auto x =
@@ -223,9 +217,8 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 				}
 				break;
 				case SDL_EVENT_USER_POINTER_SET:
-					sdl->setCursor(static_cast<rdpPointer*>(windowEvent.user.data1));
-					if (!sdl_Pointer_Set_Process(sdl))
-						throw ErrorMsg{ -1, windowEvent.type, "sdl_Pointer_Set_Process" };
+					if (!sdl->setCursor(static_cast<rdpPointer*>(windowEvent.user.data1)))
+						throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
 					break;
 				case SDL_EVENT_USER_QUIT:
 				default:
diff --git a/client/SDL/SDL3/sdl_pointer.cpp b/client/SDL/SDL3/sdl_pointer.cpp
index 292aec296c0f..13832254172a 100644
--- a/client/SDL/SDL3/sdl_pointer.cpp
+++ b/client/SDL/SDL3/sdl_pointer.cpp
@@ -103,7 +103,7 @@ static void sdl_Pointer_Free(rdpContext* context, rdpPointer* pointer)
 	return sdl_push_user_event(SDL_EVENT_USER_POINTER_SET, pointer);
 }
 
-BOOL sdl_Pointer_Set_Process(SdlContext* sdl)
+bool sdl_Pointer_Set_Process(SdlContext* sdl)
 {
 	WINPR_ASSERT(sdl);
 
@@ -111,7 +111,7 @@ BOOL sdl_Pointer_Set_Process(SdlContext* sdl)
 	auto pointer = sdl->cursor();
 	auto ptr = reinterpret_cast<sdlPointer*>(pointer);
 	if (!ptr)
-		return TRUE;
+		return true;
 
 	rdpGdi* gdi = context->gdi;
 	WINPR_ASSERT(gdi);
@@ -121,7 +121,7 @@ BOOL sdl_Pointer_Set_Process(SdlContext* sdl)
 	auto isw = static_cast<float>(pointer->width);
 	auto ish = static_cast<float>(pointer->height);
 
-	SDL_Window* window = SDL_GetMouseFocus();
+	auto window = SDL_GetMouseFocus();
 	if (!window)
 		return sdl_Pointer_SetDefault(context);
 
@@ -134,9 +134,11 @@ BOOL sdl_Pointer_Set_Process(SdlContext* sdl)
 	ptr->image =
 	    SDL_CreateSurface(static_cast<int>(pos.w), static_cast<int>(pos.h), sdl->pixelFormat());
 	if (!ptr->image)
-		return FALSE;
+		return false;
+
+	if (!SDL_LockSurface(ptr->image))
+		return false;
 
-	SDL_LockSurface(ptr->image);
 	auto pixels = static_cast<BYTE*>(ptr->image->pixels);
 	auto data = static_cast<const BYTE*>(ptr->data);
 	const BOOL rc = freerdp_image_scale(
@@ -145,13 +147,13 @@ BOOL sdl_Pointer_Set_Process(SdlContext* sdl)
 	    gdi->dstFormat, 0, 0, 0, static_cast<UINT32>(isw), static_cast<UINT32>(ish));
 	SDL_UnlockSurface(ptr->image);
 	if (!rc)
-		return FALSE;
+		return false;
 
 	// create a cursor image in 100% display scale to trick SDL into creating the cursor with the
 	// correct size
 	auto fw = sdl->getFirstWindow();
 	if (!fw)
-		return FALSE;
+		return false;
 
 	const auto hidpi_scale =
 	    sdl->pixelToScreen(fw->id(), SDL_FPoint{ static_cast<float>(ptr->image->w),
@@ -159,20 +161,24 @@ BOOL sdl_Pointer_Set_Process(SdlContext* sdl)
 	auto normal = SDL_CreateSurface(static_cast<int>(hidpi_scale.x),
 	                                static_cast<int>(hidpi_scale.y), ptr->image->format);
 	assert(normal);
-	SDL_BlitSurfaceScaled(ptr->image, nullptr, normal, nullptr,
-	                      SDL_ScaleMode::SDL_SCALEMODE_LINEAR);
-	SDL_AddSurfaceAlternateImage(normal, ptr->image);
+	if (!SDL_BlitSurfaceScaled(ptr->image, nullptr, normal, nullptr,
+	                           SDL_ScaleMode::SDL_SCALEMODE_LINEAR))
+		return false;
+	if (!SDL_AddSurfaceAlternateImage(normal, ptr->image))
+		return false;
 
 	ptr->cursor = SDL_CreateColorCursor(normal, static_cast<int>(pos.x), static_cast<int>(pos.y));
 	if (!ptr->cursor)
-		return FALSE;
+		return false;
 
 	SDL_DestroySurface(normal);
 
-	SDL_SetCursor(ptr->cursor);
-	SDL_ShowCursor();
+	if (!SDL_SetCursor(ptr->cursor))
+		return false;
+	if (!SDL_ShowCursor())
+		return false;
 	sdl->setHasCursor(true);
-	return TRUE;
+	return true;
 }
 
 [[nodiscard]] static BOOL sdl_Pointer_SetNull(rdpContext* context)
@@ -190,7 +196,7 @@ BOOL sdl_Pointer_Set_Process(SdlContext* sdl)
 	return sdl_push_user_event(SDL_EVENT_USER_POINTER_POSITION, x, y);
 }
 
-BOOL sdl_register_pointer(rdpGraphics* graphics)
+bool sdl_register_pointer(rdpGraphics* graphics)
 {
 	const rdpPointer pointer = { sizeof(sdlPointer),
 		                         sdl_Pointer_New,
@@ -211,5 +217,5 @@ BOOL sdl_register_pointer(rdpGraphics* graphics)
 		                         nullptr,
 		                         {} };
 	graphics_register_pointer(graphics, &pointer);
-	return TRUE;
+	return true;
 }
diff --git a/client/SDL/SDL3/sdl_pointer.hpp b/client/SDL/SDL3/sdl_pointer.hpp
index 830bee7c2cb7..fc134e749655 100644
--- a/client/SDL/SDL3/sdl_pointer.hpp
+++ b/client/SDL/SDL3/sdl_pointer.hpp
@@ -25,6 +25,6 @@
 
 #include <freerdp/graphics.h>
 
-[[nodiscard]] BOOL sdl_register_pointer(rdpGraphics* graphics);
+[[nodiscard]] bool sdl_register_pointer(rdpGraphics* graphics);
 
-[[nodiscard]] BOOL sdl_Pointer_Set_Process(SdlContext* sdl);
+[[nodiscard]] bool sdl_Pointer_Set_Process(SdlContext* sdl);

From a97a2ef24d9b911ebe2f3ca23e4d6f9758704170 Mon Sep 17 00:00:00 2001
From: akallabeth <akallabeth@posteo.net>
Date: Wed, 28 Jan 2026 22:05:02 +0100
Subject: [PATCH 9/9] [client,sdl] refactor exception class

---
 client/SDL/SDL3/sdl_freerdp.cpp | 43 +++++++++++++++++++++++++--------
 1 file changed, 33 insertions(+), 10 deletions(-)

diff --git a/client/SDL/SDL3/sdl_freerdp.cpp b/client/SDL/SDL3/sdl_freerdp.cpp
index 678547d88be7..9dc1c9416453 100644
--- a/client/SDL/SDL3/sdl_freerdp.cpp
+++ b/client/SDL/SDL3/sdl_freerdp.cpp
@@ -70,6 +70,37 @@
 #define SDL_TAG CLIENT_TAG("SDL")
 #endif
 
+class ErrorMsg : public std::exception
+{
+  public:
+	ErrorMsg(int rc, Uint32 type, const std::string& msg);
+
+	[[nodiscard]] int rc() const;
+	[[nodiscard]] const char* what() const noexcept override;
+
+  private:
+	int _rc;
+	Uint32 _type;
+	std::string _msg;
+	std::string _buffer;
+};
+const char* ErrorMsg::what() const noexcept
+{
+	return _buffer.c_str();
+}
+
+int ErrorMsg::rc() const
+{
+	return _rc;
+}
+
+ErrorMsg::ErrorMsg(int rc, Uint32 type, const std::string& msg) : _rc(rc), _type(type), _msg(msg)
+{
+	std::stringstream ss;
+	ss << _msg << " {" << sdl::utils::toString(_type) << "}";
+	_buffer = ss.str();
+}
+
 static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const char* signame,
                              [[maybe_unused]] void* context)
 {
@@ -81,13 +112,6 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 	int rc = -1;
 	WINPR_ASSERT(sdl);
 
-	struct ErrorMsg
-	{
-		int rc;
-		Uint32 type;
-		std::string msg;
-	};
-
 	try
 	{
 		while (!sdl->shallAbort())
@@ -230,9 +254,8 @@ static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const
 	}
 	catch (ErrorMsg& msg)
 	{
-		WLog_Print(sdl->getWLog(), WLOG_ERROR, "[exception] %s {%s}", msg.msg.c_str(),
-		           sdl::utils::toString(msg.type).c_str());
-		rc = msg.rc;
+		WLog_Print(sdl->getWLog(), WLOG_ERROR, "[exception] %s", msg.what());
+		rc = msg.rc();
 	}
 
 	return rc;
