Repository: kivy/kivy

Issue:
Expose a method on `kivy.core.window` to get the window handle
Sometimes, and #8596 is one of these cases, we need to get the native Window handle.

With SDL2 (our main window core provider) that could be done by exposing the info retrieved via `SDL_GetWindowWMInfo`, but the API should be provider-agnostic and be ready for the next decade of Window providers.


Discussion:

Resolving patch:
diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py
--- a/kivy/core/window/__init__.py
+++ b/kivy/core/window/__init__.py
@@ -990,6 +990,26 @@ def _set_window_opacity(self, opacity):
     to `1.0`.
     '''
 
+    def _get_native_handle(self):
+        return self._get_window_native_handle()
+
+    def _get_window_native_handle(self):
+        Logger.warning('Window: Native handle is not implemented in the '
+                       'current window provider')
+
+    native_handle = AliasProperty(_get_native_handle, None)
+    '''Read only property to get the native handle of the window.
+
+    .. note::
+        The value is platform-specific and is mostly meant to be passed to
+        other libraries. This feature is currently only supported on Windows
+        and Linux platforms.
+
+    .. versionadded:: 3.0.0
+
+    :attr:`native_handle` is an :class:`~kivy.properties.AliasProperty`.
+    '''
+
     @property
     def __self__(self):
         return self
diff --git a/kivy/core/window/window_sdl2.py b/kivy/core/window/window_sdl2.py
--- a/kivy/core/window/window_sdl2.py
+++ b/kivy/core/window/window_sdl2.py
@@ -343,7 +343,7 @@ def create_window(self, *largs):
                         # make windows dispatch,
                         # WM_NCCALCSIZE explicitly
                         ctypes.windll.user32.SetWindowPos(
-                            self._win.get_window_info().window,
+                            self.native_handle,
                             win32con.HWND_TOP,
                             *self._win.get_window_pos(),
                             *self.system_size,
@@ -491,6 +491,9 @@ def _set_window_opacity(self, opacity):
         if self.opacity != opacity:
             return self._win.set_window_opacity(opacity)
 
+    def _get_window_native_handle(self):
+        return self._win.get_native_handle()
+
     # Transparent Window background
     def _is_shaped(self):
         return self._win.is_window_shaped()


Test patch:
diff --git a/kivy/tests/test_window_base.py b/kivy/tests/test_window_base.py
--- a/kivy/tests/test_window_base.py
+++ b/kivy/tests/test_window_base.py
@@ -1,5 +1,6 @@
 from itertools import product
 
+from kivy import setupconfig
 from kivy.tests import GraphicUnitTest
 from kivy.logger import LoggerHistory
 
@@ -126,3 +127,19 @@ def test_window_opacity_clamping_negative(self):
         if self.check_opacity_support():
             self.Window.opacity = -1.5
             self.assertEqual(self.Window.opacity, 0.0)
+
+
+class WindowNativeHandleTest(GraphicUnitTest):
+
+    def test_native_handle_non_zero(self):
+        win = self.Window
+        native_handle = win.native_handle
+
+        if setupconfig.USE_X11:
+            self.assertNotEqual(native_handle, 0)
+
+        if setupconfig.USE_WAYLAND:
+            self.assertNotEqual(native_handle, 0)
+
+        if setupconfig.PLATFORM == 'win32':
+            self.assertNotEqual(native_handle, 0)