OSC: Change ::OscSend format and restore String support to Button Actions. (#388)

* OscSend: support strings poc
- change value delimiter to `;`
- default convert values to String when not converted to another type

* add `_arg<n>="<value>"` format instead of `;` delimiter
that is, vrchat chatbox is controlled like `_press="::OscSend /chatbox/input" _arg0="${send}" _arg1="true" _arg2="true"` now

old format still supported as shorthand, with same "strings can't have spaces" limit as before. `_arg<n>` are appended after these.

* fix accidentally resetting `osc_args` to empty between formats

* update readme to document `::OscSend` paramter-form

* clean up, use `while let` instead of `while match`

* merge `parse_osc_value` into while condition

* remove second log from shortform arg parsing
This commit is contained in:
Jay
2026-01-16 13:13:44 +11:00
committed by GitHub
parent da644c97d3
commit fe61319fc2
3 changed files with 43 additions and 12 deletions

View File

@@ -80,22 +80,40 @@ This button action executes a shell script using the `sh` shell.
<button _press="::ShellExec $HOME/myscript.sh test-argument" [...] />
```
##### `::OscSend <path> <args ..>`
##### `::OscSend <path>` `::OscSend <path> <args ..>`
Send an OSC message. The target port comes from the `osc_out_port` configuration setting.
There are two formats; here is an example for both formats writing a message to the VRChat Chatbox:
```xml
<button _press="::OscSend /avatar/parameters/MyInt 1i32" [...] />
<!-- parameter form - OSC arguments are listed as parameters labelled `_arg<n>` where `n` is 0-indexed -->
<Button _press="::OscSend /chatbox/input" _arg0="Hello World! I am WayVR." _arg1="true" _arg2="true"> </Button>
<!-- will send: ("Hello World! I am WayVR.", True, True) to /chatbox/input -->
```
```xml
<!-- shorthand form - OSC arguments are space-separated in one string. note that single strings cannot contain spaces -->
<Button _press="::OscSend /chatbox/input Hello_World!_I_am_WayVR. true true"> </Button>
<!-- will send: ("Hello_World!_I_am_WayVR.", True, True) to /chatbox/input -->
```
The two can be combined; parameter-form arguments will be appended after shorthand-form arguments:
```xml
<!-- combined-form - rectangle bounds with a name -->
<Button _press="::OscSend /graphthing/rectangle 0i32 0i32 50i32 200i32" _arg0="tall rectangle"> </Button>
<!-- will send: (0, 0, 50, 200, "tall rectangle") to /graphthing/rectangle -->
```
Available argument value types (case insensitive):
- Bool: `true` or `false`
- Nil: `nil`
- Inf: `inf`
- Int: `-1i32`, `1i32`, etc
- Long: `-1i64`, `1i64`, etc
- Float: `1f32`, `1.0f32`, etc
- Double: `1f64`, `1.0f64`, etc
- Int: suffix `i32` (`-1i32`, `1i32`, etc)
- Long: suffix `i64` (`-1i64`, `1i64`, etc)
- Float: suffix `f32` (`1f32`, `1.0f32`, etc)
- Double: suffix `f64` (`1f64`, `1.0f64`, etc)
- String: any other value
- Shorthand form will treat Strings with spaces as multiple arguments. Use parameter form if you need spaces.
##### `::SendKey <VirtualKey> <UP|DOWN>`

View File

@@ -692,15 +692,28 @@ pub(super) fn setup_custom_button<S: 'static>(
};
let mut osc_args = vec![];
// collect arguments specified in the initial string
for arg in args {
let Ok(osc_arg) = parse_osc_value(arg)
.inspect_err(|e| log::warn!("Could not parse OSC value '{arg}': {e:?}"))
else {
let msg = format!("expected OscValue, found \"{arg}\"");
if let Ok(osc_arg) = parse_osc_value(arg).inspect_err(|e| {
let msg = format!("Could not parse OSC value \"{arg}\": {e:?}");
log_cmd_invalid_arg(parser_state, TAG, name, command, &msg);
return;
};
}) {
osc_args.push(osc_arg);
}
}
// collect arguments from _arg<n> attributes.
let mut arg_index = 0;
while let Some(arg) = attribs.get_value(&format!("_arg{arg_index}"))
&& let Ok(osc_arg) = parse_osc_value(arg).inspect_err(|e| {
let msg = format!("Could not parse OSC value \"{arg}\": {e:?}");
log_cmd_invalid_arg(parser_state, TAG, name, command, &msg);
})
{
osc_args.push(osc_arg);
arg_index += 1;
}
Box::new(move |_common, data, app, _| {

View File

@@ -243,7 +243,7 @@ pub fn parse_osc_value(s: &str) -> anyhow::Result<OscType> {
}
}
anyhow::bail!("Unknown OSC type literal: {s}")
Ok(OscType::String(s.to_string()))
}
}
}