diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 1ae02814..4caae5d5 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -120,7 +120,7 @@ "DEFAULT_CURVATURE": "Default overlay curvature", "DEFAULT_OPACITY": "Default overlay opacity", "DEFAULT_POSITIONING": "Default overlay positioning", - "DEFAULT_OVERLAY_DEFAULTS_HELP": "Only applies to new overlays", + "OVERLAY_DEFAULTS_HELP": "Only applies to new overlays", "ENABLED": "Enabled", "FEATURES": "Features", "FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch", diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs index 6ea81100..f6061ee2 100644 --- a/wayvr/src/backend/input.rs +++ b/wayvr/src/backend/input.rs @@ -851,7 +851,7 @@ fn start_grab( app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( OverlaySelector::Name(ANCHOR_NAME.clone()), Box::new(|app, o| { - o.activate(app); + o.activate(app, true); }), ))); @@ -870,7 +870,7 @@ fn start_grab( o.default_state.positioning = Positioning::FollowHand { hand, lerp: 0.1 }; o.default_state.align_to_hmd = true; - o.activate(app); + o.activate(app, true); }), ))); } diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index b63b5026..a3ecb808 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -354,7 +354,7 @@ pub fn openxr_run(args: &Args) -> Result<(), BackendError> { let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic if watch.config.active_state.is_none() { - watch.config.activate(&mut app); + watch.config.activate(&mut app, true); } let watch_state = watch.config.active_state.as_mut().unwrap(); let watch_transform = watch_state.transform; diff --git a/wayvr/src/backend/task.rs b/wayvr/src/backend/task.rs index 94a8ee06..1b9e5435 100644 --- a/wayvr/src/backend/task.rs +++ b/wayvr/src/backend/task.rs @@ -6,6 +6,7 @@ use std::{ }; use serde::Deserialize; +use strum::EnumIs; use crate::{ backend::input, @@ -97,10 +98,12 @@ pub enum ToggleMode { Toggle, } -#[derive(Clone)] +#[derive(Clone, EnumIs)] pub enum SpawnPos { - /// Always spawn at the designated pos + /// Always spawn at designated position Fixed, + /// Always spawn at designated position and orientation + FixedNoRealign, /// Automatically spread out for user experience Spread, /// Spawn relative to a different overlay diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index 93ffc509..62b04049 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -956,7 +956,7 @@ impl WvrServerState { tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( OverlaySelector::Name(ALTTAB_HELP_NAME.clone()), Box::new(move |app, o| { - o.activate(app); + o.activate(app, true); }), ))); } else { diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 8d9581e7..04b5f948 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -11,7 +11,7 @@ use wlx_common::{ config::{ AltModifier, CaptureMethod, ChromaKeyParams, DefaultPositioning, GeneralConfig, HandsfreeAltTab, HandsfreePointer, InputCaptureMethod, InputEmulationMethod, PinnedApp, - SerializedWindowSet, SerializedWindowStates, + SerializedWindowSet, }, config_io, locale::Language, @@ -287,7 +287,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { #[derive(Serialize)] pub struct AutoState { pub sets: Vec, - pub global_set: SerializedWindowStates, + pub spawn_overlays: Vec>, pub last_set: u32, pub attribs: AStrMap>, } @@ -302,7 +302,7 @@ pub fn save_state(config: &GeneralConfig) -> anyhow::Result<()> { let conf = AutoState { sets: config.sets.clone(), last_set: config.last_set, - global_set: config.global_set.clone(), + spawn_overlays: config.spawn_overlays.clone(), attribs: config.attribs.clone(), }; diff --git a/wayvr/src/gui/panel/button.rs b/wayvr/src/gui/panel/button.rs index a050607b..fd53d5a9 100644 --- a/wayvr/src/gui/panel/button.rs +++ b/wayvr/src/gui/panel/button.rs @@ -562,7 +562,9 @@ pub(super) fn setup_custom_button( return Ok(EventResult::Pass); } - let name = crate::overlays::passthrough::new_passtrhu_name(); + let name = crate::overlays::passthrough::new_passthru_name( + &app.session.config.spawn_overlays, + ); app.tasks.enqueue(TaskType::Overlay(OverlayTask::Spawn( OverlaySelector::Name(name.clone()), SpawnPos::Spread, diff --git a/wayvr/src/overlays/passthrough.rs b/wayvr/src/overlays/passthrough.rs index 24d79204..87f5b00e 100644 --- a/wayvr/src/overlays/passthrough.rs +++ b/wayvr/src/overlays/passthrough.rs @@ -27,16 +27,22 @@ use crate::{ }, }; -pub const PASSTHRU_PREFIX: &str = "__wvr_passthru-"; +pub const PASSTHRU_PREFIX: &str = "Passthru-"; static PASSTHRU_COUNTER: AtomicUsize = AtomicUsize::new(1); -pub fn new_passtrhu_name() -> Arc { - format!( - "{PASSTHRU_PREFIX}{}", - PASSTHRU_COUNTER.fetch_add(1, Ordering::Relaxed) - ) - .into() +pub fn new_passthru_name(existing: &[Arc]) -> Arc { + let mut max_num = PASSTHRU_COUNTER.load(Ordering::Relaxed) - 1; + for name in existing { + if let Some(n) = name + .strip_prefix(PASSTHRU_PREFIX) + .and_then(|s| s.parse::().ok()) + { + max_num = max_num.max(n); + } + } + let next = max_num + 1; + format!("{PASSTHRU_PREFIX}{next}").into() } pub fn new_passthru(name: Arc, app: &AppState) -> OverlayWindowConfig { diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index 314fe2ca..d26e553b 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -24,6 +24,7 @@ use crate::{ dashboard::{DASH_NAME, create_dash_frontend}, edit::EditWrapperManager, keyboard::create_keyboard, + passthrough::{PASSTHRU_PREFIX, new_passthru}, screen::create_screens, toast::Toast, watch::{WATCH_NAME, create_watch}, @@ -42,6 +43,7 @@ use crate::{ }; pub const MAX_OVERLAY_SETS: usize = 6; +pub const GLOBAL_SET: &str = "global"; pub struct OverlayWindowManager { wrappers: EditWrapperManager, @@ -163,20 +165,21 @@ where let saved_passthrus: Vec<_> = app .session .config - .global_set + .spawn_overlays .iter() - .filter(|(name, _)| name.starts_with(crate::overlays::passthrough::PASSTHRU_PREFIX)) - .map(|(name, state)| (name.clone(), state.clone())) + .filter(|name| name.starts_with(PASSTHRU_PREFIX)) + .cloned() .collect(); - for (name, saved_state) in saved_passthrus { + for name in saved_passthrus { if me.lookup(&name).is_none() { - let config = crate::overlays::passthrough::new_passthru(name.clone(), app); - let id = me.add(OverlayWindowData::from_config(config), app); - if let Some(o) = me.mut_by_id(id) { - o.config.active_state = Some(saved_state); - o.config.reset(app, false); - log::debug!("restored passthru {}", name); - } + let mut config = new_passthru(name.clone(), app); + config.show_on_spawn = !me.global_set.hidden_overlays.arc_get(&*name).is_some(); + + me.add_with_spawn_pos( + OverlayWindowData::from_config(config), + app, + SpawnPos::FixedNoRealign, + ); } } @@ -208,7 +211,7 @@ where OverlayTask::ResetOverlay(sel) => { if let Some(o) = self.mut_by_selector(&sel) { let was_active = o.config.is_active(); - o.config.activate(app); + o.config.activate(app, true); if !was_active { self.visible_overlays_changed(app)?; } @@ -260,7 +263,7 @@ where o.config.reset(app, false); } else { // no saved state - o.config.activate(app); + o.config.activate(app, true); } self.visible_overlays_changed(app)?; } @@ -272,7 +275,7 @@ where self.mut_by_selector(&OverlaySelector::Name(DASH_NAME.into())) { if overlay.config.active_state.is_none() { - overlay.config.activate(app); + overlay.config.activate(app, true); } else { overlay.config.deactivate(); } @@ -294,7 +297,7 @@ where } self.sets.push(OverlayWindowSet::default()); self.switch_to_set(app, Some(new_idx), false); - self.overlays[self.keyboard_id].config.activate(app); + self.overlays[self.keyboard_id].config.activate(app, true); self.sets_changed(app); self.visible_overlays_changed(app)?; } @@ -335,7 +338,7 @@ where if watch.config.active_state.is_some() { watch.config.deactivate(); } else { - watch.config.activate(app); + watch.config.activate(app, true); } } @@ -481,10 +484,11 @@ where } } -const SAVED_ATTRIBS: [BackendAttrib; 3] = [ +const SAVED_ATTRIBS: &[BackendAttrib] = &[ BackendAttrib::Stereo, BackendAttrib::StereoFullFrame, BackendAttrib::MouseTransform, + BackendAttrib::WindowSize, ]; impl OverlayWindowManager { @@ -503,7 +507,7 @@ impl OverlayWindowManager { } pub fn persist_layout(&mut self, app: &mut AppState) { - app.session.config.global_set.clear(); + app.session.config.spawn_overlays.clear(); app.session.config.sets.clear(); app.session.config.sets.reserve(self.sets.len()); app.session.config.last_set = self.restore_set as _; @@ -533,7 +537,12 @@ impl OverlayWindowManager { } } - let hidden_overlays: HashMap<_, _> = set.hidden_overlays.iter().cloned().collect(); + let hidden_overlays: HashMap<_, _> = set + .hidden_overlays + .iter() + .filter(|(k, _)| !overlays.contains_key(k.as_ref())) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); let serialized = SerializedWindowSet { name: set.name.clone(), @@ -543,29 +552,38 @@ impl OverlayWindowManager { app.session.config.sets.push(serialized); } - // global overlays + for o in self.overlays.values() { + if matches!(o.config.category, OverlayCategory::Passthru) { + app.session + .config + .spawn_overlays + .push(o.config.name.clone()); + } + } + + // serialize global set into `sets` with key GLOBAL_SET + // overlays: active states + inactive states; hidden_overlays: explicitly toggled off + let mut global_overlays: HashMap<_, _> = + self.global_set.inactive_overlays.iter().cloned().collect(); for o in self.overlays.values() { if o.config.global { if let Some(state) = &o.config.active_state { - app.session - .config - .global_set - .insert(o.config.name.clone(), state.clone()); + global_overlays.insert(o.config.name.clone(), state.clone()); } } } - for (name, state) in &self.global_set.hidden_overlays { - app.session - .config - .global_set - .insert(name.clone(), state.clone()); - } - for (name, state) in &self.global_set.inactive_overlays { - app.session - .config - .global_set - .insert(name.clone(), state.clone()); - } + let global_hidden: HashMap<_, _> = self + .global_set + .hidden_overlays + .iter() + .filter(|(k, _)| !global_overlays.contains_key(k.as_ref())) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + app.session.config.sets.push(SerializedWindowSet { + name: GLOBAL_SET.into(), + overlays: global_overlays, + hidden_overlays: global_hidden, + }); // BackendAttrib for o in self.overlays.values() { @@ -599,6 +617,10 @@ impl OverlayWindowManager { self.sets.reserve(app.session.config.sets.len()); for (i, s) in app.session.config.sets.iter().enumerate() { + if s.name.as_ref() == GLOBAL_SET { + continue; + } + let mut overlays = SecondaryMap::new(); let mut inactive_overlays = AStrMap::new(); @@ -628,33 +650,69 @@ impl OverlayWindowManager { }); } - // global overlays - for (name, ows) in app.session.config.global_set.clone() { - let mut ows = ows.clone(); + // global overlays: prefer `sets` with key GLOBAL_SET, fall back to legacy `global_set` + if let Some(global_s) = app + .session + .config + .sets + .iter() + .find(|s| s.name.as_ref() == GLOBAL_SET) + { + let global_s = global_s.clone(); + let mut overlays = SecondaryMap::new(); + let mut inactive_overlays = AStrMap::new(); - // fix angle_fade missing on watch if loading older state - if name.as_ref() == WATCH_NAME { - ows.angle_fade = true; + for (name, o) in &global_s.overlays { + if let Some(id) = self.lookup(name) { + log::debug!("global set: loaded state for {name}"); + overlays.insert(id, o.clone()); + if let Some(ov) = self.mut_by_id(id) + && ov.config.active_state.is_some() + { + ov.config.global = true; + ov.config.active_state = Some(o.clone()); + ov.config.reset(app, false); + } + } else { + log::debug!( + "global set has saved state for {name} which doesn't exist. will apply state once added." + ); + inactive_overlays.arc_set(name.clone(), o.clone()); + } } - if let Some(oid) = self.lookup(&name) - && let Some(o) = self.mut_by_id(oid) - { - o.config.global = true; - if o.config.active_state.is_none() { - self.global_set.hidden_overlays.arc_set(name.clone(), ows); - } else { + let hidden_overlays: AStrMap<_> = global_s + .hidden_overlays + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + self.global_set.overlays = overlays; + self.global_set.inactive_overlays = inactive_overlays; + self.global_set.hidden_overlays = hidden_overlays; + } else { + for (name, ows) in app.session.config.global_set.clone() { + let mut ows = ows.clone(); + + // fix angle_fade missing on watch if loading older state + if name.as_ref() == WATCH_NAME { + ows.angle_fade = true; + } + + if let Some(oid) = self.lookup(&name) + && let Some(o) = self.mut_by_id(oid) + && o.config.active_state.is_some() + { + o.config.global = true; o.config.active_state = Some(ows); o.config.reset(app, false); + log::debug!("global set: loaded state for {name}"); + } else { + log::debug!( + "global set has saved state for {name} which doesn't exist or is inactive. will apply state once added." + ); + self.global_set.inactive_overlays.arc_set(name.clone(), ows); } - log::debug!("global set: loaded state for {name}"); - } else { - log::debug!( - "global set has saved state for {name} which doesn't exist. will apply state once added." - ); - self.global_set - .inactive_overlays - .arc_set(name.clone(), ows.clone()); } } @@ -875,7 +933,9 @@ impl OverlayWindowManager { if !shown && show_on_spawn { log::debug!("activating {name} due to show_on_spawn"); - self.overlays[oid].config.activate(app); + self.overlays[oid] + .config + .activate(app, !spawn_pos.is_fixed_no_realign()); self.apply_spawn_pos(app, oid, spawn_pos); } if !internal && let Err(e) = self.overlays_changed(app) { @@ -889,7 +949,7 @@ impl OverlayWindowManager { fn apply_spawn_pos(&mut self, app: &mut AppState, oid: OverlayID, spawn_pos: SpawnPos) { match spawn_pos { - SpawnPos::Fixed => {} + SpawnPos::Fixed | SpawnPos::FixedNoRealign => {} SpawnPos::Spread => { let Some(parent_id) = self.spread_parent_for(oid) else { return; diff --git a/wayvr/src/windowing/window.rs b/wayvr/src/windowing/window.rs index a61ceb12..34a15196 100644 --- a/wayvr/src/windowing/window.rs +++ b/wayvr/src/windowing/window.rs @@ -122,11 +122,11 @@ impl OverlayWindowConfig { /// only call this directly for `OverlayCategory::Internal` /// for anything else, use `OverlayTask::ToggleOverlay` instead - pub fn activate(&mut self, app: &mut AppState) { + pub fn activate(&mut self, app: &mut AppState, realign: bool) { log::debug!("activate {}", self.name.as_ref()); self.dirty = true; self.active_state = Some(self.default_state.clone()); - self.reset(app, true); + self.reset(app, realign); } /// only call this directly for `OverlayCategory::Internal` diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index f1ced2c4..b49085ba 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -498,8 +498,13 @@ pub struct GeneralConfig { pub sets: Vec, #[serde(default)] + /// Obsolete, only used for reading + /// Use `sets` with `name: "global"` pub global_set: SerializedWindowStates, + #[serde(default)] + pub spawn_overlays: Vec>, + #[serde(default)] pub autostart_apps: Vec, diff --git a/wlx-common/src/overlays.rs b/wlx-common/src/overlays.rs index 2728b596..d0598e3e 100644 --- a/wlx-common/src/overlays.rs +++ b/wlx-common/src/overlays.rs @@ -27,6 +27,7 @@ pub enum BackendAttrib { MouseTransform, Icon, Resizable, + WindowSize, } #[derive(Debug, Clone, Serialize, Deserialize)]