Metadata-Version: 2.4
Name: hyprland-state
Version: 0.4.0
Summary: Live state interface for Hyprland — options, animations, monitors, binds, and devices
Project-URL: Repository, https://github.com/BlueManCZ/hyprland-state
License-Expression: MIT
License-File: LICENSE
Keywords: compositor,config,hyprland,state,wayland
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Desktop Environment
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: hyprland-config>=0.6.1
Requires-Dist: hyprland-monitors>=0.5.0
Requires-Dist: hyprland-schema>=0.6.0
Requires-Dist: hyprland-socket>=0.12.1
Description-Content-Type: text/markdown

# hyprland-state

Live state interface for [Hyprland](https://hyprland.org) — read, write, and inspect the running compositor's configuration.

Bridges the gap between [hyprland-config](https://github.com/BlueManCZ/hyprland-config) (disk), [hyprland-socket](https://github.com/BlueManCZ/hyprland-socket) (IPC), and [hyprland-schema](https://github.com/BlueManCZ/hyprland-schema) (metadata) into a single coherent API.

## What it does

- **Options** — read effective values (IPC > disk > schema default), apply changes, inspect metadata, validate against schema constraints
- **Animations** — read/write animation states, manage bezier curves, navigate the animation tree
- **Monitors** — read monitor layout from IPC, apply monitor configuration
- **Binds** — read keybind definitions, execute dispatchers
- **Devices** — detect input devices (touchpad, etc.)
- **Persistence** — track pending changes, save to disk, discard/revert

## Installation

```
pip install hyprland-state
```

## Quick start

```python
from hyprland_state import HyprlandState

# Schema is auto-loaded for the running Hyprland version
state = HyprlandState()

print(state.version)  # "0.54.2"

# Read the live value (typed via schema — returns int, not str)
border = state.get("general:border_size")  # 2

# Inspect schema metadata
info = state.inspect("general:border_size")
print(info.type, info.default, info.min, info.max)  # int 1 0 20

# Write to the running compositor (validated against schema)
state.apply("general:border_size", 3)
state.apply_batch([("general:gaps_in", 5), ("general:gaps_out", 10)])

# Animations
for anim in state.animations.get_all():
    print(f"{anim.name}: enabled={anim.enabled}, speed={anim.speed}")

state.animations.apply("windows", True, 3.0, "easeOut", "slide")

# Monitors
for mon in state.monitors.get_all():
    print(f"{mon.name}: {mon.width}x{mon.height}@{mon.refresh_rate}Hz")

# Devices
if state.has_touchpad():
    print("Touchpad detected")
```

## Pending state and persistence

Changes made via `apply()` take effect immediately in the compositor but are tracked as pending until explicitly saved or discarded:

```python
state.apply("general:border_size", 5)
state.apply("decoration:rounding", 10)

state.is_dirty()  # True
state.pending()   # ["general:border_size", "decoration:rounding"]

state.save()      # writes to config file and reloads compositor
# or
state.discard()   # reverts compositor to saved values (on-disk, or schema default)
```

## Validation

Values are validated against schema constraints (min/max, enum) before being sent to the compositor. Invalid values raise `ValueError`:

```python
state.apply("general:border_size", 999)  # ValueError: above maximum 20

# Bypass validation when needed
state.apply("general:border_size", 999, validate=False)
```

## Lua-mode configs (Hyprland 0.55+)

When the running compositor was started with a `hyprland.lua` entrypoint (`configProvider: lua`), the legacy `hyprctl keyword` IPC is rejected. `HyprlandState` detects this once via `is_live_lua_mode()` and transparently routes `apply()`, `apply_batch()`, `keyword()`, and `dispatch()` through `hyprctl eval` with the equivalent `hl.*` snippet — callers don't have to branch:

```python
state = HyprlandState()
state.is_live_lua_mode()  # True on a Hyprland 0.55+ Lua config

state.apply("general:border_size", 3)   # hl.config({ general = { border_size = 3 } })
state.dispatch("workspace", "1")        # hl.dispatch(hl.dsp.workspace(1))
```

For submap registration use `define_submap()` — the bare `submap` keyword has no per-line Lua equivalent (Lua's submap API is declarative), and the method also batches the Hyprlang `submap=` / `bind=` / `submap=reset` sequence atomically:

```python
state.define_submap("resize", [
    ("bind", ", H, resizeactive, -10 0"),
    ("bind", ", L, resizeactive, 10 0"),
])
```

Untranslatable keywords or dispatchers (e.g. a dispatcher with no `hl.dsp.*` mapping) surface as `hyprland_socket.CommandError`, matching the legacy-mode failure mode.

## Offline mode

Works without a running Hyprland instance — reads from config files and schema:

```python
state = HyprlandState(offline=True)
value = state.get_disk("general:border_size")  # from config file
default = state.get_default("general:border_size")  # from schema
```

Use `reconnect()` to switch to online mode when the compositor becomes available:

```python
state.reconnect()  # True if Hyprland is now reachable
```

## Dependencies

- [hyprland-config](https://github.com/BlueManCZ/hyprland-config) — Hyprlang parser
- [hyprland-monitors](https://github.com/BlueManCZ/hyprland-monitors) — Monitor model and utilities
- [hyprland-schema](https://github.com/BlueManCZ/hyprland-schema) — Option metadata
- [hyprland-socket](https://github.com/BlueManCZ/hyprland-socket) — IPC communication

## License

MIT
