diff --git a/wayvr/src/assets/gui/keyboard.xml b/wayvr/src/assets/gui/keyboard.xml index 5de2e66..396691c 100644 --- a/wayvr/src/assets/gui/keyboard.xml +++ b/wayvr/src/assets/gui/keyboard.xml @@ -165,21 +165,21 @@ - - - - - + + + + + + + - - diff --git a/wayvr/src/gui/panel/button.rs b/wayvr/src/gui/panel/button.rs index 2453a58..ab14b66 100644 --- a/wayvr/src/gui/panel/button.rs +++ b/wayvr/src/gui/panel/button.rs @@ -37,6 +37,8 @@ use crate::{ windowing::{OverlaySelector, backend::OverlayEventData, window::OverlayCategory}, }; +pub const BUTTON_EVENT_SUFFIX: &[&str] = &["", "2", "3", "4", "5", "6", "7", "8", "9"]; + #[allow(clippy::type_complexity)] pub const BUTTON_EVENTS: [( &str, @@ -196,549 +198,557 @@ pub(super) fn setup_custom_button( const TAG: &str = "Button"; for (name, kind, test_button, test_duration) in &BUTTON_EVENTS { - let Some(action) = attribs.get_value(name) else { - continue; - }; + for suffix in BUTTON_EVENT_SUFFIX { + let name = &format!("{name}{suffix}"); + let Some(action) = attribs.get_value(name) else { + //if no _press2 then don't attempt _press3 etc + break; + }; - let mut args = action.split_whitespace(); - let Some(command) = args.next() else { - continue; - }; + let mut args = action.split_whitespace(); + let Some(command) = args.next() else { + continue; + }; - let button = button.clone(); + let button = button.clone(); - let callback: EventCallback = match command { - "::ContextMenuOpen" => { - let Some(template_name) = args.next() else { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; + let callback: EventCallback = match command { + "::ContextMenuOpen" => { + let Some(template_name) = args.next() else { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; - // pass attribs with key `_context_{name}` to the context_menu template - let mut template_params = HashMap::new(); - for AttribPair { attrib, value } in &attribs.pairs { - const PREFIX: &'static str = "_context_"; - if attrib.starts_with(PREFIX) { - template_params.insert(attrib[PREFIX.len()..].into(), value.clone()); + // pass attribs with key `_context_{name}` to the context_menu template + let mut template_params = HashMap::new(); + for AttribPair { attrib, value } in &attribs.pairs { + const PREFIX: &'static str = "_context_"; + if attrib.starts_with(PREFIX) { + template_params.insert(attrib[PREFIX.len()..].into(), value.clone()); + } } + + let template_name: Rc = template_name.into(); + let context_menu = context_menu.clone(); + let on_custom_attribs = on_custom_attribs.clone(); + + Box::new({ + move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + context_menu.borrow_mut().open(OpenParams { + on_custom_attribs: Some(on_custom_attribs.clone()), + blueprint: Blueprint::Template { + template_name: template_name.clone(), + template_params: template_params.clone(), + }, + position: data.metadata.get_mouse_pos_absolute().unwrap(), //want panic + }); + Ok(EventResult::Consumed) + } + }) } + "::ContextMenuClose" => { + let context_menu = context_menu.clone(); - let template_name: Rc = template_name.into(); - let context_menu = context_menu.clone(); - let on_custom_attribs = on_custom_attribs.clone(); - - Box::new({ - move |_common, data, app, _| { + Box::new(move |_common, data, app, _| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } - context_menu.borrow_mut().open(OpenParams { - on_custom_attribs: Some(on_custom_attribs.clone()), - blueprint: Blueprint::Template { - template_name: template_name.clone(), - template_params: template_params.clone(), - }, - position: data.metadata.get_mouse_pos_absolute().unwrap(), //want panic - }); + context_menu.borrow_mut().close(); + Ok(EventResult::Consumed) - } - }) - } - "::ContextMenuClose" => { - let context_menu = context_menu.clone(); - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - context_menu.borrow_mut().close(); - - Ok(EventResult::Consumed) - }) - } - "::ElementSetDisplay" => { - let (Some(id), Some(value)) = (args.next(), args.next()) else { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - let Ok(widget_id) = parser_state.data.get_widget_id(id) else { - let msg = format!("no element with ID \"{id}\""); - log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); - return; - }; - - let display = match value { - "none" => taffy::Display::None, - "flex" => taffy::Display::Flex, - "block" => taffy::Display::Block, - "grid" => taffy::Display::Grid, - _ => { - let msg = format!("unexpected \"{value}\""); - log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); - return; - } - }; - - Box::new(move |common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - common - .alterables - .set_style(widget_id, StyleSetRequest::Display(display)); - Ok(EventResult::Consumed) - }) - } - "::DashToggle" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); - Ok(EventResult::Consumed) - }), - "::SetToggle" => { - let arg = args.next().unwrap_or_default(); - let Ok(set_idx) = arg.parse() else { - let msg = format!("expected integer, found \"{arg}\""); - log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); - return; - }; - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::ToggleSet(set_idx))); - Ok(EventResult::Consumed) - }) - } - "::SetSwitch" => { - let arg = args.next().unwrap_or_default(); - let Ok(set_idx) = arg.parse::() else { - let msg = format!("expected integer, found \"{arg}\""); - log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); - return; - }; - let maybe_set = if set_idx < 0 { - None - } else { - Some(set_idx as usize) - }; - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::SwitchSet(maybe_set))); - Ok(EventResult::Consumed) - }) - } - "::OverlayReset" => { - let arg: Arc = args.collect::>().join(" ").into(); - if arg.len() < 1 { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::ResetOverlay( - OverlaySelector::Name(arg.clone()), - ))); - Ok(EventResult::Consumed) - }) - } - "::OverlayToggle" => { - let arg: Arc = args.collect::>().join(" ").into(); - if arg.len() < 1 { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::ToggleOverlay( - OverlaySelector::Name(arg.clone()), - ToggleMode::Toggle, - ))); - Ok(EventResult::Consumed) - }) - } - "::OverlayDrop" => { - let arg: Arc = args.collect::>().join(" ").into(); - if arg.len() < 1 { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::Drop(OverlaySelector::Name( - arg.clone(), - )))); - Ok(EventResult::Consumed) - }) - } - "::DeleteSet" => Box::new(move |_common, data, app, _state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::DeleteActiveSet)); - Ok(EventResult::Consumed) - }), - "::AddSet" => Box::new(move |_common, data, app, _state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks.enqueue(TaskType::Overlay(OverlayTask::AddSet)); - Ok(EventResult::Consumed) - }), - "::CustomOverlayReload" => { - let arg: Arc = args.collect::>().join(" ").into(); - if arg.len() < 1 { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( - OverlaySelector::Name(arg.clone()), - Box::new(|app, owc| { - if !matches!(owc.category, OverlayCategory::Panel) { - return; - } - let name = owc.name.clone(); - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop( - OverlaySelector::Name(name.clone()), - ))); - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create( - OverlaySelector::Name(owc.name.clone()), - Box::new(move |app| { - if let Some(mut owc) = create_custom(app, name) { - owc.show_on_spawn = true; - Some(owc) - } else { - None - } - }), - ))); - }), - ))); - Ok(EventResult::Consumed) - }) - } - "::WvrOverlayCloseWindow" => { - let arg: Arc = args.collect::>().join(" ").into(); - if arg.len() < 1 { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( - OverlaySelector::Name(arg.clone()), - Box::new(move |app, owc| { - let _ = owc - .backend - .notify(app, OverlayEventData::WvrCommand(WvrCommand::CloseWindow)) - .log_warn("Could not close window"); - }), - ))); - Ok(EventResult::Consumed) - }) - } - "::WvrOverlayKillProcess" | "::WvrOverlayTermProcess" => { - let arg: Arc = args.collect::>().join(" ").into(); - if arg.len() < 1 { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - let signal = if command == "::WvrOverlayKillProcess" { - KillSignal::Kill - } else { - KillSignal::Term - }; - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( - OverlaySelector::Name(arg.clone()), - Box::new(move |app, owc| { - let _ = owc - .backend - .notify( - app, - OverlayEventData::WvrCommand(WvrCommand::KillProcess(signal)), - ) - .log_warn("Could not kill process"); - }), - ))); - Ok(EventResult::Consumed) - }) - } - "::EditToggle" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode)); - Ok(EventResult::Consumed) - }), - #[cfg(feature = "wayland")] - "::NewMirror" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - let name = crate::overlays::screen::mirror::new_mirror_name(); - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create( - OverlaySelector::Name(name.clone()), - Box::new(move |app| { - Some(crate::overlays::screen::mirror::new_mirror( - name, - &app.session, - )) - }), - ))); - Ok(EventResult::Consumed) - }), - "::CleanupMirrors" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::CleanupMirrors)); - Ok(EventResult::Consumed) - }), - "::PlayspaceReset" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks.enqueue(TaskType::Playspace(PlayspaceTask::Reset)); - Ok(EventResult::Consumed) - }), - "::PlayspaceRecenter" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.tasks - .enqueue(TaskType::Playspace(PlayspaceTask::Recenter)); - Ok(EventResult::Consumed) - }), - "::PlayspaceFixFloor" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - Toast::new( - ToastTopic::System, - "TOAST.FIXING_FLOOR".into(), - "TOAST.ONE_CONTROLLER_ON_FLOOR".into(), - ) - .with_timeout(5.) - .with_sound(true) - .submit(app); - - app.tasks.enqueue_at( - TaskType::Playspace(PlayspaceTask::FixFloor), - Instant::now() + Duration::from_secs(5), - ); - Ok(EventResult::Consumed) - }), - "::Shutdown" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - RUNNING.store(false, Ordering::Relaxed); - Ok(EventResult::Consumed) - }), - "::Restart" => Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - RUNNING.store(false, Ordering::Relaxed); - RESTART.store(true, Ordering::Relaxed); - - Ok(EventResult::Consumed) - }), - "::HandsfreeMode" => { - let Some(arg) = args.next() else { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - let Ok(val) = HandsfreePointer::from_str(arg) else { - let msg = format!("expected HandsfreePointer, found \"{arg}\""); - log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); - return; - }; - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - app.session.config.handsfree_pointer = val; - Ok(EventResult::Consumed) - }) - } - "::SendKey" => { - let Some(arg) = args.next() else { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - let Ok(key) = VirtualKey::from_str(arg) else { - let msg = format!("expected VirtualKey, found \"{arg}\""); - log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); - return; - }; - let Some(arg) = args.next() else { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - let down = match arg.to_lowercase().as_str() { - "down" => true, - "up" => false, - _ => { - let msg = format!("expected \"down\" or \"up\", found \"{arg}\""); - log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); - return; - } - }; - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - app.hid_provider - .send_key_routed(app.wvr_server.as_mut(), key, down); - Ok(EventResult::Consumed) - }) - } - "::ShellExec" => { - let state = Rc::new(ShellButtonState { - button: button.clone(), - exec: args.fold(String::new(), |c, n| c + " " + n), - mut_state: RefCell::new(ShellButtonMutableState::default()), - carry_over: RefCell::new(None), - }); - - layout.add_event_listener::( - attribs.widget_id, - EventListenerKind::InternalStateChange, - Box::new({ - let state = state.clone(); - move |_, _, _, _| { - shell_on_tick(&state); - Ok(EventResult::Pass) - } - }), - ); - - Box::new(move |_common, data, app, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - let _ = shell_on_action(&state).inspect_err(|e| log::error!("{e:?}")); - Ok(EventResult::Consumed) - }) - } - #[cfg(feature = "osc")] - "::OscSend" => { - use crate::subsystem::osc::parse_osc_value; - - let Some(address) = args.next().map(std::string::ToString::to_string) else { - log_cmd_missing_arg(parser_state, TAG, name, command); - return; - }; - - let mut osc_args = vec![]; - - // collect arguments specified in the initial string - for arg in args { - 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 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, _| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - let Some(sender) = app.osc_sender.as_mut() else { - log::error!("OscSend: sender is not available."); - return Ok(EventResult::Consumed); + "::ElementSetDisplay" => { + let (Some(id), Some(value)) = (args.next(), args.next()) else { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; }; - let _ = sender - .send_message(address.clone(), osc_args.clone()) - .inspect_err(|e| log::error!("OscSend: Could not send message: {e:?}")); + let Ok(widget_id) = parser_state.data.get_widget_id(id) else { + let msg = format!("no element with ID \"{id}\""); + log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); + return; + }; + + let display = match value { + "none" => taffy::Display::None, + "flex" => taffy::Display::Flex, + "block" => taffy::Display::Block, + "grid" => taffy::Display::Grid, + _ => { + let msg = format!("unexpected \"{value}\""); + log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); + return; + } + }; + + Box::new(move |common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + common + .alterables + .set_style(widget_id, StyleSetRequest::Display(display)); + Ok(EventResult::Consumed) + }) + } + "::DashToggle" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); + Ok(EventResult::Consumed) + }), + "::SetToggle" => { + let arg = args.next().unwrap_or_default(); + let Ok(set_idx) = arg.parse() else { + let msg = format!("expected integer, found \"{arg}\""); + log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); + return; + }; + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::ToggleSet(set_idx))); + Ok(EventResult::Consumed) + }) + } + "::SetSwitch" => { + let arg = args.next().unwrap_or_default(); + let Ok(set_idx) = arg.parse::() else { + let msg = format!("expected integer, found \"{arg}\""); + log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); + return; + }; + let maybe_set = if set_idx < 0 { + None + } else { + Some(set_idx as usize) + }; + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::SwitchSet(maybe_set))); + Ok(EventResult::Consumed) + }) + } + "::OverlayReset" => { + let arg: Arc = args.collect::>().join(" ").into(); + if arg.len() < 1 { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::ResetOverlay( + OverlaySelector::Name(arg.clone()), + ))); + Ok(EventResult::Consumed) + }) + } + "::OverlayToggle" => { + let arg: Arc = args.collect::>().join(" ").into(); + if arg.len() < 1 { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::ToggleOverlay( + OverlaySelector::Name(arg.clone()), + ToggleMode::Toggle, + ))); + Ok(EventResult::Consumed) + }) + } + "::OverlayDrop" => { + let arg: Arc = args.collect::>().join(" ").into(); + if arg.len() < 1 { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop( + OverlaySelector::Name(arg.clone()), + ))); + Ok(EventResult::Consumed) + }) + } + "::DeleteSet" => Box::new(move |_common, data, app, _state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::DeleteActiveSet)); + Ok(EventResult::Consumed) + }), + "::AddSet" => Box::new(move |_common, data, app, _state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks.enqueue(TaskType::Overlay(OverlayTask::AddSet)); + Ok(EventResult::Consumed) + }), + "::CustomOverlayReload" => { + let arg: Arc = args.collect::>().join(" ").into(); + if arg.len() < 1 { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(arg.clone()), + Box::new(|app, owc| { + if !matches!(owc.category, OverlayCategory::Panel) { + return; + } + let name = owc.name.clone(); + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop( + OverlaySelector::Name(name.clone()), + ))); + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create( + OverlaySelector::Name(owc.name.clone()), + Box::new(move |app| { + if let Some(mut owc) = create_custom(app, name) { + owc.show_on_spawn = true; + Some(owc) + } else { + None + } + }), + ))); + }), + ))); + Ok(EventResult::Consumed) + }) + } + "::WvrOverlayCloseWindow" => { + let arg: Arc = args.collect::>().join(" ").into(); + if arg.len() < 1 { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(arg.clone()), + Box::new(move |app, owc| { + let _ = owc + .backend + .notify( + app, + OverlayEventData::WvrCommand(WvrCommand::CloseWindow), + ) + .log_warn("Could not close window"); + }), + ))); + Ok(EventResult::Consumed) + }) + } + "::WvrOverlayKillProcess" | "::WvrOverlayTermProcess" => { + let arg: Arc = args.collect::>().join(" ").into(); + if arg.len() < 1 { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + + let signal = if command == "::WvrOverlayKillProcess" { + KillSignal::Kill + } else { + KillSignal::Term + }; + + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(arg.clone()), + Box::new(move |app, owc| { + let _ = owc + .backend + .notify( + app, + OverlayEventData::WvrCommand(WvrCommand::KillProcess( + signal, + )), + ) + .log_warn("Could not kill process"); + }), + ))); + Ok(EventResult::Consumed) + }) + } + "::EditToggle" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode)); + Ok(EventResult::Consumed) + }), + #[cfg(feature = "wayland")] + "::NewMirror" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let name = crate::overlays::screen::mirror::new_mirror_name(); + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create( + OverlaySelector::Name(name.clone()), + Box::new(move |app| { + Some(crate::overlays::screen::mirror::new_mirror( + name, + &app.session, + )) + }), + ))); + Ok(EventResult::Consumed) + }), + "::CleanupMirrors" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::CleanupMirrors)); + Ok(EventResult::Consumed) + }), + "::PlayspaceReset" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks.enqueue(TaskType::Playspace(PlayspaceTask::Reset)); + Ok(EventResult::Consumed) + }), + "::PlayspaceRecenter" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.tasks + .enqueue(TaskType::Playspace(PlayspaceTask::Recenter)); + Ok(EventResult::Consumed) + }), + "::PlayspaceFixFloor" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + Toast::new( + ToastTopic::System, + "TOAST.FIXING_FLOOR".into(), + "TOAST.ONE_CONTROLLER_ON_FLOOR".into(), + ) + .with_timeout(5.) + .with_sound(true) + .submit(app); + + app.tasks.enqueue_at( + TaskType::Playspace(PlayspaceTask::FixFloor), + Instant::now() + Duration::from_secs(5), + ); + Ok(EventResult::Consumed) + }), + "::Shutdown" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + RUNNING.store(false, Ordering::Relaxed); + Ok(EventResult::Consumed) + }), + "::Restart" => Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + RUNNING.store(false, Ordering::Relaxed); + RESTART.store(true, Ordering::Relaxed); Ok(EventResult::Consumed) - }) - } - // shell - _ => return, - }; + }), + "::HandsfreeMode" => { + let Some(arg) = args.next() else { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; - let id = layout.add_event_listener(attribs.widget_id, *kind, callback); - log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id); + let Ok(val) = HandsfreePointer::from_str(arg) else { + let msg = format!("expected HandsfreePointer, found \"{arg}\""); + log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); + return; + }; + + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + app.session.config.handsfree_pointer = val; + Ok(EventResult::Consumed) + }) + } + "::SendKey" => { + let Some(arg) = args.next() else { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + let Ok(key) = VirtualKey::from_str(arg) else { + let msg = format!("expected VirtualKey, found \"{arg}\""); + log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); + return; + }; + let Some(arg) = args.next() else { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + let down = match arg.to_lowercase().as_str() { + "down" => true, + "up" => false, + _ => { + let msg = format!("expected \"down\" or \"up\", found \"{arg}\""); + log_cmd_invalid_arg(parser_state, TAG, name, command, &msg); + return; + } + }; + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + app.hid_provider + .send_key_routed(app.wvr_server.as_mut(), key, down); + Ok(EventResult::Consumed) + }) + } + "::ShellExec" => { + let state = Rc::new(ShellButtonState { + button: button.clone(), + exec: args.fold(String::new(), |c, n| c + " " + n), + mut_state: RefCell::new(ShellButtonMutableState::default()), + carry_over: RefCell::new(None), + }); + + layout.add_event_listener::( + attribs.widget_id, + EventListenerKind::InternalStateChange, + Box::new({ + let state = state.clone(); + move |_, _, _, _| { + shell_on_tick(&state); + Ok(EventResult::Pass) + } + }), + ); + + Box::new(move |_common, data, app, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let _ = shell_on_action(&state).inspect_err(|e| log::error!("{e:?}")); + Ok(EventResult::Consumed) + }) + } + #[cfg(feature = "osc")] + "::OscSend" => { + use crate::subsystem::osc::parse_osc_value; + + let Some(address) = args.next().map(std::string::ToString::to_string) else { + log_cmd_missing_arg(parser_state, TAG, name, command); + return; + }; + + let mut osc_args = vec![]; + + // collect arguments specified in the initial string + for arg in args { + 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 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, _| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let Some(sender) = app.osc_sender.as_mut() else { + log::error!("OscSend: sender is not available."); + return Ok(EventResult::Consumed); + }; + + let _ = sender + .send_message(address.clone(), osc_args.clone()) + .inspect_err(|e| log::error!("OscSend: Could not send message: {e:?}")); + + Ok(EventResult::Consumed) + }) + } + // shell + _ => return, + }; + + let id = layout.add_event_listener(attribs.widget_id, *kind, callback); + log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id); + } } } diff --git a/wayvr/src/overlays/edit/mod.rs b/wayvr/src/overlays/edit/mod.rs index c3b5270..78db708 100644 --- a/wayvr/src/overlays/edit/mod.rs +++ b/wayvr/src/overlays/edit/mod.rs @@ -23,7 +23,10 @@ use crate::{ input::HoverResult, task::{OverlayTask, TaskContainer, TaskType}, }, - gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS}, + gui::panel::{ + GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, + button::{BUTTON_EVENT_SUFFIX, BUTTON_EVENTS}, + }, overlays::edit::{ lock::InteractLockHandler, mouse::new_mouse_tab_handler, @@ -279,86 +282,32 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result { }; for (name, kind, test_button, test_duration) in &BUTTON_EVENTS { - let Some(action) = attribs.get_value(name) else { - continue; - }; + for suffix in BUTTON_EVENT_SUFFIX { + let name = &format!("{name}{suffix}"); + let Some(action) = attribs.get_value(name) else { + break; + }; - let mut args = action.split_whitespace(); - let Some(command) = args.next() else { - continue; - }; + let mut args = action.split_whitespace(); + let Some(command) = args.next() else { + continue; + }; - let button = button.clone(); + let button = button.clone(); - let callback: EventCallback = match command { - "::EditModeToggleLock" => Box::new(move |common, data, app, state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - let sel = OverlaySelector::Id(*state.id.borrow()); - let task = state.lock.toggle(common, app, anim_mult); - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); - Ok(EventResult::Consumed) - }), - "::EditModeToggleGrab" => Box::new(move |_common, data, app, state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - let sel = OverlaySelector::Id(*state.id.borrow()); - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( - sel, - Box::new(|_app, owc| { - let state = owc.active_state.as_mut().unwrap(); //want panic - state.grabbable = !state.grabbable; - }), - ))); - Ok(EventResult::Consumed) - }), - "::EditModeTab" => { - let tab_name = args.next().unwrap().to_owned(); - Box::new(move |common, data, app, state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - state.tabs.tab_button_clicked(common, &tab_name); - Ok(EventResult::Consumed) - }) - } - "::EditModeSetPos" => { - let key = args.next().unwrap().to_owned(); - Box::new(move |common, data, app, state| { + let callback: EventCallback = match command { + "::EditModeToggleLock" => Box::new(move |common, data, app, state| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } let sel = OverlaySelector::Id(*state.id.borrow()); - let task = state.pos.button_clicked(common, &key); + let task = state.lock.toggle(common, app, anim_mult); app.tasks .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); Ok(EventResult::Consumed) - }) - } - "::EditModeSetStereo" => { - let key = args.next().unwrap().to_owned(); - Box::new(move |common, data, app, state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } - - let sel = OverlaySelector::Id(*state.id.borrow()); - let task = state.stereo.button_clicked(common, &key); - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); - Ok(EventResult::Consumed) - }) - } - "::EditModeSetStereoFullFrame" => { - let full_frame = args.next().unwrap().parse::().unwrap(); - Box::new(move |_common, data, app, state| { + }), + "::EditModeToggleGrab" => Box::new(move |_common, data, app, state| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } @@ -366,46 +315,103 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result { let sel = OverlaySelector::Id(*state.id.borrow()); app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( sel, - Box::new(move |_app, owc| { - let attrib = BackendAttribValue::StereoFullFrame(full_frame); - owc.backend.set_attrib(_app, attrib); + Box::new(|_app, owc| { + let state = owc.active_state.as_mut().unwrap(); //want panic + state.grabbable = !state.grabbable; }), ))); Ok(EventResult::Consumed) - }) - } - "::EditModeSetMouse" => { - let key = args.next().unwrap().to_owned(); - Box::new(move |common, data, app, state| { + }), + "::EditModeTab" => { + let tab_name = args.next().unwrap().to_owned(); + Box::new(move |common, data, app, state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + state.tabs.tab_button_clicked(common, &tab_name); + Ok(EventResult::Consumed) + }) + } + "::EditModeSetPos" => { + let key = args.next().unwrap().to_owned(); + Box::new(move |common, data, app, state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let sel = OverlaySelector::Id(*state.id.borrow()); + let task = state.pos.button_clicked(common, &key); + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); + Ok(EventResult::Consumed) + }) + } + "::EditModeSetStereo" => { + let key = args.next().unwrap().to_owned(); + Box::new(move |common, data, app, state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let sel = OverlaySelector::Id(*state.id.borrow()); + let task = state.stereo.button_clicked(common, &key); + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); + Ok(EventResult::Consumed) + }) + } + "::EditModeSetStereoFullFrame" => { + let full_frame = args.next().unwrap().parse::().unwrap(); + Box::new(move |_common, data, app, state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let sel = OverlaySelector::Id(*state.id.borrow()); + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + sel, + Box::new(move |_app, owc| { + let attrib = BackendAttribValue::StereoFullFrame(full_frame); + owc.backend.set_attrib(_app, attrib); + }), + ))); + Ok(EventResult::Consumed) + }) + } + "::EditModeSetMouse" => { + let key = args.next().unwrap().to_owned(); + Box::new(move |common, data, app, state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } + + let sel = OverlaySelector::Id(*state.id.borrow()); + let task = state.mouse.button_clicked(common, &key); + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); + Ok(EventResult::Consumed) + }) + } + "::EditModeDelete" => Box::new(move |_common, data, app, state| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } - let sel = OverlaySelector::Id(*state.id.borrow()); - let task = state.mouse.button_clicked(common, &key); - app.tasks - .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Id(*state.id.borrow()), + Box::new(move |_app, owc| { + owc.active_state = None; + }), + ))); Ok(EventResult::Consumed) - }) - } - "::EditModeDelete" => Box::new(move |_common, data, app, state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } + }), + _ => return, + }; - app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( - OverlaySelector::Id(*state.id.borrow()), - Box::new(move |_app, owc| { - owc.active_state = None; - }), - ))); - Ok(EventResult::Consumed) - }), - _ => return, - }; - - let id = layout.add_event_listener(attribs.widget_id, *kind, callback); - log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id); + let id = layout.add_event_listener(attribs.widget_id, *kind, callback); + log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id); + } } }); diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index cf90ba8..e995a7e 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -32,7 +32,10 @@ use crate::{ wayvr::{self, SurfaceBufWithImage, process::KillSignal, window::WindowHandle}, }, graphics::{ExtentExt, Vert2Uv, upload_quad_vertices}, - gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS}, + gui::panel::{ + GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, + button::{BUTTON_EVENT_SUFFIX, BUTTON_EVENTS}, + }, overlays::screen::capture::ScreenPipeline, state::{self, AppState}, subsystem::{hid::WheelDelta, input::KeyboardFocus}, @@ -141,32 +144,35 @@ impl WvrWindowBackend { }; for (name, kind, test_button, test_duration) in &BUTTON_EVENTS { - let Some(action) = attribs.get_value(name) else { - continue; - }; + for suffix in BUTTON_EVENT_SUFFIX { + let name = &format!("{name}{suffix}"); + let Some(action) = attribs.get_value(name) else { + break; + }; - let mut args = action.split_whitespace(); - let Some(command) = args.next() else { - continue; - }; + let mut args = action.split_whitespace(); + let Some(command) = args.next() else { + continue; + }; - let button = button.clone(); + let button = button.clone(); - let callback: EventCallback = match command { - "::DecorCloseWindow" => Box::new(move |_common, data, app, state| { - if !test_button(data) || !test_duration(&button, app) { - return Ok(EventResult::Pass); - } + let callback: EventCallback = match command { + "::DecorCloseWindow" => Box::new(move |_common, data, app, state| { + if !test_button(data) || !test_duration(&button, app) { + return Ok(EventResult::Pass); + } - app.wvr_server.as_mut().unwrap().close_window(*state); + app.wvr_server.as_mut().unwrap().close_window(*state); - Ok(EventResult::Consumed) - }), - _ => return, - }; + Ok(EventResult::Consumed) + }), + _ => return, + }; - let id = layout.add_event_listener(attribs.widget_id, *kind, callback); - log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id); + let id = layout.add_event_listener(attribs.widget_id, *kind, callback); + log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id); + } } });