diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json
index a8f282a2..2f892f7b 100644
--- a/dash-frontend/assets/lang/de.json
+++ b/dash-frontend/assets/lang/de.json
@@ -243,7 +243,11 @@
"GRID_OPACITY_HELP": "Deckkraft des Bodengitters, wenn die Skymap aktiviert ist",
"XWAYLAND_BY_DEFAULT": "Apps standardmäßig im Kompatibilitätsmodus starten",
"SAVE": "Speichern",
- "CANCEL": "Abbrechen"
+ "CANCEL": "Abbrechen",
+ "STATISTICS": "Statistiken",
+ "ROTATIONS": "HMD-Rotationen",
+ "SESSION_TIME": "Sitzungszeit",
+ "IPD_MM": "Pupillenabstand (mm)"
},
"APPLICATION_LAUNCHER": "Programmstarter",
"APPLICATION_STARTED": "Anwendung gestartet",
@@ -346,4 +350,4 @@
"ADD_TO_FAVOURITES_HINT": "Voreinstellung speichern und an die App-Schublade anheften",
"REMOVE_FROM_FAVOURITES": "Aus angehefteten Anwendungen entfernen",
"EMPTY": "Leer"
-}
+}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/en.csv b/dash-frontend/assets/lang/en.csv
index 70d7a88a..cb9aa58b 100644
--- a/dash-frontend/assets/lang/en.csv
+++ b/dash-frontend/assets/lang/en.csv
@@ -132,6 +132,7 @@ APP_SETTINGS.INPUT_PROFILES,Change Input Bindings,Button label in Settings > Con
APP_SETTINGS.INPUT_PROFILES_HELP,OpenXR controller input bindings,Tooltip for the Change Input Bindings button.
APP_SETTINGS.INVERT_SCROLL_DIRECTION_X,Invert horizontal scroll direction,Checkbox label in Settings > Features tab. Inverts the horizontal scroll direction.
APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y,Invert vertical scroll direction,Checkbox label in Settings > Features tab. Inverts the vertical scroll direction.
+APP_SETTINGS.IPD_MM,Interpupilary distance (mm),Distance between eyes (IPD), in milimeters.
APP_SETTINGS.KEYBOARD_MIDDLE_CLICK,Keyboard middle click,Dropdown label in Settings > Features tab. Selects the modifier key used for middle-click when typing with the laser pointer.
APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP,"Modifier to use when typing
with purple laser",Tooltip for the keyboard middle-click setting.
@@ -225,6 +226,9 @@ APP_SETTINGS.SPACE_GRAVITY_GROUND_FRICTION_HELP,"Amount of friction slowing you
APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT,Floor height,Setting in the space gravity physics panel. The Y position where the virtual floor is.
APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT_HELP,The Y position where the floor is. Gravity stops when you reach this height.,Tooltip for the floor height setting.
APP_SETTINGS.SPACE_ROTATE_UNLOCKED,Allow space rotate on all axes,"Checkbox in Settings > Space Drag tab. Unlocks space rotate to work on all axes. ""Space"" in this context refers to ""Playspace"" which was translated as ""${DEFINITIONS.PLAYSPACE}""."
+APP_SETTINGS.ROTATIONS,HMD rotations,Number of full 360-degree rotations of the HMD since startup. Shown in Settings > Statistics.
+APP_SETTINGS.SESSION_TIME,Session time,Time since WayVR started. Shown in Settings > Statistics.
+APP_SETTINGS.STATISTICS,Statistics,Category header in Settings.
APP_SETTINGS.TROUBLESHOOTING,Troubleshooting,"Category header in Settings. Groups diagnostic/reset actions like clearing tokens, wiping config, and restarting."
APP_SETTINGS.UI_GRADIENT_INTENSITY,UI Gradient intensity,Slider in Settings > Look & Feel tab. Controls the intensity of gradient effects on the UI (range 0.0-1.0).
APP_SETTINGS.UPRIGHT_SCREEN_FIX,Upright screen fix,Checkbox in Settings > Miscellaneous tab. Fixes upright screen orientation on some desktop environments.
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json
index c1008b36..fc58407f 100644
--- a/dash-frontend/assets/lang/en.json
+++ b/dash-frontend/assets/lang/en.json
@@ -146,6 +146,7 @@
"INPUT_PROFILES_HELP": "OpenXR controller input bindings",
"INVERT_SCROLL_DIRECTION_X": "Invert horizontal scroll direction",
"INVERT_SCROLL_DIRECTION_Y": "Invert vertical scroll direction",
+ "IPD_MM": "Interpupilary distance (mm)",
"KEYBOARD_MIDDLE_CLICK": "Keyboard middle click",
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser",
"KEYBOARD_SOUND_ENABLED": "Keyboard sounds",
@@ -223,6 +224,9 @@
"SPACE_GRAVITY_FLOOR_HEIGHT": "Floor height",
"SPACE_GRAVITY_FLOOR_HEIGHT_HELP": "The Y position where the floor is. Gravity stops when you reach this height.",
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
+ "ROTATIONS": "HMD rotations",
+ "SESSION_TIME": "Session time",
+ "STATISTICS": "Statistics",
"TROUBLESHOOTING": "Troubleshooting",
"UI_GRADIENT_INTENSITY": "UI Gradient intensity",
"UPRIGHT_SCREEN_FIX": "Upright screen fix",
@@ -346,4 +350,4 @@
"ADD_TO_FAVOURITES_HINT": "Save preset and pin it to the app drawer",
"REMOVE_FROM_FAVOURITES": "Remove from pinned apps",
"EMPTY": "Empty"
-}
+}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json
index 6fea379a..ac46ae83 100644
--- a/dash-frontend/assets/lang/es.json
+++ b/dash-frontend/assets/lang/es.json
@@ -243,7 +243,11 @@
"GRID_OPACITY_HELP": "Opacidad de la rejilla del suelo cuando el skymap está activado",
"XWAYLAND_BY_DEFAULT": "Ejecutar apps en modo de compatibilidad por defecto",
"SAVE": "Guardar",
- "CANCEL": "Cancelar"
+ "CANCEL": "Cancelar",
+ "STATISTICS": "Estadísticas",
+ "ROTATIONS": "Rotaciones del HMD",
+ "SESSION_TIME": "Tiempo de sesión",
+ "IPD_MM": "Distancia interpupilar (mm)"
},
"APPLICATION_LAUNCHER": "Lanzador de aplicaciones",
"APPLICATION_STARTED": "Aplicación iniciada",
@@ -346,4 +350,4 @@
"ADD_TO_FAVOURITES_HINT": "Guardar ajuste y fijarlo al cajón de aplicaciones",
"REMOVE_FROM_FAVOURITES": "Quitar de aplicaciones fijadas",
"EMPTY": "Vacío"
-}
+}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json
index 4fffe6f5..6a77090d 100644
--- a/dash-frontend/assets/lang/it.json
+++ b/dash-frontend/assets/lang/it.json
@@ -243,7 +243,11 @@
"GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skymap è attivo",
"XWAYLAND_BY_DEFAULT": "Avvia le app in modalità compatibilità per impostazione predefinita",
"SAVE": "Salva",
- "CANCEL": "Annulla"
+ "CANCEL": "Annulla",
+ "STATISTICS": "Statistiche",
+ "ROTATIONS": "Rotazioni HMD",
+ "SESSION_TIME": "Tempo sessione",
+ "IPD_MM": "Distanza interpupillare (mm)"
},
"APPLICATION_LAUNCHER": "Avvio applicazioni",
"APPLICATION_STARTED": "Applicazione avviata",
@@ -346,4 +350,4 @@
"ADD_TO_FAVOURITES_HINT": "Salva il preset e fissalo al cassetto delle app",
"REMOVE_FROM_FAVOURITES": "Rimuovi dalle app fissate",
"EMPTY": "Vuoto"
-}
+}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json
index 4aa9e508..017c7ff1 100644
--- a/dash-frontend/assets/lang/ja.json
+++ b/dash-frontend/assets/lang/ja.json
@@ -243,7 +243,11 @@
"GRID_OPACITY_HELP": "スカイマップ有効時のフロアグリッドの不透明度",
"XWAYLAND_BY_DEFAULT": "デフォルトで互換モードでアプリを起動",
"SAVE": "保存",
- "CANCEL": "キャンセル"
+ "CANCEL": "キャンセル",
+ "STATISTICS": "統計",
+ "ROTATIONS": "HMD回転数",
+ "SESSION_TIME": "セッション時間",
+ "IPD_MM": "瞳孔間距離 (mm)"
},
"APPLICATION_LAUNCHER": "アプリランチャー",
"APPLICATION_STARTED": "アプリが起動しました",
@@ -346,4 +350,4 @@
"ADD_TO_FAVOURITES_HINT": "プリセットを保存し、アプリドロワーに固定する",
"REMOVE_FROM_FAVOURITES": "固定済みアプリから外す",
"EMPTY": "なし"
-}
+}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json
index a0c382b6..71008517 100644
--- a/dash-frontend/assets/lang/pl.json
+++ b/dash-frontend/assets/lang/pl.json
@@ -243,7 +243,11 @@
"GRID_OPACITY_HELP": "Przezroczystość siatki podłogi przy włączonym Skymapie",
"XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności",
"SAVE": "Zapisz",
- "CANCEL": "Anuluj"
+ "CANCEL": "Anuluj",
+ "STATISTICS": "Statystyki",
+ "ROTATIONS": "Obroty HMD",
+ "SESSION_TIME": "Czas sesji",
+ "IPD_MM": "Rozstaw źrenic (mm)"
},
"APPLICATION_LAUNCHER": "Launcher aplikacji",
"APPLICATION_STARTED": "Uruchomiono aplikację",
@@ -346,4 +350,4 @@
"ADD_TO_FAVOURITES_HINT": "Zapisz preset i przypnij go do szuflady aplikacji",
"REMOVE_FROM_FAVOURITES": "Usuń z przypiętych aplikacji",
"EMPTY": "Puste"
-}
+}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json
index 94862c4d..fa18e776 100644
--- a/dash-frontend/assets/lang/zh_CN.json
+++ b/dash-frontend/assets/lang/zh_CN.json
@@ -243,7 +243,11 @@
"GRID_OPACITY_HELP": "启用天空盒时地面网格的不透明度",
"XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用程序",
"SAVE": "保存",
- "CANCEL": "取消"
+ "CANCEL": "取消",
+ "STATISTICS": "统计数据",
+ "ROTATIONS": "HMD 旋转次数",
+ "SESSION_TIME": "会话时间",
+ "IPD_MM": "瞳距 (mm)"
},
"APPLICATION_LAUNCHER": "应用启动器",
"APPLICATION_STARTED": "应用程序已启动",
@@ -346,4 +350,4 @@
"ADD_TO_FAVOURITES_HINT": "保存预设并将其固定到应用抽屉",
"REMOVE_FROM_FAVOURITES": "从固定应用中移除",
"EMPTY": "暂无"
-}
+}
\ No newline at end of file
diff --git a/dash-frontend/src/tab/settings/macros.rs b/dash-frontend/src/tab/settings/macros.rs
index efb0cd98..7e79dede 100644
--- a/dash-frontend/src/tab/settings/macros.rs
+++ b/dash-frontend/src/tab/settings/macros.rs
@@ -35,6 +35,20 @@ pub fn options_category(
mp.parser_state.get_widget_id(&id)
}
+pub fn options_stat_row(mp: &mut MacroParams, parent: WidgetID, translation: &str) -> anyhow::Result
{
+ let id = mp.idx.to_string();
+ mp.idx += 1;
+
+ let mut params = TemplateParams::new();
+ params.insert("id", &id);
+ params.insert("translation", translation);
+
+ mp.parser_state
+ .instantiate_template(mp.doc_params, "StatRow", mp.layout, parent, params)?;
+
+ mp.parser_state.get_widget_id(&id)
+}
+
pub fn options_checkbox(mp: &mut MacroParams, parent: WidgetID, setting: SettingType) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs
index d2f16356..f1423984 100644
--- a/dash-frontend/src/tab/settings/mod.rs
+++ b/dash-frontend/src/tab/settings/mod.rs
@@ -23,7 +23,7 @@ use wgui::{
use wlx_common::{
config::GeneralConfig,
config_io::ConfigRoot,
- dash_interface::{ConfigChangeKind, DashPlayspaceTask, InterfaceFeats},
+ dash_interface::{ConfigChangeKind, DashPlayspaceTask, HmdStats, InterfaceFeats},
};
use crate::{
@@ -40,6 +40,7 @@ mod tab_look_and_feel;
mod tab_misc;
mod tab_skybox;
mod tab_space_drag;
+mod tab_statistics;
mod tab_troubleshooting;
#[derive(Clone)]
@@ -51,6 +52,7 @@ pub(crate) enum TabNameEnum {
Misc,
Skybox,
SpaceDrag,
+ Statistics,
Troubleshooting,
}
@@ -64,6 +66,7 @@ impl TabNameEnum {
"misc" => Some(TabNameEnum::Misc),
"skybox" => Some(TabNameEnum::Skybox),
"space_drag" => Some(TabNameEnum::SpaceDrag),
+ "statistics" => Some(TabNameEnum::Statistics),
"troubleshooting" => Some(TabNameEnum::Troubleshooting),
_ => None,
}
@@ -101,8 +104,12 @@ struct SettingUpdatedParams<'a> {
setting_type: SettingType,
}
+struct UpdateExtra {
+ hmd_stats: HmdStats,
+}
+
trait SettingsTab {
- fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
+ fn update(&mut self, _par: &mut ViewUpdateParams, _extra: &UpdateExtra) -> anyhow::Result<()> {
Ok(())
}
@@ -144,12 +151,17 @@ impl Tab for TabSettings {
if let Some(tab) = &mut self.current_tab {
let mut config_change_kind = None;
- tab.update(&mut ViewUpdateParams {
- layout: &mut frontend.layout,
- executor: &frontend.executor,
- general_config: frontend.interface.general_config(data),
- config_change_kind: &mut config_change_kind,
- })?;
+ let hmd_stats = frontend.interface.hmd_stats(data);
+
+ tab.update(
+ &mut ViewUpdateParams {
+ layout: &mut frontend.layout,
+ executor: &frontend.executor,
+ general_config: frontend.interface.general_config(data),
+ config_change_kind: &mut config_change_kind,
+ },
+ &UpdateExtra { hmd_stats },
+ )?;
if let Some(kind) = config_change_kind {
frontend.interface.config_changed(data, kind);
@@ -734,6 +746,9 @@ impl TabSettings {
TabNameEnum::Skybox => {
self.current_tab = Some(Box::new(tab_skybox::State::mount(settings_mount_params)?));
}
+ TabNameEnum::Statistics => {
+ self.current_tab = Some(Box::new(tab_statistics::State::mount(settings_mount_params)?));
+ }
}
Ok(())
diff --git a/dash-frontend/src/tab/settings/tab_controls.rs b/dash-frontend/src/tab/settings/tab_controls.rs
index 01454a23..963c3581 100644
--- a/dash-frontend/src/tab/settings/tab_controls.rs
+++ b/dash-frontend/src/tab/settings/tab_controls.rs
@@ -4,7 +4,7 @@ use wgui::{
components::button::ComponentButton, globals::WguiGlobals, layout::WidgetID, parser::Fetchable, task::Tasks,
};
-use crate::tab::settings::{horiz_cell, mount_requires_restart};
+use crate::tab::settings::{UpdateExtra, horiz_cell, mount_requires_restart};
use crate::util::popup_manager::PopupHolder;
use crate::{
@@ -29,7 +29,7 @@ pub struct State {
}
impl SettingsTab for State {
- fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
+ fn update(&mut self, par: &mut ViewUpdateParams, _extra: &UpdateExtra) -> anyhow::Result<()> {
self.popup_input_profiles.update(par)?;
for task in self.tasks.drain() {
diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs
index cc2c9161..83e8d9bd 100644
--- a/dash-frontend/src/tab/settings/tab_features.rs
+++ b/dash-frontend/src/tab/settings/tab_features.rs
@@ -16,7 +16,7 @@ use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_inte
use crate::{
frontend::FrontendTasks,
tab::settings::{
- SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, horiz_cell,
+ SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, UpdateExtra, horiz_cell,
macros::{MacroParams, options_category, options_checkbox, options_range_f32},
},
util::{
@@ -50,7 +50,7 @@ pub struct State {
}
impl SettingsTab for State {
- fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
+ fn update(&mut self, par: &mut ViewUpdateParams, _extra: &UpdateExtra) -> anyhow::Result<()> {
self.popup_download.update(par)?;
self.popup_dialog.update(par)?;
@@ -79,7 +79,7 @@ impl SettingsTab for State {
par.general_config.whisper_model = model.file_name.into();
par.config_change_kind.replace(ConfigChangeKind::Other);
- // reload tab so that the downloaded checkmarks get populated
+ // reload tab so that the downloaded check marks get populated
self.parent_tasks.push(ParentTask::SetTab(TabNameEnum::Features));
}
}
diff --git a/dash-frontend/src/tab/settings/tab_look_and_feel.rs b/dash-frontend/src/tab/settings/tab_look_and_feel.rs
index ca200997..6e1720e6 100644
--- a/dash-frontend/src/tab/settings/tab_look_and_feel.rs
+++ b/dash-frontend/src/tab/settings/tab_look_and_feel.rs
@@ -11,7 +11,7 @@ use wgui::{
use crate::{
frontend::FrontendTasks,
tab::settings::{
- SettingType, SettingsMountParams, SettingsTab, horiz_cell,
+ SettingType, SettingsMountParams, SettingsTab, UpdateExtra, horiz_cell,
macros::{options_category, options_checkbox, options_dropdown, options_slider_f32},
mount_requires_restart,
},
@@ -32,7 +32,7 @@ pub struct State {
}
impl SettingsTab for State {
- fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
+ fn update(&mut self, par: &mut ViewUpdateParams, _extra: &UpdateExtra) -> anyhow::Result<()> {
self.popup_color_palettes.update(par)?;
for task in self.tasks.drain() {
diff --git a/dash-frontend/src/tab/settings/tab_skybox.rs b/dash-frontend/src/tab/settings/tab_skybox.rs
index 66c2edf8..145c7ed7 100644
--- a/dash-frontend/src/tab/settings/tab_skybox.rs
+++ b/dash-frontend/src/tab/settings/tab_skybox.rs
@@ -3,7 +3,7 @@ use wgui::{assets::AssetPath, i18n::Translation, layout::Layout, task::Tasks};
use crate::{
frontend::FrontendTasks,
tab::settings::{
- SettingType, SettingsMountParams, SettingsTab,
+ SettingType, SettingsMountParams, SettingsTab, UpdateExtra,
macros::{options_category, options_checkbox, options_slider_f32},
},
util::{popup_manager::PopupHolder, wgui_simple},
@@ -22,7 +22,7 @@ pub struct State {
}
impl SettingsTab for State {
- fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
+ fn update(&mut self, par: &mut ViewUpdateParams, _extra: &UpdateExtra) -> anyhow::Result<()> {
self.popup_skymap_list.update(par)?;
for task in self.tasks.drain() {
diff --git a/dash-frontend/src/tab/settings/tab_statistics.rs b/dash-frontend/src/tab/settings/tab_statistics.rs
new file mode 100644
index 00000000..5d566490
--- /dev/null
+++ b/dash-frontend/src/tab/settings/tab_statistics.rs
@@ -0,0 +1,99 @@
+use crate::{
+ tab::settings::{
+ SettingsMountParams, SettingsTab, UpdateExtra,
+ macros::{options_category, options_stat_row},
+ },
+ views::ViewUpdateParams,
+};
+use std::f32::consts::TAU;
+use wgui::{i18n::Translation, layout::WidgetID, widget::label::WidgetLabel};
+
+pub struct State {
+ id_label_rotations: WidgetID,
+ id_label_session_time: WidgetID,
+ id_label_ipd: WidgetID,
+ last_rotations: f32,
+ last_session_time_ms: u64,
+ last_ipd: f32,
+}
+
+impl SettingsTab for State {
+ fn update(&mut self, par: &mut ViewUpdateParams, extra: &UpdateExtra) -> anyhow::Result<()> {
+ let hmd_stats = &extra.hmd_stats;
+
+ let mut common = par.layout.common();
+
+ // 1 - one rotation, 2 - two rotations
+ // accuracy up to 0.01 rotations
+ let rotations = (hmd_stats.rotations_rad / TAU * 100.0).round() / 100.0;
+
+ if rotations != self.last_rotations {
+ self.last_rotations = rotations;
+
+ let mut label = common.state.widgets.cast_as::(self.id_label_rotations)?;
+ label.set_text(&mut common, Translation::from_raw_text_string(rotations.to_string()));
+ }
+
+ if hmd_stats.session_time_ms != self.last_session_time_ms {
+ self.last_session_time_ms = hmd_stats.session_time_ms;
+
+ let mut label = common
+ .state
+ .widgets
+ .cast_as::(self.id_label_session_time)?;
+ label.set_text(
+ &mut common,
+ Translation::from_raw_text_string(format_duration(hmd_stats.session_time_ms)),
+ );
+ }
+
+ if hmd_stats.ipd != self.last_ipd {
+ self.last_ipd = hmd_stats.ipd;
+
+ let mut label = common.state.widgets.cast_as::(self.id_label_ipd)?;
+ label.set_text(
+ &mut common,
+ Translation::from_raw_text_string(format!("{:.2}", hmd_stats.ipd)),
+ );
+ }
+
+ Ok(())
+ }
+}
+
+fn format_duration(ms: u64) -> String {
+ let total_secs = ms / 1000;
+ let h = total_secs / 3600;
+ let m = (total_secs % 3600) / 60;
+ let s = total_secs % 60;
+
+ if h > 0 {
+ format!("{h}:{m:02}:{s:02}")
+ } else {
+ format!("{m}:{s:02}")
+ }
+}
+
+impl State {
+ pub fn mount(par: SettingsMountParams) -> anyhow::Result {
+ let c = options_category(
+ par.mp,
+ par.id_parent,
+ "APP_SETTINGS.STATISTICS",
+ "dashboard/barchart.svg",
+ )?;
+
+ let id_label_rotations = options_stat_row(par.mp, c, "APP_SETTINGS.ROTATIONS")?;
+ let id_label_session_time = options_stat_row(par.mp, c, "APP_SETTINGS.SESSION_TIME")?;
+ let id_label_ipd = options_stat_row(par.mp, c, "APP_SETTINGS.IPD_MM")?;
+
+ Ok(State {
+ id_label_rotations,
+ id_label_session_time,
+ id_label_ipd,
+ last_rotations: f32::MIN,
+ last_session_time_ms: u64::MAX,
+ last_ipd: f32::MIN,
+ })
+ }
+}
diff --git a/scripts/translator/run.sh b/scripts/translator/run.sh
index 928b3558..00976545 100755
--- a/scripts/translator/run.sh
+++ b/scripts/translator/run.sh
@@ -15,10 +15,10 @@ cd "$(dirname "$0")"
bun install
# model URL:
-# https://huggingface.co/unsloth/gemma-4-26B-A4B-it-GGUF
+# https://huggingface.co/unsloth/gemma-4-31B-it-GGUF
# you can specify your own if you want to.
if [ -z "${MODEL}" ]; then
- export MODEL="gemma-4-26B-A4B-it-UD-Q3_K_M"
+ export MODEL="gemma-4-31B-it-Q4_K_M"
fi
if [ -z "${LLAMA_BASE_URL}" ]; then
diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs
index 3f1b361f..c27af4b1 100644
--- a/wayvr/src/backend/input.rs
+++ b/wayvr/src/backend/input.rs
@@ -1,4 +1,4 @@
-use std::f32::consts::PI;
+use std::f32::consts::{PI, TAU};
use std::process::{Child, Command};
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -75,6 +75,8 @@ pub struct InputState {
pub picking_focus: FocusPickState,
processes: Vec,
disable_lerp_until: Instant,
+ pub head_yaw_total: f64,
+ head_last_yaw: Option,
}
impl InputState {
@@ -88,6 +90,8 @@ impl InputState {
handsfree_state: PointerState::default(),
picking_focus: FocusPickState::None,
disable_lerp_until: Instant::now(),
+ head_yaw_total: 0.0,
+ head_last_yaw: None,
}
}
@@ -102,6 +106,34 @@ impl InputState {
}
}
+ fn update_head_yaw(&mut self) {
+ let fwd = self.hmd.transform_vector3a(Vec3A::NEG_Z);
+ if fwd.x * fwd.x + fwd.z * fwd.z < 0.01 {
+ return;
+ }
+
+ let yaw = (-fwd.x).atan2(-fwd.z) as f64;
+ if !yaw.is_finite() {
+ return;
+ }
+
+ let Some(last) = self.head_last_yaw else {
+ self.head_last_yaw = Some(yaw as f32);
+ return;
+ };
+
+ let mut delta = (yaw - last as f64).rem_euclid(TAU as f64);
+ if delta > PI as f64 {
+ delta -= TAU as f64;
+ }
+
+ if delta.abs() <= (PI / 4.0) as f64 {
+ self.head_yaw_total -= delta;
+ }
+
+ self.head_last_yaw = Some(yaw as f32);
+ }
+
pub fn apply_handsfree_action(&mut self, params: HandsfreeParams) {
const fn set_true(v: &mut bool) {
*v = true;
@@ -154,6 +186,8 @@ impl InputState {
}
pub fn post_update(&mut self, session: &AppSession) {
+ self.update_head_yaw();
+
for hand in &mut self.pointers {
#[cfg(debug_assertions)]
debug_print_hand(hand);
diff --git a/wayvr/src/backend/openvr/input.rs b/wayvr/src/backend/openvr/input.rs
index 952b8e36..e573d25b 100644
--- a/wayvr/src/backend/openvr/input.rs
+++ b/wayvr/src/backend/openvr/input.rs
@@ -185,7 +185,9 @@ impl OpenVrInputSource {
let _ = input.update_actions(&mut [aas_left, aas_right]);
let devices = system.get_device_to_absolute_tracking_pose(universe.clone(), 0.005);
- app.input_state.hmd = devices[0].mDeviceToAbsoluteTracking.to_affine();
+ if devices[0].bPoseIsValid {
+ app.input_state.hmd = devices[0].mDeviceToAbsoluteTracking.to_affine();
+ }
for i in 0..2 {
let hand = &mut self.hands[i];
diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs
index 0af1c39d..7ad89175 100644
--- a/wayvr/src/backend/wayvr/mod.rs
+++ b/wayvr/src/backend/wayvr/mod.rs
@@ -928,7 +928,7 @@ impl WvrServerState {
if !self.grab_toast_sent {
self.grab_toast_sent = true;
let text = &globals.i18n().translate("NOTIFICATION.WE_ARE_GRABBING");
- let _ = DbusConnector::notify_send("WayVR", &text, 1, 5000, 0, true);
+ let _ = DbusConnector::notify_send("WayVR", text, 1, 5000, 0, true);
}
}
input_capture::CapturedEvent::Ungrabbed => {
diff --git a/wayvr/src/ipc/ipc_server.rs b/wayvr/src/ipc/ipc_server.rs
index e78f017f..632f7975 100644
--- a/wayvr/src/ipc/ipc_server.rs
+++ b/wayvr/src/ipc/ipc_server.rs
@@ -181,7 +181,7 @@ impl Connection {
pos: to_arr(¶ms.input_state.pointers[0].raw_pose.translation),
},
right: WlxInputStatePointer {
- pos: to_arr(¶ms.input_state.pointers[0].raw_pose.translation),
+ pos: to_arr(¶ms.input_state.pointers[1].raw_pose.translation),
},
},
)),
diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs
index 15d65ad6..59cb4bc7 100644
--- a/wayvr/src/overlays/dashboard.rs
+++ b/wayvr/src/overlays/dashboard.rs
@@ -25,7 +25,7 @@ use wlx_common::{
overlays::{BackendAttrib, BackendAttribValue},
};
use wlx_common::{
- timestep::Timestep,
+ timestep::{self, Timestep},
windowing::{OverlayWindowState, Positioning},
};
@@ -579,6 +579,14 @@ impl DashInterface for DashInterfaceLive {
data.feats
}
+ fn hmd_stats(&mut self, data: &mut AppState) -> dash_interface::HmdStats {
+ dash_interface::HmdStats {
+ rotations_rad: data.input_state.head_yaw_total as f32,
+ session_time_ms: timestep::get_micros() / 1000,
+ ipd: data.input_state.ipd,
+ }
+ }
+
#[cfg(feature = "openxr")]
fn monado_client_list(
&mut self,
diff --git a/wlx-common/src/dash_interface.rs b/wlx-common/src/dash_interface.rs
index 5af7307f..1ccf12e4 100644
--- a/wlx-common/src/dash_interface.rs
+++ b/wlx-common/src/dash_interface.rs
@@ -66,6 +66,13 @@ impl InterfaceFeats {
}
}
+#[derive(Clone, Copy, Default)]
+pub struct HmdStats {
+ pub rotations_rad: f32, // total raw in radians
+ pub session_time_ms: u64,
+ pub ipd: f32,
+}
+
pub trait DashInterface {
fn window_list(&mut self, data: &mut T) -> anyhow::Result>;
fn window_set_visible(&mut self, data: &mut T, handle: WvrWindowHandle, visible: bool) -> anyhow::Result<()>;
@@ -92,6 +99,7 @@ pub trait DashInterface {
fn restart(&mut self, data: &mut T);
fn toggle_dashboard(&mut self, data: &mut T);
fn get_feats(&mut self, data: &mut T) -> InterfaceFeats;
+ fn hmd_stats(&mut self, data: &mut T) -> HmdStats;
}
#[derive(Default, Clone, Copy)]
diff --git a/wlx-common/src/dash_interface_emulated.rs b/wlx-common/src/dash_interface_emulated.rs
index 2b9d9143..c4b7d243 100644
--- a/wlx-common/src/dash_interface_emulated.rs
+++ b/wlx-common/src/dash_interface_emulated.rs
@@ -243,6 +243,14 @@ impl DashInterface<()> for DashInterfaceEmulated {
}
}
+ fn hmd_stats(&mut self, _data: &mut ()) -> dash_interface::HmdStats {
+ dash_interface::HmdStats {
+ rotations_rad: 0.42,
+ session_time_ms: crate::timestep::get_micros() / 1000,
+ ipd: 0.0,
+ }
+ }
+
fn monado_client_list(
&mut self,
_data: &mut (),