wryview 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2332,8 +2332,9 @@ dependencies = [
2332
2332
 
2333
2333
  [[package]]
2334
2334
  name = "wryview"
2335
- version = "0.3.0"
2335
+ version = "0.3.2"
2336
2336
  dependencies = [
2337
+ "gtk",
2337
2338
  "pyo3",
2338
2339
  "raw-window-handle",
2339
2340
  "wry",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "wryview"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  edition = "2021"
5
5
  description = "Comprehensive Python binding for wry — embeddable cross-platform webview."
6
6
  readme = "README.md"
@@ -14,6 +14,9 @@ pyo3 = { version = "0.28", features = ["extension-module"] }
14
14
  wry = { version = "0.55", features = ["devtools"] }
15
15
  raw-window-handle = "0.6"
16
16
 
17
+ [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
18
+ gtk = "0.18"
19
+
17
20
  [profile.release]
18
21
  lto = true
19
22
  codegen-units = 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wryview
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Classifier: Development Status :: 2 - Pre-Alpha
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.10
@@ -9,7 +9,7 @@ authors = [
9
9
  ]
10
10
  description = "Comprehensive Python binding for wry — cross-platform embeddable webview, no event loop required."
11
11
  readme = "README.md"
12
- version = "0.3.0"
12
+ version = "0.3.2"
13
13
  requires-python = ">=3.10"
14
14
  license = "MPL-2.0"
15
15
  license-files = ["LICENSE"]
@@ -26,7 +26,7 @@ Usage:
26
26
  print(f"{c.name} = {c.value}")
27
27
  """
28
28
 
29
- from wryview._core import WebView, CookieDict
29
+ from wryview._core import WebView, CookieDict, pump_events, ensure_gtk_init
30
30
 
31
- __version__ = "0.3.0"
32
- __all__ = ["WebView", "CookieDict"]
31
+ __version__ = "0.3.2"
32
+ __all__ = ["WebView", "CookieDict", "pump_events", "ensure_gtk_init"]
@@ -110,15 +110,41 @@ impl WebView {
110
110
  headers: Option<pyo3::Py<pyo3::PyAny>>,
111
111
  as_child: bool,
112
112
  ) -> PyResult<Self> {
113
- // ── Build native window handle ─────────────────────────────────
114
- use raw_window_handle::{RawWindowHandle, Win32WindowHandle};
113
+ // ── Build native window handle (platform-specific) ───────────
114
+ #[cfg(target_os = "windows")]
115
+ let window_handle = {
116
+ use raw_window_handle::{RawWindowHandle, Win32WindowHandle};
117
+ let hwnd_nz = NonZero::new(parent_hwnd).ok_or_else(|| {
118
+ PyErr::new::<pyo3::exceptions::PyValueError, _>("parent_hwnd is null")
119
+ })?;
120
+ let win32 = Win32WindowHandle::new(hwnd_nz);
121
+ let raw = RawWindowHandle::Win32(win32);
122
+ unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) }
123
+ };
124
+
125
+ #[cfg(target_os = "macos")]
126
+ let window_handle = {
127
+ use raw_window_handle::{AppKitWindowHandle, RawWindowHandle};
128
+ use std::ptr::NonNull;
129
+ let ptr = parent_hwnd as *mut std::ffi::c_void;
130
+ let ns_view = NonNull::new(ptr).ok_or_else(|| {
131
+ PyErr::new::<pyo3::exceptions::PyValueError, _>("parent_hwnd is null")
132
+ })?;
133
+ let appkit = AppKitWindowHandle::new(ns_view);
134
+ let raw = RawWindowHandle::AppKit(appkit);
135
+ unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) }
136
+ };
115
137
 
116
- let hwnd_nz = NonZero::new(parent_hwnd).ok_or_else(|| {
117
- PyErr::new::<pyo3::exceptions::PyValueError, _>("parent_hwnd is null")
118
- })?;
119
- let win32 = Win32WindowHandle::new(hwnd_nz);
120
- let raw = RawWindowHandle::Win32(win32);
121
- let window_handle = unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) };
138
+ #[cfg(all(unix, not(target_os = "macos")))]
139
+ let window_handle = {
140
+ use raw_window_handle::{RawWindowHandle, XlibWindowHandle};
141
+ if parent_hwnd == 0 {
142
+ return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>("parent_hwnd is null"));
143
+ }
144
+ let xlib = XlibWindowHandle::new(parent_hwnd as u64);
145
+ let raw = RawWindowHandle::Xlib(xlib);
146
+ unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) }
147
+ };
122
148
 
123
149
  // ── Callback slots ─────────────────────────────────────────────
124
150
  let ipc_cb: Arc<Mutex<Option<pyo3::Py<pyo3::PyAny>>>> = Arc::new(Mutex::new(ipc_handler));
@@ -306,7 +332,13 @@ impl WebView {
306
332
  for (k, v) in &resp_headers {
307
333
  builder = builder.header(k.as_str(), v.as_str());
308
334
  }
309
- let _ = r.respond(builder.body(std::borrow::Cow::Owned(resp_body)).unwrap());
335
+ let response =
336
+ builder.body(std::borrow::Cow::Owned(resp_body)).unwrap();
337
+ Python::attach(|py| {
338
+ py.detach(move || {
339
+ let _ = r.respond(response);
340
+ });
341
+ });
310
342
  }
311
343
  Ok::<_, pyo3::PyErr>(unsafe { Python::assume_attached() }.None())
312
344
  }
@@ -371,6 +403,23 @@ impl WebView {
371
403
  builder = builder.with_html(h);
372
404
  }
373
405
 
406
+ // ── GTK init (Linux only, idempotent) ──────────────────────────
407
+ #[cfg(all(unix, not(target_os = "macos")))]
408
+ {
409
+ match gtk::init() {
410
+ Ok(()) => { /* GTK initialized */ }
411
+ Err(ref e) => {
412
+ // "already initialized" is harmless — gtk::init() is idempotent
413
+ if !gtk::is_initialized() {
414
+ return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
415
+ format!("GTK init failed: {}. Is $DISPLAY set?", e),
416
+ ));
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ // ── Build the webview ──────────────────────────────────────────
374
423
  let webview = if as_child {
375
424
  builder.build_as_child(&window_handle)
376
425
  } else {
@@ -751,11 +800,56 @@ impl CookieDict {
751
800
  }
752
801
  }
753
802
 
803
+ // ── Platform event pump ─────────────────────────────────────────────────
804
+ ///
805
+ /// Pump platform-specific events (no-op on Windows/macOS).
806
+ ///
807
+ /// On Linux, call this periodically (e.g. via ``QTimer`` at ~60 fps) to
808
+ /// keep WebKitGTK rendering when the host app runs a non-GTK event loop
809
+ /// such as Qt's xcb backend.
810
+ ///
811
+ /// This is a module-level function — it does not belong to any single
812
+ /// ``WebView`` instance.
813
+ #[pyfunction]
814
+ fn pump_events() {
815
+ #[cfg(all(unix, not(target_os = "macos")))]
816
+ {
817
+ // Process pending GTK/GLib events without blocking.
818
+ // Equivalent to Python: while Gtk.events_pending(): Gtk.main_iteration_do(False)
819
+ while gtk::main_iteration_do(false) {
820
+ // keep pumping while events remain
821
+ }
822
+ }
823
+ // Windows / macOS: nothing needed — the native webview integrates with
824
+ // the same event loop that Qt uses (Windows message pump / NSRunLoop).
825
+ }
826
+
827
+ /// Initialise GTK from Rust's perspective (no-op on Windows/macOS).
828
+ ///
829
+ /// Python's ``gi.repository.Gtk.init()`` calls C's ``gtk_init()``, which
830
+ /// initialises GTK at the OS level. However the Rust ``gtk`` crate tracks
831
+ /// initialisation state in a *separate* static ``AtomicBool``, and the
832
+ /// ``webkit2gtk`` crate (which wry uses internally on Linux) checks that
833
+ /// Rust-level flag.
834
+ ///
835
+ /// Call this **once** before creating the first ``WebView`` on Linux. It is
836
+ /// idempotent — safe to call after Python's ``Gtk.init()`` or multiple times.
837
+ #[pyfunction]
838
+ fn ensure_gtk_init() {
839
+ #[cfg(all(unix, not(target_os = "macos")))]
840
+ {
841
+ let _ = gtk::init(); // idempotent; sets the Rust IS_INITIALIZED atomic
842
+ }
843
+ // Windows / macOS: nothing needed
844
+ }
845
+
754
846
  // ── Module ─────────────────────────────────────────────────────────────────
755
847
 
756
848
  #[pymodule]
757
849
  fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
758
850
  m.add_class::<WebView>()?;
759
851
  m.add_class::<CookieDict>()?;
852
+ m.add_function(wrap_pyfunction!(pump_events, m)?)?;
853
+ m.add_function(wrap_pyfunction!(ensure_gtk_init, m)?)?;
760
854
  Ok(())
761
855
  }
File without changes
File without changes
File without changes
File without changes
File without changes