Monado app switcher, lang update

This commit is contained in:
Aleksander
2026-01-08 19:46:34 +01:00
parent 650bc99a95
commit e421c39539
22 changed files with 566 additions and 153 deletions

View File

@@ -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."
}
}
}

View File

@@ -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."
}
}
}

View File

@@ -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": "最大セット数に達しました。"
}
}
}

View File

@@ -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."
}
}
}

View File

@@ -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
})
});

View File

@@ -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);
}
}
}

View File

@@ -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")]

View File

@@ -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 {

View File

@@ -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(())
}

View File

@@ -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 {