Skip to main content

Keep Tkinter — embed a real system WebView in your Frame (wry, child-window embedding)

Project description

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 (x86_64, arm64) and macOS (Apple Silicon + Intel) — these are the primary release targets. Linux is best-effort: build from source (sdist / git); timing and headless behavior are not guaranteed in v0.0.x.


🔧 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 must be installed (pre-installed on many Windows 10/11 systems).
    • Without WebView2, tkwry is not supported on Windows — there is no fallback engine.
  • macOS — 11 (Big Sur) or later; Apple Silicon (arm64) or Intel (x86_64); system WKWebView (no extra runtime)
  • Linux — WebKitGTK 4.1 + GTK 3 dev packages; X11 or XWayland ($DISPLAY); build the extension from source (see below)

📦 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 also builds from source (sdist via git), not a pre-built wheel. It requires Rust and will fail on machines without a working toolchain — same as pip install . / pip install -e .. Prefer the PyPI wheel on Windows and macOS when you do not need bleeding-edge changes.

Linux (source install, best-effort)

Linux builds from source and runs for many apps, but v0.0.x does not treat Linux stability as a release requirement — focus is on Windows and macOS wheels. Install system dependencies, then:

# 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()

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. Events that occurred before a handler was registered are discarded when you call set_on_page_load (or pass on_page_load in the constructor from the start).

Callback threads: on_page_load and on_title_changed run on the Tk main thread (queued). on_navigation and on_new_window run synchronously on the WebKit thread — wry needs an immediate return value, so they cannot be queued. Keep those handlers fast and avoid Tk widget calls; defer work with root.after if needed.

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

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
Layout pack, grid, place, sync_bounds (delegate to host Frame except sync_bounds)
Lifecycle destroy, destroyed, native

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

Enums: PageLoadEvent, NewWindowResponse, DragDropEvent.

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


⚠️ Known limitations

  • Alpha — APIs may change; not recommended for production yet
  • Windows — WebView2 Runtime required; systems without it are unsupported
  • Linux — source install only (no PyPI wheel); best-effort in v0.0.x — headless CI and event timing are not release blockers
  • DevTools — macOS uses private APIs; avoid in Mac App Store release builds
  • macOS input — Tk text widgets and the WebView share one window; tkwry routes focus automatically (see macOS embedding). IME and other advanced input may still differ from a standalone browser
  • Drag & drop — drop target is the WebView area only (not arbitrary Tk widgets; use tkinterdnd2 for those)

See CHANGELOG.md for release history.


🌐 Platform notes

OS Arch Parent handle Engine Notes
Windows x86_64, arm64 Frame.winfo_id() → HWND WebView2 WebView2 Runtime required
macOS arm64, x86_64 Toplevel content NSView WKWebView See macOS embedding below
Linux winfo_id() → X11 window ID WebKitGTK Source install; best-effort (not a wheel release target)

macOS embedding

On macOS, Tk child Frames usually do not get their own NSView — that is a property of the Tk Aqua backend, not something tkwry can turn into per-frame native views without upstream Tk changes.

tkwry works around this by:

  1. Attaching the WebView to the toplevel content view
  2. Positioning it with set_bounds to match your Frame (<Configure>)
  3. Hiding it with set_visible(False) when the frame is unmapped (<Unmap>) — e.g. another ttk.Notebook tab is selected

Keyboard focus (macOS): tkwry routes input between Tk widgets (Entry, Text, …) and the WebView automatically. Rust hit-tests clicks at the NSEvent layer and switches first responder; Python drains focus signals on the Tk main thread so keystrokes reach the correct target. Use web.focus() and web.focus_parent() when you need explicit control — see examples/url_demo.py. IME and other advanced input may still behave differently than in a standalone browser.

You do not need extra code for tabs or panes — see examples/multi_demo.py. IPC, page-load, title, eval, and drag-and-drop handlers are dispatched on the Tk main thread via an internal queue (avoids WebKit deadlocks). on_navigation and on_new_window are synchronous WebKit-thread hooks — 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 hookson_page_load, on_title_changed (Tk thread); on_navigation, on_new_window (WebKit thread, synchronous)
  • 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)
  • Alpha, but tested — CI runs pytest tests/ on Windows (x86_64 + arm64), macOS, and Linux (Xvfb + WebKitGTK); many timing-sensitive integration tests are skipped on Linux CI (best-effort, not a release blocker)

📁 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
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

Project details


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.0.7.tar.gz (101.0 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.0.7-cp310-abi3-win_arm64.whl (331.7 kB view details)

Uploaded CPython 3.10+Windows ARM64

tkwry-0.0.7-cp310-abi3-win_amd64.whl (344.4 kB view details)

Uploaded CPython 3.10+Windows x86-64

tkwry-0.0.7-cp310-abi3-macosx_11_0_arm64.whl (408.9 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

tkwry-0.0.7-cp310-abi3-macosx_10_12_x86_64.whl (432.6 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for tkwry-0.0.7.tar.gz
Algorithm Hash digest
SHA256 152d80c68f3ec8ea9a87f5bdc666d630065084b6674dbb1af4f244bc5cae0c88
MD5 214a2d3b50991d16ad398042d15bbf79
BLAKE2b-256 e420bd985f0bd1ef23a70bf9b78723bdaf560685b7aa1a344fb0f74509ab9879

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for tkwry-0.0.7-cp310-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 420ff7c861d8601ebc866c0d5bc93e3755099cb511a56741059c36ddbeffc4c3
MD5 9245ddc90c1f3b6c6ab8a36529e2299e
BLAKE2b-256 6399ab279709cb5397b524093d36d27b1c7c0bd7d68e6463a512668efc4e8054

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tkwry-0.0.7-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 344.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.0.7-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 94e377c040ef6a886c10fc04f285c459f6c4c14f5c96d066caff50f38da0300d
MD5 5bffd399a4e0a64a33348c67b5d63799
BLAKE2b-256 793d33463c26f33c0f1c5a5022189e4450cf592520ea349f2ed3540b57b43c4f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tkwry-0.0.7-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 43bbd9b280cbdc8c72e5493912c75c480615d4ea894e7619c44305f245ffee4f
MD5 12750fea1a20494c81a3dc649a06c31e
BLAKE2b-256 5f3635b6d04b8f6e2021dc9e37e95be2ed8971a969ff5c12e1b180474e83e024

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tkwry-0.0.7-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 efb1c3ea11b1218f034519ae27f789c431c102ac2955bdfef430750c0d09631e
MD5 e8fd466adf565d4009afe43e99a9f700
BLAKE2b-256 3f4684bb7287a4507e129fd6fe330ed7c0c272ef54f1e5d5c38ace80b3c3b577

See more details on using hashes here.

Supported by

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