Skip to main content

tkwry

License: MIT PyPI - Python Version GitHub Release PyPI Version Downloads Status: Alpha CI

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.


📖 Overview

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 embeddingbuild_as_child via HWND, NSView, or X11 window ID
  • One event loop — Tk mainloop only; no separate app runtime
  • IPC bridge — JavaScript → Python callbacks without freezing the UI
  • Layout-aware — tracks pack / grid / place, tabs, and PanedWindow

Pre-built abi3 wheels ship for Windows and macOS. Linux is source-only (best-effort by design) — see Platform notes.


🔧 Requirements

  • Python 3.10+
  • Tkinter (included with most Python builds)
  • Building from source (git clone, pip install git+…, or Linux) — Rust toolchain (stable); pip uses 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)

📦 Installation

PyPI (recommended — Windows / macOS wheels)

pip install tkwry

From a git clone (source build)

Cloning 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.

Install a git revision with pip (source build)

pip install git+https://github.com/mashu3/tkwry.git

This 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.

Linux (source install)

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.


🚀 Usage

Basic WebView

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")

root.mainloop()

IPC (JavaScript → Python)

def on_message(msg: str) -> None:
    print("from JS:", msg)

web = WebView(
    frame,
    html='<button onclick="window.ipc.postMessage(\'hi\')">send</button>',
    ipc_handler=on_message,
)

Load HTML / evaluate JavaScript

web.load_html("<h1>Hello</h1>")
web.eval_js("document.title = 'Hi'")  # fire-and-forget (Tk idle, no return value)
web.eval_js("bad()", on_error=lambda exc: print("eval failed:", exc))
web.eval_js_with_callback("document.title", print)  # async; callback on Tk main thread
web.load_url("https://example.com")
web.reload()
print(web.url)
web.focus()
web.open_devtools()

Rapid load_url / load_html calls are coalesced (last-wins)load(A); load(B); load(C) loads C only.

eval_js does not return a result (not synchronous). Use eval_js_with_callback when you need the JavaScript return value as a str. Pass on_error= to handle evaluation failures on the Tk main thread; otherwise the traceback is printed to stderr (EvalErrorHandler).

Layout / resize

Bounds sync runs automatically on <Configure>, <Map>, and <Unmap>. Call sync_bounds() manually after custom layout changes so the WebView reflows (e.g. centered images):

web.sync_bounds()

Size contract: once the host is laid out, the mapped Frame.winfo_width() / winfo_height() are the sole source of truth for native bounds. Constructor width/height and explicit place(..., width=, height=) are only used before Tk reports a real size (winfo_* <= 1). Prefer passing width/height to place() so the host gets a definite allocation (especially on Linux / Xvfb).

Navigation / lifecycle callbacks

from tkwry import NewWindowResponse, PageLoadEvent

web = WebView(
    frame,
    url="https://example.com",
    on_page_load=lambda evt, url: print(evt, url),
    on_title_changed=lambda title: root.title(title),
    on_navigation=lambda url: url.startswith("https://"),
    on_new_window=lambda url: NewWindowResponse.Deny,
)

on_page_load fires PageLoadEvent.Started and PageLoadEvent.Finished for every navigation while a handler is registered (native listening follows the handler). Events are not replayed for navigations that happened before set_on_page_load / constructor on_page_load.

Callback threads: all user handlers run on the Tk main thread. on_page_load, on_title_changed, IPC, and drag-and-drop are queued asynchronously. on_navigation and on_new_window are also invoked on Tk, but WebKit blocks until they return a value — keep them fast (heavy work → return deny/default and defer with root.after). Timed-out sync hooks are canceled after about 60s total wait.

Async queues (IPC, page-load, title, drag-drop, eval) cap at 2048 pending items each; further events are compacted or dropped. Use take_queue_drop_counts() to observe overflows.

Callback exceptions are printed to stderr and do not stop event delivery.

Drag & drop (native OS path)

File drops from Finder / Explorer are handled by the OS WebView. Your handler runs on the Tk main thread (tkwry queues events from WebKit automatically). The handler is notify-only (-> None); drops are always accepted and cannot be denied from Python.

from tkwry import DragDropEvent

def on_drop(event, paths, position):
    if event == DragDropEvent.Drop:
        print("files:", paths)

web = WebView(frame, html="...", drag_drop_handler=on_drop)

See examples/dnd_demo.py.

Cleanup

web.destroy()   # release native webview; host Frame is kept
# or destroy the host Frame — both tear down the webview

📚 API summary

Category Members
Content load_url, load_html, reload, url
JavaScript eval_js (on_error), eval_js_with_callback
IPC set_ipc_handler
Callbacks set_on_navigation, set_on_page_load, set_on_title_changed, set_on_new_window, set_drag_drop_handler
Appearance set_background_color, focus, focus_parent, open_devtools, close_devtools, is_devtools_open
Create-only set_user_agent, set_initialization_script (raise after native create)
Layout pack, grid, place, sync_bounds (delegate to host Frame except sync_bounds)
Lifecycle ready, phase / WebViewPhase, when_ready, wait_until_ready, bind, destroy, destroyed, native, creation_failed, creation_error
Diagnostics take_queue_drop_counts

Constructor options: url, html, ipc_handler, devtools, background_color, user_agent, initialization_script, focused, plus the callback hooks above.

Enums: PageLoadEvent, NewWindowResponse, DragDropEvent, WebViewPhase.

Type aliases: IpcHandler, NavigationHandler, PageLoadHandler, TitleChangedHandler, NewWindowHandler, DragDropHandler, EvalCallback, EvalErrorHandler.


⚠️ Known limitations

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 → WebViewCreationError
  • 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)
  • macOS DevTools — optional (devtools=True / open_devtools()); 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 tkwry before AppKit/NSApplication, or you may see a double titlebar
  • url() on macOS — may be None for inline HTML until a concrete load_url
  • Hidden Notebook tabs — native view is hidden; ready can stay True while unmapped
  • Sync hooks / queueson_navigation / on_new_window may block WebKit up to ~60s; async event queues cap at 2048 (see Navigation / lifecycle callbacks)
  • Drag & drop — WebView area only (use tkinterdnd2 for arbitrary Tk widgets)

See CHANGELOG.md for release history.


🌐 Platform notes

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

Windows

WebView2 Runtime must be installed (common on Windows 10/11). Without it, creation fails with WebViewCreationError (install link in the message). There is no fallback engine.

Linux

By design in v0.1.x: no PyPI wheel; install from source (sdist / git). Support is best-effort — not a release blocker for Windows/macOS wheels. CI runs the integration suite under Xvfb; real-desktop / Wayland timing may still differ. GTK is pumped on a Tk timer automatically after install.

For place layouts, pass explicit width/height so host winfo_* settles; native size follows those winfo_* values (see Layout / resize).

Concurrent eval: calling eval_js_with_callback on multiple WebViews at the same time can stall under WebKitGTK (especially headless / Xvfb). Prefer one eval at a time — wait for each callback (or error) before starting the next — when you have several views.

macOS embedding

Tk child Frames usually do not get their own NSView (Tk Aqua). tkwry attaches to the toplevel content view, positions with set_bounds on <Configure>, and hides with set_visible(False) on <Unmap> (e.g. another Notebook tab). Per-frame native views would need upstream Tk changes.

Keyboard focus: clicks are hit-tested at the NSEvent layer; Python drains focus signals on the Tk main thread. Use web.focus() / web.focus_parent() for explicit control (examples/url_demo.py). focused=True waits for <<WebViewReady>>, then calls focus(); call focus() yourself after later layout changes.

IME: composition stays with the current first responder. Switching Tk ↔ WebView mid-composition (or fighting the system candidate window) can cancel or mis-deliver input vs Safari. Not a v0.1 goal — finish composition before changing focus, or keep IME editing in one surface.

Import order / double titlebar: import tkwry before anything that starts AppKit / NSApplication. On import, tkwry disables process-level automatic window tabbing on the main thread. If AppKit starts first, macOS may show a double titlebar strip — see examples/macos_double_titlebar_repro.py. If per-window tabbing disable during create fails, tkwry logs and retries asynchronously (non-fatal).

url(): may be None for inline HTML (html= / load_html) or when WKWebView has no document NSURL. After load_url, it becomes the concrete URI.

Notebook / tabs: unmapped tabs hide the native view (set_visible(False)) and show again on <Map> — required because frames share the toplevel NSView. ready is layout-based (can stay True while hidden); prefer visible-tab work after the tab is selected. No extra app code for tabs/panes — examples/multi_demo.py.

All user handlers run on the Tk main thread. on_navigation / on_new_window still make WebKit wait for a return value — see Navigation / lifecycle callbacks.


💡 Why child-window embedding?

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.


🧩 Features

  • 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 / Notebook work out of the box on macOS)
  • Deferred callbacks — IPC, page load, title, eval results, and DnD queue to Tk (avoids macOS deadlocks)
  • URL safety — normalizes and validates URLs before navigation
  • DevToolsopen_devtools() / devtools=True for debugging
  • Native drag & drop — OS-level file drops into the WebView (no tkinterdnd2)
  • Navigation hooks — all handlers on the Tk thread; on_navigation / on_new_window block WebKit until they return
  • Multiple layouts — works with pack, grid, place, Notebook, and PanedWindow (see examples)
  • Plotly-ready — load HTML + eval_js for interactive charts
  • Folium-ready — embed Leaflet maps from Folium HTML (right-click to pin)
  • Markdown-ready — Monaco editor + live preview in a PanedWindow (see examples/markdown_demo.py; CDN required)
  • CI-testedpytest on Windows (x86_64 + arm64), macOS, and Linux (Xvfb + WebKitGTK)

📁 Examples

python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e .
Script Description
examples/url_demo.py URL bar + embedded page
examples/ipc_demo.py JavaScript ↔ Tkinter IPC
examples/multi_demo.py Multiple WebViews, tabs, panes
examples/plotly_demo.py Plotly charts (pip install plotly)
examples/folium_demo.py Folium maps (pip install folium)
examples/markdown_demo.py Monaco markdown editor + live preview (CDN)
examples/dnd_demo.py Native file drag & drop into WebView
examples/macos_double_titlebar_repro.py macOS import-order / double titlebar comparison
python examples/url_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.py

📝 License

This 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.


👨‍💻 Author

mashu3

Contributors

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tkwry-0.1.0.tar.gz (172.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

tkwry-0.1.0-cp310-abi3-win_arm64.whl (389.4 kB view details)

Uploaded CPython 3.10+Windows ARM64

tkwry-0.1.0-cp310-abi3-win_amd64.whl (401.4 kB view details)

Uploaded CPython 3.10+Windows x86-64

tkwry-0.1.0-cp310-abi3-macosx_11_0_arm64.whl (476.1 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

tkwry-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl (494.8 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file tkwry-0.1.0.tar.gz.

File metadata

  • Download URL: tkwry-0.1.0.tar.gz
  • Upload date:
  • Size: 172.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: maturin/1.14.1

File hashes

Hashes for tkwry-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f7a4461d4d541949f4147148097efac78bb0e065b4bd2a49db24448924f1811f
MD5 c6f3385bbfec35bb3b5d7ab776bf9e40
BLAKE2b-256 cbbef96c21fad8f3831488dac8f32cce57501bbe0e1d4388a570d6c341792297

See more details on using hashes here.

File details

Details for the file tkwry-0.1.0-cp310-abi3-win_arm64.whl.

File metadata

  • Download URL: tkwry-0.1.0-cp310-abi3-win_arm64.whl
  • Upload date:
  • Size: 389.4 kB
  • Tags: CPython 3.10+, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: maturin/1.14.1

File hashes

Hashes for tkwry-0.1.0-cp310-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 95a65e96b519ebd24eabeba9ef6bba0a9fe3d49b49189dcf610cb6a7351cde78
MD5 c5e624e65f191dbdad511f91cdb97d59
BLAKE2b-256 25186c87b92e3f938d55746d6321fb876a3ac872e46974d62b085de84c20bc31

See more details on using hashes here.

File details

Details for the file tkwry-0.1.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: tkwry-0.1.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 401.4 kB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: maturin/1.14.1

File hashes

Hashes for tkwry-0.1.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 e0add8537b97d1849110c77e9cff26a4424541757bafc0942be393ab80748023
MD5 191c6f4e64d84b15f8f0549453ebadf4
BLAKE2b-256 eea4b39dda5e28fdcacceeb39870f288313bd2b07505a5ec54a5e8b9c4c0c145

See more details on using hashes here.

File details

Details for the file tkwry-0.1.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for tkwry-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8f1354924ca195b4cd9fbe36a98b2bfd174e86e42f504290f110730b3915fd8d
MD5 2e98c484656eb71c27755fbb8dc6382b
BLAKE2b-256 a8bb151b954d3d5c2ca8cad8217a41216588212835e95af73a027d1b9c67d3a6

See more details on using hashes here.

File details

Details for the file tkwry-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for tkwry-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a0e41298195b2a4b6473e304b38adf1e913afdbbfb35effc295bc09ab52d10bf
MD5 bba534ce24859a096dff4c96c3ebeb4a
BLAKE2b-256 23c033842b8bd5cc196c27dd91cd8c09b237d759d40fa5316473faf9db26ad62

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.4

5 files

0.1.3

5 files

0.1.2

5 files

0.1.1

5 files

This release

0.1.0 This release

5 files

0.0.9

5 files

0.0.8

5 files

0.0.7

5 files

0.0.6

5 files

0.0.5

4 files

0.0.4

4 files

0.0.3

4 files

0.0.2

4 files

0.0.1

4 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page