mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 01:00:04 +02:00
Add opt-in experimental overlay input priority
This commit is contained in:
1 parent
46b3c37a8a
commit
69f5de5a09
14 files changed
+131
-49
No files matched your search
@@ -117,6 +117,7 @@ if(BUILD_TESTING AND NOT CMAKE_CROSSCOMPILING)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_executable(frameyap_config_test tests/config_test.cpp)
|
||||
target_link_libraries(frameyap_config_test PRIVATE frameyap_config)
|
||||
target_compile_options(frameyap_config_test PRIVATE -UNDEBUG)
|
||||
add_test(NAME frameyap.config COMMAND frameyap_config_test "${CMAKE_CURRENT_SOURCE_DIR}/assets")
|
||||
add_executable(frameyap_mount_test tests/mount_test.cpp)
|
||||
target_link_libraries(frameyap_mount_test PRIVATE frameyap_mount)
|
||||
|
||||
@@ -34,6 +34,11 @@ See [third-party notes](docs/third-party.md). No GitHub release is published yet
|
||||
panel colors, a font path and Frame controller button mappings; missing fonts
|
||||
fall back to bundled Inconsolata. The installer creates/checks this file and
|
||||
backs it up before repairs. See [overlay configuration](docs/overlay.md#user-theme-and-controller-configuration).
|
||||
- **Experimental input priority:** set `"input_priority": "experimental"` in
|
||||
that config and enable SteamVR's Developer option **Enable global input from
|
||||
overlays**. FrameYap then requests priority for its bound controller sources.
|
||||
This may consume controls used by games or the dashboard; coexistence on Frame
|
||||
is under test. The default is `"normal"`; restart FrameYap after changing it.
|
||||
- **Review-first:** focus your destination, then press Insert. No automatic insertion
|
||||
or submission. Maximum clip 20 seconds; accidental taps under 200 ms are discarded.
|
||||
- While the native app is Ready, it keeps the mic device open and discards idle
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"font": "",
|
||||
"input_priority": "normal",
|
||||
"theme": {
|
||||
"background": "#0c101b",
|
||||
"card": "#141c2b",
|
||||
|
||||
+5
-3
@@ -112,9 +112,11 @@ silently takes a game's button away. Check action activity and neutral rearm.
|
||||
|
||||
OpenVR 2.15.6 documents experimental overlay action-set priorities
|
||||
`0x01000000..0x01FFFFFF`, gated by SteamVR's **Experimental overlay input overrides**
|
||||
setting. This can selectively override scene input, but is not guaranteed enabled
|
||||
or usable on this Frame. Do not toggle it automatically. Begin with overlay mic
|
||||
controls; validate a global PTT binding separately with a scene active, dashboard
|
||||
setting. FrameYap now requests the minimum experimental priority when its config
|
||||
has `"input_priority": "experimental"`; the default is `"normal"` (priority zero).
|
||||
This can selectively override scene input, but delivered input and dashboard
|
||||
coexistence still need testing on Frame. FrameYap only reads the SteamVR permission
|
||||
setting and never toggles it automatically. Validate bindings with a scene active, dashboard
|
||||
open/closed, lost tracking, and reconnection. Overlay interactivity/input ownership
|
||||
is distinct from OS keyboard focus.
|
||||
|
||||
|
||||
@@ -51,3 +51,11 @@ not establish SteamVR texture acceptance, orientation, click behavior or a
|
||||
flicker fix. No microphone, inference or text/Enter delivery was exercised.
|
||||
The next human headset check should compare both static action clicks and
|
||||
content-changing tabs using the installed Vulkan build's `--check-controls`.
|
||||
|
||||
## Subsequent wearer report — 2026-09-24
|
||||
|
||||
After testing the installed Vulkan build, the user confirmed that the flicker
|
||||
is fixed. They separately reported controller shortcuts becoming unavailable
|
||||
in system laser/dashboard interaction states while pointer clicks work. This
|
||||
is wearer confirmation of the visual fix and a distinct input-routing issue;
|
||||
it does not establish microphone, transcription or text-delivery acceptance.
|
||||
+38
-6
@@ -73,6 +73,7 @@ installer creates one with defaults on first install. Copy the shipped
|
||||
```json
|
||||
{
|
||||
"font": "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"input_priority": "normal",
|
||||
"theme": {
|
||||
"background": "#0c101b", "card": "#141c2b", "ink": "#e6f0f9",
|
||||
"muted": "#97adc1", "accent": "#1ff0a4", "warning": "#ff6e87",
|
||||
@@ -101,8 +102,36 @@ the exact prior bytes under `config.json.backup-*` before a repair and refuses
|
||||
symlink/oversized config paths; valid customizations remain intact. The installed
|
||||
launcher no longer pins `--font`, so this selection takes effect. Direct native
|
||||
launches with bad JSON, colors or button mappings fail startup rather than
|
||||
silently changing input behavior. This does not enable experimental SteamVR
|
||||
action overrides or prove delivery in games.
|
||||
silently changing input behavior.
|
||||
|
||||
### Experimental controller input priority
|
||||
|
||||
There are two independent gates:
|
||||
|
||||
1. SteamVR's Developer setting **Enable global input from overlays** (called
|
||||
**Experimental overlay input overrides** in the SDK documentation) permits
|
||||
global action priority. FrameYap reads it and never changes it.
|
||||
2. FrameYap config `"input_priority": "experimental"` requests
|
||||
`k_nActionSetOverlayGlobalPriorityMin` (`0x01000000`) for its existing action
|
||||
set. `"normal"` or an omitted field requests priority zero. Restart FrameYap
|
||||
after changing the config. Invalid values fail native startup; the installer
|
||||
repairs them to normal and backs up the prior bytes.
|
||||
|
||||
This applies to all controller sources bound to FrameYap actions, including
|
||||
custom SteamVR bindings. It does not replace the action manifest or change the
|
||||
physical button mappings. Bound sources can take input away from games or the
|
||||
dashboard. The request stays the same with the dashboard open/closed and Lasers
|
||||
anytime on/off so the experiment can compare those modes. Successful API calls
|
||||
do not prove that controller actions arrive or that dashboard interaction works.
|
||||
|
||||
Native startup logs the requested priority and SteamVR permission separately.
|
||||
The no-audio/no-delivery `--check-controls` probe also logs dashboard state, the
|
||||
Lasers anytime flag, `IsInputAvailable`, panel visibility/focus, and activity,
|
||||
press state and pose/role acceptance for all six actions. The laser flag is our
|
||||
request, not a detector for every system laser. Compare the same bindings in
|
||||
each dashboard/laser state; check pointer clicks and press/release through mode
|
||||
transitions too. Return `input_priority` to `normal` and relaunch to end the
|
||||
FrameYap experiment.
|
||||
|
||||
### Placement settings
|
||||
|
||||
@@ -139,7 +168,10 @@ missing, symlinked or invalid files mean off. A failed save keeps the new
|
||||
choice only for the running session and shows a warning. To turn it back on
|
||||
after disabling it with the dashboard closed, open the dashboard to use its
|
||||
laser on the Settings button. Controls-only checks can toggle it temporarily
|
||||
but do not save the preference. This has not yet been accepted in a headset.
|
||||
but do not save the preference. The wearer reports that pointer clicks work,
|
||||
while normal-priority controller actions become unavailable in system laser
|
||||
mode, including when this preference is enabled. Experimental-priority
|
||||
coexistence remains unverified.
|
||||
|
||||
### Hardware-free UI checks
|
||||
|
||||
@@ -199,9 +231,9 @@ action explicitly begins on down and ends on up. On tracking-pose invalidity,
|
||||
action inactivity or overlay focus loss, a held capture emits Cancel, and
|
||||
reconnection requires a neutral observation before any new press. PTT and left
|
||||
Enter require an enabled panel; clickable Record remains available for retry
|
||||
after an error and Cancel is always available. The action set has normal priority: global input
|
||||
while a scene is active is not guaranteed, and experimental overlay overrides
|
||||
are not switched on automatically.
|
||||
after an error and Cancel is always available. The action set defaults to normal
|
||||
priority; the experimental config request is described above. Neither priority
|
||||
guarantees delivery while a game or dashboard owns input.
|
||||
|
||||
The UI cannot itself guarantee a capture started when a BeginRecord action
|
||||
arrives: the owning runtime checks worker readiness. `Cancel` invalidates
|
||||
|
||||
@@ -33,6 +33,7 @@ VERSION_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,95}\Z")
|
||||
DIGEST_RE = re.compile(r"[a-fA-F0-9]{64}\Z")
|
||||
CONFIG_DEFAULTS = {
|
||||
"font": "",
|
||||
"input_priority": "normal",
|
||||
"theme": {"background": "#0c101b", "card": "#141c2b", "ink": "#e6f0f9",
|
||||
"muted": "#97adc1", "accent": "#1ff0a4", "warning": "#ff6e87",
|
||||
"frame_start": "#1fff91", "frame_end": "#1f70ff"},
|
||||
@@ -87,6 +88,8 @@ def normalized_config(data):
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
fixed = {"font": data.get("font") if isinstance(data.get("font"), str) else ""}
|
||||
priority = data.get("input_priority", "normal")
|
||||
fixed["input_priority"] = priority if priority in ("normal", "experimental") else "normal"
|
||||
for section in ("theme", "buttons"):
|
||||
source = data.get(section)
|
||||
source = source if isinstance(source, dict) else {}
|
||||
|
||||
@@ -22,6 +22,7 @@ VERSION_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,95}\Z")
|
||||
DIGEST_RE = re.compile(r"[a-fA-F0-9]{64}\Z")
|
||||
CONFIG_DEFAULTS = {
|
||||
"font": "",
|
||||
"input_priority": "normal",
|
||||
"theme": {"background": "#0c101b", "card": "#141c2b", "ink": "#e6f0f9",
|
||||
"muted": "#97adc1", "accent": "#1ff0a4", "warning": "#ff6e87",
|
||||
"frame_start": "#1fff91", "frame_end": "#1f70ff"},
|
||||
@@ -76,6 +77,8 @@ def normalized_config(data):
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
fixed = {"font": data.get("font") if isinstance(data.get("font"), str) else ""}
|
||||
priority = data.get("input_priority", "normal")
|
||||
fixed["input_priority"] = priority if priority in ("normal", "experimental") else "normal"
|
||||
for section in ("theme", "buttons"):
|
||||
source = data.get(section)
|
||||
source = source if isinstance(source, dict) else {}
|
||||
|
||||
@@ -173,6 +173,10 @@ Config load_config(const std::filesystem::path& path) {
|
||||
if (key == "font") {
|
||||
if (!value.is_string) throw std::runtime_error("Config font must be a path string");
|
||||
config.font = value.value;
|
||||
} else if (key == "input_priority") {
|
||||
if (!value.is_string || (value.value != "normal" && value.value != "experimental"))
|
||||
throw std::runtime_error("Config input_priority must be normal or experimental");
|
||||
config.experimental_input_priority = value.value == "experimental";
|
||||
} else if (key == "theme") {
|
||||
if (!value.is_object) throw std::runtime_error("Config theme must be an object");
|
||||
for (const auto& [name, v] : value.object) {
|
||||
|
||||
@@ -15,6 +15,8 @@ struct Theme {
|
||||
struct Config {
|
||||
Theme theme;
|
||||
std::string font; // absolute TTF/OTF path; empty uses the bundled face
|
||||
// Requests OpenVR's experimental global action priority; SteamVR must allow it too.
|
||||
bool experimental_input_priority = false;
|
||||
// OpenVR action name -> physical Frame controller input path; empty disables it.
|
||||
std::map<std::string, std::string> buttons;
|
||||
};
|
||||
|
||||
@@ -27,6 +27,8 @@ void help() {
|
||||
"Theme, font and Frame button mappings: $XDG_CONFIG_HOME/frameyap/config.json\n"
|
||||
"(or ~/.config/frameyap/config.json). CLI --font overrides config; missing fonts\n"
|
||||
"fall back to bundled Inconsolata, then a system DejaVu face.\n"
|
||||
"Config input_priority: normal (default) or experimental; also requires\n"
|
||||
"SteamVR Developer setting Enable global input from overlays.\n"
|
||||
"Right X: hold to speak, release to review (default Frame binding).\n"
|
||||
"Grip gestures are remappable but may be unavailable in the dashboard.\n"
|
||||
"Left grip: double-tap for explicit Enter when active.\n"
|
||||
|
||||
+41
-39
@@ -34,6 +34,7 @@ void configure_registry() {
|
||||
::setenv("VR_PATHREG_OVERRIDE", registry.c_str(), 0);
|
||||
}
|
||||
constexpr int W = PanelSurface::width, H = PanelSurface::height;
|
||||
constexpr std::array<const char*, 6> action_names{{"left_grip", "right_grip", "ptt", "cancel", "insert", "enter"}};
|
||||
void overlay_check(vr::EVROverlayError err, vr::IVROverlay* api, const char* op) {
|
||||
if (err != vr::VROverlayError_None)
|
||||
throw std::runtime_error(std::string(op) + ": " + api->GetOverlayErrorNameFromEnum(err));
|
||||
@@ -136,15 +137,13 @@ struct Overlay::Impl {
|
||||
throw std::runtime_error("Could not set OpenVR action manifest path");
|
||||
if (input->GetActionSetHandle("/actions/frameyap", &action_set) != vr::VRInputError_None)
|
||||
throw std::runtime_error("Could not find FrameYap action set");
|
||||
constexpr std::array<const char*, 6> names{{"left_grip", "right_grip", "ptt", "cancel", "insert", "enter"}};
|
||||
for (size_t i = 0; i < names.size(); ++i)
|
||||
if (input->GetActionHandle((std::string("/actions/frameyap/in/") + names[i]).c_str(), &actions[i]) != vr::VRInputError_None)
|
||||
throw std::runtime_error(std::string("Could not find action ") + names[i]);
|
||||
for (size_t i = 0; i < action_names.size(); ++i)
|
||||
if (input->GetActionHandle((std::string("/actions/frameyap/in/") + action_names[i]).c_str(), &actions[i]) != vr::VRInputError_None)
|
||||
throw std::runtime_error(std::string("Could not find action ") + action_names[i]);
|
||||
overlay_check(overlay->CreateOverlay("local.frameyap.overlay.panel", "FrameYap", &handle), overlay, "CreateOverlay");
|
||||
overlay_check(overlay->SetOverlayWidthInMeters(handle, 0.85f), overlay, "SetOverlayWidthInMeters");
|
||||
overlay_check(overlay->SetOverlayInputMethod(handle, vr::VROverlayInputMethod_Mouse), overlay, "SetOverlayInputMethod");
|
||||
// Normal priority actions stay unchanged. This separate, explicit
|
||||
// preference requests system-wide laser mouse mode only while the
|
||||
// This separate preference requests system-wide laser mouse mode only while the
|
||||
// panel is visible; it may affect interaction with a running game.
|
||||
if (lasers_anytime && overlay->SetOverlayFlag(handle, vr::VROverlayFlags_MakeOverlaysInteractiveIfVisible, true) != vr::VROverlayError_None) {
|
||||
lasers_anytime = false;
|
||||
@@ -157,6 +156,7 @@ struct Overlay::Impl {
|
||||
system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0, poses.data(), uint32_t(poses.size()));
|
||||
place();
|
||||
draw(Panel{"Disabled", "", "Record to start local worker", false, false});
|
||||
std::cout << input_mode_status() << std::endl;
|
||||
} catch (...) { cleanup(); throw; }
|
||||
}
|
||||
~Impl() { cleanup(); }
|
||||
@@ -250,6 +250,28 @@ struct Overlay::Impl {
|
||||
for (size_t i = 1; i < edges.size(); ++i) edges[i].reset();
|
||||
surface.reset_pointers();
|
||||
}
|
||||
int32_t action_priority() const {
|
||||
return config.experimental_input_priority ? vr::k_nActionSetOverlayGlobalPriorityMin : 0;
|
||||
}
|
||||
std::string input_mode_status() const {
|
||||
// Read the runtime permission separately: requesting a priority does not
|
||||
// enable SteamVR's global setting or prove delivery of controller input.
|
||||
vr::EVRSettingsError settings_error = vr::VRSettingsError_None;
|
||||
auto* settings = vr::VRSettings();
|
||||
const bool allowed = settings && settings->GetBool(vr::k_pch_SteamVR_Section,
|
||||
vr::k_pch_SteamVR_AllowGlobalActionSetPriority, &settings_error);
|
||||
const char* permission = !settings || settings_error != vr::VRSettingsError_None ? "unavailable" :
|
||||
allowed ? "enabled" : "disabled";
|
||||
bool laser_flag = false;
|
||||
const auto laser_error = overlay->GetOverlayFlag(handle,
|
||||
vr::VROverlayFlags_MakeOverlaysInteractiveIfVisible, &laser_flag);
|
||||
return std::string("Input priority-request=") + (config.experimental_input_priority ? "experimental" : "normal") +
|
||||
" (" + std::to_string(action_priority()) + ") SteamVR-global-input=" + permission +
|
||||
"\nMode dashboard=" + (overlay->IsDashboardVisible() ? "Y" : "N") +
|
||||
" lasers-anytime=" + (laser_error == vr::VROverlayError_None ? (laser_flag ? "Y" : "N") : "n/a") +
|
||||
" system-input-available=" + (system->IsInputAvailable() ? "Y" : "N") +
|
||||
" panel-shown=" + (shown ? "Y" : "N") + " focus-gate=" + (focus ? "Y" : "N");
|
||||
}
|
||||
// Bound actions are accepted only with a connected tracked source. A held input
|
||||
// following loss of activity must return to neutral before generating an edge.
|
||||
std::pair<bool, bool> digital(size_t index) {
|
||||
@@ -350,7 +372,10 @@ struct Overlay::Impl {
|
||||
vr::VRActiveActionSet_t set{};
|
||||
set.ulActionSet = action_set;
|
||||
set.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle;
|
||||
set.nPriority = 0; // normal priority; no experimental scene-input overrides
|
||||
// Explicit opt-in affects only sources bound to our existing action set.
|
||||
// Keep requesting it across dashboard/laser states: those are what the
|
||||
// experiment compares. SteamVR's separate permission gate is never changed here.
|
||||
set.nPriority = action_priority();
|
||||
action_update_error = input->UpdateActionState(&set, sizeof(set), 1);
|
||||
if (action_update_error != vr::VRInputError_None) {
|
||||
reset_input(result); return result;
|
||||
@@ -392,42 +417,19 @@ Overlay::~Overlay() = default;
|
||||
std::vector<UiAction> Overlay::poll() { return impl_->poll(); }
|
||||
void Overlay::draw(const Panel& panel) { impl_->draw(panel); }
|
||||
std::string Overlay::controls_status() {
|
||||
// Diagnostic only: distinguish SteamVR binding/activity from our stricter
|
||||
// pose/role gate. Raw legacy state is read-only and may be unavailable.
|
||||
bool laser_flag = false;
|
||||
const auto laser_error = impl_->overlay->GetOverlayFlag(impl_->handle,
|
||||
vr::VROverlayFlags_MakeOverlaysInteractiveIfVisible, &laser_flag);
|
||||
std::string result = "SteamVR update=" + std::to_string(int(impl_->action_update_error)) +
|
||||
" dashboard=" + (impl_->overlay->IsDashboardVisible() ? "Y" : "N") +
|
||||
" lasers-anytime=" + (laser_error == vr::VROverlayError_None ? (laser_flag ? "Y" : "N") : "n/a");
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
const auto role = i == 0 ? vr::TrackedControllerRole_LeftHand : vr::TrackedControllerRole_RightHand;
|
||||
const auto device = impl_->system->GetTrackedDeviceIndexForControllerRole(role);
|
||||
const bool tracked = device < impl_->poses.size() && impl_->poses[device].bPoseIsValid &&
|
||||
impl_->system->IsTrackedDeviceConnected(device);
|
||||
// Compare the same actions across modes, before and after our pose/role gate.
|
||||
// IsInputAvailable and a successful UpdateActionState are not delivery proof.
|
||||
std::string result = impl_->input_mode_status() +
|
||||
" update=" + std::to_string(int(impl_->action_update_error));
|
||||
for (size_t i = 0; i < action_names.size(); ++i) {
|
||||
vr::InputDigitalActionData_t data{};
|
||||
const auto error = impl_->input->GetDigitalActionData(impl_->actions[i], &data, sizeof(data), vr::k_ulInvalidInputValueHandle);
|
||||
vr::InputOriginInfo_t origin{};
|
||||
const bool origin_ok = error == vr::VRInputError_None && data.activeOrigin != vr::k_ulInvalidInputValueHandle &&
|
||||
impl_->input->GetOriginTrackedDeviceInfo(data.activeOrigin, &origin, sizeof(origin)) == vr::VRInputError_None &&
|
||||
origin.trackedDeviceIndex == device;
|
||||
vr::VRControllerState_t raw{};
|
||||
const bool raw_ok = device < impl_->poses.size() && impl_->system->GetControllerState(device, &raw, sizeof(raw));
|
||||
result += std::string(i == 0 ? "\nLeft:" : "\nRight:") +
|
||||
const auto [accepted, down] = impl_->digital(i);
|
||||
result += std::string("\nAction ") + action_names[i] +
|
||||
" err=" + std::to_string(int(error)) + " active=" + (data.bActive ? "Y" : "N") +
|
||||
" down=" + (data.bState ? "Y" : "N") + " pose=" + (tracked ? "Y" : "N") +
|
||||
" origin=" + (origin_ok ? "Y" : "N") +
|
||||
" raw=" + (raw_ok ? ((raw.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_Grip)) ? "down" : "up") : "n/a");
|
||||
" down=" + (data.bState ? "Y" : "N") +
|
||||
" pose-role-accepted=" + (accepted ? "Y" : "N") + " gated-down=" + (down ? "Y" : "N");
|
||||
}
|
||||
// The default Frame binding maps right X to the named hold-to-talk action.
|
||||
// Report the action's own activity separately from the stricter pose gate;
|
||||
// remapped bindings may intentionally have a different origin.
|
||||
vr::InputDigitalActionData_t ptt{};
|
||||
const auto ptt_error = impl_->input->GetDigitalActionData(impl_->actions[2], &ptt, sizeof(ptt), vr::k_ulInvalidInputValueHandle);
|
||||
const auto [accepted, down] = impl_->digital(2);
|
||||
result += "\nPTT (default right X): err=" + std::to_string(int(ptt_error)) +
|
||||
" active=" + (ptt.bActive ? "Y" : "N") + " down=" + (ptt.bState ? "Y" : "N") +
|
||||
" accepted=" + (accepted ? "Y" : "N") + " gated-down=" + (down ? "Y" : "N");
|
||||
return result;
|
||||
}
|
||||
std::string Overlay::pointer_status() const {
|
||||
|
||||
@@ -27,10 +27,24 @@ 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).experimental_input_priority);
|
||||
auto example = load_config(std::filesystem::path(argv[1]) / "config.example.json");
|
||||
assert(example.buttons.at("ptt") == "/user/hand/right/input/x");
|
||||
assert(example.font.empty());
|
||||
assert(!example.experimental_input_priority);
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
put(path, R"({"input_priority":"experimental"})");
|
||||
auto experimental = load_config(path);
|
||||
assert(experimental.experimental_input_priority);
|
||||
// A priority request must preserve the user's existing action manifest/bindings.
|
||||
assert(action_manifest(argv[1], experimental) == std::filesystem::absolute(std::filesystem::path(argv[1]) / "actions.json"));
|
||||
put(path, R"({"input_priority":"normal"})");
|
||||
assert(!load_config(path).experimental_input_priority);
|
||||
for (const auto* invalid : {R"({"input_priority":true})", R"({"input_priority":16777216})",
|
||||
R"({"input_priority":"highest"})", R"({"input_priority":null})"}) {
|
||||
put(path, invalid);
|
||||
fails([&] { load_config(path); });
|
||||
}
|
||||
put(path, R"({"theme":{"background":"#123ABC","accent":"#abcdef","frame_end":"#010203"},"font":"/nonexistent/face.ttf","buttons":{"ptt":"/user/hand/left/input/y","cancel":"/user/hand/right/input/b","right_grip":""}})");
|
||||
auto config = load_config(path);
|
||||
assert((config.theme.background == Rgba{0x12, 0x3a, 0xbc, 255}));
|
||||
|
||||
@@ -124,21 +124,24 @@ class InstallTests(unittest.TestCase):
|
||||
self.assertEqual(fixed["buttons"]["ptt"], "/user/hand/left/input/y")
|
||||
self.assertEqual(fixed["theme"]["card"], installer.CONFIG_DEFAULTS["theme"]["card"])
|
||||
self.assertEqual(fixed["buttons"]["cancel"], "")
|
||||
self.assertEqual(fixed["input_priority"], "normal")
|
||||
backups = list(config.parent.glob("config.json.backup-*"))
|
||||
self.assertEqual(len(backups), 1)
|
||||
self.assertEqual(backups[0].read_bytes(), original)
|
||||
fixed["input_priority"] = "experimental"
|
||||
compact = json.dumps(fixed, separators=(",", ":")).encode()
|
||||
config.write_bytes(compact)
|
||||
self.install("v1", archive, digest)
|
||||
self.assertEqual(config.read_bytes(), compact)
|
||||
self.assertEqual(len(list(config.parent.glob("config.json.backup-*"))), 1)
|
||||
original = b'{"font":"/system/face.ttf","theme":{"ink":"bad","retired":"#123456"},"buttons":{"ptt":"/user/hand/left/input/grip"},"old_option":4}'
|
||||
original = b'{"font":"/system/face.ttf","input_priority":"highest","theme":{"ink":"bad","retired":"#123456"},"buttons":{"ptt":"/user/hand/left/input/grip"},"old_option":4}'
|
||||
config.write_bytes(original)
|
||||
self.install("v1", archive, digest)
|
||||
fixed = json.loads(config.read_text())
|
||||
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["input_priority"], "normal")
|
||||
self.assertNotIn("old_option", fixed)
|
||||
self.assertEqual(sorted(p.read_bytes() for p in config.parent.glob("config.json.backup-*")), sorted([backups[0].read_bytes(), original]))
|
||||
original = b'{"font":"one","font":"two"'
|
||||
|
||||
Reference in new issue
Block a user