Compare commits

...

6 Commits

Author SHA1 Message Date
galister 4652f708b0 meta → altgr 2026-07-20 16:34:12 +09:00
galister e1879ca891 playspace center? 2026-07-20 00:26:53 +09:00
galister 786660a4d5 fix mirrors only allowing screens 2026-07-19 09:29:17 +09:00
galister 9e43b9b3ad openvr: copy reset_offset logic from openxr 2026-07-19 09:22:35 +09:00
galister 7c16748f88 openvr: copy recenter logic from openxr 2026-07-19 09:22:35 +09:00
galister a29451ceaa build xwayland-satellite for release 2026-07-14 22:19:33 +09:00
16 changed files with 165 additions and 52 deletions

View File

@ -27,6 +27,9 @@ jobs:
- name: Cargo Build
run: |
../.github/workflows/scripts/appimage_build_wlx.sh
- name: Build xwayland-satellite
run: |
../.github/workflows/scripts/appimage_build_satellite.sh
- name: Package AppImage
run: |
../.github/workflows/scripts/appimage_package.sh

View File

@ -22,7 +22,7 @@ use wgui::{
use wlx_common::{
async_executor::AsyncExecutor,
audio,
dash_interface::{BoxDashInterface, ConfigChangeKind, RecenterMode},
dash_interface::{BoxDashInterface, ConfigChangeKind, DashPlayspaceTask},
locale::WayVRLangProvider,
palette::load_palette,
timestep::{self, Timestep},
@ -553,7 +553,7 @@ impl<T: 'static> Frontend<T> {
}
fn action_recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()> {
self.interface.recenter_playspace(data, RecenterMode::Recenter)?;
self.interface.playspace(data, DashPlayspaceTask::Recenter)?;
Ok(())
}

View File

@ -23,7 +23,7 @@ use wgui::{
use wlx_common::{
config::GeneralConfig,
config_io::ConfigRoot,
dash_interface::{ConfigChangeKind, InterfaceFeats, RecenterMode},
dash_interface::{ConfigChangeKind, DashPlayspaceTask, InterfaceFeats},
};
use crate::{
@ -201,7 +201,7 @@ impl<T> Tab<T> for TabSettings<T> {
std::fs::create_dir(&path)?;
}
Task::ResetPlayspace => {
frontend.interface.recenter_playspace(data, RecenterMode::Reset)?;
frontend.interface.playspace(data, DashPlayspaceTask::Reset)?;
return Ok(());
}
Task::RestartSoftware => {

View File

@ -4,10 +4,12 @@ use ovr_overlay::{
compositor::CompositorManager,
sys::{EChaperoneConfigFile, ETrackingUniverseOrigin, HmdMatrix34_t},
};
use wgui::log::LogErr;
use wlx_common::config::GeneralConfig;
use crate::{
backend::{input::InputState, playspace_common, task::PlayspaceTask},
state::AppState,
state::{AppState, PlayspaceState, load_playspace_state, save_playspace_state},
windowing::manager::OverlayWindowManager,
};
@ -23,14 +25,18 @@ pub(super) struct PlayspaceMover {
universe: ETrackingUniverseOrigin,
drag: Option<MoverData<Vec3A>>,
rotate: Option<MoverData<Quat>>,
floor_y: Option<f32>,
playspace_state: PlayspaceState,
}
impl PlayspaceMover {
pub const fn new() -> Self {
pub fn new() -> Self {
Self {
universe: ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated,
drag: None,
rotate: None,
floor_y: None,
playspace_state: load_playspace_state().unwrap_or_default(),
}
}
@ -50,6 +56,9 @@ impl PlayspaceMover {
PlayspaceTask::Recenter => {
self.recenter(chaperone_mgr, &app.input_state);
}
PlayspaceTask::SaveCenter => {
self.save_center(chaperone_mgr);
}
}
}
@ -177,13 +186,10 @@ impl PlayspaceMover {
}
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
let mut height = 1.6;
if let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) {
height = input.hmd.translation.y - mat.translation.y;
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
apply_chaperone_transform(mat, chaperone_mgr);
}
}
let height = match self.floor_y {
Some(y) => y,
None => input.hmd.translation.y - 1.7,
};
let xform = if self.universe == ETrackingUniverseOrigin::TrackingUniverseSeated {
Affine3A::from_translation(Vec3::NEG_Y * height)
@ -213,6 +219,7 @@ impl PlayspaceMover {
};
let offset = y1.min(y2) - 0.03;
mat.translation.y += offset;
self.floor_y = Some(mat.translation.y);
set_working_copy(&self.universe, chaperone_mgr, &mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
@ -228,17 +235,6 @@ impl PlayspaceMover {
}
pub fn recenter(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
let Some(mut mat) = get_working_copy(&self.universe, chaperone_mgr) else {
log::warn!("Can't recenter - failed to get zero pose");
return;
};
mat.translation.x += input.hmd.translation.x;
mat.translation.z += input.hmd.translation.z;
set_working_copy(&self.universe, chaperone_mgr, &mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
if self.drag.is_some() {
log::info!("Space drag interrupted by recenter");
self.drag = None;
@ -247,6 +243,45 @@ impl PlayspaceMover {
log::info!("Space rotate interrupted by recenter");
self.rotate = None;
}
let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) else {
log::warn!("Can't recenter - failed to get zero pose");
return;
};
let cur_rot: Quat = Quat::from_affine3(&mat);
let cur_pos: Vec3 = Vec3::from(mat.translation);
let mut stage_offset = Affine3A::from_rotation_translation(cur_rot, cur_pos);
let horiz_hmd_pos = Vec3::new(input.hmd.translation.x, 0.0, input.hmd.translation.z);
let fwd = input.hmd.transform_vector3a(Vec3A::NEG_Z);
let horiz_len_sq = fwd.x.mul_add(fwd.x, fwd.z * fwd.z);
let hmd_yaw = if horiz_len_sq > f32::EPSILON {
let yaw = (-fwd.x).atan2(-fwd.z);
Quat::from_rotation_y(yaw)
} else {
Quat::IDENTITY
};
let recenter_offset = Affine3A::from_rotation_translation(hmd_yaw, horiz_hmd_pos);
stage_offset *= recenter_offset;
let (_, new_rot, new_pos) = stage_offset.to_scale_rotation_translation();
let new_mat = if horiz_len_sq > f32::EPSILON {
Affine3A::from_rotation_translation(new_rot, new_pos)
} else {
let mut m = mat;
m.translation = new_pos.into();
m
};
set_working_copy(&self.universe, chaperone_mgr, &new_mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
}
pub fn playspace_changed(
@ -277,6 +312,26 @@ impl PlayspaceMover {
pub fn get_universe(&self) -> ETrackingUniverseOrigin {
self.universe.clone()
}
pub fn save_center(&mut self, chaperone_mgr: &mut ChaperoneSetupManager) {
if self.drag.is_some() {
log::info!("Space drag interrupted by save center");
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by save center");
self.rotate = None;
}
let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) else {
log::warn!("Can't save center - failed to get zero pose");
return;
};
self.playspace_state.openvr_space_center = mat;
let _ =
save_playspace_state(&self.playspace_state).log_err("Could not save playspace state");
}
}
const fn universe_str(universe: &ETrackingUniverseOrigin) -> &'static str {

View File

@ -8,7 +8,7 @@ use crate::{
playspace_common::{self, SpaceGravity, SpaceGravityUpdateParams},
task::PlayspaceTask,
},
state::AppState,
state::{AppState, PlayspaceState, load_playspace_state},
windowing::manager::OverlayWindowManager,
};
@ -25,6 +25,7 @@ pub(super) struct PlayspaceMover {
rotate: Option<MoverData<Quat>>,
gravity: SpaceGravity,
floor_y: f32,
playspace_state: PlayspaceState,
}
impl PlayspaceMover {
@ -42,6 +43,7 @@ impl PlayspaceMover {
rotate: None,
gravity: SpaceGravity::new(),
floor_y: pose.position.y,
playspace_state: load_playspace_state().unwrap_or_default(),
})
}
@ -60,6 +62,7 @@ impl PlayspaceMover {
PlayspaceTask::Recenter => {
self.recenter(&app.input_state, &mut monado.ipc);
}
PlayspaceTask::SaveCenter => { /* OpenVR only */ }
}
}
@ -325,6 +328,28 @@ impl PlayspaceMover {
.set_reference_space_offset(ReferenceSpaceType::Stage, pose)
.inspect_err(|e| log::warn!("Could not fix floor due to libmonado error: {e:?}"));
}
pub fn save_center(&mut self, monado: &mut Monado) {
if self.drag.is_some() {
log::info!("Space drag interrupted by save center");
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by save center");
self.rotate = None;
}
let Ok(mut pose) = monado
.get_reference_space_offset(ReferenceSpaceType::Stage)
.inspect_err(|e| log::warn!("Could not fix floor due to libmonado error: {e:?}"))
else {
return;
};
self.playspace_state.openxr_space_center = mat;
let _ =
save_playspace_state(&self.playspace_state).log_err("Could not save playspace state");
}
}
fn apply_offset(transform: Affine3A, monado: &mut Monado) {

View File

@ -63,6 +63,8 @@ pub enum PlayspaceTask {
Recenter,
Reset,
FixFloor,
#[cfg(feature = "openvr")]
SaveCenter,
}
#[derive(Debug, Clone)]

View File

@ -20,7 +20,7 @@ use wgui::{
widget::EventResult,
};
use wlx_common::{
dash_interface::{self, ConfigChangeKind, DashInterface, RecenterMode},
dash_interface::{self, ConfigChangeKind, DashInterface, DashPlayspaceTask},
locale::WayVRLangProvider,
overlays::{BackendAttrib, BackendAttribValue},
};
@ -504,11 +504,17 @@ impl DashInterface<AppState> for DashInterfaceLive {
Ok(())
}
fn recenter_playspace(&mut self, app: &mut AppState, mode: RecenterMode) -> anyhow::Result<()> {
fn playspace_task(
&mut self,
app: &mut AppState,
mode: DashPlayspaceTask,
) -> anyhow::Result<()> {
let task = match mode {
RecenterMode::FixFloor => PlayspaceTask::FixFloor,
RecenterMode::Recenter => PlayspaceTask::Recenter,
RecenterMode::Reset => PlayspaceTask::Reset,
DashPlayspaceTask::FixFloor => PlayspaceTask::FixFloor,
DashPlayspaceTask::Recenter => PlayspaceTask::Recenter,
DashPlayspaceTask::Reset => PlayspaceTask::Reset,
#[cfg(feature = "openvr")]
DashPlayspaceTask::SaveCenter => PlayspaceTask::SaveCenter,
};
app.tasks.enqueue(TaskType::Playspace(task));
Ok(())

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{
config::{ConfigType, load_known_yaml},
subsystem::hid::{
KEYS_TO_MODS, KeyType, META, NUM_LOCK, SHIFT, VirtualKey, XkbKeymap, get_key_type,
ALTGR, KEYS_TO_MODS, KeyType, NUM_LOCK, SHIFT, VirtualKey, XkbKeymap, get_key_type,
},
};
@ -83,7 +83,7 @@ impl Layout {
label.push(label1);
if has_altgr {
cap_type = KeyCapType::LetterAltGr;
label.push(keymap.label_for_key(vk, META));
label.push(keymap.label_for_key(vk, ALTGR));
} else {
cap_type = KeyCapType::Letter;
}
@ -91,7 +91,7 @@ impl Layout {
label.push(label0);
label.push(label1);
if has_altgr {
label.push(keymap.label_for_key(vk, META));
label.push(keymap.label_for_key(vk, ALTGR));
cap_type = KeyCapType::SymbolAltGr;
} else {
cap_type = KeyCapType::Symbol;

View File

@ -17,7 +17,7 @@ use crate::{
subsystem::{
dbus::DbusConnector,
hid::{
ALT, CTRL, KeyModifier, META, SHIFT, SUPER, VirtualKey, WheelDelta, XkbKeymap,
ALT, ALTGR, CTRL, KeyModifier, SHIFT, SUPER, VirtualKey, WheelDelta, XkbKeymap,
get_keymap_wl, get_keymap_x11,
},
},
@ -46,7 +46,7 @@ pub mod builder;
mod layout;
pub const KEYBOARD_NAME: &str = "kbd";
const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META];
const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, ALTGR];
const SYSTEM_LAYOUT_ALIASES: [&str; 5] = ["mozc", "pinyin", "hangul", "sayura", "unikey"];
pub fn create_keyboard(app: &mut AppState, wayland: bool) -> anyhow::Result<OverlayWindowConfig> {
@ -127,7 +127,7 @@ const fn alt_modifier_to_key(m: AltModifier) -> KeyModifier {
AltModifier::Ctrl => CTRL,
AltModifier::Alt => ALT,
AltModifier::Super => SUPER,
AltModifier::Meta => META,
AltModifier::AltGr => ALTGR,
_ => 0,
}
}

View File

@ -26,9 +26,9 @@ key_sizes:
- [1.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1]
- [1.25, 1.25, 1.25, 6.25, 1.25, 1.25, 1.25, 1.25, 0.5, 1, 1, 1, 0.5, 2, 1, 1]
# The main (blue) layout of the keyboard.
# The layout of the keyboard.
# Accepted are:
# - virtual keys. For a full list, look at enum VirtualKey in https://github.com/galister/wlx-overlay-s/blob/main/src/hid.rs
# - virtual keys. For a full list, see https://github.com/wayvr-org/wayvr/blob/786660a4d54b73714399c9433a68c8cc35eb55f4/wayvr/src/subsystem/hid/mod.rs#L79
# - exec_commands (defined below)
# - macros (defined below)
# - ~ (null) will leave an empty space with the corresponding size from key_sizes
@ -38,19 +38,19 @@ main_layout:
- ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"]
- ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~]
- ["LShift", "Oem102", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"]
- ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", "MAP"]
- ["LCtrl", "LSuper", "LAlt", "Space", "AltGr", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", "MAP"]
# Shell commands to be used in a layout.
# Value is an array of string arguments.
exec_commands:
STT: [ "whisper_stt", "--lang", "en" ]
MAP: [ "killall", "-USR1", "wayvr"] # reloads keymap
# Series of keypresses to be used in a layout.
# Format: keyName [DOWN|UP]
# keyName must be a valid virtual key from the VirtualKey enum (see above)
# DOWN|UP: can be omitted for an implicit "keyName DOWN, keyName UP"
macros:
MAP: [ "killall", "-USR1", "wayvr"] # reloads keymap
KILL: [ "LSuper DOWN", "LCtrl DOWN", "Escape", "LCtrl UP", "LSuper UP" ]
COPY: [ "LCtrl DOWN", "C", "LCtrl UP" ]
PASTE: [ "LCtrl DOWN", "V", "LCtrl UP" ]

View File

@ -336,3 +336,22 @@ pub fn load_pw_token_config() -> anyhow::Result<PwTokenMap> {
let conf: TokenConf = serde_yaml::from_str(yaml.as_str())?;
Ok(conf.pw_tokens)
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct PlayspaceState {
pub openvr_space_center: Affine3A,
pub openxr_space_center: Affine3A,
}
pub fn load_playspace_state() -> anyhow::Result<PlayspaceState> {
let json = std::fs::read_to_string(config_io::get_config_file_path("playspace.json5"))?;
let state: PlayspaceState = serde_json5::from_str(json.as_str())?;
Ok(state)
}
pub fn save_playspace_state(state: &PlayspaceState) -> anyhow::Result<()> {
let json = serde_json5::to_string(state)?;
std::fs::write(config_io::get_config_file_path("playspace.json5"), json)?;
Ok(())
}

View File

@ -71,7 +71,7 @@ pub const CTRL: KeyModifier = 0x04;
pub const ALT: KeyModifier = 0x08;
pub const NUM_LOCK: KeyModifier = 0x10;
pub const SUPER: KeyModifier = 0x40;
pub const META: KeyModifier = 0x80;
pub const ALTGR: KeyModifier = 0x80;
#[allow(non_camel_case_types)]
#[repr(u16)]
@ -173,7 +173,8 @@ pub enum VirtualKey {
RCtrl,
KP_Divide,
Print,
Meta, // Right Alt aka AltGr
#[strum(serialize = "AltGr", serialize = "Meta")]
AltGr,
Home = 110,
Up,
Prior,
@ -242,7 +243,7 @@ pub static KEYS_TO_MODS: LazyLock<IdMap<VirtualKey, KeyModifier>> = LazyLock::ne
VirtualKey::NumLock => NUM_LOCK,
VirtualKey::LSuper => SUPER,
VirtualKey::RSuper => SUPER,
VirtualKey::Meta => META,
VirtualKey::AltGr => ALTGR,
}
});
@ -254,7 +255,7 @@ pub static MODS_TO_KEYS: LazyLock<IdMap<KeyModifier, Vec<VirtualKey>>> = LazyLoc
ALT => vec![VirtualKey::LAlt],
NUM_LOCK => vec![VirtualKey::NumLock],
SUPER => vec![VirtualKey::LSuper, VirtualKey::RSuper],
META => vec![VirtualKey::Meta],
ALTGR => vec![VirtualKey::AltGr],
}
});
@ -353,7 +354,7 @@ impl XkbKeymap {
let state0 = xkb::State::new(&self.inner);
let mut state1 = xkb::State::new(&self.inner);
state1.update_key(
xkb::Keycode::from(VirtualKey::Meta as u32),
xkb::Keycode::from(VirtualKey::AltGr as u32),
xkb::KeyDirection::Down,
);

View File

@ -484,9 +484,9 @@ impl ScreenCastManager {
))?;
let requested_types = if params.screens_only {
SOURCE_MONITOR | SOURCE_WINDOW | SOURCE_VIRTUAL
} else {
SOURCE_MONITOR
} else {
SOURCE_MONITOR | SOURCE_WINDOW | SOURCE_VIRTUAL
};
let source_types = requested_types & globals.source_types;

View File

@ -71,7 +71,8 @@ pub enum AltModifier {
Ctrl,
Alt,
Super,
Meta,
#[serde(alias = "Meta")]
AltGr,
}
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)]

View File

@ -37,10 +37,11 @@ pub struct MonadoDumpSessionFrame {
}
#[derive(Clone, Copy)]
pub enum RecenterMode {
pub enum DashPlayspaceTask {
FixFloor,
Recenter,
Reset,
SaveCenter,
}
#[derive(Clone, Copy)]
@ -69,7 +70,7 @@ pub trait DashInterface<T> {
fn monado_brightness_set(&mut self, data: &mut T, brightness: f32) -> Option<()>;
fn monado_metrics_set_enabled(&mut self, data: &mut T, enabled: bool) -> bool;
fn monado_metrics_dump_session_frames(&mut self, data: &mut T) -> Vec<MonadoDumpSessionFrame>;
fn recenter_playspace(&mut self, data: &mut T, mode: RecenterMode) -> anyhow::Result<()>;
fn playspace_task(&mut self, data: &mut T, mode: DashPlayspaceTask) -> 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;
fn config_changed(&mut self, data: &mut T, kind: ConfigChangeKind);

View File

@ -5,7 +5,7 @@ use wayvr_ipc::{
use crate::{
config::GeneralConfig,
dash_interface::{self, ConfigChangeKind, DashInterface, RecenterMode},
dash_interface::{self, ConfigChangeKind, DashInterface, DashPlayspaceTask},
desktop_finder::DesktopFinder,
gen_id,
};
@ -217,7 +217,7 @@ impl DashInterface<()> for DashInterfaceEmulated {
}
}
fn recenter_playspace(&mut self, _: &mut (), _: RecenterMode) -> anyhow::Result<()> {
fn playspace(&mut self, _: &mut (), _: DashPlayspaceTask) -> anyhow::Result<()> {
// stub!
Ok(())
}