From a53e2fc3a8a0a9a32548c5ce8679b506d0b64755 Mon Sep 17 00:00:00 2001 From: Felix Igelbrink Date: Tue, 28 Apr 2026 15:12:49 +0200 Subject: [PATCH 1/4] improved wayland detection at runtime on hybrid systems --- rendercanvas/glfw.py | 46 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/rendercanvas/glfw.py b/rendercanvas/glfw.py index d00c057d..03b185dd 100644 --- a/rendercanvas/glfw.py +++ b/rendercanvas/glfw.py @@ -11,6 +11,7 @@ import sys import time +import warnings import glfw @@ -28,10 +29,19 @@ api_is_wayland = False if sys.platform.startswith("linux") and SYSTEM_IS_WAYLAND: if not hasattr(glfw, "get_x11_window"): - # Probably glfw was imported before this module, so we missed our chance + # glfw was imported before this module, so we missed our chance # to set the env var to make glfw use x11. api_is_wayland = True logger.warning("Using GLFW with Wayland, which is experimental.") + else: + # On some systems glfw is built with both X11 and Wayland support. + # In that case get_x11_window exists but returns None when glfw is + # actually running on the Wayland backend. Log an info note so that + # the runtime check in get_glfw_present_info can handle this case. + logger.debug( + "GLFW has get_x11_window but system is Wayland; " + "will verify backend at runtime." + ) # Some glfw functions are not always available @@ -135,22 +145,48 @@ def get_glfw_present_info(window): } elif sys.platform.startswith("linux"): - if api_is_wayland: + # When the respective platform (X11 or Wayland) is not initialized, glfw emits a GLFWError warning and returns None. We treat None as "backend not active" here so that the warning carries no actionable information for the caller. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", glfw.GLFWError) + wayland_display = ( + glfw.get_wayland_display() + if hasattr(glfw, "get_wayland_display") + else None + ) + + if api_is_wayland or wayland_display is not None: + if wayland_display is None: + raise RuntimeError( + "GLFW Wayland backend is active but get_wayland_display() " + "returned None. Is libglfw3-wayland installed?" + ) return { "method": "screen", "platform": "wayland", "window": int(glfw.get_wayland_window(window)), - "display": int(glfw.get_wayland_display()), + "display": int(wayland_display), } - else: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", glfw.GLFWError) + x11_display = ( + glfw.get_x11_display() if hasattr(glfw, "get_x11_display") else None + ) + if x11_display is not None: return { "method": "screen", "platform": "x11", "window": int(glfw.get_x11_window(window)), - "display": int(glfw.get_x11_display()), + "display": int(x11_display), } + raise RuntimeError( + "Cannot get GLFW surface info on Linux: neither a Wayland nor " + "an X11 display handle is available. " + "Make sure a display server is running and the matching glfw " + "library variant is installed." + ) + else: raise RuntimeError(f"Cannot get GLFW surface info on {sys.platform}.") From 19104462cabf327fc12b951fc53d364481d5de03 Mon Sep 17 00:00:00 2001 From: Felix Igelbrink Date: Thu, 30 Apr 2026 13:46:16 +0200 Subject: [PATCH 2/4] removed import-time checks --- rendercanvas/glfw.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/rendercanvas/glfw.py b/rendercanvas/glfw.py index 03b185dd..defc93e4 100644 --- a/rendercanvas/glfw.py +++ b/rendercanvas/glfw.py @@ -25,25 +25,6 @@ if glfw_version_info < (1, 9): raise ImportError("rendercanvas requires glfw 1.9 or higher.") -# Do checks to prevent pitfalls on hybrid Xorg/Wayland systems -api_is_wayland = False -if sys.platform.startswith("linux") and SYSTEM_IS_WAYLAND: - if not hasattr(glfw, "get_x11_window"): - # glfw was imported before this module, so we missed our chance - # to set the env var to make glfw use x11. - api_is_wayland = True - logger.warning("Using GLFW with Wayland, which is experimental.") - else: - # On some systems glfw is built with both X11 and Wayland support. - # In that case get_x11_window exists but returns None when glfw is - # actually running on the Wayland backend. Log an info note so that - # the runtime check in get_glfw_present_info can handle this case. - logger.debug( - "GLFW has get_x11_window but system is Wayland; " - "will verify backend at runtime." - ) - - # Some glfw functions are not always available set_window_content_scale_callback = lambda *args: None set_window_maximize_callback = lambda *args: None @@ -154,12 +135,7 @@ def get_glfw_present_info(window): else None ) - if api_is_wayland or wayland_display is not None: - if wayland_display is None: - raise RuntimeError( - "GLFW Wayland backend is active but get_wayland_display() " - "returned None. Is libglfw3-wayland installed?" - ) + if wayland_display is not None: return { "method": "screen", "platform": "wayland", From e4a428e7259b891564cfc1d36b2dfd828cf12c0f Mon Sep 17 00:00:00 2001 From: Felix Igelbrink Date: Mon, 4 May 2026 11:29:43 +0200 Subject: [PATCH 3/4] use SYSTEM_IS_WAYLAND again to determine if wayland or x11 is expected --- rendercanvas/glfw.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/rendercanvas/glfw.py b/rendercanvas/glfw.py index defc93e4..fc2e4fee 100644 --- a/rendercanvas/glfw.py +++ b/rendercanvas/glfw.py @@ -17,7 +17,7 @@ from .base import BaseRenderCanvas, BaseCanvasGroup from .asyncio import loop -from .core.coreutils import SYSTEM_IS_WAYLAND, weakbind, logger +from .core.coreutils import SYSTEM_IS_WAYLAND, weakbind # Make sure that glfw is new enough @@ -127,15 +127,20 @@ def get_glfw_present_info(window): elif sys.platform.startswith("linux"): # When the respective platform (X11 or Wayland) is not initialized, glfw emits a GLFWError warning and returns None. We treat None as "backend not active" here so that the warning carries no actionable information for the caller. - with warnings.catch_warnings(): - warnings.simplefilter("ignore", glfw.GLFWError) - wayland_display = ( - glfw.get_wayland_display() - if hasattr(glfw, "get_wayland_display") - else None - ) - - if wayland_display is not None: + if SYSTEM_IS_WAYLAND: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", glfw.GLFWError) + wayland_display = ( + glfw.get_wayland_display() + if hasattr(glfw, "get_wayland_display") + else None + ) + + if wayland_display is None: + raise RuntimeError( + "GLFW Wayland backend is active but get_wayland_display() " + "returned None. Is libglfw3-wayland installed?" + ) return { "method": "screen", "platform": "wayland", @@ -143,6 +148,7 @@ def get_glfw_present_info(window): "display": int(wayland_display), } + # fall back to X11 with warnings.catch_warnings(): warnings.simplefilter("ignore", glfw.GLFWError) x11_display = ( From 3af63220d72ea53d1d3582055c03d6dcfe21569b Mon Sep 17 00:00:00 2001 From: Felix Igelbrink Date: Mon, 4 May 2026 12:43:20 +0200 Subject: [PATCH 4/4] removed glfw x11 workaround in coreutils and modified gflw backend detection to fall back to x11 (xwayland) in case wayland is not available --- rendercanvas/core/coreutils.py | 3 --- rendercanvas/glfw.py | 27 +++++++++++---------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/rendercanvas/core/coreutils.py b/rendercanvas/core/coreutils.py index c39a6262..8af60074 100644 --- a/rendercanvas/core/coreutils.py +++ b/rendercanvas/core/coreutils.py @@ -345,9 +345,6 @@ def close_agen(agen): SYSTEM_IS_WAYLAND = "wayland" in os.getenv("XDG_SESSION_TYPE", "").lower() if sys.platform.startswith("linux") and SYSTEM_IS_WAYLAND: - # Force glfw to use X11. Note that this does not work if glfw is already imported. - if "glfw" not in sys.modules: - os.environ["PYGLFW_LIBRARY_VARIANT"] = "x11" # Force Qt to use X11. Qt is more flexible - it ok if e.g. PySide6 is already imported. os.environ["QT_QPA_PLATFORM"] = "xcb" # Force wx to use X11, probably. diff --git a/rendercanvas/glfw.py b/rendercanvas/glfw.py index fc2e4fee..deaffac2 100644 --- a/rendercanvas/glfw.py +++ b/rendercanvas/glfw.py @@ -17,7 +17,7 @@ from .base import BaseRenderCanvas, BaseCanvasGroup from .asyncio import loop -from .core.coreutils import SYSTEM_IS_WAYLAND, weakbind +from .core.coreutils import weakbind # Make sure that glfw is new enough @@ -127,20 +127,15 @@ def get_glfw_present_info(window): elif sys.platform.startswith("linux"): # When the respective platform (X11 or Wayland) is not initialized, glfw emits a GLFWError warning and returns None. We treat None as "backend not active" here so that the warning carries no actionable information for the caller. - if SYSTEM_IS_WAYLAND: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", glfw.GLFWError) - wayland_display = ( - glfw.get_wayland_display() - if hasattr(glfw, "get_wayland_display") - else None - ) - - if wayland_display is None: - raise RuntimeError( - "GLFW Wayland backend is active but get_wayland_display() " - "returned None. Is libglfw3-wayland installed?" - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", glfw.GLFWError) + wayland_display = ( + glfw.get_wayland_display() + if hasattr(glfw, "get_wayland_display") + else None + ) + + if wayland_display is not None: return { "method": "screen", "platform": "wayland", @@ -148,7 +143,7 @@ def get_glfw_present_info(window): "display": int(wayland_display), } - # fall back to X11 + # fall back to X11 if obtaining a wayland window handle fails (e.g. if Wayland is not available) with warnings.catch_warnings(): warnings.simplefilter("ignore", glfw.GLFWError) x11_display = (