Monado app switcher, lang update
This commit is contained in:
@@ -1,7 +1,35 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
|
||||
<!-- key: str, value: str -->
|
||||
<template name="BoolFlag">
|
||||
<div flex_direction="row" gap="4">
|
||||
<label text="${key}" />
|
||||
<label weight="bold" text="${value}" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- name, checked, flag_* -->
|
||||
<template name="Cell">
|
||||
<rectangle macro="group_box">
|
||||
<CheckBox id="checkbox" text="${name}" checked="${checked}" />
|
||||
<div flex_direction="row" gap="8">
|
||||
<BoolFlag key="Active:" value="${flag_active}" />
|
||||
<BoolFlag key="Focused:" value="${flag_focused}" />
|
||||
<BoolFlag key="IO active:" value="${flag_io_active}" />
|
||||
<BoolFlag key="Overlay:" value="${flag_overlay}" />
|
||||
<BoolFlag key="Primary:" value="${flag_primary}" />
|
||||
<BoolFlag key="Visible:" value="${flag_visible}" />
|
||||
</div>
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="MONADO_RUNTIME" icon="dashboard/monado.svg" />
|
||||
<div id="list_parent" flex_direction="column" gap="8">
|
||||
<!-- filled at runtime -->
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -14,11 +14,11 @@
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<elements>
|
||||
<div flex_direction="row" gap="16">
|
||||
<div flex_direction="row" gap="16" flex_grow="1">
|
||||
<rectangle macro="group_box" id="icon_parent" padding="16" color="#0033aa66" color2="#00000022" gradient="vertical" justify_content="center">
|
||||
|
||||
</rectangle>
|
||||
<div flex_direction="column" gap="8" min_width="720" max_width="720">
|
||||
<div flex_direction="column" gap="8" flex_grow="1">
|
||||
<label id="label_title" weight="bold" size="32" overflow="hidden" />
|
||||
<Subtext label_id="label_exec" overflow="hidden" />
|
||||
<Separator />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"HOME_SCREEN": "Startbildschirm",
|
||||
"MONADO_RUNTIME": "„Monado”-Laufzeitumgebung",
|
||||
"MONADO_RUNTIME": "Monado-Laufzeitumgebung",
|
||||
"APPLICATIONS": "Anwendungen",
|
||||
"GAMES": "Spiele",
|
||||
"SETTINGS": "Einstellungen",
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
"HOME_SCREEN": "Home",
|
||||
"LIST_OF_PROCESSES": "Process list",
|
||||
"LIST_OF_WINDOWS": "Window list",
|
||||
"MONADO_RUNTIME": "„Monado” runtime",
|
||||
"MONADO_RUNTIME": "Monado runtime",
|
||||
"NO_WINDOWS_FOUND": "No windows found",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Resolution"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"HOME_SCREEN": "Inicio",
|
||||
"MONADO_RUNTIME": "„Monado” tiempo de ejecución",
|
||||
"MONADO_RUNTIME": "Monado tiempo de ejecución",
|
||||
"APPLICATIONS": "Aplicaciones",
|
||||
"GAMES": "Juegos",
|
||||
"SETTINGS": "Ajustes",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"USE_SKYBOX_HELP": "Wyświetlaj niebo, jeśli nie ma aplikacji sceny lub passthrough",
|
||||
"USE_PASSTHROUGH_HELP": "Pozwól na passthrough, jeśli runtime XR to obsługuje",
|
||||
"SCREEN_RENDER_DOWN_HELP": "Pomaga redukować aliasing na ekranach o wysokiej rozdzielczości",
|
||||
"SETS_ON_WATCH": "Lista zestawówna zegarku",
|
||||
"SETS_ON_WATCH": "Lista zestawów na zegarku",
|
||||
"TROUBLESHOOTING": "Rozwiązywanie problemów",
|
||||
"CLEAR_SAVED_STATE": "Wyczyść zapisany stan",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire",
|
||||
|
||||
@@ -1,43 +1,163 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{collections::HashMap, marker::PhantomData, rc::Rc};
|
||||
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::checkbox::ComponentCheckbox,
|
||||
globals::WguiGlobals,
|
||||
layout::WidgetID,
|
||||
parser::{ParseDocumentParams, ParserState},
|
||||
parser::{self, Fetchable, ParseDocumentParams, ParserState},
|
||||
task::Tasks,
|
||||
};
|
||||
use wlx_common::dash_interface;
|
||||
|
||||
use crate::{
|
||||
frontend::Frontend,
|
||||
tab::{Tab, TabType},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Task {
|
||||
Refresh,
|
||||
FocusClient(String),
|
||||
}
|
||||
|
||||
pub struct TabMonado<T> {
|
||||
#[allow(dead_code)]
|
||||
pub state: ParserState,
|
||||
state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
|
||||
marker: PhantomData<T>,
|
||||
|
||||
globals: WguiGlobals,
|
||||
id_list_parent: WidgetID,
|
||||
|
||||
cells: Vec<parser::ParserData>,
|
||||
|
||||
ticks: u32,
|
||||
}
|
||||
|
||||
impl<T> Tab<T> for TabMonado<T> {
|
||||
fn get_type(&self) -> TabType {
|
||||
TabType::Games
|
||||
}
|
||||
|
||||
fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
for task in self.tasks.drain() {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(frontend, data)?,
|
||||
Task::FocusClient(name) => self.focus_client(frontend, data, name)?,
|
||||
}
|
||||
}
|
||||
|
||||
// every few seconds
|
||||
if self.ticks.is_multiple_of(500) {
|
||||
self.tasks.push(Task::Refresh);
|
||||
}
|
||||
|
||||
self.ticks += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||
ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/monado.xml"),
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn yesno(n: bool) -> &'static str {
|
||||
match n {
|
||||
true => "yes",
|
||||
false => "no",
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TabMonado<T> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||
let state = wgui::parser::parse_from_assets(
|
||||
&ParseDocumentParams {
|
||||
globals: frontend.layout.state.globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/tab/monado.xml"),
|
||||
extra: Default::default(),
|
||||
},
|
||||
&mut frontend.layout,
|
||||
parent_id,
|
||||
)?;
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
let state = wgui::parser::parse_from_assets(&doc_params(&globals), &mut frontend.layout, parent_id)?;
|
||||
|
||||
let id_list_parent = state.get_widget_id("list_parent")?;
|
||||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
marker: PhantomData,
|
||||
tasks,
|
||||
globals,
|
||||
id_list_parent,
|
||||
ticks: 0,
|
||||
cells: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn mount_client(&mut self, frontend: &mut Frontend<T>, client: &dash_interface::MonadoClient) -> anyhow::Result<()> {
|
||||
let mut par = HashMap::<Rc<str>, Rc<str>>::new();
|
||||
par.insert(
|
||||
"checked".into(),
|
||||
if client.is_primary {
|
||||
Rc::from("1")
|
||||
} else {
|
||||
Rc::from("0")
|
||||
},
|
||||
);
|
||||
par.insert("name".into(), client.name.clone().into());
|
||||
par.insert("flag_active".into(), yesno(client.is_active).into());
|
||||
par.insert("flag_focused".into(), yesno(client.is_focused).into());
|
||||
par.insert("flag_io_active".into(), yesno(client.is_io_active).into());
|
||||
par.insert("flag_overlay".into(), yesno(client.is_overlay).into());
|
||||
par.insert("flag_primary".into(), yesno(client.is_primary).into());
|
||||
par.insert("flag_visible".into(), yesno(client.is_visible).into());
|
||||
|
||||
let state_cell = self.state.parse_template(
|
||||
&doc_params(&self.globals),
|
||||
"Cell",
|
||||
&mut frontend.layout,
|
||||
self.id_list_parent,
|
||||
par,
|
||||
)?;
|
||||
|
||||
let checkbox = state_cell.fetch_component_as::<ComponentCheckbox>("checkbox")?;
|
||||
checkbox.on_toggle({
|
||||
let tasks = self.tasks.clone();
|
||||
let client_name = client.name.clone();
|
||||
Box::new(move |_common, e| {
|
||||
if e.checked {
|
||||
tasks.push(Task::FocusClient(client_name.clone()));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
self.cells.push(state_cell);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
||||
log::debug!("refreshing monado client list");
|
||||
|
||||
let clients = frontend.interface.monado_client_list(data)?;
|
||||
|
||||
frontend.layout.remove_children(self.id_list_parent);
|
||||
self.cells.clear();
|
||||
|
||||
for client in clients {
|
||||
self.mount_client(frontend, &client)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn focus_client(&mut self, frontend: &mut Frontend<T>, data: &mut T, name: String) -> anyhow::Result<()> {
|
||||
frontend.interface.monado_client_focus(data, &name)?;
|
||||
self.tasks.push(Task::Refresh);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ pub fn stop(app_id: AppID, force_kill: bool) -> anyhow::Result<()> {
|
||||
|
||||
log::info!("Killing process with PID {} and its children", game.pid);
|
||||
let _ = std::process::Command::new("pkill")
|
||||
.arg(if force_kill { "-9" } else { "-11" })
|
||||
.arg(if force_kill { "-9" } else { "-15" })
|
||||
.arg("-P")
|
||||
.arg(format!("{}", game.pid))
|
||||
.spawn()?;
|
||||
|
||||
@@ -5,6 +5,7 @@ Glossary:
|
||||
- wlx-overlay-s: The name of this software (also called WlxOverlay-S)
|
||||
- WayVR: A Wayland compositor intended to be used in VR
|
||||
- WayVR Dashboard: An application (and game) launcher which is displayed in front of the user
|
||||
- Monado: A VR compositor
|
||||
- OpenVR: API made by Valve
|
||||
- OpenXR: API made by Khronos
|
||||
- OSC: OpenSoundControl
|
||||
|
||||
@@ -10,9 +10,16 @@ mod widget_rectangle;
|
||||
mod widget_sprite;
|
||||
|
||||
use crate::{
|
||||
assets::{normalize_path, AssetPath, AssetPathOwned}, components::{Component, ComponentWeak}, drawing::{self}, globals::WguiGlobals, i18n::Translation, layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair}, log::LogErr, parser::{
|
||||
assets::{AssetPath, AssetPathOwned, normalize_path},
|
||||
components::{Component, ComponentWeak},
|
||||
drawing::{self},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
|
||||
log::LogErr,
|
||||
parser::{
|
||||
component_button::parse_component_button,
|
||||
component_checkbox::{parse_component_checkbox, CheckboxKind},
|
||||
component_checkbox::{CheckboxKind, parse_component_checkbox},
|
||||
component_radio_group::parse_component_radio_group,
|
||||
component_slider::parse_component_slider,
|
||||
widget_div::parse_widget_div,
|
||||
@@ -20,7 +27,9 @@ use crate::{
|
||||
widget_label::parse_widget_label,
|
||||
widget_rectangle::parse_widget_rectangle,
|
||||
widget_sprite::parse_widget_sprite,
|
||||
}, widget::ConstructEssentials, windowing::context_menu
|
||||
},
|
||||
widget::ConstructEssentials,
|
||||
windowing::context_menu,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use ouroboros::self_referencing;
|
||||
@@ -215,7 +224,10 @@ impl ParserState {
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
) -> anyhow::Result<ParserData> {
|
||||
let Some(template) = self.data.templates.get(template_name) else {
|
||||
anyhow::bail!("{:?}: no template named \"{template_name}\" found", self.path.get_path_buf());
|
||||
anyhow::bail!(
|
||||
"{:?}: no template named \"{template_name}\" found",
|
||||
self.path.get_path_buf().display()
|
||||
);
|
||||
};
|
||||
|
||||
let mut ctx = ParserContext {
|
||||
@@ -291,7 +303,7 @@ impl ParserState {
|
||||
if !other.starts_with('_') {
|
||||
anyhow::bail!("unexpected \"{other}\" attribute");
|
||||
}
|
||||
attribs.push(AttribPair::new(key, replace_vars(value, &template_params)));
|
||||
attribs.push(AttribPair::new(key, replace_vars(value, template_params)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,14 +317,12 @@ impl ParserState {
|
||||
});
|
||||
}
|
||||
other => {
|
||||
anyhow::bail!("{:?}: unexpected <{other}> tag", self.path.get_path_buf());
|
||||
anyhow::bail!("{:?}: unexpected <{other}> tag", self.path.get_path_buf().display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(
|
||||
cells,
|
||||
)
|
||||
Ok(cells)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,65 +480,71 @@ impl ParserContext<'_> {
|
||||
insert_color_vars!(self, "faded", def.faded_color, def.translucent_alpha);
|
||||
insert_color_vars!(self, "bg", def.bg_color, def.translucent_alpha);
|
||||
}
|
||||
fn print_invalid_attrib(&self, tag_name: &str, key: &str, value: &str) {
|
||||
log::warn!("{}: <{tag_name}> value for \"{key}\" is invalid: \"{value}\"", self.doc_params.path.get_str());
|
||||
}
|
||||
|
||||
fn print_missing_attrib(&self, tag_name: &str, attr: &str) {
|
||||
log::warn!("{}: <{tag_name}> is missing \"{attr}\".", self.doc_params.path.get_str());
|
||||
}
|
||||
|
||||
fn parse_val(&self, tag_name: &str, key: &str, value: &str) -> Option<f32> {
|
||||
let Ok(val) = value.parse::<f32>() else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
return None;
|
||||
};
|
||||
Some(val)
|
||||
}
|
||||
|
||||
fn parse_percent(&self, tag_name: &str, key: &str, value: &str) -> Option<f32> {
|
||||
let Some(val_str) = value.split('%').next() else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(val) = val_str.parse::<f32>() else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
return None;
|
||||
};
|
||||
Some(val / 100.0)
|
||||
}
|
||||
|
||||
fn parse_size_unit<T>(&self, tag_name: &str, key: &str, value: &str) -> Option<T>
|
||||
where
|
||||
T: taffy::prelude::FromPercent + taffy::prelude::FromLength,
|
||||
{
|
||||
if is_percent(value) {
|
||||
Some(taffy::prelude::percent(self.parse_percent(tag_name, key, value)?))
|
||||
} else {
|
||||
Some(taffy::prelude::length(parse_f32(value)?))
|
||||
fn print_invalid_attrib(&self, tag_name: &str, key: &str, value: &str) {
|
||||
log::warn!(
|
||||
"{}: <{tag_name}> value for \"{key}\" is invalid: \"{value}\"",
|
||||
self.doc_params.path.get_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_check_i32(&self, tag_name: &str, key: &str, value: &str, num: &mut i32) -> bool {
|
||||
if let Some(value) = parse_i32(value) {
|
||||
*num = value;
|
||||
true
|
||||
} else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
false
|
||||
fn print_missing_attrib(&self, tag_name: &str, attr: &str) {
|
||||
log::warn!(
|
||||
"{}: <{tag_name}> is missing \"{attr}\".",
|
||||
self.doc_params.path.get_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_check_f32(&self, tag_name: &str, key: &str, value: &str, num: &mut f32) -> bool {
|
||||
if let Some(value) = parse_f32(value) {
|
||||
*num = value;
|
||||
true
|
||||
} else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
false
|
||||
fn parse_val(&self, tag_name: &str, key: &str, value: &str) -> Option<f32> {
|
||||
let Ok(val) = value.parse::<f32>() else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
return None;
|
||||
};
|
||||
Some(val)
|
||||
}
|
||||
|
||||
fn parse_percent(&self, tag_name: &str, key: &str, value: &str) -> Option<f32> {
|
||||
let Some(val_str) = value.split('%').next() else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(val) = val_str.parse::<f32>() else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
return None;
|
||||
};
|
||||
Some(val / 100.0)
|
||||
}
|
||||
|
||||
fn parse_size_unit<T>(&self, tag_name: &str, key: &str, value: &str) -> Option<T>
|
||||
where
|
||||
T: taffy::prelude::FromPercent + taffy::prelude::FromLength,
|
||||
{
|
||||
if is_percent(value) {
|
||||
Some(taffy::prelude::percent(self.parse_percent(tag_name, key, value)?))
|
||||
} else {
|
||||
Some(taffy::prelude::length(parse_f32(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_check_i32(&self, tag_name: &str, key: &str, value: &str, num: &mut i32) -> bool {
|
||||
if let Some(value) = parse_i32(value) {
|
||||
*num = value;
|
||||
true
|
||||
} else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_check_f32(&self, tag_name: &str, key: &str, value: &str, num: &mut f32) -> bool {
|
||||
if let Some(value) = parse_f32(value) {
|
||||
*num = value;
|
||||
true
|
||||
} else {
|
||||
self.print_invalid_attrib(tag_name, key, value);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_i32(value: &str) -> Option<i32> {
|
||||
@@ -583,7 +599,6 @@ fn require_tag_by_name<'a>(node: &roxmltree::Node<'a, 'a>, name: &str) -> anyhow
|
||||
get_tag_by_name(node, name).ok_or_else(|| anyhow::anyhow!("Tag \"{name}\" not found"))
|
||||
}
|
||||
|
||||
|
||||
fn parse_widget_other_internal(
|
||||
template: &Rc<Template>,
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
@@ -617,7 +632,10 @@ fn parse_widget_other(
|
||||
attribs: &[AttribPair],
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(template) = ctx.get_template(xml_tag_name) else {
|
||||
log::error!("{}: Undefined tag named \"{xml_tag_name}\"", ctx.doc_params.path.get_str());
|
||||
log::error!(
|
||||
"{}: Undefined tag named \"{xml_tag_name}\"",
|
||||
ctx.doc_params.path.get_str()
|
||||
);
|
||||
return Ok(()); // not critical
|
||||
};
|
||||
|
||||
@@ -746,6 +764,7 @@ pub fn replace_vars(input: &str, vars: &HashMap<Rc<str>, Rc<str>>) -> Rc<str> {
|
||||
}
|
||||
|
||||
#[allow(clippy::manual_strip)]
|
||||
#[allow(clippy::single_match_else)]
|
||||
fn process_attrib(
|
||||
template_parameters: &HashMap<Rc<str>, Rc<str>>,
|
||||
ctx: &ParserContext,
|
||||
@@ -760,7 +779,7 @@ fn process_attrib(
|
||||
None => {
|
||||
log::warn!("{}: undefined variable \"{value}\"", ctx.doc_params.path.get_str());
|
||||
AttribPair::new(key, "undefined")
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AttribPair::new(key, replace_vars(value, template_parameters))
|
||||
@@ -797,7 +816,10 @@ fn process_attribs<'a>(
|
||||
res.push(process_attrib(&file.template_parameters, ctx, macro_key, macro_value));
|
||||
}
|
||||
} else {
|
||||
log::warn!("{}: requested macro named \"{value}\" not found!", ctx.doc_params.path.get_str());
|
||||
log::warn!(
|
||||
"{}: requested macro named \"{value}\" not found!",
|
||||
ctx.doc_params.path.get_str()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
res.push(process_attrib(&file.template_parameters, ctx, key, value));
|
||||
@@ -816,7 +838,10 @@ fn parse_tag_theme<'a>(ctx: &mut ParserContext, node: roxmltree::Node<'a, 'a>) {
|
||||
}
|
||||
"" => { /* ignore */ }
|
||||
_ => {
|
||||
log::warn!("{}: <{child_name}> is not a valid child to <theme>.", ctx.doc_params.path.get_str());
|
||||
log::warn!(
|
||||
"{}: <{child_name}> is not a valid child to <theme>.",
|
||||
ctx.doc_params.path.get_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -865,7 +890,11 @@ fn parse_tag_macro(file: &ParserFile, ctx: &mut ParserContext, node: roxmltree::
|
||||
}
|
||||
_ => {
|
||||
if macro_attribs.insert(pair.attrib.clone(), pair.value).is_some() {
|
||||
log::warn!("{}: macro attrib \"{}\" already defined!", ctx.doc_params.path.get_str(), pair.attrib);
|
||||
log::warn!(
|
||||
"{}: macro attrib \"{}\" already defined!",
|
||||
ctx.doc_params.path.get_str(),
|
||||
pair.attrib
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -963,19 +992,29 @@ fn parse_child<'a>(
|
||||
new_widget_id = Some(parse_widget_div(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
}
|
||||
"rectangle" => {
|
||||
new_widget_id = Some(parse_widget_rectangle(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
new_widget_id = Some(parse_widget_rectangle(
|
||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||
)?);
|
||||
}
|
||||
"label" => {
|
||||
new_widget_id = Some(parse_widget_label(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
new_widget_id = Some(parse_widget_label(
|
||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||
)?);
|
||||
}
|
||||
"sprite" => {
|
||||
new_widget_id = Some(parse_widget_sprite(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
new_widget_id = Some(parse_widget_sprite(
|
||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||
)?);
|
||||
}
|
||||
"image" => {
|
||||
new_widget_id = Some(parse_widget_image(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
new_widget_id = Some(parse_widget_image(
|
||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||
)?);
|
||||
}
|
||||
"Button" => {
|
||||
new_widget_id = Some(parse_component_button(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
new_widget_id = Some(parse_component_button(
|
||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||
)?);
|
||||
}
|
||||
"Slider" => {
|
||||
new_widget_id = Some(parse_component_slider(ctx, parent_id, &attribs, tag_name)?);
|
||||
@@ -999,7 +1038,9 @@ fn parse_child<'a>(
|
||||
)?);
|
||||
}
|
||||
"RadioGroup" => {
|
||||
new_widget_id = Some(parse_component_radio_group(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
new_widget_id = Some(parse_component_radio_group(
|
||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||
)?);
|
||||
}
|
||||
"" => { /* ignore */ }
|
||||
other_tag_name => {
|
||||
|
||||
@@ -5,6 +5,17 @@ use wayvr_ipc::{
|
||||
|
||||
use crate::{config::GeneralConfig, desktop_finder::DesktopFinder};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonadoClient {
|
||||
pub name: String,
|
||||
pub is_primary: bool,
|
||||
pub is_active: bool,
|
||||
pub is_visible: bool,
|
||||
pub is_focused: bool,
|
||||
pub is_overlay: bool,
|
||||
pub is_io_active: bool,
|
||||
}
|
||||
|
||||
pub trait DashInterface<T> {
|
||||
fn window_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrWindow>>;
|
||||
fn window_set_visible(&mut self, data: &mut T, handle: WvrWindowHandle, visible: bool) -> anyhow::Result<()>;
|
||||
@@ -18,6 +29,8 @@ pub trait DashInterface<T> {
|
||||
) -> anyhow::Result<WvrProcessHandle>;
|
||||
fn process_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrProcess>>;
|
||||
fn process_terminate(&mut self, data: &mut T, handle: WvrProcessHandle) -> anyhow::Result<()>;
|
||||
fn monado_client_list(&mut self, data: &mut T) -> anyhow::Result<Vec<MonadoClient>>;
|
||||
fn monado_client_focus(&mut self, data: &mut T, name: &str) -> anyhow::Result<()>;
|
||||
fn recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()>;
|
||||
fn desktop_finder<'a>(&'a mut self, data: &'a mut T) -> &'a mut DesktopFinder;
|
||||
fn general_config<'a>(&'a mut self, data: &'a mut T) -> &'a mut GeneralConfig;
|
||||
|
||||
@@ -3,7 +3,12 @@ use wayvr_ipc::{
|
||||
packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle},
|
||||
};
|
||||
|
||||
use crate::{config::GeneralConfig, dash_interface::DashInterface, desktop_finder::DesktopFinder, gen_id};
|
||||
use crate::{
|
||||
config::GeneralConfig,
|
||||
dash_interface::{self, DashInterface},
|
||||
desktop_finder::DesktopFinder,
|
||||
gen_id,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EmuProcess {
|
||||
@@ -56,6 +61,7 @@ pub struct DashInterfaceEmulated {
|
||||
windows: EmuWindowVec,
|
||||
desktop_finder: DesktopFinder,
|
||||
general_config: GeneralConfig,
|
||||
monado_clients: Vec<dash_interface::MonadoClient>,
|
||||
}
|
||||
|
||||
impl DashInterfaceEmulated {
|
||||
@@ -77,11 +83,42 @@ impl DashInterfaceEmulated {
|
||||
// Use serde defaults
|
||||
let general_config = serde_json::from_str("{}").unwrap();
|
||||
|
||||
let monado_clients = vec![
|
||||
dash_interface::MonadoClient {
|
||||
name: String::from("The Best VR Game 3000"),
|
||||
is_active: true,
|
||||
is_focused: true,
|
||||
is_io_active: true,
|
||||
is_overlay: false,
|
||||
is_primary: true,
|
||||
is_visible: true,
|
||||
},
|
||||
dash_interface::MonadoClient {
|
||||
name: String::from("Second app"),
|
||||
is_active: true,
|
||||
is_focused: false,
|
||||
is_io_active: true,
|
||||
is_overlay: false,
|
||||
is_primary: false,
|
||||
is_visible: true,
|
||||
},
|
||||
dash_interface::MonadoClient {
|
||||
name: String::from("Third app"),
|
||||
is_active: true,
|
||||
is_focused: false,
|
||||
is_io_active: true,
|
||||
is_overlay: false,
|
||||
is_primary: false,
|
||||
is_visible: true,
|
||||
},
|
||||
];
|
||||
|
||||
Self {
|
||||
processes,
|
||||
windows,
|
||||
desktop_finder,
|
||||
general_config,
|
||||
monado_clients,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,4 +224,23 @@ impl DashInterface<()> for DashInterfaceEmulated {
|
||||
fn config_changed(&mut self, _: &mut ()) {}
|
||||
|
||||
fn restart(&mut self, _data: &mut ()) {}
|
||||
|
||||
fn monado_client_list(&mut self, _data: &mut ()) -> anyhow::Result<Vec<dash_interface::MonadoClient>> {
|
||||
Ok(self.monado_clients.clone())
|
||||
}
|
||||
|
||||
fn monado_client_focus(&mut self, _data: &mut (), name: &str) -> anyhow::Result<()> {
|
||||
for client in self.monado_clients.iter_mut() {
|
||||
client.is_focused = false;
|
||||
client.is_active = false;
|
||||
client.is_primary = false;
|
||||
}
|
||||
|
||||
if let Some(client) = self.monado_clients.iter_mut().find(|m| m.name == name) {
|
||||
client.is_active = true;
|
||||
client.is_focused = true;
|
||||
client.is_primary = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,17 @@
|
||||
"ANCHOR": {
|
||||
"CENTER": "Zentrum"
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Neuen Spiegel-Overlay hinzufügen"
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Neuen Spiegel-Overlay hinzufügen",
|
||||
"EDIT_MODE_TOGGLE": "Bearbeitungsmodus umschalten",
|
||||
"ADD_NEW_SET": "Neues Set hinzufügen",
|
||||
"DELETE_CURRENT_SET": "Aktuelles Set löschen",
|
||||
"TOGGLE_VISIBILITY": "Sichtbarkeit umschalten",
|
||||
"RESET_POSITION": "Position zurücksetzen",
|
||||
"RELOAD_FROM_DISK": "XML-Datei von der Festplatte neu laden",
|
||||
"CLOSE_MIRROR": "Spiegel schließen",
|
||||
"CLOSE_APP": "App schließen",
|
||||
"FORCE_CLOSE_APP": "App zwangsweise schließen"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "Spielbereich neu zentrieren",
|
||||
@@ -14,7 +23,8 @@
|
||||
"ADD_NEW_SET": "Neuen Satz hinzufügen",
|
||||
"SWITCH_TO_SET": "Zum Satz wechseln",
|
||||
"TOGGLE_FOR_CURRENT_SET": "Sichtbarkeit im aktuellen Satz umschalten",
|
||||
"LONG_PRESS_TO_DELETE_SET": "Lange drücken, um Satz zu löschen"
|
||||
"LONG_PRESS_TO_DELETE_SET": "Lange drücken, um Satz zu löschen",
|
||||
"CLEANUP_MIRRORS": "Spiegel entfernen, die\nderzeit nicht sichtbar sind"
|
||||
},
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "Krümmung anpassen",
|
||||
@@ -83,6 +93,8 @@
|
||||
"EMPTY_SET": "Leeres Set!",
|
||||
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
|
||||
"FIXING_FLOOR": "Boden wird in 5 Sekunden fixiert...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!"
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!",
|
||||
"CANNOT_ADD_SET": "Satz kann nicht hinzugefügt werden!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht."
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,16 @@
|
||||
"CENTER": "Centro"
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Agregar una nueva superposición de espejo"
|
||||
"ADD_MIRROR": "Agregar una nueva superposición de espejo",
|
||||
"EDIT_MODE_TOGGLE": "Activar/desactivar el modo de edición",
|
||||
"ADD_NEW_SET": "Añadir nuevo set",
|
||||
"DELETE_CURRENT_SET": "Eliminar set actual",
|
||||
"TOGGLE_VISIBILITY": "Alternar visibilidad",
|
||||
"RESET_POSITION": "Restablecer posición",
|
||||
"RELOAD_FROM_DISK": "Volver a cargar XML desde el disco",
|
||||
"CLOSE_MIRROR": "Cerrar espejo",
|
||||
"CLOSE_APP": "Cerrar aplicación",
|
||||
"FORCE_CLOSE_APP": "Forzar cierre de la aplicación"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "Recentrar el área de juego",
|
||||
@@ -14,7 +23,8 @@
|
||||
"ADD_NEW_SET": "Añadir un nuevo conjunto",
|
||||
"SWITCH_TO_SET": "Cambiar al conjunto",
|
||||
"TOGGLE_FOR_CURRENT_SET": "Alternar visibilidad en el conjunto actual",
|
||||
"LONG_PRESS_TO_DELETE_SET": "Mantén presionado para eliminar el conjunto"
|
||||
"LONG_PRESS_TO_DELETE_SET": "Mantén presionado para eliminar el conjunto",
|
||||
"CLEANUP_MIRRORS": "Eliminar los espejos que\nno son actualmente visibles"
|
||||
},
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "Ajustar curvatura",
|
||||
@@ -83,6 +93,8 @@
|
||||
"EMPTY_SET": "¡Conjunto vacío!",
|
||||
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
|
||||
"FIXING_FLOOR": "Fijando el suelo en 5 segundos...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!"
|
||||
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!",
|
||||
"CANNOT_ADD_SET": "¡No se puede agregar el conjunto!",
|
||||
"MAXIMUM_SETS_REACHED": "Se ha alcanzado el número máximo de sets."
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,17 @@
|
||||
"ANCHOR": {
|
||||
"CENTER": "センター"
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "新しいミラーを追加"
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "新しいミラーを追加",
|
||||
"EDIT_MODE_TOGGLE": "編集モードの切り替え",
|
||||
"ADD_NEW_SET": "新しいセットを追加",
|
||||
"DELETE_CURRENT_SET": "現在のセットを削除",
|
||||
"TOGGLE_VISIBILITY": "表示/非表示の切り替え",
|
||||
"RESET_POSITION": "位置をリセット",
|
||||
"RELOAD_FROM_DISK": "ディスクからXMLを再読み込み",
|
||||
"CLOSE_MIRROR": "ミラーを閉じる",
|
||||
"CLOSE_APP": "アプリを閉じる",
|
||||
"FORCE_CLOSE_APP": "アプリを強制終了"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "プレイスペースをリセンター",
|
||||
@@ -14,7 +23,8 @@
|
||||
"ADD_NEW_SET": "新しいセットを追加",
|
||||
"SWITCH_TO_SET": "セットに切り替える",
|
||||
"TOGGLE_FOR_CURRENT_SET": "現在のセットで表示を切り替え",
|
||||
"LONG_PRESS_TO_DELETE_SET": "長押しでセットを削除"
|
||||
"LONG_PRESS_TO_DELETE_SET": "長押しでセットを削除",
|
||||
"CLEANUP_MIRRORS": "現在表示されていないミラーを削除"
|
||||
},
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "曲率の調整",
|
||||
@@ -81,6 +91,8 @@
|
||||
"EMPTY_SET": "空のセットです!",
|
||||
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
|
||||
"FIXING_FLOOR": "5秒後にフロアを固定します...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!"
|
||||
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!",
|
||||
"CANNOT_ADD_SET": "セットを追加できません!",
|
||||
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。"
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,17 @@
|
||||
"ANCHOR": {
|
||||
"CENTER": "Centrum"
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Dodaj nowy widok lustrzany"
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Dodaj nowy widok lustrzany",
|
||||
"EDIT_MODE_TOGGLE": "Przełącz tryb edycji",
|
||||
"ADD_NEW_SET": "Dodaj nowy zestaw",
|
||||
"DELETE_CURRENT_SET": "Usuń aktualny zestaw",
|
||||
"TOGGLE_VISIBILITY": "Przełącz widoczność",
|
||||
"RESET_POSITION": "Zresetuj pozycję",
|
||||
"RELOAD_FROM_DISK": "Przeładuj XML z dysku",
|
||||
"CLOSE_MIRROR": "Zamknij lustro",
|
||||
"CLOSE_APP": "Zamknij aplikację",
|
||||
"FORCE_CLOSE_APP": "Wymuś zamknięcie aplikacji"
|
||||
},
|
||||
"WATCH": {
|
||||
"RECENTER": "Wyśrodkuj przestrzeń gry",
|
||||
@@ -14,7 +23,8 @@
|
||||
"ADD_NEW_SET": "Dodaj nowy zestaw",
|
||||
"SWITCH_TO_SET": "Przełącz na zestaw",
|
||||
"TOGGLE_FOR_CURRENT_SET": "Przełącz widoczność w bieżącym zestawie",
|
||||
"LONG_PRESS_TO_DELETE_SET": "Przytrzymaj, aby usunąć zestaw"
|
||||
"LONG_PRESS_TO_DELETE_SET": "Przytrzymaj, aby usunąć zestaw",
|
||||
"CLEANUP_MIRRORS": "Usuń lustra, które\nnie są obecnie widoczne"
|
||||
},
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "Dostosuj zakrzywienie",
|
||||
@@ -81,6 +91,8 @@
|
||||
"EMPTY_SET": "Pusty zestaw!",
|
||||
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
|
||||
"FIXING_FLOOR": "Naprawianie podłogi za 5 sekund...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!"
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!",
|
||||
"CANNOT_ADD_SET": "Nie można dodać zestawu!",
|
||||
"MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów."
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,18 @@ impl InputBlocker {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, state: &AppState, watch_id: OverlayID, monado: &mut Monado) {
|
||||
if !state.session.config.block_game_input {
|
||||
pub fn update(&mut self, app: &mut AppState, watch_id: OverlayID) {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return; // monado not available
|
||||
};
|
||||
|
||||
if !app.session.config.block_game_input {
|
||||
return;
|
||||
}
|
||||
|
||||
let any_hovered = state.input_state.pointers.iter().any(|p| {
|
||||
let any_hovered = app.input_state.pointers.iter().any(|p| {
|
||||
p.interaction.hovered_id.is_some_and(|id| {
|
||||
id != watch_id || !state.session.config.block_game_input_ignore_watch
|
||||
id != watch_id || !app.session.config.block_game_input_ignore_watch
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||
use wlx_common::config_io;
|
||||
|
||||
use crate::{
|
||||
backend::input::{Haptics, Pointer, TrackedDevice, TrackedDeviceRole},
|
||||
backend::input::{Haptics, InputState, Pointer, TrackedDevice, TrackedDeviceRole},
|
||||
state::{AppSession, AppState},
|
||||
};
|
||||
|
||||
@@ -227,12 +227,12 @@ impl OpenXrInputSource {
|
||||
fn update_device_battery_status(
|
||||
device: &mut mnd::Device,
|
||||
role: TrackedDeviceRole,
|
||||
app: &mut AppState,
|
||||
input_state: &mut InputState,
|
||||
) {
|
||||
if let Ok(status) = device.battery_status()
|
||||
&& status.present
|
||||
{
|
||||
app.input_state.devices.push(TrackedDevice {
|
||||
input_state.devices.push(TrackedDevice {
|
||||
soc: Some(status.charge),
|
||||
charging: status.charging,
|
||||
role,
|
||||
@@ -247,7 +247,11 @@ impl OpenXrInputSource {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_devices(app: &mut AppState, monado: &mut mnd::Monado) -> bool {
|
||||
pub fn update_devices(app: &mut AppState) -> bool {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return false; // monado not available
|
||||
};
|
||||
|
||||
let old_len = app.input_state.devices.len();
|
||||
app.input_state.devices.clear();
|
||||
|
||||
@@ -267,13 +271,14 @@ impl OpenXrInputSource {
|
||||
),
|
||||
];
|
||||
let mut seen = Vec::<u32>::with_capacity(32);
|
||||
|
||||
for (mnd_role, wlx_role) in roles {
|
||||
let device = monado.device_from_role(mnd_role);
|
||||
if let Ok(mut device) = device
|
||||
&& !seen.contains(&device.index)
|
||||
{
|
||||
seen.push(device.index);
|
||||
Self::update_device_battery_status(&mut device, wlx_role, app);
|
||||
Self::update_device_battery_status(&mut device, wlx_role, &mut app.input_state);
|
||||
}
|
||||
}
|
||||
if let Ok(devices) = monado.devices() {
|
||||
@@ -284,7 +289,7 @@ impl OpenXrInputSource {
|
||||
} else {
|
||||
TrackedDeviceRole::None
|
||||
};
|
||||
Self::update_device_battery_status(&mut device, role, app);
|
||||
Self::update_device_battery_status(&mut device, role, &mut app.input_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::{
|
||||
|
||||
use glam::{Affine3A, Vec3};
|
||||
use input::OpenXrInputSource;
|
||||
use libmonado::Monado;
|
||||
use openxr as xr;
|
||||
use skybox::create_skybox;
|
||||
use vulkano::{Handle, VulkanObject};
|
||||
@@ -98,17 +97,15 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
|
||||
let mut delete_queue = vec![];
|
||||
|
||||
let mut monado = Monado::auto_connect()
|
||||
.map_err(|e| log::warn!("Will not use libmonado: {e}"))
|
||||
.ok();
|
||||
app.monado_init();
|
||||
|
||||
let mut playspace = monado.as_mut().and_then(|m| {
|
||||
let mut playspace = app.monado.as_mut().and_then(|m| {
|
||||
playspace::PlayspaceMover::new(m)
|
||||
.map_err(|e| log::warn!("Will not use Monado playspace mover: {e}"))
|
||||
.ok()
|
||||
});
|
||||
|
||||
let mut blocker = monado.is_some().then(blocker::InputBlocker::new);
|
||||
let mut blocker = app.monado.is_some().then(blocker::InputBlocker::new);
|
||||
|
||||
let (session, mut frame_wait, mut frame_stream) = unsafe {
|
||||
let raw_session = helpers::create_overlay_session(
|
||||
@@ -223,10 +220,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
}
|
||||
}
|
||||
|
||||
if next_device_update <= Instant::now()
|
||||
&& let Some(monado) = &mut monado
|
||||
{
|
||||
let changed = OpenXrInputSource::update_devices(&mut app, monado);
|
||||
if app.monado.is_some() && next_device_update <= Instant::now() {
|
||||
let changed = OpenXrInputSource::update_devices(&mut app);
|
||||
if changed {
|
||||
overlays.devices_changed(&mut app)?;
|
||||
}
|
||||
@@ -278,11 +273,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
app.input_state.post_update(&app.session);
|
||||
|
||||
if let Some(ref mut blocker) = blocker {
|
||||
blocker.update(
|
||||
&app,
|
||||
watch_id,
|
||||
monado.as_mut().unwrap(), // safe
|
||||
);
|
||||
blocker.update(&mut app, watch_id);
|
||||
}
|
||||
|
||||
if app
|
||||
@@ -307,11 +298,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
|
||||
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
|
||||
if let Some(ref mut space_mover) = playspace {
|
||||
space_mover.update(
|
||||
&mut overlays,
|
||||
&app,
|
||||
monado.as_mut().unwrap(), // safe
|
||||
);
|
||||
space_mover.update(&mut overlays, &mut app);
|
||||
}
|
||||
|
||||
for o in overlays.values_mut() {
|
||||
@@ -489,8 +476,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
overlays.handle_task(&mut app, task)?;
|
||||
}
|
||||
TaskType::Playspace(task) => {
|
||||
if let (Some(playspace), Some(monado)) = (playspace.as_mut(), monado.as_mut()) {
|
||||
playspace.handle_task(&app, monado, task);
|
||||
if let Some(playspace) = playspace.as_mut() {
|
||||
playspace.handle_task(&mut app, task);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "openvr")]
|
||||
|
||||
@@ -43,7 +43,11 @@ impl PlayspaceMover {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_task(&mut self, app: &AppState, monado: &mut Monado, task: PlayspaceTask) {
|
||||
pub fn handle_task(&mut self, app: &mut AppState, task: PlayspaceTask) {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return; // monado not available
|
||||
};
|
||||
|
||||
match task {
|
||||
PlayspaceTask::FixFloor => {
|
||||
self.fix_floor(&app.input_state, monado);
|
||||
@@ -60,9 +64,12 @@ impl PlayspaceMover {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
|
||||
app: &AppState,
|
||||
monado: &mut Monado,
|
||||
app: &mut AppState,
|
||||
) {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return; // monado not available
|
||||
};
|
||||
|
||||
for pointer in &app.input_state.pointers {
|
||||
if pointer.now.space_reset {
|
||||
if !pointer.before.space_reset {
|
||||
|
||||
@@ -16,7 +16,7 @@ use wgui::{
|
||||
widget::EventResult,
|
||||
};
|
||||
use wlx_common::{
|
||||
dash_interface::DashInterface,
|
||||
dash_interface::{self, DashInterface},
|
||||
overlays::{BackendAttrib, BackendAttribValue},
|
||||
};
|
||||
use wlx_common::{
|
||||
@@ -444,4 +444,92 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
||||
RUNNING.store(false, Ordering::Relaxed);
|
||||
RESTART.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn monado_client_list(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
) -> anyhow::Result<Vec<dash_interface::MonadoClient>> {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return Ok(Vec::new()); // no monado available
|
||||
};
|
||||
|
||||
let clients = monado_list_clients_filtered(monado)?;
|
||||
|
||||
let mut res = Vec::<dash_interface::MonadoClient>::new();
|
||||
|
||||
for mut client in clients {
|
||||
let name = client.name()?;
|
||||
let state = client.state()?;
|
||||
|
||||
res.push(dash_interface::MonadoClient {
|
||||
name,
|
||||
is_primary: state.contains(libmonado::ClientState::ClientPrimaryApp),
|
||||
is_active: state.contains(libmonado::ClientState::ClientSessionActive),
|
||||
is_visible: state.contains(libmonado::ClientState::ClientSessionVisible),
|
||||
is_focused: state.contains(libmonado::ClientState::ClientSessionFocused),
|
||||
is_overlay: state.contains(libmonado::ClientState::ClientSessionOverlay),
|
||||
is_io_active: state.contains(libmonado::ClientState::ClientIoActive),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn monado_client_focus(&mut self, app: &mut AppState, name: &str) -> anyhow::Result<()> {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return Ok(()); // no monado avoilable
|
||||
};
|
||||
|
||||
monado_client_focus(monado, name)?;
|
||||
|
||||
// Restart monado (BUG!)
|
||||
// https://gitlab.freedesktop.org/monado/monado/-/issues/497
|
||||
app.monado_init();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const CLIENT_NAME_BLACKLIST: [&str; 2] = ["wlx-overlay-s", "libmonado"];
|
||||
|
||||
fn monado_list_clients_filtered(
|
||||
monado: &mut libmonado::Monado,
|
||||
) -> anyhow::Result<Vec<libmonado::Client<'_>>> {
|
||||
let mut clients: Vec<_> = monado.clients()?.into_iter().collect();
|
||||
|
||||
let clients: Vec<_> = clients
|
||||
.iter_mut()
|
||||
.filter_map(|client| {
|
||||
let Ok(name) = client.name() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
for cell in CLIENT_NAME_BLACKLIST {
|
||||
if cell == name {
|
||||
// blacklisted!
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(client.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(clients)
|
||||
}
|
||||
|
||||
fn monado_client_focus(monado: &mut libmonado::Monado, name: &str) -> anyhow::Result<()> {
|
||||
let clients = monado_list_clients_filtered(monado)?;
|
||||
|
||||
for mut client in clients {
|
||||
let client_name = client.name()?;
|
||||
if client_name != name {
|
||||
continue;
|
||||
}
|
||||
|
||||
log::info!("Monado focus set to {client_name}");
|
||||
client.set_primary()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ pub struct AppState {
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub wvr_server: Option<WvrServerState>,
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
pub monado: Option<libmonado::Monado>,
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
@@ -163,8 +166,20 @@ impl AppState {
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
wvr_server,
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
monado: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
pub fn monado_init(&mut self) {
|
||||
log::debug!("Connecting to Monado IPC");
|
||||
self.monado = None; // stop connection first
|
||||
self.monado = libmonado::Monado::auto_connect()
|
||||
.map_err(|e| log::warn!("Will not use libmonado: {e}"))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppSession {
|
||||
|
||||
Reference in New Issue
Block a user