Metadata-Version: 2.4
Name: handsets
Version: 0.1.2
Summary: Pythonic bindings for the Handsets Android control CLI
Author: Handsets contributors
License: MIT
Project-URL: Homepage, https://github.com/elliotgao2/handsets
Project-URL: Documentation, https://elliotgao2.github.io/handsets/
Project-URL: Source, https://github.com/elliotgao2/handsets/tree/main/bindings/python
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Operating System Kernels :: Linux
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# handsets — Python bindings

A small, Pythonic wrapper around the [Handsets](https://github.com/elliotgao2/handsets)
CLI (`hs`). Drives Android devices from Python without reimplementing the
subprocess + JSON-parse + exit-code boilerplate every caller used to write
by hand.

## Install

```bash
pip install handsets
```

You also need the `hs` binary on `$PATH`. See the project
[install instructions](https://github.com/elliotgao2/handsets#install).

## Usage

```python
from handsets import Session

with Session() as d:                   # `hs use` on enter, `hs drop` on exit
    for node in d.ui():
        print(node.cls, node.text, node.coords)

    d.tap("Continue")                  # text lookup
    d.tap(540, 860)                    # raw coords
    d.type("EditText", "you@x.com")    # selector + text — atomic ACTION_SET_TEXT
    d.submit()
    d.wait("Welcome", timeout="15s")
```

Errors map to typed exceptions:

```python
from handsets import Session, NotFound, Timeout, Ambiguous

try:
    d.tap("Submit", unique=True, timeout="5s")
except NotFound:
    ...  # exit code 2 — selector matched nothing
except Timeout:
    ...  # exit code 3 — wait budget exhausted
except Ambiguous:
    ...  # exit code 4 — --unique saw multiple matches
```

Everything else (daemon errors, bad arguments, secure-window blocks)
raises a generic `HandsetsError` whose `.code` attribute carries the
structured `ErrCode` enum value from the CLI's JSON output.

## Talking to a specific device

```python
Session(serial="PIXEL6_SERIAL")
```

Multiple sessions can run side-by-side; each one shells out independently.

## Why a thin wrapper?

The CLI already does the hard work: warm daemon, push-mirrored state,
millisecond round-trips. The Python layer's job is to make that ergonomic
— context managers, typed exceptions, no manual `subprocess.run`. Future
versions may keep an `hs run` subprocess warm and stream commands over its
stdin to amortise per-call process overhead.
