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 (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). 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
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. On macOS, focused=True is deferred until <<WebViewReady>> (then focus() runs automatically); call focus() yourself after layout changes. IME and other advanced input may still behave differently than in a standalone browser.

Import order (macOS): Import tkwry before any library that touches AppKit / NSApplication (e.g. from AppKit import NSApp or NSApplication.sharedApplication()). If full AppKit starts first, macOS may reserve automatic window-tab chrome and you can see a double titlebar strip. tkwry calls disable_process_automatic_window_tabbing at import on the main thread; that must run before NSApplication initializes. See examples/macos_double_titlebar_repro.py.

Window tabbing disable: If disable_window_tabbing fails during WebView create, tkwry logs to stderr and retries asynchronously (Python <Map> / deferred hooks). This is non-fatal and does not raise WebViewCreationError.

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
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.0.9.tar.gz (151.2 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.9-cp310-abi3-win_arm64.whl (379.8 kB view details)

Uploaded CPython 3.10+Windows ARM64

tkwry-0.0.9-cp310-abi3-win_amd64.whl (392.0 kB view details)

Uploaded CPython 3.10+Windows x86-64

tkwry-0.0.9-cp310-abi3-macosx_11_0_arm64.whl (466.6 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

tkwry-0.0.9-cp310-abi3-macosx_10_12_x86_64.whl (485.2 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for tkwry-0.0.9.tar.gz
Algorithm Hash digest
SHA256 36dd004fa1144534e68927b9d63452a5624233814e6f8b2be577d45f241d1e85
MD5 0d11a17f57df0ce809e15cdd3762005f
BLAKE2b-256 b397bde2d464bfac70502dc14bd1de77c19154db19b50c2261b37e3198a30d1e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tkwry-0.0.9-cp310-abi3-win_arm64.whl
  • Upload date:
  • Size: 379.8 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.9-cp310-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 43f48c62c8fc76a0eb80d9155955c448fa58c435b96a868bd8259696c0e63bc2
MD5 e574d6bfd7b994a475645e254479f70f
BLAKE2b-256 875fe05b5994c53712fe8c64e11ae554ad71cf00ff21c5f893b9036111e0d4d4

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tkwry-0.0.9-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 392.0 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.9-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 0904344f8bc2b40b306efa77afba1e50ef999618e684f8c0ffca9345270695b4
MD5 f2014bc0c797f761fa47673061950c33
BLAKE2b-256 cc58a9327e801d8aa2781ec12f361697a8c4511c52230c71251ab476cb19cefc

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tkwry-0.0.9-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 cba9380b13440bf71bd58613f326d4160787b61caa478496de81e6b2b001cecc
MD5 6631474ad4e67f1200e72b20f3a15f10
BLAKE2b-256 fe0fbdc097bab34b7fb6dce140152322837ccbc9f83ef3431d37ab56af1c12e9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tkwry-0.0.9-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 728785a8725f97121305ef402cd29feb190de361c25409d4136c036787768321
MD5 33112af4b8f755cf8c2c8b542917c7a2
BLAKE2b-256 827d506dbff36819591b3cb6fa7d8c5ee28c874381d93f43205a7db1091ec605

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

0.1.0

5 files

This release

0.0.9 This release

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