Metadata-Version: 2.5
Name: tkinter-web-backend
Version: 0.1.3
Summary: A web backend for tkinter: run tkinter programs in a web browser
Project-URL: Homepage, https://forge.apps.education.fr/basthon/tkinter-web-backend
Author-email: Romain Casati <romain.casati@basthon.fr>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Education
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: User Interfaces
Requires-Python: >=3.9
Requires-Dist: websockets>=13; sys_platform != 'emscripten'
Provides-Extra: test
Requires-Dist: playwright; extra == 'test'
Requires-Dist: pytest; extra == 'test'
Description-Content-Type: text/markdown

# tkinter-web-backend

**Run tkinter programs in a web browser, unchanged.**

tkinter-web-backend is a web backend for [tkinter](https://docs.python.org/3/library/tkinter.html):
instead of opening native windows, your program serves a local page (as
matplotlib's WebAgg backend does) where its windows are displayed and used
with the mouse and the keyboard.

| | | |
|:-:|:-:|:-:|
| ![widgets](docs/images/widgets.png) | ![ttk](docs/images/ttk_demo.png) | ![turtle](docs/images/turtle_spiral.png) |
| ![snake](docs/images/snake.png) | ![paint](docs/images/paint.png) | ![clock](docs/images/clock.png) |
| ![calculator](docs/images/calculator.png) | ![breakout](docs/images/breakout.png) | ![mandelbrot](docs/images/mandelbrot.png) |

It was written for teaching: students' tkinter programs (games on a Canvas,
forms, small applications, the `turtle` module...) run as they are, and the
same engine is designed to run in the browser itself (Pyodide) for
[Basthon](https://basthon.fr).

## Usage

```bash
pip install tkinter-web-backend
python -m tkinter_web my_program.py
```

(the package is installed as `tkinter-web-backend` and imported as
`tkinter_web`)

A browser tab opens on `http://127.0.0.1:<port>/<token>/` and shows the
windows of the program. Only this page can talk to the program: the server
answers under the random token of the address, to its own names (DNS
rebinding) and to WebSockets from the page itself (other sites open in the
browser can't drive the program). Closing the tab ends the program (as closing its main window).

You can also select the backend from the program itself, as with
matplotlib's backends: commenting out the `install()` line gives back the
native Tk.

```python
import tkinter as tk
import tkinter_web
tkinter_web.install()

root = tk.Tk()
tk.Button(root, text="Hello", command=root.destroy).pack()
root.mainloop()
```

`install()` can be called before or after importing tkinter (or modules
using it, such as `turtle`), but before the first window is created.

`python -m tkinter_web` without a script opens an interactive console where
windows update as you type (as with IDLE).

### Options

| Command line | Environment variable | |
|---|---|---|
| `--port N` | `TKINTER_WEB_PORT` | port of the local server (any free port by default) |
| | `TKINTER_WEB_HOST` | interface of the server (`127.0.0.1` by default) |
| | `TKINTER_WEB_TOKEN` | secret part of the page's address (random by default) |
| `--no-browser` | `TKINTER_WEB_BROWSER=0` | do not open a browser tab |
| | `TKINTER_WEB_WAIT` | seconds to wait for the page before running the program's timers (10) |
| | `TKINTER_WEB_CLOSE_DELAY` | seconds without any page before the windows are closed (3, 0 to disable) |
| | `TKINTER_WEB_QUIET=1` | do not print the address of the page |
| | `TKINTER_WEB_VENDORED=1` | use the bundled copy of tkinter (used anyway when Python has none) |

### In a web page (Pyodide)

Where Python runs in the browser (Pyodide), there is no server and no
thread: `tkinter_web.embed` runs the windows on the page's event loop. The
host gives a function displaying a client page (a single HTML document, for
instance in an iframe) and returning a `MessagePort` to talk to it:

```python
import tkinter_web.embed

tkinter_web.embed.setup(open_port)  # open_port(html) -> MessagePort
```

Then `mainloop()` returns at once and the program lives on the page's event
loop (timers, user input). Waiting for the user (dialogs of `messagebox`,
`simpledialog`..., `wait_window`, `wait_variable`) also needs
`wait_message(key)`, a function blocking until the page posts a message with
this key through the host (the page sends `{"type": "iframe-message",
key, message}` on its "gui" port while the program waits). This is how
[Basthon](https://basthon.fr) displays tkinter programs.

## How it works

```
 your program ── tkinter (standard library, unchanged)
                     │  Tcl/Tk commands: "button .b -text Hello", "pack .b"...
                     ▼
                 _tkinter replacement ──► Tk engine (pure Python)
                                          windows, options, pack/grid/place,
                                          fonts, canvas items, images, bindings,
                                          events, timers, ttk styles...
                                              │ display operations      ▲ mouse, keyboard,
                                              ▼ (JSON, WebSocket)       │ window manager
                                          browser page: draws the windows on <canvas>
```

* **The tkinter module is not modified.** Only `_tkinter`, the C extension
  module between tkinter and Tcl/Tk, is replaced. Everything above it
  (`tkinter.ttk`, `messagebox`, `simpledialog`, `filedialog`, `turtle`...)
  works as with the native Tk.
* **The engine is a Tk written in Python.** It keeps the state of the windows,
  answers every query immediately (`cget`, `winfo`, `coords`, `bbox`,
  `find_overlapping`, `index`...) and computes the geometry itself: the
  geometry managers are ports of Tk's own algorithms (tkPack.c, tkGrid.c,
  tkPlace.c), widget sizes follow Tk's C code, and option specifications and
  defaults were extracted from the real Tk 8.6.
* **Text is measured and drawn with the same fonts.** The DejaVu fonts are
  bundled: the engine measures text with their metrics, the browser draws it
  with them as web fonts. So `winfo_width()` returns what the real Tk returns
  on Linux, and the page shows exactly what was laid out. The test suite
  compares the geometry of many layouts with the real Tk, pixel for pixel.
* **The browser only draws and listens.** It receives display lists (3D
  borders, text, canvas items, images...) and sends input events back. The
  page is a small desktop: windows can be moved, resized, minimized and
  closed; menus, dialogs and popups are windows of their own.
* **Event loop.** `mainloop()` processes the events coming from the page in
  the main thread, as Tk does with X events; `update()`, `after()`,
  `wait_window()`, `wait_variable()` (used by dialogs) work as usual, so do
  game loops written with `update()` and `time.sleep()`.

## What is supported

* **Widgets:** Tk, Toplevel, Frame, LabelFrame, Label, Button, Checkbutton,
  Radiobutton, Entry, Spinbox, Scale, Scrollbar, Listbox, Text (tags, marks,
  search, undo, embedded windows and images), Canvas (all item types, tags
  and tag expressions, item bindings, scrolling), Message, Menu (menu bars,
  cascades, popup menus, check and radio entries), Menubutton, OptionMenu,
  PanedWindow.
* **ttk:** Button, Label, Entry, Checkbutton, Radiobutton, Combobox, Spinbox,
  Scale, Progressbar (determinate and indeterminate), Scrollbar, Separator,
  Sizegrip, Frame, LabelFrame, Menubutton, Notebook, PanedWindow, Treeview;
  `ttk.Style` with the default, clam, alt and classic themes, `configure`,
  `map`, `lookup`, widget states.
* **Geometry:** pack, grid (weights, uniform groups, spans, minsize, anchor),
  place; geometry propagation; `<Configure>` events.
* **Events:** mouse (with double and triple clicks, implicit grab, Enter and
  Leave), wheel (`<MouseWheel>` and buttons 4/5), keyboard (keysyms,
  characters, input methods, virtual keyboards), focus and Tab traversal,
  virtual events, `event_generate`, `bind_all`, bindtags and `"break"`.
* **Dialogs:** `messagebox`, `simpledialog`, `filedialog` (files of the
  computer running the program), `colorchooser`.
* **Images:** `PhotoImage` (PNG, GIF with frames, PPM/PGM, `put`, `get`, `copy`,
  `zoom`, `subsample`, `write`, `data`) and `BitmapImage`; Tk's built-in
  bitmaps.
* **Fonts:** named fonts, `tkinter.font.Font`, `measure`, `metrics`; families
  are mapped to DejaVu Sans, Sans Mono or Serif.
* **Window manager:** `title`, `geometry`, `resizable`, `minsize`, `maxsize`,
  `state`, `withdraw`, `iconify`, `deiconify`, `protocol` (`WM_DELETE_WINDOW`),
  `attributes` (`-topmost`, `-alpha`), `overrideredirect`, `transient`,
  `iconphoto`, modal dialogs (`grab_set`).
* **Clipboard** between the program and the browser (copy, cut, paste).

## Real programs

Beyond the examples of this repository, large tkinter applications of the
standard library run in the browser too: `turtledemo` (the turtle demo
viewer, with its syntax colored sources) and even IDLE
(`python -m tkinter_web -m idlelib -n`: shell, editor with syntax
highlighting, calltips, menus, settings dialog...).

![IDLE in a browser](docs/images/idle.png)

## Limitations

* There is no Tcl interpreter: tkinter's own Tcl calls are supported, but
  arbitrary Tcl scripts (`root.tk.eval(...)`, Tcl packages, `createfilehandler`)
  are not.
* The look is that of Tk on Linux (X11), whatever the system.
* `Canvas.postscript()` returns an empty document.
* Text measurement matches the real Tk in about 98% of the cases (the
  hinting of a few glyphs differs by one pixel).
* Window positions are those of the page's desktop, not of the screen.

## Development

```bash
python -m venv .venv && .venv/bin/pip install -e ".[test]"
.venv/bin/python -m pytest
```

* `tests/test_tkinter.py`: the tkinter API on the engine (no browser).
* `tests/test_differential.py`: the same programs with the real Tk (needs
  tkinter and `xvfb-run`) and with tkinter-web-backend; geometry and query results
  must match.
* `tests/test_browser.py`: end-to-end tests in Chromium with Playwright
  (screenshots of the examples are saved in `test-results/`).

The `tools/` directory holds the scripts that generated the data files: option
specifications extracted from the real Tk, the bundled fonts and their
metrics, the color names and keysyms tables, the screenshots of this page.

```
src/tkinter_web/
  _tkinter.py        replacement of the _tkinter C module
  app.py             event loop shared by the Tk instances, link with the clients
  transport/         local HTTP and WebSocket server
  tk/                the Tk engine (does not depend on tkinter)
    interp.py        commands, timers, bindings dispatch
    widgets/         classic widgets
    ttk/             themed widgets and styles
    geometry.py      pack, grid, place
    layout.py        geometry propagation
    render.py        display operations for the clients
    input.py         events from the clients
  client/            the web page (JavaScript, no build step)
  _vendor/tkinter/   copy of CPython's tkinter, for Pythons without it
```

## License

tkinter-web-backend is distributed under the GNU General Public License v3 or later.

It bundles the DejaVu fonts (Bitstream Vera license, see
`src/tkinter_web/client/fonts/LICENSE-DejaVu.txt`), a copy of CPython's
tkinter package (PSF license, see `src/tkinter_web/_vendor/`), and data and
algorithms derived from Tk (BSD-style license, see
`src/tkinter_web/tk/LICENSE-Tk.txt`).
