mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 01:00:04 +02:00
Add quick chat picker and hold-to-quit overlay control
This commit is contained in:
1 parent
d1485ff71a
commit
1bde401aa8
23 files changed
+291
-71
No files matched your search
@@ -39,6 +39,7 @@ int main(int argc, char** argv) {
|
||||
assert(default_config_path().empty());
|
||||
::setenv("XDG_CONFIG_HOME", dir.c_str(), 1);
|
||||
assert(load_config(path).buttons.empty());
|
||||
assert((load_config(path).quick_inputs == std::vector<std::string>{"/new", "/questions", "/help"}));
|
||||
assert(!load_config(path).experimental_input_priority);
|
||||
assert(!load_config(path).advanced_debug);
|
||||
assert(!load_config(path).auto_insert);
|
||||
@@ -48,6 +49,9 @@ int main(int argc, char** argv) {
|
||||
assert(load_config(path).wrist.width == .30f);
|
||||
auto example = load_config(std::filesystem::path(argv[1]) / "config.example.json");
|
||||
assert(example.buttons.at("ptt") == "/user/hand/right/input/x");
|
||||
assert(example.buttons.at("quick_chat") == "/user/hand/right/input/y");
|
||||
assert(example.buttons.at("enter").empty());
|
||||
assert(example.quick_inputs.size() == 3);
|
||||
assert(example.font.empty());
|
||||
assert(!example.experimental_input_priority);
|
||||
assert(!example.advanced_debug);
|
||||
@@ -183,6 +187,7 @@ int main(int argc, char** argv) {
|
||||
assert(generated.find("/actions/frameyap/in/right_grip") == std::string::npos);
|
||||
assert(generated.find("/user/hand/right/input/a") != std::string::npos);
|
||||
assert(generated.find("/user/hand/right/input/y") != std::string::npos);
|
||||
assert(generated.find("/actions/frameyap/in/quick_chat") != std::string::npos);
|
||||
config.buttons["cancel"] = ""; config.buttons["insert"] = ""; config.buttons["enter"] = "";
|
||||
auto disabled = action_manifest(argv[1], config);
|
||||
generated = get(disabled.parent_path() / "bindings_frame_controller.json");
|
||||
@@ -190,6 +195,17 @@ int main(int argc, char** argv) {
|
||||
assert(generated.find("/actions/frameyap/in/insert") == std::string::npos);
|
||||
assert(generated.find("/actions/frameyap/in/enter") == std::string::npos);
|
||||
assert(action_manifest(argv[1], {}) == std::filesystem::absolute(std::filesystem::path(argv[1]) / "actions.json"));
|
||||
put(path, R"({"buttons":{"enter":"/user/hand/right/input/y"}})");
|
||||
generated = get(action_manifest(argv[1], load_config(path)).parent_path() / "bindings_frame_controller.json");
|
||||
assert(generated.find("/actions/frameyap/in/quick_chat") != std::string::npos);
|
||||
assert(generated.find("/actions/frameyap/in/enter") == std::string::npos);
|
||||
put(path, R"({"quick_inputs":["/new","custom phrase"]})");
|
||||
assert((load_config(path).quick_inputs == std::vector<std::string>{"/new", "custom phrase"}));
|
||||
for (const auto* invalid : {R"({"quick_inputs":[]})", R"({"quick_inputs":[42]})",
|
||||
R"({"quick_inputs":["line\nfeed"]})", R"({"quick_inputs":["\u001b"]})",
|
||||
R"({"quick_inputs":["", "/new"]})", R"({"quick_inputs":"/new"})"}) {
|
||||
put(path, invalid); fails([&] { load_config(path); });
|
||||
}
|
||||
put(path, R"({"buttons":{"ptt":"/user/hand/left/input/grip"}})");
|
||||
fails([&] { action_manifest(argv[1], load_config(path)); }); // overlapping bindings are never silently chosen
|
||||
put(path, R"({"buttons":{"ptt":"/user/hand/left/input/x\"}hack"}})");
|
||||
|
||||
@@ -41,6 +41,26 @@ Session review(std::string_view transcript) {
|
||||
return session;
|
||||
}
|
||||
void delivery_checks() {
|
||||
{
|
||||
auto s = review("pending review"); FakeDelivery fake;
|
||||
CHECK(deliver_quick("/questions", fake.factory()) == DeliveryResult::EnterQueued);
|
||||
CHECK((fake.events == std::vector<std::string>{"text:/questions", "enter"}));
|
||||
CHECK(fake.acquisitions == 2 && fake.active_leases == 0);
|
||||
CHECK(s.state() == State::Review && s.text() == "pending review");
|
||||
for (auto bad : {"", " ", "two\nlines", "\x1b[0m"}) {
|
||||
try { (void)deliver_quick(bad, fake.factory()); CHECK(false); }
|
||||
catch (const std::runtime_error&) {}
|
||||
}
|
||||
CHECK(fake.acquisitions == 2);
|
||||
}
|
||||
{
|
||||
FakeDelivery fake; fake.fail_text = true;
|
||||
CHECK(deliver_quick("/new", fake.factory()) == DeliveryResult::TextUncertain);
|
||||
CHECK((fake.events == std::vector<std::string>{"text:/new"}));
|
||||
FakeDelivery unavailable; unavailable.fail_acquisition = 2;
|
||||
CHECK(deliver_quick("/new", unavailable.factory()) == DeliveryResult::TextQueuedEnterUnavailable);
|
||||
CHECK((unavailable.events == std::vector<std::string>{"text:/new"}));
|
||||
}
|
||||
{
|
||||
auto s = review("Hello 世界"); FakeDelivery fake;
|
||||
CHECK(deliver_insert(s, fake.factory()) == DeliveryResult::TextQueued);
|
||||
|
||||
+23
-2
@@ -134,6 +134,18 @@ int main(int argc, char** argv) {
|
||||
if (argc >= 3) snapshot(surface, std::string(argv[2]) + "-review.ppm");
|
||||
assert(click(surface, 480, 610).action == UiAction::Insert);
|
||||
assert(click(surface, 680, 610).action == UiAction::Enter);
|
||||
p.quick_inputs = {"/new", "/questions", "/help"};
|
||||
p.quick_open = true;
|
||||
assert(surface.render(p));
|
||||
const auto first_quick = surface.pixels();
|
||||
no_action(click(surface, 900, 430)); // review pagination hidden behind picker
|
||||
p.quick_selected = 1;
|
||||
assert(surface.render(p) && surface.pixels() != first_quick);
|
||||
assert(click(surface, 680, 610).action == UiAction::Enter);
|
||||
assert(click(surface, 280, 610).action == UiAction::Cancel);
|
||||
no_action(click(surface, 480, 610));
|
||||
p.quick_open = false; p.quick_selected = 0;
|
||||
assert(surface.render(p));
|
||||
// Down/up must use the same controller, and release rechecks availability.
|
||||
surface.pointer_down(0, 480, 610);
|
||||
no_action(surface.pointer_up(1, 480, 610));
|
||||
@@ -282,8 +294,17 @@ int main(int argc, char** argv) {
|
||||
// A press does not re-upload pixels. A completed action still reaches the
|
||||
// caller; meaningful panel changes redraw.
|
||||
assert(!surface.render(p));
|
||||
surface.pointer_down(0, 900, 610); assert(!surface.render(p));
|
||||
assert(surface.pointer_up(0, 900, 610).action == UiAction::Quit);
|
||||
const auto at = PanelSurface::Clock::now();
|
||||
surface.pointer_down(0, 900, 610, at);
|
||||
no_action(surface.pointer_up(0, 900, 610, at + PanelSurface::quit_hold - std::chrono::milliseconds(1)));
|
||||
surface.pointer_down(0, 900, 610, at);
|
||||
no_action(surface.pointer_up(0, 280, 610, at + PanelSurface::quit_hold));
|
||||
surface.pointer_down(0, 900, 610, at);
|
||||
surface.reset_pointers();
|
||||
no_action(surface.pointer_up(0, 900, 610, at + PanelSurface::quit_hold));
|
||||
surface.pointer_down(0, 900, 610, at);
|
||||
assert(surface.pointer_up(0, 900, 610, at + PanelSurface::quit_hold).action == UiAction::Quit);
|
||||
assert(surface.render(p));
|
||||
assert(!surface.render(p));
|
||||
std::cout << "panel checks passed (no OpenVR, microphone or input injection)\n";
|
||||
}
|
||||
@@ -14,12 +14,15 @@ class ActionBindingTests(unittest.TestCase):
|
||||
config = json.loads((ROOT / "assets/config.example.json").read_text())
|
||||
sources = binding["bindings"]["/actions/frameyap"]["sources"]
|
||||
self.assertEqual(len({s["path"] for s in sources}), len(sources))
|
||||
for action, button in {"ptt": "x", "cancel": "b", "insert": "a", "enter": "y"}.items():
|
||||
for action, button in {"ptt": "x", "cancel": "b", "insert": "a", "quick_chat": "y"}.items():
|
||||
name = f"/actions/frameyap/in/{action}"
|
||||
path = f"/user/hand/right/input/{button}"
|
||||
self.assertIn({"name": name, "type": "boolean"}, manifest["actions"])
|
||||
self.assertEqual(config["buttons"][action], path)
|
||||
self.assertEqual([s["path"] for s in sources if s["inputs"]["click"]["output"] == name], [path])
|
||||
self.assertEqual(config["buttons"]["enter"], "")
|
||||
self.assertFalse(any(s["inputs"]["click"]["output"] == "/actions/frameyap/in/enter" for s in sources))
|
||||
self.assertEqual(config["quick_inputs"], ["/new", "/questions", "/help"])
|
||||
|
||||
def test_right_x_is_hold_to_talk(self):
|
||||
manifest = json.loads((ROOT / "assets/actions.json").read_text())
|
||||
|
||||
@@ -133,7 +133,14 @@ class InstallTests(unittest.TestCase):
|
||||
self.assertIs(fixed["lock_layout"], False)
|
||||
self.assertIs(fixed["clock_24h"], False)
|
||||
self.assertEqual(fixed["date_format"], "mdy")
|
||||
self.assertEqual(fixed["quick_inputs"], ["/new", "/questions", "/help"])
|
||||
self.assertEqual(fixed["buttons"]["quick_chat"], "/user/hand/right/input/y")
|
||||
self.assertEqual(fixed["wrist"], installer.CONFIG_DEFAULTS["wrist"])
|
||||
legacy = installer.normalized_config({"buttons": {"enter": "/user/hand/right/input/y"}})
|
||||
self.assertEqual(legacy["buttons"]["enter"], "")
|
||||
self.assertEqual(legacy["buttons"]["quick_chat"], "/user/hand/right/input/y")
|
||||
self.assertEqual(installer.normalized_config({"quick_inputs": ["oops\n"]})["quick_inputs"],
|
||||
["/new", "/questions", "/help"])
|
||||
backups = list(config.parent.glob("config.json.backup-*"))
|
||||
self.assertEqual(len(backups), 1)
|
||||
self.assertEqual(backups[0].read_bytes(), original)
|
||||
@@ -144,6 +151,7 @@ class InstallTests(unittest.TestCase):
|
||||
fixed["clock_24h"] = True
|
||||
fixed["date_format"] = "iso"
|
||||
fixed["buttons"]["enter"] = "" # intentional disabling survives upgrades
|
||||
fixed["quick_inputs"] = ["/new", "/questions", "hello there"]
|
||||
fixed["wrist"]["y"] = 0.2
|
||||
compact = json.dumps(fixed, separators=(",", ":")).encode()
|
||||
config.write_bytes(compact)
|
||||
@@ -168,6 +176,7 @@ class InstallTests(unittest.TestCase):
|
||||
self.assertEqual(fixed["font"], "/system/face.ttf")
|
||||
self.assertEqual(fixed["theme"]["ink"], installer.CONFIG_DEFAULTS["theme"]["ink"])
|
||||
self.assertEqual(fixed["buttons"], installer.CONFIG_DEFAULTS["buttons"]) # colliding paths reset
|
||||
self.assertEqual(fixed["quick_inputs"], installer.CONFIG_DEFAULTS["quick_inputs"])
|
||||
self.assertEqual(fixed["input_priority"], "normal")
|
||||
self.assertIs(fixed["advanced_debug"], False)
|
||||
self.assertIs(fixed["auto_insert"], False)
|
||||
|
||||
Reference in new issue
Block a user