Keep Tkinter — give it the WebView it never had.
Embed a real system WebView (wry) inside your Frame: modern HTML, JS, and IPC in the same layout as your buttons and tabs — one mainloop, no floating overlay.
Alpha — Early preview (see PyPI badge for the current version). APIs and behavior may change without notice. Not recommended for production use yet.
Tkinter is still a solid GUI shell — it just had no first-class way to host modern web content inside a widget. Overlay-style WebViews drift out of sync when you move, resize, or switch tabs.
tkwry fills that missing piece:
- True child embedding —
build_as_childvia HWND, NSView, or X11 window ID - One event loop — Tk
mainlooponly; no separate app runtime - Local apps —
app=serves HTML/CSS/JS viatkwry://(no localhost HTTP server) - IPC / RPC / emit — JS↔Python events, request/response, and streams without freezing the UI
- Trust boundaries — IPC/RPC default to the initial origin;
untrusted=Truefor arbitrary sites - Layout-aware — tracks
pack/grid/place, tabs, andPanedWindow
Pre-built abi3 wheels ship for Windows and macOS. Linux is source-only (best-effort by design) — see Platform notes.
| Topic | Doc |
|---|---|
Usage (app=, eval, layout, nav, downloads, API) |
docs/usage.md |
Trust boundaries (untrusted, bridge_origins, recipes) |
docs/trust.md |
IPC / RPC / emit (expose, call / stream, cancel, limits) |
docs/rpc.md |
| Platform notes (Windows / macOS / Linux, print, window chrome) | docs/platforms.md |
- Python 3.10+
- Tkinter (included with most Python builds)
- Building from source (git clone,
pip install git+…, or Linux) — Rust toolchain (stable);pipuses maturin as the build backend - Windows (x86_64, arm64) — WebView2 Runtime (no fallback engine; see Platform notes)
- macOS — 11 (Big Sur)+, arm64 or x86_64; system WKWebView
- Linux — WebKitGTK 4.1 + GTK 3; X11 or XWayland (
$DISPLAY); source build only (see Installation and Platform notes)
pip install tkwryCloning the repo and installing locally compiles the Rust extension on your machine. You need a Rust toolchain (rustup) and platform runtimes from Requirements above (WebView2 on Windows, etc.). pip pulls in maturin automatically as the build backend.
git clone https://github.com/mashu3/tkwry.git
cd tkwry
pip install -e .Use this for development and for running the examples from the tree.
pip install git+https://github.com/mashu3/tkwry.gitThis builds from source (sdist via git), not a pre-built wheel — needs Rust, same as pip install .. Prefer the PyPI wheel on Windows and macOS unless you need unreleased commits.
Install system dependencies, then build from source (support posture: Platform notes):
# Debian / Ubuntu
sudo apt install \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libglib2.0-dev
# Runtime (for end users of your app)
# sudo apt install libwebkit2gtk-4.1-0 libgtk-3-0
pip install maturin
git clone https://github.com/mashu3/tkwry.git
cd tkwry
pip install .GTK events are pumped automatically on a Tk timer while your app runs.
import tkinter as tk
from tkwry import WebView
root = tk.Tk()
root.geometry("900x600")
frame = tk.Frame(root, bg="#222")
frame.pack(fill="both", expand=True, padx=8, pady=8)
web = WebView(frame, url="https://github.com")
web.when_failed(lambda exc: print("native create failed:", exc))
root.mainloop()The constructor does not raise if native create fails. Handle
when_failed / <<WebViewCreateFailed>>. How-to (app=, eval, layout,
navigation, downloads, cleanup) and the API table:
Usage. IPC / RPC / stream: docs/rpc.md.
Trust (untrusted, bridge_origins): docs/trust.md.
Short checklist — details live in Platform notes (especially macOS embedding).
- Alpha — APIs may change; not for production yet (see banner above)
- Windows — WebView2 Runtime required; missing runtime →
creation_failed/<<WebViewCreateFailed>>(gated APIs raiseWebViewCreationErrorwith install text) - Print —
web.print()opens the system dialog; no PDF, no return value, no success/fail/cancel (wry has none). See Platform notes — Print - Window chrome — title / icon / geometry / fullscreen / min/max /
-topmostare the host Toplevel; WebView size follows the Frame (sync_bounds). See Usage — Layout / resize - Windows DevTools — wry/WebView2 reports
is_devtools_open()asFalseandclose_devtools()is a no-op;open_devtools()still opens the inspector - Linux — no PyPI wheel (by design); best-effort source install
- Linux concurrent
eval_js_with_callback— evaluating on multiple WebViews at once can stall WebKitGTK; prefer sequential evals (see Linux) - Shared
WebSession+app=— WebViews that share a non-ephemeral session must use the sameapp=root (ValueErrorotherwise; Linux can registertkwry://only once per context); do not share a persistent profile with untrusted sites - Trust / external content — RPC/IPC default to the initial origin (optional path prefix /
bridge_allow);bridge_origins="*"warns and needsexpose(..., allow_any_origin=True);app=locks navigation totkwry://(navigation_allow/open_external=Truefor extra origins + system browser);untrusted=Truealso denies downloads unlessdownload_allow/on_downloadpermits (see Trust boundaries) - macOS DevTools — create with
devtools=True, thenopen_devtools()(flag alone does not open;open_devtools()without the flag is a no-op on macOS); uses private APIs — avoid in Mac App Store builds - macOS IME / focus — not Safari-parity; mid-composition focus flips can mis-route input
- macOS import order — import
tkwrybefore AppKit/NSApplication, or you may see a double titlebar url()on macOS — may beNonefor inline HTML until a concreteload_url(WKWebView has no documentNSURL)- Sync hooks / queues —
on_navigation/on_new_windowmay block WebKit up to ~60s; do not create a WebView fromon_new_window(useopen_external=True/open_in_browser); async event queues cap at 2048; IPC/RPC messages cap at 10 MiB (see Usage — Navigation / lifecycle callbacks) - RPC cancel / destroy — timeout, JS
cancel, anddestroy()are cooperative only (rpc_cancelled()), including open streams; Python cannot preempt a running worker.destroy()joins the pool for ~2 seconds; leftover threads are logged to stderr (see IPC / RPC / emit) - Eval / navigation timeout —
eval_js_with_callbacktimeout (30s) isWebViewTimeoutError(on_error,<<WebViewEvalFailed>>,last_eval_error);on_navigation/on_new_windowtimeout still returns the default deny and signalsWebViewNavigationError(<<WebViewNavigationFailed>>,last_navigation_error) — not raised on the WebKit thread - Drag & drop — WebView area only (use tkinterdnd2 for arbitrary Tk widgets)
- Screenshot — no
WebViewcapture API; wry 0.56.1 does not expose one yet (wry#1674). tkwry will wrap it when upstream ships; no JS fallback (see Platform notes)
See CHANGELOG.md for release history.
Pre-built wheels: Windows and macOS. Linux is source-only (best-effort).
| OS | Arch | Parent handle | Engine |
|---|---|---|---|
| Windows | x86_64, arm64 | Frame.winfo_id() → HWND |
WebView2 |
| macOS | arm64, x86_64 | Toplevel content NSView |
WKWebView |
| Linux | — | winfo_id() → X11 window ID |
WebKitGTK |
DPI, WebView2, macOS embedding / IME / import order, and Linux eval caveats: Platform notes.
Tkinter apps already have a window and a layout. The web belongs inside a Frame — same mainloop, same tabs and panes — not in a separate top-level webview that floats beside your UI. tkwry wraps wry's build_as_child against the native surface Tk gives your widgets.
- Local app assets —
app=+tkwry://(SPA fallback,app_devno-store, ETag/HEAD/Range, default CSP, optional COOP/CORP, boundedwatch_app(); open-then-verify symlink/junction confinement) - IPC / RPC / emit — events vs request/response; sync-generator
stream; worker RPC; typed TypeError; protocolversion; JScancel; Python→JSemit; origin/path allowlist (bridge_origins) +bridge_allow+untrusted=viewer mode - WebSession — shared wry
WebContext; sharedapp=roots must match;emit_allbroadcast - Testing helpers —
tkwry.testing.wait_until/wait_ready/wait_eval/wait_title - Child-window embedding — WebView is a native child of your Tk window surface, not a floating overlay
- Bounds & visibility sync — follows
<Configure>,<Map>, and<Unmap>(tabs /Notebookhide unmapped views) - Typed failure signals — create:
<<WebViewCreateFailed>>/when_failed; eval:<<WebViewEvalFailed>>/WebViewTimeoutError; nav hook timeout:<<WebViewNavigationFailed>>/WebViewNavigationError(native still returns the default deny); downloads:<<WebViewDownloadComplete>>/<<WebViewDownloadFailed>>/last_download - Deferred callbacks — IPC, RPC, page load, title, eval results, and DnD queue to Tk (avoids macOS deadlocks)
- URL safety — Python
load_urlnormalizes/validates schemes; in-page nav deniesjavascript:/blob:/… (data:underapp=);app=stays ontkwry://; IPC/RPC origin/path allowlist +bridge_allow - DevTools —
devtools=Trueat create, thenopen_devtools()/close_devtools()/is_devtools_open()(macOS: private APIs) - Print —
web.print()opens the system print dialog (no PDF / no result) - Downloads —
on_download/on_download_complete+download_allow;untrusted=Truedenies unless permitted;last_download+<<WebViewDownloadComplete>>/<<WebViewDownloadFailed>>;unique_download_pathfor same-name files (absolute dest only; no overwrite policy) - Native drag & drop — OS-level file drops into the WebView (no tkinterdnd2)
- Navigation hooks — all handlers on the Tk thread;
on_navigation/on_new_windowblock WebKit until they return - Multiple layouts — works with
pack,grid,place,Notebook, andPanedWindow(see examples) - Plotly-ready — load HTML +
eval_js; demo toggles CDN vs localapp= - Folium-ready — embed Leaflet maps from Folium HTML (right-click to pin)
- Markdown-ready — Monaco editor + live preview in a
PanedWindow(seeexamples/markdown_demo.py; CDN required — or vendor underapp=) - CI-tested —
pyteston Windows (x86_64 + arm64), macOS, and Linux (Xvfb + WebKitGTK)
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .| Script | Description |
|---|---|
examples/browser_demo.py |
URL bar, tabs, shared WebSession, print / downloads / emit_all (bridge_origins="*"; no expose) |
examples/ipc_demo.py |
IPC events, RPC (call / kwargs / worker), stream (ticks + cancel), and emit |
examples/multi_demo.py |
Multiple WebViews, tabs, panes; emit_all flash |
examples/plotly_demo.py |
Plotly charts — CDN or local app= (pip install plotly) |
examples/folium_demo.py |
Folium maps (pip install folium; tiles need the network) |
examples/markdown_demo.py |
Monaco markdown editor + live preview (CDN) |
examples/dnd_demo.py |
Native file drag & drop into WebView |
python examples/browser_demo.py
python examples/ipc_demo.py
python examples/multi_demo.py
python examples/plotly_demo.py
python examples/folium_demo.py
python examples/markdown_demo.py
python examples/dnd_demo.pyThis project is licensed under the MIT License. See LICENSE.
This project links against wry, which is dual-licensed (Apache-2.0 or MIT). tkwry uses wry under MIT; see NOTICE for attribution.