Monado app switcher, lang update
This commit is contained in:
@@ -1,7 +1,35 @@
|
|||||||
<layout>
|
<layout>
|
||||||
<include src="t_tab_title.xml" />
|
<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>
|
<elements>
|
||||||
<TabTitle translation="MONADO_RUNTIME" icon="dashboard/monado.svg" />
|
<TabTitle translation="MONADO_RUNTIME" icon="dashboard/monado.svg" />
|
||||||
|
<div id="list_parent" flex_direction="column" gap="8">
|
||||||
|
<!-- filled at runtime -->
|
||||||
|
</div>
|
||||||
</elements>
|
</elements>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
<include src="../t_group_box.xml" />
|
<include src="../t_group_box.xml" />
|
||||||
|
|
||||||
<elements>
|
<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 macro="group_box" id="icon_parent" padding="16" color="#0033aa66" color2="#00000022" gradient="vertical" justify_content="center">
|
||||||
|
|
||||||
</rectangle>
|
</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" />
|
<label id="label_title" weight="bold" size="32" overflow="hidden" />
|
||||||
<Subtext label_id="label_exec" overflow="hidden" />
|
<Subtext label_id="label_exec" overflow="hidden" />
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"HOME_SCREEN": "Startbildschirm",
|
"HOME_SCREEN": "Startbildschirm",
|
||||||
"MONADO_RUNTIME": "„Monado”-Laufzeitumgebung",
|
"MONADO_RUNTIME": "Monado-Laufzeitumgebung",
|
||||||
"APPLICATIONS": "Anwendungen",
|
"APPLICATIONS": "Anwendungen",
|
||||||
"GAMES": "Spiele",
|
"GAMES": "Spiele",
|
||||||
"SETTINGS": "Einstellungen",
|
"SETTINGS": "Einstellungen",
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
"HOME_SCREEN": "Home",
|
"HOME_SCREEN": "Home",
|
||||||
"LIST_OF_PROCESSES": "Process list",
|
"LIST_OF_PROCESSES": "Process list",
|
||||||
"LIST_OF_WINDOWS": "Window list",
|
"LIST_OF_WINDOWS": "Window list",
|
||||||
"MONADO_RUNTIME": "„Monado” runtime",
|
"MONADO_RUNTIME": "Monado runtime",
|
||||||
"NO_WINDOWS_FOUND": "No windows found",
|
"NO_WINDOWS_FOUND": "No windows found",
|
||||||
"POPUP_ADD_DISPLAY": {
|
"POPUP_ADD_DISPLAY": {
|
||||||
"RESOLUTION": "Resolution"
|
"RESOLUTION": "Resolution"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"HOME_SCREEN": "Inicio",
|
"HOME_SCREEN": "Inicio",
|
||||||
"MONADO_RUNTIME": "„Monado” tiempo de ejecución",
|
"MONADO_RUNTIME": "Monado tiempo de ejecución",
|
||||||
"APPLICATIONS": "Aplicaciones",
|
"APPLICATIONS": "Aplicaciones",
|
||||||
"GAMES": "Juegos",
|
"GAMES": "Juegos",
|
||||||
"SETTINGS": "Ajustes",
|
"SETTINGS": "Ajustes",
|
||||||
|
|||||||
@@ -1,43 +1,163 @@
|
|||||||
use std::marker::PhantomData;
|
use std::{collections::HashMap, marker::PhantomData, rc::Rc};
|
||||||
|
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
|
components::checkbox::ComponentCheckbox,
|
||||||
|
globals::WguiGlobals,
|
||||||
layout::WidgetID,
|
layout::WidgetID,
|
||||||
parser::{ParseDocumentParams, ParserState},
|
parser::{self, Fetchable, ParseDocumentParams, ParserState},
|
||||||
|
task::Tasks,
|
||||||
};
|
};
|
||||||
|
use wlx_common::dash_interface;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frontend::Frontend,
|
frontend::Frontend,
|
||||||
tab::{Tab, TabType},
|
tab::{Tab, TabType},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Task {
|
||||||
|
Refresh,
|
||||||
|
FocusClient(String),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TabMonado<T> {
|
pub struct TabMonado<T> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub state: ParserState,
|
state: ParserState,
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
|
||||||
marker: PhantomData<T>,
|
marker: PhantomData<T>,
|
||||||
|
|
||||||
|
globals: WguiGlobals,
|
||||||
|
id_list_parent: WidgetID,
|
||||||
|
|
||||||
|
cells: Vec<parser::ParserData>,
|
||||||
|
|
||||||
|
ticks: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Tab<T> for TabMonado<T> {
|
impl<T> Tab<T> for TabMonado<T> {
|
||||||
fn get_type(&self) -> TabType {
|
fn get_type(&self) -> TabType {
|
||||||
TabType::Games
|
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> {
|
impl<T> TabMonado<T> {
|
||||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||||
let state = wgui::parser::parse_from_assets(
|
let globals = frontend.layout.state.globals.clone();
|
||||||
&ParseDocumentParams {
|
let state = wgui::parser::parse_from_assets(&doc_params(&globals), &mut frontend.layout, parent_id)?;
|
||||||
globals: frontend.layout.state.globals.clone(),
|
|
||||||
path: AssetPath::BuiltIn("gui/tab/monado.xml"),
|
let id_list_parent = state.get_widget_id("list_parent")?;
|
||||||
extra: Default::default(),
|
|
||||||
},
|
let tasks = Tasks::<Task>::new();
|
||||||
&mut frontend.layout,
|
|
||||||
parent_id,
|
tasks.push(Task::Refresh);
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
state,
|
state,
|
||||||
marker: PhantomData,
|
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);
|
log::info!("Killing process with PID {} and its children", game.pid);
|
||||||
let _ = std::process::Command::new("pkill")
|
let _ = std::process::Command::new("pkill")
|
||||||
.arg(if force_kill { "-9" } else { "-11" })
|
.arg(if force_kill { "-9" } else { "-15" })
|
||||||
.arg("-P")
|
.arg("-P")
|
||||||
.arg(format!("{}", game.pid))
|
.arg(format!("{}", game.pid))
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Glossary:
|
|||||||
- wlx-overlay-s: The name of this software (also called WlxOverlay-S)
|
- wlx-overlay-s: The name of this software (also called WlxOverlay-S)
|
||||||
- WayVR: A Wayland compositor intended to be used in VR
|
- 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
|
- WayVR Dashboard: An application (and game) launcher which is displayed in front of the user
|
||||||
|
- Monado: A VR compositor
|
||||||
- OpenVR: API made by Valve
|
- OpenVR: API made by Valve
|
||||||
- OpenXR: API made by Khronos
|
- OpenXR: API made by Khronos
|
||||||
- OSC: OpenSoundControl
|
- OSC: OpenSoundControl
|
||||||
|
|||||||
@@ -10,9 +10,16 @@ mod widget_rectangle;
|
|||||||
mod widget_sprite;
|
mod widget_sprite;
|
||||||
|
|
||||||
use crate::{
|
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_button::parse_component_button,
|
||||||
component_checkbox::{parse_component_checkbox, CheckboxKind},
|
component_checkbox::{CheckboxKind, parse_component_checkbox},
|
||||||
component_radio_group::parse_component_radio_group,
|
component_radio_group::parse_component_radio_group,
|
||||||
component_slider::parse_component_slider,
|
component_slider::parse_component_slider,
|
||||||
widget_div::parse_widget_div,
|
widget_div::parse_widget_div,
|
||||||
@@ -20,7 +27,9 @@ use crate::{
|
|||||||
widget_label::parse_widget_label,
|
widget_label::parse_widget_label,
|
||||||
widget_rectangle::parse_widget_rectangle,
|
widget_rectangle::parse_widget_rectangle,
|
||||||
widget_sprite::parse_widget_sprite,
|
widget_sprite::parse_widget_sprite,
|
||||||
}, widget::ConstructEssentials, windowing::context_menu
|
},
|
||||||
|
widget::ConstructEssentials,
|
||||||
|
windowing::context_menu,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use ouroboros::self_referencing;
|
use ouroboros::self_referencing;
|
||||||
@@ -215,7 +224,10 @@ impl ParserState {
|
|||||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||||
) -> anyhow::Result<ParserData> {
|
) -> anyhow::Result<ParserData> {
|
||||||
let Some(template) = self.data.templates.get(template_name) else {
|
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 {
|
let mut ctx = ParserContext {
|
||||||
@@ -291,7 +303,7 @@ impl ParserState {
|
|||||||
if !other.starts_with('_') {
|
if !other.starts_with('_') {
|
||||||
anyhow::bail!("unexpected \"{other}\" attribute");
|
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 => {
|
other => {
|
||||||
anyhow::bail!("{:?}: unexpected <{other}> tag", self.path.get_path_buf());
|
anyhow::bail!("{:?}: unexpected <{other}> tag", self.path.get_path_buf().display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(
|
Ok(cells)
|
||||||
cells,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,11 +481,17 @@ impl ParserContext<'_> {
|
|||||||
insert_color_vars!(self, "bg", def.bg_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) {
|
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());
|
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) {
|
fn print_missing_attrib(&self, tag_name: &str, attr: &str) {
|
||||||
log::warn!("{}: <{tag_name}> is missing \"{attr}\".", self.doc_params.path.get_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> {
|
fn parse_val(&self, tag_name: &str, key: &str, value: &str) -> Option<f32> {
|
||||||
@@ -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"))
|
get_tag_by_name(node, name).ok_or_else(|| anyhow::anyhow!("Tag \"{name}\" not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn parse_widget_other_internal(
|
fn parse_widget_other_internal(
|
||||||
template: &Rc<Template>,
|
template: &Rc<Template>,
|
||||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||||
@@ -617,7 +632,10 @@ fn parse_widget_other(
|
|||||||
attribs: &[AttribPair],
|
attribs: &[AttribPair],
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(template) = ctx.get_template(xml_tag_name) else {
|
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
|
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::manual_strip)]
|
||||||
|
#[allow(clippy::single_match_else)]
|
||||||
fn process_attrib(
|
fn process_attrib(
|
||||||
template_parameters: &HashMap<Rc<str>, Rc<str>>,
|
template_parameters: &HashMap<Rc<str>, Rc<str>>,
|
||||||
ctx: &ParserContext,
|
ctx: &ParserContext,
|
||||||
@@ -760,7 +779,7 @@ fn process_attrib(
|
|||||||
None => {
|
None => {
|
||||||
log::warn!("{}: undefined variable \"{value}\"", ctx.doc_params.path.get_str());
|
log::warn!("{}: undefined variable \"{value}\"", ctx.doc_params.path.get_str());
|
||||||
AttribPair::new(key, "undefined")
|
AttribPair::new(key, "undefined")
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AttribPair::new(key, replace_vars(value, template_parameters))
|
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));
|
res.push(process_attrib(&file.template_parameters, ctx, macro_key, macro_value));
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
res.push(process_attrib(&file.template_parameters, ctx, key, value));
|
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 */ }
|
"" => { /* 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() {
|
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)?);
|
new_widget_id = Some(parse_widget_div(file, ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||||
}
|
}
|
||||||
"rectangle" => {
|
"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" => {
|
"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" => {
|
"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" => {
|
"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" => {
|
"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" => {
|
"Slider" => {
|
||||||
new_widget_id = Some(parse_component_slider(ctx, parent_id, &attribs, tag_name)?);
|
new_widget_id = Some(parse_component_slider(ctx, parent_id, &attribs, tag_name)?);
|
||||||
@@ -999,7 +1038,9 @@ fn parse_child<'a>(
|
|||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
"RadioGroup" => {
|
"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 */ }
|
"" => { /* ignore */ }
|
||||||
other_tag_name => {
|
other_tag_name => {
|
||||||
|
|||||||
@@ -5,6 +5,17 @@ use wayvr_ipc::{
|
|||||||
|
|
||||||
use crate::{config::GeneralConfig, desktop_finder::DesktopFinder};
|
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> {
|
pub trait DashInterface<T> {
|
||||||
fn window_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrWindow>>;
|
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<()>;
|
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>;
|
) -> anyhow::Result<WvrProcessHandle>;
|
||||||
fn process_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrProcess>>;
|
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 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 recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()>;
|
||||||
fn desktop_finder<'a>(&'a mut self, data: &'a mut T) -> &'a mut DesktopFinder;
|
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;
|
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},
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct EmuProcess {
|
pub struct EmuProcess {
|
||||||
@@ -56,6 +61,7 @@ pub struct DashInterfaceEmulated {
|
|||||||
windows: EmuWindowVec,
|
windows: EmuWindowVec,
|
||||||
desktop_finder: DesktopFinder,
|
desktop_finder: DesktopFinder,
|
||||||
general_config: GeneralConfig,
|
general_config: GeneralConfig,
|
||||||
|
monado_clients: Vec<dash_interface::MonadoClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DashInterfaceEmulated {
|
impl DashInterfaceEmulated {
|
||||||
@@ -77,11 +83,42 @@ impl DashInterfaceEmulated {
|
|||||||
// Use serde defaults
|
// Use serde defaults
|
||||||
let general_config = serde_json::from_str("{}").unwrap();
|
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 {
|
Self {
|
||||||
processes,
|
processes,
|
||||||
windows,
|
windows,
|
||||||
desktop_finder,
|
desktop_finder,
|
||||||
general_config,
|
general_config,
|
||||||
|
monado_clients,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,4 +224,23 @@ impl DashInterface<()> for DashInterfaceEmulated {
|
|||||||
fn config_changed(&mut self, _: &mut ()) {}
|
fn config_changed(&mut self, _: &mut ()) {}
|
||||||
|
|
||||||
fn restart(&mut self, _data: &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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,16 @@
|
|||||||
"CENTER": "Zentrum"
|
"CENTER": "Zentrum"
|
||||||
},
|
},
|
||||||
"BAR": {
|
"BAR": {
|
||||||
"ADD_MIRROR": "Neuen Spiegel-Overlay hinzufügen"
|
"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": {
|
"WATCH": {
|
||||||
"RECENTER": "Spielbereich neu zentrieren",
|
"RECENTER": "Spielbereich neu zentrieren",
|
||||||
@@ -14,7 +23,8 @@
|
|||||||
"ADD_NEW_SET": "Neuen Satz hinzufügen",
|
"ADD_NEW_SET": "Neuen Satz hinzufügen",
|
||||||
"SWITCH_TO_SET": "Zum Satz wechseln",
|
"SWITCH_TO_SET": "Zum Satz wechseln",
|
||||||
"TOGGLE_FOR_CURRENT_SET": "Sichtbarkeit im aktuellen Satz umschalten",
|
"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": {
|
"EDIT_MODE": {
|
||||||
"ADJUST_CURVATURE": "Krümmung anpassen",
|
"ADJUST_CURVATURE": "Krümmung anpassen",
|
||||||
@@ -83,6 +93,8 @@
|
|||||||
"EMPTY_SET": "Leeres Set!",
|
"EMPTY_SET": "Leeres Set!",
|
||||||
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
|
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
|
||||||
"FIXING_FLOOR": "Boden wird in 5 Sekunden fixiert...",
|
"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"
|
"CENTER": "Centro"
|
||||||
},
|
},
|
||||||
"BAR": {
|
"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": {
|
"WATCH": {
|
||||||
"RECENTER": "Recentrar el área de juego",
|
"RECENTER": "Recentrar el área de juego",
|
||||||
@@ -14,7 +23,8 @@
|
|||||||
"ADD_NEW_SET": "Añadir un nuevo conjunto",
|
"ADD_NEW_SET": "Añadir un nuevo conjunto",
|
||||||
"SWITCH_TO_SET": "Cambiar al conjunto",
|
"SWITCH_TO_SET": "Cambiar al conjunto",
|
||||||
"TOGGLE_FOR_CURRENT_SET": "Alternar visibilidad en el conjunto actual",
|
"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": {
|
"EDIT_MODE": {
|
||||||
"ADJUST_CURVATURE": "Ajustar curvatura",
|
"ADJUST_CURVATURE": "Ajustar curvatura",
|
||||||
@@ -83,6 +93,8 @@
|
|||||||
"EMPTY_SET": "¡Conjunto vacío!",
|
"EMPTY_SET": "¡Conjunto vacío!",
|
||||||
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
|
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
|
||||||
"FIXING_FLOOR": "Fijando el suelo en 5 segundos...",
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,16 @@
|
|||||||
"CENTER": "センター"
|
"CENTER": "センター"
|
||||||
},
|
},
|
||||||
"BAR": {
|
"BAR": {
|
||||||
"ADD_MIRROR": "新しいミラーを追加"
|
"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": {
|
"WATCH": {
|
||||||
"RECENTER": "プレイスペースをリセンター",
|
"RECENTER": "プレイスペースをリセンター",
|
||||||
@@ -14,7 +23,8 @@
|
|||||||
"ADD_NEW_SET": "新しいセットを追加",
|
"ADD_NEW_SET": "新しいセットを追加",
|
||||||
"SWITCH_TO_SET": "セットに切り替える",
|
"SWITCH_TO_SET": "セットに切り替える",
|
||||||
"TOGGLE_FOR_CURRENT_SET": "現在のセットで表示を切り替え",
|
"TOGGLE_FOR_CURRENT_SET": "現在のセットで表示を切り替え",
|
||||||
"LONG_PRESS_TO_DELETE_SET": "長押しでセットを削除"
|
"LONG_PRESS_TO_DELETE_SET": "長押しでセットを削除",
|
||||||
|
"CLEANUP_MIRRORS": "現在表示されていないミラーを削除"
|
||||||
},
|
},
|
||||||
"EDIT_MODE": {
|
"EDIT_MODE": {
|
||||||
"ADJUST_CURVATURE": "曲率の調整",
|
"ADJUST_CURVATURE": "曲率の調整",
|
||||||
@@ -81,6 +91,8 @@
|
|||||||
"EMPTY_SET": "空のセットです!",
|
"EMPTY_SET": "空のセットです!",
|
||||||
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
|
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
|
||||||
"FIXING_FLOOR": "5秒後にフロアを固定します...",
|
"FIXING_FLOOR": "5秒後にフロアを固定します...",
|
||||||
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!"
|
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!",
|
||||||
|
"CANNOT_ADD_SET": "セットを追加できません!",
|
||||||
|
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,16 @@
|
|||||||
"CENTER": "Centrum"
|
"CENTER": "Centrum"
|
||||||
},
|
},
|
||||||
"BAR": {
|
"BAR": {
|
||||||
"ADD_MIRROR": "Dodaj nowy widok lustrzany"
|
"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": {
|
"WATCH": {
|
||||||
"RECENTER": "Wyśrodkuj przestrzeń gry",
|
"RECENTER": "Wyśrodkuj przestrzeń gry",
|
||||||
@@ -14,7 +23,8 @@
|
|||||||
"ADD_NEW_SET": "Dodaj nowy zestaw",
|
"ADD_NEW_SET": "Dodaj nowy zestaw",
|
||||||
"SWITCH_TO_SET": "Przełącz na zestaw",
|
"SWITCH_TO_SET": "Przełącz na zestaw",
|
||||||
"TOGGLE_FOR_CURRENT_SET": "Przełącz widoczność w bieżącym zestawie",
|
"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": {
|
"EDIT_MODE": {
|
||||||
"ADJUST_CURVATURE": "Dostosuj zakrzywienie",
|
"ADJUST_CURVATURE": "Dostosuj zakrzywienie",
|
||||||
@@ -81,6 +91,8 @@
|
|||||||
"EMPTY_SET": "Pusty zestaw!",
|
"EMPTY_SET": "Pusty zestaw!",
|
||||||
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
|
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
|
||||||
"FIXING_FLOOR": "Naprawianie podłogi za 5 sekund...",
|
"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) {
|
pub fn update(&mut self, app: &mut AppState, watch_id: OverlayID) {
|
||||||
if !state.session.config.block_game_input {
|
let Some(monado) = &mut app.monado else {
|
||||||
|
return; // monado not available
|
||||||
|
};
|
||||||
|
|
||||||
|
if !app.session.config.block_game_input {
|
||||||
return;
|
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| {
|
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 wlx_common::config_io;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::input::{Haptics, Pointer, TrackedDevice, TrackedDeviceRole},
|
backend::input::{Haptics, InputState, Pointer, TrackedDevice, TrackedDeviceRole},
|
||||||
state::{AppSession, AppState},
|
state::{AppSession, AppState},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -227,12 +227,12 @@ impl OpenXrInputSource {
|
|||||||
fn update_device_battery_status(
|
fn update_device_battery_status(
|
||||||
device: &mut mnd::Device,
|
device: &mut mnd::Device,
|
||||||
role: TrackedDeviceRole,
|
role: TrackedDeviceRole,
|
||||||
app: &mut AppState,
|
input_state: &mut InputState,
|
||||||
) {
|
) {
|
||||||
if let Ok(status) = device.battery_status()
|
if let Ok(status) = device.battery_status()
|
||||||
&& status.present
|
&& status.present
|
||||||
{
|
{
|
||||||
app.input_state.devices.push(TrackedDevice {
|
input_state.devices.push(TrackedDevice {
|
||||||
soc: Some(status.charge),
|
soc: Some(status.charge),
|
||||||
charging: status.charging,
|
charging: status.charging,
|
||||||
role,
|
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();
|
let old_len = app.input_state.devices.len();
|
||||||
app.input_state.devices.clear();
|
app.input_state.devices.clear();
|
||||||
|
|
||||||
@@ -267,13 +271,14 @@ impl OpenXrInputSource {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
let mut seen = Vec::<u32>::with_capacity(32);
|
let mut seen = Vec::<u32>::with_capacity(32);
|
||||||
|
|
||||||
for (mnd_role, wlx_role) in roles {
|
for (mnd_role, wlx_role) in roles {
|
||||||
let device = monado.device_from_role(mnd_role);
|
let device = monado.device_from_role(mnd_role);
|
||||||
if let Ok(mut device) = device
|
if let Ok(mut device) = device
|
||||||
&& !seen.contains(&device.index)
|
&& !seen.contains(&device.index)
|
||||||
{
|
{
|
||||||
seen.push(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() {
|
if let Ok(devices) = monado.devices() {
|
||||||
@@ -284,7 +289,7 @@ impl OpenXrInputSource {
|
|||||||
} else {
|
} else {
|
||||||
TrackedDeviceRole::None
|
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 glam::{Affine3A, Vec3};
|
||||||
use input::OpenXrInputSource;
|
use input::OpenXrInputSource;
|
||||||
use libmonado::Monado;
|
|
||||||
use openxr as xr;
|
use openxr as xr;
|
||||||
use skybox::create_skybox;
|
use skybox::create_skybox;
|
||||||
use vulkano::{Handle, VulkanObject};
|
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 delete_queue = vec![];
|
||||||
|
|
||||||
let mut monado = Monado::auto_connect()
|
app.monado_init();
|
||||||
.map_err(|e| log::warn!("Will not use libmonado: {e}"))
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
let mut playspace = monado.as_mut().and_then(|m| {
|
let mut playspace = app.monado.as_mut().and_then(|m| {
|
||||||
playspace::PlayspaceMover::new(m)
|
playspace::PlayspaceMover::new(m)
|
||||||
.map_err(|e| log::warn!("Will not use Monado playspace mover: {e}"))
|
.map_err(|e| log::warn!("Will not use Monado playspace mover: {e}"))
|
||||||
.ok()
|
.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 (session, mut frame_wait, mut frame_stream) = unsafe {
|
||||||
let raw_session = helpers::create_overlay_session(
|
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()
|
if app.monado.is_some() && next_device_update <= Instant::now() {
|
||||||
&& let Some(monado) = &mut monado
|
let changed = OpenXrInputSource::update_devices(&mut app);
|
||||||
{
|
|
||||||
let changed = OpenXrInputSource::update_devices(&mut app, monado);
|
|
||||||
if changed {
|
if changed {
|
||||||
overlays.devices_changed(&mut app)?;
|
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);
|
app.input_state.post_update(&app.session);
|
||||||
|
|
||||||
if let Some(ref mut blocker) = blocker {
|
if let Some(ref mut blocker) = blocker {
|
||||||
blocker.update(
|
blocker.update(&mut app, watch_id);
|
||||||
&app,
|
|
||||||
watch_id,
|
|
||||||
monado.as_mut().unwrap(), // safe
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if app
|
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
|
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
|
||||||
if let Some(ref mut space_mover) = playspace {
|
if let Some(ref mut space_mover) = playspace {
|
||||||
space_mover.update(
|
space_mover.update(&mut overlays, &mut app);
|
||||||
&mut overlays,
|
|
||||||
&app,
|
|
||||||
monado.as_mut().unwrap(), // safe
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for o in overlays.values_mut() {
|
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)?;
|
overlays.handle_task(&mut app, task)?;
|
||||||
}
|
}
|
||||||
TaskType::Playspace(task) => {
|
TaskType::Playspace(task) => {
|
||||||
if let (Some(playspace), Some(monado)) = (playspace.as_mut(), monado.as_mut()) {
|
if let Some(playspace) = playspace.as_mut() {
|
||||||
playspace.handle_task(&app, monado, task);
|
playspace.handle_task(&mut app, task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "openvr")]
|
#[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 {
|
match task {
|
||||||
PlayspaceTask::FixFloor => {
|
PlayspaceTask::FixFloor => {
|
||||||
self.fix_floor(&app.input_state, monado);
|
self.fix_floor(&app.input_state, monado);
|
||||||
@@ -60,9 +64,12 @@ impl PlayspaceMover {
|
|||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
|
overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
|
||||||
app: &AppState,
|
app: &mut AppState,
|
||||||
monado: &mut Monado,
|
|
||||||
) {
|
) {
|
||||||
|
let Some(monado) = &mut app.monado else {
|
||||||
|
return; // monado not available
|
||||||
|
};
|
||||||
|
|
||||||
for pointer in &app.input_state.pointers {
|
for pointer in &app.input_state.pointers {
|
||||||
if pointer.now.space_reset {
|
if pointer.now.space_reset {
|
||||||
if !pointer.before.space_reset {
|
if !pointer.before.space_reset {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use wgui::{
|
|||||||
widget::EventResult,
|
widget::EventResult,
|
||||||
};
|
};
|
||||||
use wlx_common::{
|
use wlx_common::{
|
||||||
dash_interface::DashInterface,
|
dash_interface::{self, DashInterface},
|
||||||
overlays::{BackendAttrib, BackendAttribValue},
|
overlays::{BackendAttrib, BackendAttribValue},
|
||||||
};
|
};
|
||||||
use wlx_common::{
|
use wlx_common::{
|
||||||
@@ -444,4 +444,92 @@ impl DashInterface<AppState> for DashInterfaceLive {
|
|||||||
RUNNING.store(false, Ordering::Relaxed);
|
RUNNING.store(false, Ordering::Relaxed);
|
||||||
RESTART.store(true, 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")]
|
#[cfg(feature = "wayvr")]
|
||||||
pub wvr_server: Option<WvrServerState>,
|
pub wvr_server: Option<WvrServerState>,
|
||||||
|
|
||||||
|
#[cfg(feature = "openxr")]
|
||||||
|
pub monado: Option<libmonado::Monado>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
@@ -163,8 +166,20 @@ impl AppState {
|
|||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
wvr_server,
|
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 {
|
pub struct AppSession {
|
||||||
|
|||||||
Reference in New Issue
Block a user