Metadata-Version: 2.5
Name: stasis-client
Version: 0.1.0
Summary: Local remote-desktop client for the Aethernet Stasis protocol.
Project-URL: Homepage, https://github.com/solment-slet/stasis-client
Project-URL: Repository, https://github.com/solment-slet/stasis-client
Author-email: Walter Kerrigan <esolment@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: aethernet,remote-control,remote-desktop,stasis
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.12
Requires-Dist: aethernet-core<4,>=3.0.0
Requires-Dist: nicegui<4,>=3.15
Requires-Dist: pillow<13,>=12.3
Requires-Dist: pygame<3,>=2.6.1
Requires-Dist: pywebview[qt]<7,>=6.2.1
Provides-Extra: build
Requires-Dist: build<2,>=1.5; extra == 'build'
Requires-Dist: twine<8,>=7; extra == 'build'
Provides-Extra: dev
Requires-Dist: build<2,>=1.5; extra == 'dev'
Requires-Dist: mypy<3,>=2.3; extra == 'dev'
Requires-Dist: ruff<1,>=0.16; extra == 'dev'
Requires-Dist: twine<8,>=7; extra == 'dev'
Provides-Extra: lint
Requires-Dist: mypy<3,>=2.3; extra == 'lint'
Requires-Dist: ruff<1,>=0.16; extra == 'lint'
Description-Content-Type: text/markdown

# stasis-client

A local remote-desktop client for the [Aethernet](https://github.com/solment-slet/aethernet) `stasis` protocol. It runs on your machine, connects to a remote host running an Aethernet stasis server, and gives you a live (or on-request) view of that host's screen with mouse, keyboard, and clipboard control - as a native window or in your browser.

![mode](https://img.shields.io/badge/modes-native%20window%20%7C%20browser-blue)
![license](https://img.shields.io/badge/license-Apache%202.0-green)

## Features

- **Native window or browser** - run as a standalone desktop window, or as a regular local web app you open in your browser. Same interface either way.
- **Live and Snap input modes** - send clicks, scrolls, and keystrokes immediately as you make them, or record a sequence of actions locally and send them to the remote host as a single batch once you're ready.
- **Configurable hotkeys** - request a screen frame, open a multi-line text input, and start/cancel/send a Snap recording, all bound to your own key combos.
- **Multi-click coalescing** - double/triple/... clicks are detected locally and sent as a single click event with the right click count, instead of arriving too far apart over the network to register as a multi-click on the remote host.
- **Automatic DPI-aware coordinate scaling** - clicks land in the right place even when the remote host's screenshot resolution differs from its logical input resolution (e.g. Retina/HiDPI displays).
- **Clipboard sync** - push your local clipboard to the remote host, or pull the remote host's clipboard back to yours, on demand.
- **Bulk text entry** - a dedicated popup for pasting or typing long text, sent as a single event instead of one keystroke at a time.
- **On-request or interval screen streaming** - choose whether frames are pushed continuously or only when you ask for one.
- **In-app log viewer** - see what the underlying connection is doing without leaving the app.
- **Dark, distraction-free interface** with a startup animation.

## Installation

```bash
pip install stasis-client
```

This installs the `stasis-client` command.

Stasis-client speaks the `stasis` protocol over an Aethernet link. The transport itself (`AggregatingLink`) comes from the separate [`aethernet-core`](https://pypi.org/project/aethernet-core/) package, which `stasis-client` depends on automatically - you don't need to install it yourself.

## Quick start

1. Create a link configuration file - a small Python file that tells stasis-client (and, if you use the same file, the Aethernet server too) how to reach your remote host (transport, address, encryption, etc.):

   ```python
   # aethernet_link.py
   import logging
   from typing import Literal
   from aethernet.transport import AggregatingLink


   async def create_link(
       mode: Literal["server", "client"], logger: logging.Logger
   ) -> AggregatingLink:
       # Build and return your AggregatingLink here.
       # `mode` is "client" when called from stasis-client.
       ...
   ```

2. Run the client from the same directory:

   ```bash
   stasis-client
   ```

   This opens a native window by default. To open it in your browser instead:

   ```bash
   stasis-client --mode browser
   ```

That's it - the settings dialog inside the app lets you configure hotkeys, input mode, and streaming behavior without touching the command line again.

## Command-line options

| Flag | Description | Default |
|---|---|---|
| `--mode {window,browser}` | Run as a native window or a local web server | `window` |
| `--link-config PATH` | Path to your link config file | `./aethernet_link.py` |
| `--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}` | Logging verbosity | `DEBUG` |

Each of these can also be set via an environment variable (`AETHERNET_UI_MODE`, `AETHERNET_LINK_CONFIG`, `AETHERNET_LOG_LEVEL`) - the CLI flag takes priority when both are set.

## In-app settings

Open the settings dialog from the top panel to configure, without restarting the app:

- **Session mode** - Live (send everything immediately) or Snap (record, review, then send).
- **Screen streaming mode** - on-request or interval-based, with a configurable interval.
- **Hotkeys** - request-frame, open-big-input, and the three Snap recording controls (start/cancel/send).
- **Multi-click window** - how long to wait after a click before deciding how many clicks it was.
- **Logs** - a live tail of the connection's log output.

Settings persist across restarts.

## Requirements

- Python 3.12+
- A running Aethernet stasis server on the host you want to control (see [github.com/solment-slet/aethernet](https://github.com/solment-slet/aethernet))

## License

Apache License 2.0 - see the [LICENSE](LICENSE) file in this repository. Matches the license used by [`aethernet-core`](https://github.com/solment-slet/aethernet).