yam-tui 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yam_tui-0.1.0/.github/workflows/release.yml +59 -0
- yam_tui-0.1.0/.gitignore +4 -0
- yam_tui-0.1.0/LICENSE +21 -0
- yam_tui-0.1.0/PKG-INFO +43 -0
- yam_tui-0.1.0/README.md +29 -0
- yam_tui-0.1.0/pyproject.toml +31 -0
- yam_tui-0.1.0/src/yam_tui/__init__.py +0 -0
- yam_tui-0.1.0/src/yam_tui/_version.py +24 -0
- yam_tui-0.1.0/src/yam_tui/app.py +199 -0
- yam_tui-0.1.0/uv.lock +8 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
concurrency: release
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
outputs:
|
|
15
|
+
version: ${{ steps.tag.outputs.version }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
- uses: astral-sh/setup-uv@v5
|
|
21
|
+
- name: Compute and push next tag
|
|
22
|
+
id: tag
|
|
23
|
+
run: |
|
|
24
|
+
if git describe --exact-match --tags --match 'v*' HEAD 2>/dev/null; then
|
|
25
|
+
echo "HEAD is already tagged, nothing to release"
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
last=$(git tag --list 'v*' --sort=-v:refname | head -n1)
|
|
29
|
+
if [ -z "$last" ]; then
|
|
30
|
+
next="v0.1.0"
|
|
31
|
+
else
|
|
32
|
+
IFS=. read -r major minor patch <<< "${last#v}"
|
|
33
|
+
next="v${major}.${minor}.$((patch + 1))"
|
|
34
|
+
fi
|
|
35
|
+
git tag "$next"
|
|
36
|
+
git push origin "$next"
|
|
37
|
+
echo "version=$next" >> "$GITHUB_OUTPUT"
|
|
38
|
+
- name: Build
|
|
39
|
+
if: steps.tag.outputs.version
|
|
40
|
+
run: uv build
|
|
41
|
+
- uses: actions/upload-artifact@v4
|
|
42
|
+
if: steps.tag.outputs.version
|
|
43
|
+
with:
|
|
44
|
+
name: dist
|
|
45
|
+
path: dist/
|
|
46
|
+
|
|
47
|
+
publish:
|
|
48
|
+
needs: build
|
|
49
|
+
if: needs.build.outputs.version
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
environment: pypi
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/download-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
name: dist
|
|
58
|
+
path: dist/
|
|
59
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
yam_tui-0.1.0/.gitignore
ADDED
yam_tui-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 René Fritze
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
yam_tui-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: yam-tui
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Minimal TUI for controlling a Yamaha RX-V475 AV receiver
|
|
5
|
+
Project-URL: Homepage, https://github.com/renefritze/yam-tui
|
|
6
|
+
Author-email: René Fritze <coding@fritze.me>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Environment :: Console :: Curses
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Home Automation
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# yam-tui
|
|
16
|
+
|
|
17
|
+
Minimal terminal UI for controlling a Yamaha RX-V475 AV receiver on your LAN
|
|
18
|
+
(uses the receiver's XML `/YamahaRemoteControl/ctrl` API; no dependencies).
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
uv tool install yam-tui # or: pipx install yam-tui
|
|
22
|
+
yam-tui 192.168.0.94 # or set YAMAHA_HOST
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Key | Action |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `p` | power |
|
|
28
|
+
| `↑` / `↓` | volume |
|
|
29
|
+
| `m` | mute |
|
|
30
|
+
| `0`–`9` | jump to input (`0` AUDIO, `1`–`5` HDMI1–5, `6`–`9` AV1–4) |
|
|
31
|
+
| `←` / `→` | previous / next input |
|
|
32
|
+
| `s` | next sound program |
|
|
33
|
+
| `o` | HDMI out on/off |
|
|
34
|
+
| `q` | quit |
|
|
35
|
+
|
|
36
|
+
Other RX-V models using the same XML protocol will probably work, but only the
|
|
37
|
+
RX-V475 is tested.
|
|
38
|
+
|
|
39
|
+
## Releases
|
|
40
|
+
|
|
41
|
+
Every merge to `main` bumps the patch version from the latest `v*` git tag
|
|
42
|
+
(starting at `v0.1.0`), tags the commit and publishes to PyPI. For a minor/major bump, push the tag
|
|
43
|
+
yourself (e.g. `git tag v0.2.0 && git push --tags`) before merging.
|
yam_tui-0.1.0/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# yam-tui
|
|
2
|
+
|
|
3
|
+
Minimal terminal UI for controlling a Yamaha RX-V475 AV receiver on your LAN
|
|
4
|
+
(uses the receiver's XML `/YamahaRemoteControl/ctrl` API; no dependencies).
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
uv tool install yam-tui # or: pipx install yam-tui
|
|
8
|
+
yam-tui 192.168.0.94 # or set YAMAHA_HOST
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Key | Action |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `p` | power |
|
|
14
|
+
| `↑` / `↓` | volume |
|
|
15
|
+
| `m` | mute |
|
|
16
|
+
| `0`–`9` | jump to input (`0` AUDIO, `1`–`5` HDMI1–5, `6`–`9` AV1–4) |
|
|
17
|
+
| `←` / `→` | previous / next input |
|
|
18
|
+
| `s` | next sound program |
|
|
19
|
+
| `o` | HDMI out on/off |
|
|
20
|
+
| `q` | quit |
|
|
21
|
+
|
|
22
|
+
Other RX-V models using the same XML protocol will probably work, but only the
|
|
23
|
+
RX-V475 is tested.
|
|
24
|
+
|
|
25
|
+
## Releases
|
|
26
|
+
|
|
27
|
+
Every merge to `main` bumps the patch version from the latest `v*` git tag
|
|
28
|
+
(starting at `v0.1.0`), tags the commit and publishes to PyPI. For a minor/major bump, push the tag
|
|
29
|
+
yourself (e.g. `git tag v0.2.0 && git push --tags`) before merging.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "yam-tui"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Minimal TUI for controlling a Yamaha RX-V475 AV receiver"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "René Fritze", email = "coding@fritze.me" }]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = []
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Environment :: Console :: Curses",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Topic :: Home Automation",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/renefritze/yam-tui"
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
yam-tui = "yam_tui.app:cli"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
25
|
+
build-backend = "hatchling.build"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.version]
|
|
28
|
+
source = "vcs"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.hooks.vcs]
|
|
31
|
+
version-file = "src/yam_tui/_version.py"
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Minimal curses TUI for Yamaha RX-V475 (XML /YamahaRemoteControl/ctrl API).
|
|
3
|
+
|
|
4
|
+
Usage: yam-tui [receiver-ip] (default: $YAMAHA_HOST or 192.168.0.94)
|
|
5
|
+
|
|
6
|
+
Keys: p power ↑/↓ volume m mute ←/→ or 0-9 input s sound program
|
|
7
|
+
o HDMI out on/off r refresh q quit
|
|
8
|
+
"""
|
|
9
|
+
import curses
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.request
|
|
14
|
+
import xml.etree.ElementTree as ET
|
|
15
|
+
|
|
16
|
+
POLL_MS = 2000
|
|
17
|
+
VOL_STEP = 5 # in 0.1 dB units
|
|
18
|
+
VOL_MIN, VOL_MAX = -800, 165
|
|
19
|
+
# number key -> input; 0 is AUDIO, 1-5 are HDMI1-5, 6-9 are AV1-4
|
|
20
|
+
HOTKEYS = {"0": "AUDIO"} | {str(n): f"HDMI{n}" for n in range(1, 6)} | {str(n + 5): f"AV{n}" for n in range(1, 5)}
|
|
21
|
+
PROGRAMS = [
|
|
22
|
+
"Hall in Munich", "Hall in Vienna", "Chamber", "Cellar Club",
|
|
23
|
+
"The Roxy Theatre", "The Bottom Line", "Sci-Fi", "Action Game",
|
|
24
|
+
"Roleplaying Game", "Music Video", "Standard", "Spectacle", "Adventure",
|
|
25
|
+
"Drama", "Mono Movie", "Surround Decoder", "2ch Stereo", "7ch Stereo",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Receiver:
|
|
30
|
+
def __init__(self, host):
|
|
31
|
+
self.url = f"http://{host}/YamahaRemoteControl/ctrl"
|
|
32
|
+
|
|
33
|
+
def _send(self, cmd, inner):
|
|
34
|
+
body = f'<YAMAHA_AV cmd="{cmd}">{inner}</YAMAHA_AV>'.encode()
|
|
35
|
+
with urllib.request.urlopen(self.url, body, timeout=3) as r:
|
|
36
|
+
root = ET.fromstring(r.read())
|
|
37
|
+
rc = root.get("RC", "0")
|
|
38
|
+
if rc != "0":
|
|
39
|
+
raise ValueError(f"receiver returned RC={rc}")
|
|
40
|
+
return root
|
|
41
|
+
|
|
42
|
+
def put(self, inner):
|
|
43
|
+
self._send("PUT", inner)
|
|
44
|
+
|
|
45
|
+
def put_main(self, inner):
|
|
46
|
+
self.put(f"<Main_Zone>{inner}</Main_Zone>")
|
|
47
|
+
|
|
48
|
+
def status(self):
|
|
49
|
+
r = self._send("GET", "<Main_Zone><Basic_Status>GetParam</Basic_Status></Main_Zone>")
|
|
50
|
+
t = lambda p: (r.findtext(".//" + p) or "").strip()
|
|
51
|
+
return {
|
|
52
|
+
"power": t("Power_Control/Power") == "On",
|
|
53
|
+
"vol": int(t("Volume/Lvl/Val") or 0),
|
|
54
|
+
"mute": t("Volume/Mute") == "On",
|
|
55
|
+
"input": t("Input/Input_Sel"),
|
|
56
|
+
"program": t("Current/Sound_Program"),
|
|
57
|
+
"straight": t("Current/Straight") == "On",
|
|
58
|
+
"hdmi_out": t("HDMI/Output/OUT_1") == "On",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def inputs(self):
|
|
62
|
+
r = self._send("GET", "<Main_Zone><Input><Input_Sel_Item>GetParam</Input_Sel_Item></Input></Main_Zone>")
|
|
63
|
+
return [(i.findtext("Param"), i.findtext("Title").strip())
|
|
64
|
+
for i in r.find(".//Input_Sel_Item")
|
|
65
|
+
if i.findtext("RW") == "RW"]
|
|
66
|
+
|
|
67
|
+
def set_power(self, on):
|
|
68
|
+
self.put_main(f"<Power_Control><Power>{'On' if on else 'Standby'}</Power></Power_Control>")
|
|
69
|
+
|
|
70
|
+
def set_volume(self, v):
|
|
71
|
+
v = max(VOL_MIN, min(VOL_MAX, v))
|
|
72
|
+
self.put_main(f"<Volume><Lvl><Val>{v}</Val><Exp>1</Exp><Unit>dB</Unit></Lvl></Volume>")
|
|
73
|
+
|
|
74
|
+
def set_mute(self, on):
|
|
75
|
+
self.put_main(f"<Volume><Mute>{'On' if on else 'Off'}</Mute></Volume>")
|
|
76
|
+
|
|
77
|
+
def set_input(self, name):
|
|
78
|
+
self.put_main(f"<Input><Input_Sel>{name}</Input_Sel></Input>")
|
|
79
|
+
|
|
80
|
+
def set_program(self, name):
|
|
81
|
+
self.put_main(f"<Surround><Program_Sel><Current><Sound_Program>{name}</Sound_Program></Current></Program_Sel></Surround>")
|
|
82
|
+
|
|
83
|
+
def set_hdmi_out(self, on):
|
|
84
|
+
self.put_main(f"<Sound_Video><HDMI><Output><OUT_1>{'On' if on else 'Off'}</OUT_1></Output></HDMI></Sound_Video>")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def draw(scr, host, st, err, inputs):
|
|
88
|
+
scr.erase()
|
|
89
|
+
h, w = scr.getmaxyx()
|
|
90
|
+
|
|
91
|
+
def put(y, text, attr=0):
|
|
92
|
+
if y < h - 1:
|
|
93
|
+
scr.addnstr(y, 2, text, max(w - 3, 0), attr)
|
|
94
|
+
|
|
95
|
+
put(0, f"Yamaha RX-V475 {host}", curses.A_BOLD)
|
|
96
|
+
if err:
|
|
97
|
+
put(2, f"! {err}", curses.A_REVERSE)
|
|
98
|
+
if st:
|
|
99
|
+
db = st["vol"] / 10
|
|
100
|
+
barw = max(min(w - 30, 40), 5)
|
|
101
|
+
fill = round(barw * (st["vol"] - VOL_MIN) / (VOL_MAX - VOL_MIN))
|
|
102
|
+
put(3, f"Power {'ON' if st['power'] else 'standby'}", curses.A_BOLD if st["power"] else 0)
|
|
103
|
+
put(4, f"Volume [{'#' * fill}{'-' * (barw - fill)}] {db:+.1f} dB")
|
|
104
|
+
put(5, f"Mute {'ON' if st['mute'] else 'off'}")
|
|
105
|
+
put(6, f"HDMI out {'on' if st['hdmi_out'] else 'OFF'}")
|
|
106
|
+
put(7, f"Program {st['program']}{' (straight)' if st['straight'] else ''}")
|
|
107
|
+
put(9, "Input", curses.A_BOLD)
|
|
108
|
+
for n, (param, title) in enumerate(inputs):
|
|
109
|
+
cur = param == st["input"]
|
|
110
|
+
key = next((k for k, v in HOTKEYS.items() if v == param), " ")
|
|
111
|
+
put(10 + n, f"{'>' if cur else ' '} {key} {param:<10} {title}", curses.A_REVERSE if cur else 0)
|
|
112
|
+
help_lines = [
|
|
113
|
+
"p power",
|
|
114
|
+
"↑ / ↓ volume",
|
|
115
|
+
"m mute",
|
|
116
|
+
"0-9 input (see list)",
|
|
117
|
+
"← / → prev / next input",
|
|
118
|
+
"s next sound program",
|
|
119
|
+
"o HDMI out on/off",
|
|
120
|
+
"r refresh",
|
|
121
|
+
"q quit",
|
|
122
|
+
]
|
|
123
|
+
if w >= 78:
|
|
124
|
+
for n, line in enumerate(help_lines):
|
|
125
|
+
if 3 + n < h:
|
|
126
|
+
scr.addnstr(3 + n, 52, line, w - 53, curses.A_DIM)
|
|
127
|
+
elif h > 1:
|
|
128
|
+
scr.addnstr(h - 1, 2, "p pwr ↑↓ vol m mute 0-9/←→ input s prog o hdmi r q", max(w - 3, 0), curses.A_DIM)
|
|
129
|
+
scr.refresh()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main(scr, host):
|
|
133
|
+
curses.curs_set(0)
|
|
134
|
+
scr.keypad(True)
|
|
135
|
+
scr.timeout(POLL_MS)
|
|
136
|
+
rx = Receiver(host)
|
|
137
|
+
st, err, inputs = None, None, []
|
|
138
|
+
|
|
139
|
+
def refresh():
|
|
140
|
+
nonlocal st, err, inputs
|
|
141
|
+
try:
|
|
142
|
+
st = rx.status()
|
|
143
|
+
if not inputs:
|
|
144
|
+
inputs = rx.inputs()
|
|
145
|
+
err = None
|
|
146
|
+
except (urllib.error.URLError, OSError, ValueError, ET.ParseError) as e:
|
|
147
|
+
err = f"{type(e).__name__}: {getattr(e, 'reason', e)}"
|
|
148
|
+
|
|
149
|
+
def act(fn, *args):
|
|
150
|
+
nonlocal err
|
|
151
|
+
try:
|
|
152
|
+
fn(*args)
|
|
153
|
+
except (urllib.error.URLError, OSError, ValueError, ET.ParseError) as e:
|
|
154
|
+
err = f"{type(e).__name__}: {getattr(e, 'reason', e)}"
|
|
155
|
+
return
|
|
156
|
+
refresh()
|
|
157
|
+
|
|
158
|
+
refresh()
|
|
159
|
+
while True:
|
|
160
|
+
draw(scr, host, st, err, inputs)
|
|
161
|
+
k = scr.getch()
|
|
162
|
+
if k in (ord("q"), 27):
|
|
163
|
+
return
|
|
164
|
+
if k in (-1, ord("r"), curses.KEY_RESIZE):
|
|
165
|
+
refresh()
|
|
166
|
+
elif not st:
|
|
167
|
+
continue
|
|
168
|
+
elif k == ord("p"):
|
|
169
|
+
act(rx.set_power, not st["power"])
|
|
170
|
+
elif k in (curses.KEY_UP, ord("+"), ord("=")):
|
|
171
|
+
act(rx.set_volume, st["vol"] + VOL_STEP)
|
|
172
|
+
elif k in (curses.KEY_DOWN, ord("-")):
|
|
173
|
+
act(rx.set_volume, st["vol"] - VOL_STEP)
|
|
174
|
+
elif k == ord("m"):
|
|
175
|
+
act(rx.set_mute, not st["mute"])
|
|
176
|
+
elif k == ord("o"):
|
|
177
|
+
act(rx.set_hdmi_out, not st["hdmi_out"])
|
|
178
|
+
elif k == ord("s"):
|
|
179
|
+
cur = PROGRAMS.index(st["program"]) if st["program"] in PROGRAMS else -1
|
|
180
|
+
act(rx.set_program, PROGRAMS[(cur + 1) % len(PROGRAMS)])
|
|
181
|
+
elif 0 <= k < 256 and chr(k) in HOTKEYS and HOTKEYS[chr(k)] in dict(inputs):
|
|
182
|
+
act(rx.set_input, HOTKEYS[chr(k)])
|
|
183
|
+
elif k in (curses.KEY_RIGHT, curses.KEY_LEFT, ord("\t")) and inputs:
|
|
184
|
+
names = [p for p, _ in inputs]
|
|
185
|
+
cur = names.index(st["input"]) if st["input"] in names else 0
|
|
186
|
+
step = -1 if k == curses.KEY_LEFT else 1
|
|
187
|
+
act(rx.set_input, names[(cur + step) % len(names)])
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def cli():
|
|
191
|
+
host = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("YAMAHA_HOST", "192.168.0.94")
|
|
192
|
+
try:
|
|
193
|
+
curses.wrapper(main, host)
|
|
194
|
+
except KeyboardInterrupt:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
cli()
|