Add filesystem/git/podman/systemd adapters, git/shell hooks, bread-emit CLI, app-detection helpers
This commit is contained in:
parent
89c5849539
commit
1208c5d1b7
29 changed files with 4098 additions and 339 deletions
296
README.md
296
README.md
|
|
@ -76,7 +76,7 @@ Optional but preferred:
|
|||
### From source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Breadway/bread.git
|
||||
git clone https://git.breadway.dev/Breadway/bread.git
|
||||
cd bread
|
||||
```
|
||||
|
||||
|
|
@ -256,295 +256,15 @@ return M
|
|||
|
||||
---
|
||||
|
||||
## Event reference
|
||||
## Event reference, Lua API, and IPC protocol
|
||||
|
||||
Events follow the namespace convention `bread.<subsystem>.<noun>.<verb>`.
|
||||
These are fully documented in [`Documentation.md`](Documentation.md) — the single canonical reference (event catalogue, per-function Lua API, runtime-state schema, and IPC protocol), versioned as **Bread Automation API v1**. This README no longer keeps a parallel copy, to avoid the two drifting apart.
|
||||
|
||||
| Event | Trigger |
|
||||
|-------|---------|
|
||||
| `bread.system.startup` | Daemon fully initialized |
|
||||
| `bread.device.connected` | Any device attached |
|
||||
| `bread.device.disconnected` | Any device removed |
|
||||
| `bread.device.<device>.connected` | Named device attached (name from `devices.lua`) |
|
||||
| `bread.device.<device>.disconnected` | Named device removed |
|
||||
| `bread.monitor.connected` | Display connected |
|
||||
| `bread.monitor.disconnected` | Display disconnected |
|
||||
| `bread.workspace.changed` | Active workspace changed |
|
||||
| `bread.window.focus.changed` | Focused window changed |
|
||||
| `bread.window.opened` | Window opened |
|
||||
| `bread.window.closed` | Window closed |
|
||||
| `bread.power.ac.connected` | AC adapter plugged in |
|
||||
| `bread.power.ac.disconnected` | AC adapter unplugged |
|
||||
| `bread.power.battery.low` | Battery ≤ 20% |
|
||||
| `bread.power.battery.very_low` | Battery ≤ 10% |
|
||||
| `bread.power.battery.critical` | Battery ≤ 5% |
|
||||
| `bread.power.battery.full` | Battery at 100% |
|
||||
| `bread.network.connected` | Network interface came online |
|
||||
| `bread.network.disconnected` | Network interface went offline |
|
||||
| `bread.bluetooth.device.paired` | Bluetooth device paired / discovered |
|
||||
| `bread.bluetooth.device.unpaired` | Bluetooth device removed from BlueZ |
|
||||
| `bread.profile.activated` | Profile switched |
|
||||
| `bread.notify.sent` | Desktop notification dispatched |
|
||||
|
||||
---
|
||||
|
||||
## Lua API
|
||||
|
||||
### Modules
|
||||
|
||||
Every module file must declare itself. The declaration is used for dependency ordering and status tracking.
|
||||
|
||||
```lua
|
||||
local M = bread.module({
|
||||
name = "my-module",
|
||||
version = "1.0.0",
|
||||
after = { "bread.devices" }, -- load after this module
|
||||
})
|
||||
|
||||
-- ... module body ...
|
||||
|
||||
return M
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
```lua
|
||||
-- Subscribe to events; returns a subscription ID
|
||||
local id = bread.on("bread.monitor.connected", function(event)
|
||||
-- event.event → "bread.monitor.connected"
|
||||
-- event.data → table of event-specific fields
|
||||
-- event.source → adapter that produced it
|
||||
bread.log(event.event)
|
||||
end)
|
||||
|
||||
-- Unsubscribe by ID
|
||||
bread.off(id)
|
||||
|
||||
-- Subscribe once, auto-unsubscribe after first delivery
|
||||
bread.once("bread.system.startup", function(event)
|
||||
bread.profile.activate("default")
|
||||
end)
|
||||
|
||||
-- Subscribe with a filter predicate. The predicate goes in an opts table.
|
||||
bread.filter("bread.device.connected", function(event)
|
||||
bread.exec("xset r rate 200 40")
|
||||
end, {
|
||||
filter = function(event)
|
||||
return event.data.device == "keyboard"
|
||||
end,
|
||||
})
|
||||
|
||||
-- Emit a custom event (for cross-module communication)
|
||||
bread.emit("mymodule.something", { key = "value" })
|
||||
```
|
||||
|
||||
Pattern matching supports `*` (single segment), `**` (any depth), and `?` (single character):
|
||||
```lua
|
||||
bread.on("bread.device.*", handler) -- matches bread.device.dock.connected
|
||||
bread.on("bread.device.**", handler) -- matches any depth under bread.device
|
||||
```
|
||||
|
||||
### State
|
||||
|
||||
```lua
|
||||
-- Read from runtime state by dot-separated path
|
||||
local monitors = bread.state.get("monitors")
|
||||
local online = bread.state.get("network.online")
|
||||
|
||||
-- Typed shorthands
|
||||
local monitors = bread.state.monitors()
|
||||
local workspace = bread.state.active_workspace()
|
||||
local window = bread.state.active_window()
|
||||
local devices = bread.state.devices()
|
||||
local power = bread.state.power()
|
||||
local network = bread.state.network()
|
||||
local profile = bread.state.profile()
|
||||
|
||||
-- Watch a state path for changes
|
||||
bread.state.watch("power.ac_connected", function(new_val, old_val)
|
||||
if new_val then
|
||||
bread.notify("AC connected")
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
### Profiles
|
||||
|
||||
```lua
|
||||
bread.profile.activate("desk")
|
||||
bread.profile.activate("default")
|
||||
```
|
||||
|
||||
### Execution and notifications
|
||||
|
||||
```lua
|
||||
-- Fire-and-forget shell command
|
||||
bread.exec("kitty")
|
||||
|
||||
-- Desktop notification (uses notify-send)
|
||||
-- First arg is the message body; opts.title sets the notification title (default: "bread")
|
||||
bread.notify("Something happened", { title = "My Module", urgency = "normal", timeout = 3000, icon = "dialog-info" })
|
||||
bread.notify("AC connected") -- title defaults to "bread"
|
||||
```
|
||||
|
||||
### Timers
|
||||
|
||||
```lua
|
||||
-- Run once after a delay (ms)
|
||||
local id = bread.after(500, function()
|
||||
bread.exec("some-delayed-command")
|
||||
end)
|
||||
|
||||
-- Run on a repeating interval (ms)
|
||||
local id = bread.every(60000, function()
|
||||
bread.log("tick")
|
||||
end)
|
||||
|
||||
-- Cancel either kind
|
||||
bread.cancel(id)
|
||||
|
||||
-- Debounce a rapidly-firing handler
|
||||
local fn = bread.debounce(200, function(event)
|
||||
reconfigure_monitors()
|
||||
end)
|
||||
bread.on("bread.monitor.*", fn)
|
||||
```
|
||||
|
||||
### Wait (inside coroutines)
|
||||
|
||||
```lua
|
||||
-- Yield until a matching event arrives
|
||||
local event = bread.wait("bread.device.dock.connected", { timeout = 5000 })
|
||||
if event then
|
||||
-- dock arrived within 5 seconds
|
||||
end
|
||||
```
|
||||
|
||||
### Machine and filesystem
|
||||
|
||||
```lua
|
||||
-- Machine identity (system hostname)
|
||||
local name = bread.machine.name()
|
||||
local tags = bread.machine.tags() -- array of strings
|
||||
local ok = bread.machine.has_tag("laptop")
|
||||
|
||||
-- Filesystem helpers (~ is expanded)
|
||||
bread.fs.write("~/.config/some/file", "content")
|
||||
local content = bread.fs.read("~/.config/some/file") -- nil if not found
|
||||
local exists = bread.fs.exists("~/some/path")
|
||||
local abs = bread.fs.expand("~/some/path")
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
```lua
|
||||
bread.log("Module loaded") -- info level
|
||||
bread.warn("Unexpected state") -- warn level
|
||||
bread.error("Something failed") -- error level
|
||||
```
|
||||
|
||||
### Hyprland bindings
|
||||
|
||||
```lua
|
||||
-- Dispatch a Hyprland command
|
||||
bread.hyprland.dispatch("workspace", "2")
|
||||
bread.hyprland.dispatch("exec", "kitty")
|
||||
|
||||
-- Set a keyword
|
||||
bread.hyprland.keyword("monitor", "HDMI-A-1, 2560x1440, 0x0, 1")
|
||||
|
||||
-- Query compositor state
|
||||
local win = bread.hyprland.active_window()
|
||||
local monitors = bread.hyprland.monitors()
|
||||
local workspaces = bread.hyprland.workspaces()
|
||||
local clients = bread.hyprland.clients()
|
||||
|
||||
-- Subscribe to raw Hyprland events (bypass normalization)
|
||||
bread.hyprland.on_raw("activewindow", function(raw)
|
||||
-- raw is the unparsed string from Hyprland's event socket
|
||||
end)
|
||||
```
|
||||
|
||||
### Bluetooth
|
||||
|
||||
The `bread.bluetooth` namespace provides BlueZ control. All operations degrade gracefully when Bluetooth hardware is unavailable.
|
||||
|
||||
```lua
|
||||
-- Power the adapter on or off
|
||||
bread.bluetooth.power(true)
|
||||
bread.bluetooth.power(false)
|
||||
|
||||
-- Query current power state (returns true/false, or nil if unavailable)
|
||||
local on = bread.bluetooth.powered()
|
||||
|
||||
-- Connect/disconnect a paired device by MAC address
|
||||
-- Fire-and-forget; result arrives as bread.device.connected/disconnected
|
||||
bread.bluetooth.connect("AA:BB:CC:DD:EE:FF")
|
||||
bread.bluetooth.disconnect("AA:BB:CC:DD:EE:FF")
|
||||
|
||||
-- Start or stop device discovery
|
||||
bread.bluetooth.scan(true)
|
||||
bread.bluetooth.scan(false)
|
||||
|
||||
-- List all devices known to BlueZ
|
||||
local devs = bread.bluetooth.devices()
|
||||
-- Returns nil if BlueZ is unavailable, otherwise:
|
||||
-- { { address, name, connected, paired }, ... }
|
||||
```
|
||||
|
||||
Example — auto-connect headphones when Bluetooth powers on:
|
||||
|
||||
```lua
|
||||
bread.state.watch("power.ac_connected", function(ac)
|
||||
if ac then
|
||||
bread.bluetooth.power(true)
|
||||
bread.bluetooth.connect("AA:BB:CC:DD:EE:FF")
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
### Module-scoped storage
|
||||
|
||||
Survives hot reload; does not survive daemon restart.
|
||||
|
||||
```lua
|
||||
M.store.set("last_profile", "docked")
|
||||
local p = M.store.get("last_profile") -- "docked"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IPC protocol
|
||||
|
||||
The daemon exposes a Unix socket at `$XDG_RUNTIME_DIR/bread/breadd.sock`. The protocol is newline-delimited JSON — useful for scripting or building tooling outside the CLI.
|
||||
|
||||
Request:
|
||||
```json
|
||||
{ "id": "1", "method": "state.get", "params": { "key": "monitors" } }
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{ "id": "1", "result": [ { "name": "HDMI-A-1", "connected": true } ] }
|
||||
```
|
||||
|
||||
Available methods:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `ping` | Connectivity check |
|
||||
| `health` | Version, uptime, PID, adapter status |
|
||||
| `state.get` | Read a value from `RuntimeState` by dotted key path |
|
||||
| `state.dump` | Return the full `RuntimeState` as JSON |
|
||||
| `modules.list` | List all loaded modules and their status |
|
||||
| `modules.reload` | Hot-reload the Lua runtime |
|
||||
| `profile.list` | List defined profiles |
|
||||
| `profile.activate` | Switch active profile |
|
||||
| `events.subscribe` | Upgrade connection to streaming mode |
|
||||
| `events.replay` | Replay buffered events from the last N ms |
|
||||
| `emit` | Inject a synthetic event into the pipeline |
|
||||
|
||||
`events.subscribe` upgrades the connection to streaming mode — the daemon pushes events line by line until the client disconnects.
|
||||
- [Dictionary: Event reference](Documentation.md#dictionary-event-reference)
|
||||
- [Dictionary: Lua API](Documentation.md#dictionary-lua-api)
|
||||
- [Dictionary: Runtime state schema](Documentation.md#dictionary-runtime-state-schema)
|
||||
- [Dictionary: IPC protocol](Documentation.md#dictionary-ipc-protocol)
|
||||
- [API Stability & Versioning](Documentation.md#api-stability--versioning)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue