#include #include #include #include #ifdef FRAMEYAP_NATIVE #include "runtime.hpp" #include "overlay.hpp" #include "text_input.hpp" #include "instance_lock.hpp" #include #include #include #endif namespace { void help() { std::cout << "FrameYap — standalone Steam Frame voice typing POC\n" "Usage: frameyap [--help | --version]\n" " --run --assets DIR --font FILE --worker FILE --model DIR\n" " [--python FILE] [--threads 2|4] [--socket gamescope-0] [--head]\n" " --register MANIFEST [--autostart] | --unregister MANIFEST\n" " --check-input [--socket gamescope-0] (discovery only, no typing)\n" " --check-overlay --assets DIR --font FILE [--head] (5s, no mic/input)\n" " --check-controls --assets DIR --font FILE [--head] (30s, gestures only)\n\n" "Right grip: double-tap, hold second squeeze to speak, release to review.\n" "Left grip: double-tap for explicit Enter. Bindings are remappable.\n" "Review first: Insert approves current focus; never automatic Enter.\n" "No device access unless an explicit runtime/check/registration mode is used.\n"; #ifndef FRAMEYAP_NATIVE std::cout << "This offline build has no hardware backends; enable FRAMEYAP_NATIVE to run.\n"; #endif } } int main(int argc, char** argv) { if (argc == 1 || (argc == 2 && std::string_view(argv[1]) == "--help")) { help(); return 0; } if (argc == 2 && std::string_view(argv[1]) == "--version") { std::cout << "frameyap " << FRAMEYAP_VERSION << "\n"; return 0; } #ifdef FRAMEYAP_NATIVE try { const std::string mode = argv[1]; if (mode == "--register" || mode == "--unregister") { if (argc == 3 || (mode == "--register" && argc == 4 && std::string_view(argv[3]) == "--autostart")) { frameyap::InstanceLock lock; return frameyap::registration(argv[2], mode == "--unregister", argc == 4); } } else if (mode == "--run" || mode == "--check-input" || mode == "--check-overlay" || mode == "--check-controls") { frameyap::Options options; const char* socket = std::getenv("GAMESCOPE_WAYLAND_DISPLAY"); options.socket = socket && *socket ? socket : "gamescope-0"; for (int i = 2; i < argc; ++i) { std::string_view arg = argv[i]; if (arg == "--head" && mode != "--check-input") { options.hand = false; continue; } if (i + 1 == argc) throw std::runtime_error("Missing option value"); std::string value = argv[++i]; if (arg == "--socket" && mode != "--check-overlay") options.socket = value; else if (arg == "--assets" && mode != "--check-input") options.assets = value; else if (arg == "--font" && mode != "--check-input") options.font = value; else if (arg == "--worker" && mode == "--run") options.worker = value; else if (arg == "--python" && mode == "--run") options.python = value; else if (arg == "--model" && mode == "--run") options.model = value; else if (arg == "--threads" && mode == "--run" && (value == "2" || value == "4")) options.threads = value == "2" ? 2 : 4; else throw std::runtime_error("Unsupported option/value"); } if (mode == "--check-input") { frameyap::InstanceLock lock; frameyap::TextInput input(options.socket); std::cout << "Gamescope IME v2 ready; no input delivered.\n"; return 0; } if (options.assets.empty() || options.font.empty()) throw std::runtime_error("--assets and --font required"); if (mode == "--check-overlay" || mode == "--check-controls") { frameyap::InstanceLock lock; frameyap::Overlay overlay(options.assets, options.font, options.hand); overlay.draw({"FrameYap five-second visual check", "No audio captured. No text or Enter delivered.", "Controls inactive during this check.", false, false}); for (vr::TrackedDeviceIndex_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { if (vr::VRSystem()->GetTrackedDeviceClass(i) != vr::TrackedDeviceClass_Controller) continue; char type[256]{}, profile[512]{}; vr::VRSystem()->GetStringTrackedDeviceProperty(i, vr::Prop_ControllerType_String, type, sizeof(type)); vr::VRSystem()->GetStringTrackedDeviceProperty(i, vr::Prop_InputProfilePath_String, profile, sizeof(profile)); std::cout << "Controller type=" << type << " profile=" << profile << '\n'; } if (mode == "--check-controls") { const auto end = std::chrono::steady_clock::now() + std::chrono::seconds(30); std::string status; bool held = false; while (std::chrono::steady_clock::now() < end) { overlay.draw({"Controls check ONLY - no mic or typing", "Tap then hold right grip; double-tap left grip.", held ? "HOLD gesture recognized. Release to finish." : "Try grips or click buttons. Auto-closes after 30 seconds.", true, held}); for (auto action : overlay.poll()) { if (action == frameyap::UiAction::Quit) return 0; if (action == frameyap::UiAction::BeginRecord) held = true; if (action == frameyap::UiAction::EndRecord || action == frameyap::UiAction::Cancel) held = false; std::cout << "Control action=" << static_cast(action) << " (diagnostic only)" << std::endl; } auto next = overlay.controls_status(); if (next != status) { status = next; std::cout << status << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } else std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout << "Overlay APIs accepted panel; physical visibility requires human confirmation.\n"; return 0; } if (options.worker.empty() || options.model.empty()) throw std::runtime_error("--worker and --model required"); return frameyap::run(options); } } catch (const std::exception& e) { std::cerr << "FrameYap: " << e.what() << '\n'; return 1; } #endif std::cerr << "Unsupported arguments or runtime disabled in this build. Use --help.\n"; return 2; }