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
  • Local appsapp= serves HTML/CSS/JS via tkwry:// (no localhost HTTP server)
  • IPC / RPC / emit — JS↔Python events and request/response without freezing the UI
  • Trust boundaries — IPC/RPC default to the initial origin; untrusted=True for arbitrary sites
  • 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 and RPC (JavaScript ↔ Python)

Use IPC for fire-and-forget events and RPC for request/response:

Direction Role Python JavaScript
JS → Python IPC (event) set_ipc_handler / ipc_handler= window.ipc.postMessage(str)
JS → Python RPC (call) @web.expose await window.tkwry.call(name, ...)
Python → JS Emit (event) web.emit(event, data) window.tkwry.on(event, handler)

These APIs run with desktop-app privileges. By default only the initial page origin may use them — see Trust boundaries.

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

Local app assets (app= / tkwry://)

Serve a directory of HTML/CSS/JS through a custom protocol — no localhost HTTP server. Relative links resolve offline (React/Vue/Svelte/Monaco bundles, etc.).

web/
├── index.html
├── style.css
└── assets/
    └── main.js
web = WebView(frame, app="./web")          # loads tkwry://localhost/index.html
# or: WebView(frame, app="./web/index.html")
# SPA client routes: spa_fallback=True
# Dev: app_dev=True (Cache-Control: no-store) + web.watch_app() for reload
# watch_app() polls web suffixes (skips node_modules/.git/.vendor; max 2000 files)

SPA fallback (spa_fallback=True): missing extension-less paths (and .html / .htm) fall back to index.html. A missing static asset such as /app.js / /style.css / /video.mp4 stays 404 — it is never replaced with index.html. If the request has an Accept header that does not include text/html or */* (for example application/json), fallback is skipped.

Cache: app_dev=True sends Cache-Control: no-store. Production (default) still emits ETag; conditional If-None-Match returns 304. HEAD and single Range: bytes= requests are supported (audio/video).

Constructor app= fixes the filesystem root at create time. Later load_url("tkwry://localhost/other.html") can navigate within that root (Windows WebView2 rewrites this to https://tkwry.localhost/... internally). The tkwry:// handler percent-decodes each path segment (so %2e%2e cannot bypass ..), rejects NUL / invalid UTF-8 / Windows drive and UNC shapes, then opens the file under the app root and checks the opened file's identity against the canonical path (symlinks, Windows junctions, and reparse points that escape return 403). Internal links that stay under the root are allowed. Monaco / CDN scripts may still be loaded from the network inside that HTML when you choose not to vendor them yet. The Plotly demo toggles CDN vs Local (app=); Local caches plotly.js under examples/.vendor/.

See examples/plotly_demo.py.

RPC (expose / window.tkwry.call)

Keep raw ipc_handler + window.ipc.postMessage for free-form events. On top, expose callables and await them from JS:

web = WebView(frame, html=HTML)

@web.expose
def greet(name: str) -> str:
    return f"hello {name}"

# Heavy I/O / CPU — run off the Tk thread so the UI stays responsive
@web.expose(thread=True, timeout=30.0)
def heavy_task(data: dict) -> dict:
    from tkwry import rpc_cancelled

    ...
    if rpc_cancelled():
        return {"status": "cancelled"}
    return result
const text = await window.tkwry.call("greet", "Ada");
// optional JS-side timeout (ms) and Python kwargs:
await window.tkwry.call("heavy_task", payload, {
  timeout: 5000,
  kwargs: { verbose: true },
});
const pending = window.tkwry.call("heavy_task", payload);
// pending.id / pending.cancel() / window.tkwry.cancel(pending.id)
pending.cancel();

Execution model: default handlers run on the Tk main thread (safe for Tk APIs; long work blocks the UI). Pass thread=True / run_in="worker" to use a background pool. Handlers may also return a concurrent.futures.Future. Return values and emit payloads must be strict JSON (no datetime, custom objects, NaN / Infinity) — otherwise the Promise rejects / emit raises RpcSerializationError. Errors reject the Promise with a structured payload (error.name / error.message; set rpc_traceback=True or TKWRY_RPC_TRACEBACK=1 for tracebacks). Duplicate method names raise unless replace=True. Destroy rejects in-flight RPCs. Keyword args go in { kwargs: { … } } (a trailing { timeout: ms } is still call options, not a positional dict).

Timeout & cancel: optional timeout on expose applies to worker handlers and returned Futures (ignored for a synchronous main-thread handler). It rejects the JS Promise and sets a cooperative cancel flag. This is specified as cooperative only: Python cannot preempt a running worker thread (Future.cancel() only skips work that has not started). Long handlers should poll rpc_cancelled() (or capture rpc_cancel_event() for other threads). destroy() joins the pool for at most ~2 seconds; uncooperative handlers may briefly outlive the WebView. JS call(..., { timeout: ms }) is independent and only settles the Promise on the JS side. window.tkwry.cancel(id) (or promise.cancel()) cancels from JS and rejects with RpcCancelledError. Argument mismatches reject with a stable TypeError payload (arity + simple annotation checks: int / float / str / bool / list / dict / Optional). Envelopes include version: 1; unknown versions reject with RpcProtocolError (omitted version is treated as 1).

Limits: IPC/RPC messages cap at 10 MiB; RPC allows at most 256 positional args and 256 kwargs. Oversized RPC rejects with RpcMessageTooLarge; too many args with RpcArgumentLimitError. RPC has its own 2048-deep queue so IPC overflow cannot drop tkwry.call.

Trust boundaries (external pages)

window.ipc / window.tkwry.call run with desktop-app privileges. A page that can call them can drive whatever you expose or handle over IPC — including after a redirect or XSS in a third-party script.

# Local UI with RPC — bridge defaults to tkwry://
web = WebView(frame, app="./web")

# Arbitrary websites — no IPC/RPC, ephemeral storage
web = WebView(frame, url="https://example.com", untrusted=True)

# One trusted origin, or a path prefix (not /application)
web = WebView(
    frame,
    url="https://trusted.example/app",
    bridge_origins=["https://trusted.example/app"],
)

Defaults:

  • Bridge origins — IPC/RPC are accepted only from the initial content origin (html=about:blank; app=tkwry:// / https://tkwry.localhost; url= → that site). Foreign pages still see window.ipc (engine injection) but messages are dropped / RPC rejects with RpcOriginError. Use bridge_origins=["https://trusted.example"] (whole origin) or a path prefix (bridge_origins=["https://trusted.example/app"]/app and /app/..., not /application). bridge_allow=lambda url: ... can further restrict by the full page URL (navigation state).
  • bridge_origins="*" — every page; emits :class:~tkwry.TkwrySecurityWarning. expose() then requires allow_any_origin=True. devtools=True with "*" warns again. Filter with PYTHONWARNINGS=ignore::tkwry.TkwrySecurityWarning only if you accept the risk.
  • app= navigation — in-page navigation stays on tkwry://; new windows are denied. Set on_navigation / on_new_window to opt into external URLs (open them in another WebView or the system browser).
  • untrusted=True — viewer mode: no IPC handler, no expose / emit, ephemeral session, http(s) only, no tkwry:// / file:, new windows denied. Cannot be combined with bridge_origins / bridge_allow. Use this for arbitrary websites.
  • Dangerous schemesjavascript: / blob: / vbscript: / mailto: are denied at the native navigation hook even without Python on_navigation. data: is not blocked there (WebView2 html= / NavigateToString); app= still rejects it.
  • tkwry:// — custom-protocol requests with a non-app Origin or Referer return 403 (top-level loads with no Origin still work).

Do not enable RPC/IPC on a WebView that shows untrusted sites. Do not share a persistent WebSession / data_directory between a local app and an external site. Prefer vendored JS (app=) over CDN scripts in pages that have a bridge — XSS in a CDN script is the page origin.

examples/browser_demo.py sets bridge_origins="*" on purpose (link interception only; expect the security warning). Copy that only if every page is trusted, and do not expose() desktop APIs without allow_any_origin=True.

Python → JS events (emit)

web.emit("data_updated", {"n": 1})
window.tkwry.on("data_updated", (payload) => { ... });
// listener errors are logged with console.error (set window.tkwry.debug = false to silence)

See examples/ipc_demo.py.

Shared session (WebSession)

Share cookies / cache / localStorage across WebViews via wry's WebContext:

from tkwry import WebSession, WebView

session = WebSession(data_directory="~/.myapp/webview")
left = WebView(frame_a, html=HTML, session=session)
right = WebView(frame_b, html=HTML, session=session)

Convenience: WebView(..., data_directory=...) or ephemeral=True creates an owned session. Keep the WebSession alive while any WebView uses it (especially with app= on macOS).

Shared app=: WebViews that share a non-ephemeral WebSession must use the same app= root. Linux can register tkwry:// only once per WebContext; tkwry raises ValueError if a second root is used (all platforms). Use a separate session for unrelated local apps. See examples/browser_demo.py.

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

DevTools need devtools=True at construction, then open_devtools() (calling open_devtools() alone is a no-op on macOS if the flag was false).

web = WebView(frame, html="<h1>Hello</h1>", devtools=True)
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).

Unmapped hosts (inactive Notebook tabs) call set_visible(False). ready stays layout-based (True while hidden); use phase is WebViewPhase.HIDDEN when you need visibility.

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: lifecycle / IPC / page-load / title / DnD handlers run on the Tk main thread. RPC handlers default to the same thread; use @web.expose(thread=True) for background work. 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). Do not create another WebView from on_new_window (even deferred): WKWebView deadlocks. Intercept links in JS instead (see examples/browser_demo.py). Timed-out sync hooks are canceled after about 60s total wait.

Async queues (IPC, RPC, page-load, title, drag-drop, eval) cap at 2048 pending items each; further events are compacted or dropped. Each IPC/RPC message also caps at 10 MiB. RPC is a separate queue from IPC. Use take_queue_drop_counts() to observe overflows — it returns (ipc, page_load, title, drag_drop, eval, rpc).

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
# in-flight RPC is cancelled cooperatively (pool join ~2s)

📚 API summary

Category Members
Content load_url, load_html, reload, url
JavaScript eval_js (on_error), eval_js_with_callback
IPC / RPC / emit set_ipc_handler, expose / unexpose (allow_any_origin=), emit, watch_app, set_bridge_origins, set_bridge_allow
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, untrusted, bridge_origins, bridge_allow
Diagnostics take_queue_drop_counts

Constructor options: width / height, url, html, app, spa_fallback, app_dev, session / data_directory / ephemeral, untrusted, bridge_origins, bridge_allow, ipc_handler, rpc_traceback, devtools, background_color, user_agent, initialization_script, focused, plus the callback hooks above.

Enums: PageLoadEvent, NewWindowResponse, DragDropEvent, WebViewPhase. Exceptions: WebViewNotReadyError, WebViewCreationError, WebViewDestroyedError, RpcTimeoutError, RpcCancelledError, RpcSerializationError. Warning: TkwrySecurityWarning. Helpers: rpc_cancelled, rpc_cancel_event.

Type aliases: IpcHandler, BridgeOrigins, BridgeAllow, 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
  • Windows DevTools — wry/WebView2 reports is_devtools_open() as False and close_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 same app= root (ValueError otherwise; Linux can register tkwry:// 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 needs expose(..., allow_any_origin=True); app= locks navigation to tkwry://; use untrusted=True for arbitrary websites (see Trust boundaries)
  • macOS DevTools — create with devtools=True, then open_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 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 (WKWebView has no document NSURL)
  • Sync hooks / queueson_navigation / on_new_window may block WebKit up to ~60s; do not create a WebView from on_new_window; async event queues cap at 2048; IPC/RPC messages cap at 10 MiB (see Navigation / lifecycle callbacks)
  • RPC cancel / destroy — timeout, JS cancel, and destroy() are cooperative only (rpc_cancelled()); Python cannot preempt a running worker. destroy() joins the pool for ~2 seconds; leftover threads are logged to stderr
  • 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.

DPI: set_bounds uses physical pixels on Windows. After process DPI awareness (e.g. tkface.win.enable_dpi_awareness() before tk.Tk()), Tk winfo_* already reports physical sizes — passing them as wry Logical would double-scale. Prefer awareness + design-pixel→physical sizing in the host app; do not monkeypatch tkwry bounds from app code.

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/browser_demo.py). On macOS/Windows, focused=True waits for <<WebViewReady>>, then calls focus() (create-time focus breaks child WKWebView / WebView2). 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. 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.

Lifecycle / IPC / page-load handlers run on the Tk main thread. RPC may use a worker (thread=True). 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

  • Local app assetsapp= + tkwry:// (SPA fallback, app_dev no-store, ETag/HEAD/Range, bounded watch_app(); open-then-verify symlink/junction confinement)
  • IPC / RPC / emit — events vs request/response; worker RPC; typed TypeError; protocol version; JS cancel; Python→JS emit; origin/path allowlist (bridge_origins) + bridge_allow + untrusted= viewer mode
  • WebSession — shared wry WebContext; shared app= roots must match
  • Testing helperstkwry.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 / Notebook hide unmapped views)
  • Deferred callbacks — IPC, RPC, page load, title, eval results, and DnD queue to Tk (avoids macOS deadlocks)
  • URL safety — Python load_url normalizes/validates schemes; in-page nav denies javascript:/blob:/… (data: under app=); app= stays on tkwry://; IPC/RPC origin/path allowlist + bridge_allow
  • DevToolsdevtools=True at create, then open_devtools() / close_devtools() / is_devtools_open() (macOS: private APIs)
  • 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; demo toggles CDN vs local app=
  • 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 — or vendor under app=)
  • 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/browser_demo.py URL bar, tabs, shared WebSession, open-in-new-tab (bridge_origins="*"; no expose)
examples/ipc_demo.py IPC events, RPC (call / kwargs / worker), and emit
examples/multi_demo.py Multiple WebViews, tabs, panes
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.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.3.tar.gz (238.3 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.3-cp310-abi3-win_arm64.whl (464.5 kB view details)

Uploaded CPython 3.10+Windows ARM64

tkwry-0.1.3-cp310-abi3-win_amd64.whl (480.4 kB view details)

Uploaded CPython 3.10+Windows x86-64

tkwry-0.1.3-cp310-abi3-macosx_11_0_arm64.whl (548.5 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

tkwry-0.1.3-cp310-abi3-macosx_10_12_x86_64.whl (570.8 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for tkwry-0.1.3.tar.gz
Algorithm Hash digest
SHA256 e7f39dac471f18326883f13d28b6b939fd18394dcc410ff088c844c929d7fa3d
MD5 c6d9060a990b4876de0f2c767e3e53db
BLAKE2b-256 524baa1d6d2afb2a5bc2b0c6c08dd41bf2671465b95a0b66268a2941ac513b09

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tkwry-0.1.3-cp310-abi3-win_arm64.whl
  • Upload date:
  • Size: 464.5 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.3-cp310-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 0482e58214477020a0959574650d634266eb389f8bdc4395b1a1f404e974c192
MD5 d60b3b80c9b5a06c569aac5237808dcb
BLAKE2b-256 f4ca1a9fabb18ff45e90d19e799fe5f6685b17bb17a5f240d4d0a3d24085c9d0

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tkwry-0.1.3-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 480.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.3-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 51f61442ab848ebc561f1f1d308f28639ddc5578563df6010290e605f19983f5
MD5 6fadc294c3f4e4e39c0484ae882fb097
BLAKE2b-256 19ee1c03a1c9259f3c7a2b3e5c7101f6ab6a9e08559e9b63e2e8ae2d950a24be

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tkwry-0.1.3-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b1e01a6ee79e646a8e636774763dedf27a59ca2137d5d3b84d3eda99649d7ff2
MD5 2269f07afdce720bdb3a313f971caf56
BLAKE2b-256 5336d7735ab0810d411a2d387bac91c547bcd5839cdaa17001d02ea64b080c36

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tkwry-0.1.3-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ea36deae6e91230f9fb5c0ed63c9b99e9c4267a195ccb21c72663956bb9204bd
MD5 98a4cbeba5e5c7c24b8f4b5d5a7af990
BLAKE2b-256 9ee16180477a680ffe24e71e5048eb7737079ce92fe45b46685271cd205bc35a

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.4

5 files

This release

0.1.3 This release

5 files

0.1.2

5 files

0.1.1

5 files

0.1.0

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