diff --git a/docs/start.rst b/docs/start.rst index d66c9c18..042645ae 100644 --- a/docs/start.rst +++ b/docs/start.rst @@ -65,10 +65,9 @@ On MacOS you need at least 10.13 (High Sierra) to have Metal/Vulkan support. Linux +++++ -On Linux, it's advisable to install the proprietary drivers of your GPU -(if you have a dedicated GPU). You may need to ``apt install -mesa-vulkan-drivers``. Wayland support is currently broken (we could use -a hand to fix this). +On Linux, it's advisable to install the proprietary drivers of your GPU (if you +have a dedicated GPU). You may need to ``apt install mesa-vulkan-drivers``. On +Wayland, wgpu-py requires XWayland (available by default on most distributions). Binary wheels for Linux are only available for **manylinux_2_24**. This means that the installation requires ``pip >= 20.3``, and you need diff --git a/examples/triangle_subprocess.py b/examples/triangle_subprocess.py index e1c2e640..59557fd2 100644 --- a/examples/triangle_subprocess.py +++ b/examples/triangle_subprocess.py @@ -12,6 +12,7 @@ """ import sys +import json import time import subprocess @@ -23,14 +24,14 @@ code = """ import sys +import json from PySide6 import QtWidgets # Use either PySide6 or PyQt6 from wgpu.gui.qt import WgpuCanvas app = QtWidgets.QApplication([]) canvas = WgpuCanvas(title="wgpu triangle in Qt subprocess") -print(canvas.get_window_id()) -#print(canvas.get_display_id()) +print(json.dumps(canvas.get_surface_info())) print(canvas.get_physical_size()) sys.stdout.flush() @@ -41,15 +42,15 @@ class ProxyCanvas(WgpuCanvasBase): def __init__(self): super().__init__() - self._window_id = int(p.stdout.readline().decode()) + self._surface_info = json.loads(p.stdout.readline().decode()) self._psize = tuple( int(x) for x in p.stdout.readline().decode().strip().strip("()").split(",") ) print(self._psize) time.sleep(0.2) - def get_window_id(self): - return self._window_id + def get_surface_info(self): + return self._surface_info def get_physical_size(self): return self._psize diff --git a/tests/test_gui_base.py b/tests/test_gui_base.py index a8e1c024..26fc5a03 100644 --- a/tests/test_gui_base.py +++ b/tests/test_gui_base.py @@ -248,7 +248,7 @@ def handler(event): def test_weakbind(): - weakbind = wgpu.gui.base.weakbind + weakbind = wgpu.gui._gui_utils.weakbind xx = [] diff --git a/tests/test_gui_glfw.py b/tests/test_gui_glfw.py index f47794c7..5d3fc28c 100644 --- a/tests/test_gui_glfw.py +++ b/tests/test_gui_glfw.py @@ -3,7 +3,6 @@ like the canvas context and surface texture. """ -import os import sys import time import weakref @@ -172,22 +171,33 @@ def __init__(self): self.window = glfw.create_window(300, 200, "canvas", None, None) self._present_context = None - def get_window_id(self): + def get_surface_info(self): if sys.platform.startswith("win"): - return int(glfw.get_win32_window(self.window)) + return { + "platform": "windows", + "window": int(glfw.get_win32_window(self.window)), + } elif sys.platform.startswith("darwin"): - return int(glfw.get_cocoa_window(self.window)) + return { + "platform": "cocoa", + "window": int(glfw.get_cocoa_window(self.window)), + } elif sys.platform.startswith("linux"): - is_wayland = "wayland" in os.getenv("XDG_SESSION_TYPE", "").lower() + is_wayland = hasattr(glfw, "get_wayland_display") if is_wayland: - return int(glfw.get_wayland_window(self.window)) + return { + "platform": "wayland", + "window": int(glfw.get_wayland_window(self.window)), + "display": int(glfw.get_wayland_display()), + } else: - return int(glfw.get_x11_window(self.window)) + return { + "platform": "x11", + "window": int(glfw.get_x11_window(self.window)), + "display": int(glfw.get_x11_display()), + } else: - raise RuntimeError(f"Cannot get GLFW window id on {sys.platform}.") - - def get_display_id(self): - return wgpu.WgpuCanvasInterface.get_display_id(self) + raise RuntimeError(f"Cannot get GLFW surafce info on {sys.platform}.") def get_physical_size(self): psize = glfw.get_framebuffer_size(self.window) diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index 8f4e6305..fb1d2746 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -198,8 +198,7 @@ def request_adapter( # able to create a surface texture for it (from this adapter). surface_id = ffi.NULL if canvas is not None: - window_id = canvas.get_window_id() - if window_id: # e.g. could be an off-screen canvas + if canvas.get_surface_info(): # e.g. could be an off-screen canvas surface_id = canvas.get_context()._get_surface_id() # ----- Select backend diff --git a/wgpu/backends/wgpu_native/_helpers.py b/wgpu/backends/wgpu_native/_helpers.py index 7b35844a..230e87d6 100644 --- a/wgpu/backends/wgpu_native/_helpers.py +++ b/wgpu/backends/wgpu_native/_helpers.py @@ -1,7 +1,6 @@ """Utilities used in the wgpu-native backend. """ -import os import sys import ctypes @@ -96,12 +95,18 @@ def get_surface_id_from_canvas(canvas): """Get an id representing the surface to render to. The way to obtain this id differs per platform and GUI toolkit. """ - win_id = canvas.get_window_id() + + # Use cached + surface_id = getattr(canvas, "_wgpu_surface_id", None) + if surface_id: + return surface_id + + surface_info = canvas.get_surface_info() if sys.platform.startswith("win"): # no-cover struct = ffi.new("WGPUSurfaceDescriptorFromWindowsHWND *") struct.hinstance = ffi.NULL - struct.hwnd = ffi.cast("void *", int(win_id)) + struct.hwnd = ffi.cast("void *", int(surface_info["window"])) struct.chain.sType = lib.WGPUSType_SurfaceDescriptorFromWindowsHWND elif sys.platform.startswith("darwin"): # no-cover @@ -115,7 +120,7 @@ def get_surface_id_from_canvas(canvas): # [ns_window.contentView setLayer:metal_layer]; # surface = wgpu_create_surface_from_metal_layer(metal_layer); # } - window = ctypes.c_void_p(win_id) + window = ctypes.c_void_p(surface_info["window"]) cw = ObjCInstance(window) try: @@ -156,26 +161,25 @@ def get_surface_id_from_canvas(canvas): struct.chain.sType = lib.WGPUSType_SurfaceDescriptorFromMetalLayer elif sys.platform.startswith("linux"): # no-cover - display_id = canvas.get_display_id() - is_wayland = "wayland" in os.getenv("XDG_SESSION_TYPE", "").lower() - is_xcb = False - if is_wayland: - # todo: wayland seems to be broken right now + platform = surface_info.get("platform", "x11") + if platform == "x11": + struct = ffi.new("WGPUSurfaceDescriptorFromXlibWindow *") + struct.display = ffi.cast("void *", surface_info["display"]) + struct.window = int(surface_info["window"]) + struct.chain.sType = lib.WGPUSType_SurfaceDescriptorFromXlibWindow + elif platform == "wayland": struct = ffi.new("WGPUSurfaceDescriptorFromWaylandSurface *") - struct.display = ffi.cast("void *", display_id) - struct.surface = ffi.cast("void *", win_id) + struct.display = ffi.cast("void *", surface_info["display"]) + struct.surface = ffi.cast("void *", surface_info["window"]) struct.chain.sType = lib.WGPUSType_SurfaceDescriptorFromWaylandSurface - elif is_xcb: + elif platform == "xcb": # todo: xcb untested struct = ffi.new("WGPUSurfaceDescriptorFromXcbWindow *") - struct.connection = ffi.NULL # ?? ffi.cast("void *", display_id) - struct.window = int(win_id) + struct.connection = ffi.cast("void *", surface_info["connection"]) # ?? + struct.window = int(surface_info["window"]) struct.chain.sType = lib.WGPUSType_SurfaceDescriptorFromXlibWindow else: - struct = ffi.new("WGPUSurfaceDescriptorFromXlibWindow *") - struct.display = ffi.cast("void *", display_id) - struct.window = int(win_id) - struct.chain.sType = lib.WGPUSType_SurfaceDescriptorFromXlibWindow + raise RuntimeError("Unexpected Linux surface platform '{platform}'.") else: # no-cover raise RuntimeError("Cannot get surface id: unsupported platform.") @@ -184,7 +188,11 @@ def get_surface_id_from_canvas(canvas): surface_descriptor.label = ffi.NULL surface_descriptor.nextInChain = ffi.cast("WGPUChainedStruct *", struct) - return lib.wgpuInstanceCreateSurface(get_wgpu_instance(), surface_descriptor) + surface_id = lib.wgpuInstanceCreateSurface(get_wgpu_instance(), surface_descriptor) + + # Cache and return + canvas._wgpu_surface_id = surface_id + return surface_id # The functions below are copied from codegen/utils.py diff --git a/wgpu/gui/__init__.py b/wgpu/gui/__init__.py index c9591689..31049f5b 100644 --- a/wgpu/gui/__init__.py +++ b/wgpu/gui/__init__.py @@ -2,6 +2,7 @@ Code to provide a canvas to render to. """ +from . import _gui_utils # noqa: F401 from .base import WgpuCanvasInterface, WgpuCanvasBase, WgpuAutoGui # noqa: F401 from .offscreen import WgpuOffscreenCanvasBase # noqa: F401 diff --git a/wgpu/gui/_gui_utils.py b/wgpu/gui/_gui_utils.py new file mode 100644 index 00000000..2f2d4152 --- /dev/null +++ b/wgpu/gui/_gui_utils.py @@ -0,0 +1,108 @@ +""" Private gui utilities. +""" + +import os +import sys +import weakref +import logging +import ctypes.util +from contextlib import contextmanager + +from .._coreutils import error_message_hash + + +logger = logging.getLogger("wgpu") + + +err_hashes = {} + + +@contextmanager +def log_exception(kind): + """Context manager to log any exceptions, but only log a one-liner + for subsequent occurances of the same error to avoid spamming by + repeating errors in e.g. a draw function or event callback. + """ + try: + yield + except Exception as err: + # Store exc info for postmortem debugging + exc_info = list(sys.exc_info()) + exc_info[2] = exc_info[2].tb_next # skip *this* function + sys.last_type, sys.last_value, sys.last_traceback = exc_info + # Show traceback, or a one-line summary + msg = str(err) + msgh = error_message_hash(msg) + if msgh not in err_hashes: + # Provide the exception, so the default logger prints a stacktrace. + # IDE's can get the exception from the root logger for PM debugging. + err_hashes[msgh] = 1 + logger.error(kind, exc_info=err) + else: + # We've seen this message before, return a one-liner instead. + err_hashes[msgh] = count = err_hashes[msgh] + 1 + msg = kind + ": " + msg.split("\n")[0].strip() + msg = msg if len(msg) <= 70 else msg[:69] + "…" + logger.error(msg + f" ({count})") + + +def weakbind(method): + """Replace a bound method with a callable object that stores the `self` using a weakref.""" + ref = weakref.ref(method.__self__) + class_func = method.__func__ + del method + + def proxy(*args, **kwargs): + self = ref() + if self is not None: + return class_func(self, *args, **kwargs) + + proxy.__name__ = class_func.__name__ + return proxy + + +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. + os.environ["GDK_BACKEND"] = "x11" + + +_x11_display = None + + +def get_alt_x11_display(): + """Get (the pointer to) a process-global x11 display instance.""" + # Ideally we'd get the real display object used by the GUI toolkit. + # But this is not always possible. In that case, using an alt display + # object can be used. + global _x11_display + assert sys.platform.startswith("linux") + if _x11_display is None: + x11 = ctypes.CDLL(ctypes.util.find_library("X11")) + x11.XOpenDisplay.restype = ctypes.c_void_p + _x11_display = x11.XOpenDisplay(None) + return _x11_display + + +_wayland_display = None + + +def get_alt_wayland_display(): + """Get (the pointer to) a process-global Wayland display instance.""" + # Ideally we'd get the real display object used by the GUI toolkit. + # This creates a global object, similar to what we do for X11. + # Unfortunately, this segfaults, so it looks like the real display object + # is needed? Leaving this here for reference. + global _wayland_display + assert sys.platform.startswith("linux") + if _wayland_display is None: + wl = ctypes.CDLL(ctypes.util.find_library("wayland-client")) + wl.wl_display_connect.restype = ctypes.c_void_p + _wayland_display = wl.wl_display_connect(None) + return _wayland_display diff --git a/wgpu/gui/base.py b/wgpu/gui/base.py index d35718b8..b94a126f 100644 --- a/wgpu/gui/base.py +++ b/wgpu/gui/base.py @@ -1,61 +1,8 @@ -import os import sys import time -import weakref -import logging -from contextlib import contextmanager -import ctypes.util from collections import defaultdict -from .._coreutils import error_message_hash - -logger = logging.getLogger("wgpu") - -err_hashes = {} - - -@contextmanager -def log_exception(kind): - """Context manager to log any exceptions, but only log a one-liner - for subsequent occurances of the same error to avoid spamming by - repeating errors in e.g. a draw function or event callback. - """ - try: - yield - except Exception as err: - # Store exc info for postmortem debugging - exc_info = list(sys.exc_info()) - exc_info[2] = exc_info[2].tb_next # skip *this* function - sys.last_type, sys.last_value, sys.last_traceback = exc_info - # Show traceback, or a one-line summary - msg = str(err) - msgh = error_message_hash(msg) - if msgh not in err_hashes: - # Provide the exception, so the default logger prints a stacktrace. - # IDE's can get the exception from the root logger for PM debugging. - err_hashes[msgh] = 1 - logger.error(kind, exc_info=err) - else: - # We've seen this message before, return a one-liner instead. - err_hashes[msgh] = count = err_hashes[msgh] + 1 - msg = kind + ": " + msg.split("\n")[0].strip() - msg = msg if len(msg) <= 70 else msg[:69] + "…" - logger.error(msg + f" ({count})") - - -def weakbind(method): - """Replace a bound method with a callable object that stores the `self` using a weakref.""" - ref = weakref.ref(method.__self__) - class_func = method.__func__ - del method - - def proxy(*args, **kwargs): - self = ref() - if self is not None: - return class_func(self, *args, **kwargs) - - proxy.__name__ = class_func.__name__ - return proxy +from ._gui_utils import log_exception class WgpuCanvasInterface: @@ -72,39 +19,16 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._canvas_context = None - def get_window_id(self): - """Get the native window id. - - This is used to obtain a surface id, so that wgpu can render - to the region of the screen occupied by the canvas. - """ - raise NotImplementedError() - - def get_display_id(self): - """Get the native display id (Linux only). + def get_surface_info(self): + """Get information about the native window / surface. - On Linux this is needed in addition to the window id to obtain - a surface id. The default implementation calls into the X11 lib - to get the display id. + This is used to obtain a surface id, so that wgpu can render to the + region of the screen occupied by the canvas. Should return None for + offscreen canvases. Otherwise, this should return a dict with a "window" + field. On Linux the dict should contain more fields, see the existing + implementations for reference. """ - # Re-use to avoid creating loads of id's - if getattr(self, "_display_id", None) is not None: - return self._display_id - - if sys.platform.startswith("linux"): - is_wayland = "wayland" in os.getenv("XDG_SESSION_TYPE", "").lower() - if is_wayland: - raise NotImplementedError( - f"Cannot (yet) get display id on {self.__class__.__name__}." - ) - else: - x11 = ctypes.CDLL(ctypes.util.find_library("X11")) - x11.XOpenDisplay.restype = ctypes.c_void_p - self._display_id = x11.XOpenDisplay(None) - else: - raise RuntimeError(f"Cannot get display id on {sys.platform}.") - - return self._display_id + return None def get_physical_size(self): """Get the physical size of the canvas in integer pixels.""" diff --git a/wgpu/gui/glfw.py b/wgpu/gui/glfw.py index 6ca33629..80620aa3 100644 --- a/wgpu/gui/glfw.py +++ b/wgpu/gui/glfw.py @@ -7,7 +7,6 @@ or ``sudo apt install libglfw3-wayland`` when using Wayland. """ -import os import sys import time import weakref @@ -15,7 +14,8 @@ import glfw -from .base import WgpuCanvasBase, WgpuAutoGui, weakbind +from .base import WgpuCanvasBase, WgpuAutoGui +from ._gui_utils import SYSTEM_IS_WAYLAND, weakbind, logger # Make sure that glfw is new enough @@ -25,13 +25,13 @@ # Do checks to prevent pitfalls on hybrid Xorg/Wayland systems is_wayland = False -if sys.platform.startswith("linux"): - is_wayland = "wayland" in os.getenv("XDG_SESSION_TYPE", "").lower() - if is_wayland and not hasattr(glfw, "get_wayland_window"): - raise RuntimeError( - "We're on Wayland but Wayland functions not available. " - + "Did you apt install libglfw3-wayland?" - ) +if sys.platform.startswith("linux") and SYSTEM_IS_WAYLAND: + if not hasattr(glfw, "get_x11_window"): + # Probably glfw was imported before we wgpu was, so we missed our chance + # to set the env var to make glfw use x11. + is_wayland = True + logger.warning("Using GLFW with Wayland, which is experimental.") + # Some glfw functions are not always available set_window_content_scale_callback = lambda *args: None # noqa: E731 @@ -120,11 +120,6 @@ def __init__(self, *, size=None, title=None, **kwargs): # Set window hints glfw.window_hint(glfw.CLIENT_API, glfw.NO_API) glfw.window_hint(glfw.RESIZABLE, True) - # see https://github.com/FlorianRhiem/pyGLFW/issues/42 - # Alternatively, from pyGLFW 1.10 one can set glfw.ERROR_REPORTING='warn' - if sys.platform.startswith("linux"): - if is_wayland: - glfw.window_hint(glfw.FOCUSED, False) # prevent Wayland focus error # Create the window (the initial size may not be in logical pixels) self._window = glfw.create_window(int(size[0]), int(size[1]), title, None, None) @@ -269,27 +264,32 @@ def _set_logical_size(self, new_logical_size): # API - def get_window_id(self): + def get_surface_info(self): if sys.platform.startswith("win"): - return int(glfw.get_win32_window(self._window)) + return { + "platform": "windows", + "window": int(glfw.get_win32_window(self._window)), + } elif sys.platform.startswith("darwin"): - return int(glfw.get_cocoa_window(self._window)) + return { + "platform": "cocoa", + "window": int(glfw.get_cocoa_window(self._window)), + } elif sys.platform.startswith("linux"): if is_wayland: - return int(glfw.get_wayland_window(self._window)) - else: - return int(glfw.get_x11_window(self._window)) - else: - raise RuntimeError(f"Cannot get GLFW window id on {sys.platform}.") - - def get_display_id(self): - if sys.platform.startswith("linux"): - if is_wayland: - return glfw.get_wayland_display() + return { + "platform": "wayland", + "window": int(glfw.get_wayland_window(self._window)), + "display": int(glfw.get_wayland_display()), + } else: - return glfw.get_x11_display() + return { + "platform": "x11", + "window": int(glfw.get_x11_window(self._window)), + "display": int(glfw.get_x11_display()), + } else: - raise RuntimeError(f"Cannot get GLFW display id on {sys.platform}.") + raise RuntimeError(f"Cannot get GLFW surafce info on {sys.platform}.") def get_pixel_ratio(self): return self._pixel_ratio diff --git a/wgpu/gui/offscreen.py b/wgpu/gui/offscreen.py index 7ea5b06d..19b24bd1 100644 --- a/wgpu/gui/offscreen.py +++ b/wgpu/gui/offscreen.py @@ -94,7 +94,7 @@ class WgpuOffscreenCanvasBase(WgpuCanvasBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def get_window_id(self): + def get_surface_info(self): """This canvas does not correspond to an on-screen window.""" return None diff --git a/wgpu/gui/qt.py b/wgpu/gui/qt.py index 90cecd9f..bc40d790 100644 --- a/wgpu/gui/qt.py +++ b/wgpu/gui/qt.py @@ -7,7 +7,11 @@ import ctypes import importlib -from .base import WgpuCanvasBase, WgpuAutoGui, weakbind +from .base import WgpuCanvasBase, WgpuAutoGui +from ._gui_utils import get_alt_x11_display, get_alt_wayland_display, weakbind + + +is_wayland = False # We force Qt to use X11 in _gui_utils.py # Select GUI toolkit @@ -102,12 +106,6 @@ } -# Make Qt not ignore XDG_SESSION_TYPE -# is_wayland = "wayland" in os.getenv("XDG_SESSION_TYPE", "").lower() -# if is_wayland: -# os.environ["QT_QPA_PLATFORM"] = "wayland" - - def enable_hidpi(): """Enable high-res displays.""" set_dpi_aware = qt_version_info < (6, 4) # Pyside @@ -160,13 +158,27 @@ def paintEvent(self, event): # noqa: N802 - this is a Qt method # Methods that we add from wgpu (snake_case) - def get_display_id(self): - # There is qx11info, but it is rarely available. - # https://doc.qt.io/qt-5/qx11info.html#display - return super().get_display_id() # uses X11 lib - - def get_window_id(self): - return int(self.winId()) + def get_surface_info(self): + if sys.platform.startswith("win") or sys.platform.startswith("darwin"): + return { + "window": int(self.winId()), + } + elif sys.platform.startswith("linux"): + # The trick to use an al display pointer works for X11, but results in a segfault on Wayland ... + if is_wayland: + return { + "platform": "wayland", + "window": int(self.winId()), + "display": int(get_alt_wayland_display()), + } + else: + return { + "platform": "x11", + "window": int(self.winId()), + "display": int(get_alt_x11_display()), + } + else: + raise RuntimeError(f"Cannot get Qt surafce info on {sys.platform}.") def get_pixel_ratio(self): # Observations: @@ -185,6 +197,7 @@ def get_physical_size(self): lsize = self.width(), self.height() lsize = float(lsize[0]), float(lsize[1]) ratio = self.devicePixelRatioF() + # When the ratio is not integer (qt6), we need to somehow round # it. It turns out that we need to round it, but also add a # small offset. Tested on Win10 with several different OS @@ -343,11 +356,10 @@ def __init__(self, *, size=None, title=None, max_fps=30, **kwargs): self._subwidget = QWgpuWidget(self, max_fps=max_fps) self._subwidget.add_event_handler(weakbind(self.handle_event), "*") - # Get the window id one time. For some reason this is needed - # to "activate" the canvas. Otherwise the viz is not shown if - # one does not provide canvas to request_adapter(). - # (AK: Cannot reproduce this now, what qtlib/os/versions was this on?) - self._subwidget.get_window_id() + # Note: At some point we called `self._subwidget.winId()` here. For some + # reason this was needed to "activate" the canvas. Otherwise the viz was + # not shown if no canvas was provided to request_adapter(). Removed + # later because could not reproduce. layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -372,11 +384,8 @@ def draw_frame(self): def draw_frame(self, f): self._subwidget.draw_frame = f - def get_display_id(self): - return self._subwidget.get_display_id() - - def get_window_id(self): - return self._subwidget.get_window_id() + def get_surface_info(self): + return self._subwidget.get_surface_info() def get_pixel_ratio(self): return self._subwidget.get_pixel_ratio() diff --git a/wgpu/gui/wx.py b/wgpu/gui/wx.py index 106d751d..e5d40a43 100644 --- a/wgpu/gui/wx.py +++ b/wgpu/gui/wx.py @@ -3,13 +3,18 @@ can be used as a standalone window or in a larger GUI. """ +import sys import ctypes -from .base import WgpuCanvasBase, weakbind +from .base import WgpuCanvasBase +from ._gui_utils import get_alt_x11_display, get_alt_wayland_display, weakbind import wx +is_wayland = False # We force wx to use X11 in _gui_utils.py + + def enable_hidpi(): """Enable high-res displays.""" try: @@ -69,8 +74,26 @@ def _on_resize_done(self, *args): # Methods that we add from wgpu - def get_window_id(self): - return int(self.GetHandle()) + def get_surface_info(self): + if sys.platform.startswith("win") or sys.platform.startswith("darwin"): + return { + "window": int(self.GetHandle()), + } + elif sys.platform.startswith("linux"): + if is_wayland: + return { + "platform": "wayland", + "window": int(self.GetHandle()), + "display": int(get_alt_wayland_display()), + } + else: + return { + "platform": "x11", + "window": int(self.GetHandle()), + "display": int(get_alt_x11_display()), + } + else: + raise RuntimeError(f"Cannot get Qt surafce info on {sys.platform}.") def get_pixel_ratio(self): # todo: this is not hidpi-ready (at least on win10) @@ -133,11 +156,8 @@ def Refresh(self): # noqa: N802 # Methods that we add from wgpu - def get_display_id(self): - return self._subwidget.get_display_id() - - def get_window_id(self): - return self._subwidget.get_window_id() + def get_surface_info(self): + return self._subwidget.get_surface_info() def get_pixel_ratio(self): return self._subwidget.get_pixel_ratio()