feat: persist screen visibility & curvature
This commit is contained in:
@@ -15,8 +15,9 @@ use thiserror::Error;
|
|||||||
use wlx_capture::wayland::{OutputChangeEvent, WlxClient};
|
use wlx_capture::wayland::{OutputChangeEvent, WlxClient};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::{AStrMapExt, AStrSetExt},
|
||||||
overlays::{
|
overlays::{
|
||||||
keyboard::create_keyboard,
|
keyboard::{create_keyboard, KEYBOARD_NAME},
|
||||||
screen::{create_screen_interaction, create_screen_renderer_wl, load_pw_token_config},
|
screen::{create_screen_interaction, create_screen_renderer_wl, load_pw_token_config},
|
||||||
watch::{create_watch, create_watch_canvas, WATCH_NAME},
|
watch::{create_watch, create_watch_canvas, WATCH_NAME},
|
||||||
},
|
},
|
||||||
@@ -66,15 +67,21 @@ where
|
|||||||
let mut show_screens = app.session.config.show_screens.clone();
|
let mut show_screens = app.session.config.show_screens.clone();
|
||||||
if show_screens.is_empty() {
|
if show_screens.is_empty() {
|
||||||
if let Some((_, s, _)) = data.screens.first() {
|
if let Some((_, s, _)) = data.screens.first() {
|
||||||
show_screens.push(s.name.clone());
|
show_screens.arc_ins(s.name.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (meta, mut state, backend) in data.screens {
|
for (meta, mut state, backend) in data.screens {
|
||||||
if show_screens.contains(&state.name) {
|
if show_screens.arc_get(state.name.as_ref()) {
|
||||||
state.show_hide = true;
|
state.show_hide = true;
|
||||||
state.want_visible = false;
|
state.want_visible = false;
|
||||||
}
|
}
|
||||||
|
state.curvature = app
|
||||||
|
.session
|
||||||
|
.config
|
||||||
|
.curve_values
|
||||||
|
.arc_get(state.name.as_ref())
|
||||||
|
.copied();
|
||||||
overlays.insert(
|
overlays.insert(
|
||||||
state.id,
|
state.id,
|
||||||
OverlayData::<T> {
|
OverlayData::<T> {
|
||||||
@@ -93,6 +100,12 @@ where
|
|||||||
let mut keyboard = create_keyboard(app)?;
|
let mut keyboard = create_keyboard(app)?;
|
||||||
keyboard.state.show_hide = true;
|
keyboard.state.show_hide = true;
|
||||||
keyboard.state.want_visible = false;
|
keyboard.state.want_visible = false;
|
||||||
|
keyboard.state.curvature = app
|
||||||
|
.session
|
||||||
|
.config
|
||||||
|
.curve_values
|
||||||
|
.arc_get(KEYBOARD_NAME)
|
||||||
|
.copied();
|
||||||
overlays.insert(keyboard.state.id, keyboard);
|
overlays.insert(keyboard.state.id, keyboard);
|
||||||
|
|
||||||
Ok(Self { overlays, wl })
|
Ok(Self { overlays, wl })
|
||||||
@@ -283,7 +296,10 @@ where
|
|||||||
self.overlays.values_mut().for_each(|o| {
|
self.overlays.values_mut().for_each(|o| {
|
||||||
if o.state.show_hide {
|
if o.state.show_hide {
|
||||||
o.state.want_visible = !any_shown;
|
o.state.want_visible = !any_shown;
|
||||||
if o.state.want_visible && app.session.config.realign_on_showhide && o.state.recenter {
|
if o.state.want_visible
|
||||||
|
&& app.session.config.realign_on_showhide
|
||||||
|
&& o.state.recenter
|
||||||
|
{
|
||||||
o.state.reset(app, false);
|
o.state.reset(app, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use glam::{Affine3A, Vec2, Vec3A};
|
|||||||
use ovr_overlay::TrackedDeviceIndex;
|
use ovr_overlay::TrackedDeviceIndex;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use crate::config::GeneralConfig;
|
use crate::config::{save_state, AStrMapExt, GeneralConfig};
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -246,6 +246,7 @@ struct RayHit {
|
|||||||
pub struct GrabData {
|
pub struct GrabData {
|
||||||
pub offset: Vec3A,
|
pub offset: Vec3A,
|
||||||
pub grabbed_id: usize,
|
pub grabbed_id: usize,
|
||||||
|
pub old_curvature: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -288,7 +289,7 @@ where
|
|||||||
let mut pointer = &mut app.input_state.pointers[idx];
|
let mut pointer = &mut app.input_state.pointers[idx];
|
||||||
if let Some(grab_data) = pointer.interaction.grabbed {
|
if let Some(grab_data) = pointer.interaction.grabbed {
|
||||||
if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) {
|
if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) {
|
||||||
pointer.handle_grabbed(grabbed, hmd, &app.session.config);
|
pointer.handle_grabbed(grabbed, hmd, &mut app.session.config);
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Grabbed overlay {} does not exist", grab_data.grabbed_id);
|
log::warn!("Grabbed overlay {} does not exist", grab_data.grabbed_id);
|
||||||
pointer.interaction.grabbed = None;
|
pointer.interaction.grabbed = None;
|
||||||
@@ -469,12 +470,17 @@ impl Pointer {
|
|||||||
self.interaction.grabbed = Some(GrabData {
|
self.interaction.grabbed = Some(GrabData {
|
||||||
offset,
|
offset,
|
||||||
grabbed_id: overlay.state.id,
|
grabbed_id: overlay.state.id,
|
||||||
|
old_curvature: overlay.state.curvature,
|
||||||
});
|
});
|
||||||
log::info!("Hand {}: grabbed {}", self.idx, overlay.state.name);
|
log::info!("Hand {}: grabbed {}", self.idx, overlay.state.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_grabbed<O>(&mut self, overlay: &mut OverlayData<O>, hmd: &Affine3A, config: &GeneralConfig)
|
fn handle_grabbed<O>(
|
||||||
where
|
&mut self,
|
||||||
|
overlay: &mut OverlayData<O>,
|
||||||
|
hmd: &Affine3A,
|
||||||
|
config: &mut GeneralConfig,
|
||||||
|
) where
|
||||||
O: Default,
|
O: Default,
|
||||||
{
|
{
|
||||||
if self.now.grab {
|
if self.now.grab {
|
||||||
@@ -509,6 +515,26 @@ impl Pointer {
|
|||||||
hmd.inverse()
|
hmd.inverse()
|
||||||
.transform_point3a(overlay.state.transform.translation),
|
.transform_point3a(overlay.state.transform.translation),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(grab_data) = self.interaction.grabbed.as_ref() {
|
||||||
|
let mut state_dirty = false;
|
||||||
|
if overlay.state.curvature != grab_data.old_curvature {
|
||||||
|
if let Some(val) = overlay.state.curvature {
|
||||||
|
config.curve_values.arc_ins(overlay.state.name.clone(), val);
|
||||||
|
} else {
|
||||||
|
let ref_name = overlay.state.name.as_ref();
|
||||||
|
config.curve_values.arc_rm(ref_name);
|
||||||
|
}
|
||||||
|
state_dirty = true;
|
||||||
|
}
|
||||||
|
if state_dirty {
|
||||||
|
match save_state(config) {
|
||||||
|
Ok(_) => log::debug!("Saved state"),
|
||||||
|
Err(e) => log::error!("Failed to save state: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.interaction.grabbed = None;
|
self.interaction.grabbed = None;
|
||||||
log::info!("Hand {}: dropped {}", self.idx, overlay.state.name);
|
log::info!("Hand {}: dropped {}", self.idx, overlay.state.name);
|
||||||
}
|
}
|
||||||
|
|||||||
146
src/config.rs
146
src/config.rs
@@ -1,3 +1,4 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::config_io;
|
use crate::config_io;
|
||||||
@@ -15,6 +16,68 @@ use log::error;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
pub type AStrMap<V> = Vec<(Arc<str>, V)>;
|
||||||
|
|
||||||
|
pub trait AStrMapExt<V> {
|
||||||
|
fn arc_ins(&mut self, key: Arc<str>, value: V) -> bool;
|
||||||
|
fn arc_get(&self, key: &str) -> Option<&V>;
|
||||||
|
fn arc_rm(&mut self, key: &str) -> Option<V>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> AStrMapExt<V> for AStrMap<V> {
|
||||||
|
fn arc_ins(&mut self, key: Arc<str>, value: V) -> bool {
|
||||||
|
if self.iter().any(|(k, _)| k.as_ref().eq(key.as_ref())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.push((key, value));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arc_get(&self, key: &str) -> Option<&V> {
|
||||||
|
self.iter()
|
||||||
|
.find_map(|(k, v)| if k.as_ref().eq(key) { Some(v) } else { None })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arc_rm(&mut self, key: &str) -> Option<V> {
|
||||||
|
let index = self.iter().position(|(k, _)| k.as_ref().eq(key));
|
||||||
|
index.map(|i| self.remove(i).1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AStrSet = Vec<Arc<str>>;
|
||||||
|
|
||||||
|
pub trait AStrSetExt {
|
||||||
|
fn arc_ins(&mut self, value: Arc<str>) -> bool;
|
||||||
|
fn arc_get(&self, value: &str) -> bool;
|
||||||
|
fn arc_rm(&mut self, value: &str) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AStrSetExt for AStrSet {
|
||||||
|
fn arc_ins(&mut self, value: Arc<str>) -> bool {
|
||||||
|
if self.iter().any(|v| v.as_ref().eq(value.as_ref())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.push(value);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arc_get(&self, value: &str) -> bool {
|
||||||
|
self.iter().any(|v| v.as_ref().eq(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arc_rm(&mut self, value: &str) -> bool {
|
||||||
|
let index = self.iter().position(|v| v.as_ref().eq(value));
|
||||||
|
index
|
||||||
|
.map(|i| {
|
||||||
|
self.remove(i);
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PwTokenMap = AStrMap<String>;
|
||||||
|
|
||||||
pub fn def_watch_pos() -> [f32; 3] {
|
pub fn def_watch_pos() -> [f32; 3] {
|
||||||
[-0.03, -0.01, 0.125]
|
[-0.03, -0.01, 0.125]
|
||||||
}
|
}
|
||||||
@@ -27,8 +90,8 @@ pub fn def_left() -> LeftRight {
|
|||||||
LeftRight::Left
|
LeftRight::Left
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn def_pw_tokens() -> Vec<(String, String)> {
|
pub fn def_pw_tokens() -> PwTokenMap {
|
||||||
Vec::new()
|
AStrMap::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn def_click_freeze_time_ms() -> u32 {
|
fn def_click_freeze_time_ms() -> u32 {
|
||||||
@@ -59,8 +122,12 @@ fn def_osc_port() -> u16 {
|
|||||||
9000
|
9000
|
||||||
}
|
}
|
||||||
|
|
||||||
fn def_screens() -> Vec<Arc<str>> {
|
fn def_screens() -> AStrSet {
|
||||||
vec![]
|
AStrSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn def_curve_values() -> AStrMap<f32> {
|
||||||
|
AStrMap::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn def_auto() -> Arc<str> {
|
fn def_auto() -> Arc<str> {
|
||||||
@@ -113,7 +180,7 @@ pub struct GeneralConfig {
|
|||||||
pub long_press_duration: f32,
|
pub long_press_duration: f32,
|
||||||
|
|
||||||
#[serde(default = "def_pw_tokens")]
|
#[serde(default = "def_pw_tokens")]
|
||||||
pub pw_tokens: Vec<(String, String)>,
|
pub pw_tokens: PwTokenMap,
|
||||||
|
|
||||||
#[serde(default = "def_osc_port")]
|
#[serde(default = "def_osc_port")]
|
||||||
pub osc_out_port: u16,
|
pub osc_out_port: u16,
|
||||||
@@ -125,7 +192,10 @@ pub struct GeneralConfig {
|
|||||||
pub double_cursor_fix: bool,
|
pub double_cursor_fix: bool,
|
||||||
|
|
||||||
#[serde(default = "def_screens")]
|
#[serde(default = "def_screens")]
|
||||||
pub show_screens: Vec<Arc<str>>,
|
pub show_screens: AStrSet,
|
||||||
|
|
||||||
|
#[serde(default = "def_curve_values")]
|
||||||
|
pub curve_values: AStrMap<f32>,
|
||||||
|
|
||||||
#[serde(default = "def_auto")]
|
#[serde(default = "def_auto")]
|
||||||
pub capture_method: Arc<str>,
|
pub capture_method: Arc<str>,
|
||||||
@@ -270,3 +340,67 @@ pub fn load_general() -> GeneralConfig {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config that is saved from the settings panel
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AutoSettings {
|
||||||
|
pub watch_pos: [f32; 3],
|
||||||
|
pub watch_rot: [f32; 4],
|
||||||
|
pub watch_hand: LeftRight,
|
||||||
|
pub watch_view_angle_min: f32,
|
||||||
|
pub watch_view_angle_max: f32,
|
||||||
|
pub notifications_enabled: bool,
|
||||||
|
pub notifications_sound_enabled: bool,
|
||||||
|
pub realign_on_showhide: bool,
|
||||||
|
pub allow_sliding: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_path() -> PathBuf {
|
||||||
|
let mut path = config_io::get_conf_d_path();
|
||||||
|
path.push("zz-saved-config.json5");
|
||||||
|
path
|
||||||
|
}
|
||||||
|
pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||||
|
let conf = AutoSettings {
|
||||||
|
watch_pos: config.watch_pos,
|
||||||
|
watch_rot: config.watch_rot,
|
||||||
|
watch_hand: config.watch_hand,
|
||||||
|
watch_view_angle_min: config.watch_view_angle_min,
|
||||||
|
watch_view_angle_max: config.watch_view_angle_max,
|
||||||
|
notifications_enabled: config.notifications_enabled,
|
||||||
|
notifications_sound_enabled: config.notifications_sound_enabled,
|
||||||
|
realign_on_showhide: config.realign_on_showhide,
|
||||||
|
allow_sliding: config.allow_sliding,
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
||||||
|
std::fs::write(get_settings_path(), json)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config that is saved after manipulating overlays
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AutoState {
|
||||||
|
pub show_screens: AStrSet,
|
||||||
|
pub curve_values: AStrMap<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state_path() -> PathBuf {
|
||||||
|
let mut path = config_io::get_conf_d_path();
|
||||||
|
path.push("zz-saved-state.json5");
|
||||||
|
path
|
||||||
|
}
|
||||||
|
pub fn save_state(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||||
|
let conf = AutoState {
|
||||||
|
show_screens: config.show_screens.clone(),
|
||||||
|
curve_values: config.curve_values.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
||||||
|
std::fs::write(get_state_path(), json)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
f32::consts::PI,
|
f32::consts::PI,
|
||||||
ops::Add,
|
ops::Add,
|
||||||
path::PathBuf,
|
|
||||||
process::{self, Child},
|
process::{self, Child},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use glam::{Quat, Vec3A, Vec4};
|
use glam::{Quat, Vec3A, Vec4};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{
|
backend::{
|
||||||
@@ -16,13 +15,12 @@ use crate::{
|
|||||||
input::PointerMode,
|
input::PointerMode,
|
||||||
overlay::RelativeTo,
|
overlay::RelativeTo,
|
||||||
},
|
},
|
||||||
config::{def_half, def_left, def_point7, def_true, def_watch_pos, def_watch_rot},
|
config::{save_settings, save_state, AStrSetExt},
|
||||||
config_io,
|
|
||||||
overlays::{
|
overlays::{
|
||||||
toast::{Toast, ToastTopic},
|
toast::{Toast, ToastTopic},
|
||||||
watch::WATCH_NAME,
|
watch::WATCH_NAME,
|
||||||
},
|
},
|
||||||
state::{AppState, LeftRight},
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ExecArgs, ModularControl, ModularData};
|
use super::{ExecArgs, ModularControl, ModularData};
|
||||||
@@ -44,6 +42,7 @@ pub enum Axis {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub enum HighlightTest {
|
pub enum HighlightTest {
|
||||||
|
AllowSliding,
|
||||||
AutoRealign,
|
AutoRealign,
|
||||||
NotificationSounds,
|
NotificationSounds,
|
||||||
Notifications,
|
Notifications,
|
||||||
@@ -51,6 +50,7 @@ pub enum HighlightTest {
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub enum SystemAction {
|
pub enum SystemAction {
|
||||||
|
ToggleAllowSliding,
|
||||||
ToggleAutoRealign,
|
ToggleAutoRealign,
|
||||||
ToggleNotificationSounds,
|
ToggleNotificationSounds,
|
||||||
ToggleNotifications,
|
ToggleNotifications,
|
||||||
@@ -289,6 +289,7 @@ fn modular_button_highlight(
|
|||||||
|
|
||||||
if let Some(test) = &data.highlight {
|
if let Some(test) = &data.highlight {
|
||||||
let lit = match test {
|
let lit = match test {
|
||||||
|
HighlightTest::AllowSliding => app.session.config.allow_sliding,
|
||||||
HighlightTest::AutoRealign => app.session.config.realign_on_showhide,
|
HighlightTest::AutoRealign => app.session.config.realign_on_showhide,
|
||||||
HighlightTest::NotificationSounds => app.session.config.notifications_sound_enabled,
|
HighlightTest::NotificationSounds => app.session.config.notifications_sound_enabled,
|
||||||
HighlightTest::Notifications => app.session.config.notifications_enabled,
|
HighlightTest::Notifications => app.session.config.notifications_enabled,
|
||||||
@@ -330,19 +331,30 @@ fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ENABLED_DISABLED: [&str; 2] = ["enabled", "disabled"];
|
||||||
|
|
||||||
fn run_system(action: &SystemAction, app: &mut AppState) {
|
fn run_system(action: &SystemAction, app: &mut AppState) {
|
||||||
match action {
|
match action {
|
||||||
|
SystemAction::ToggleAllowSliding => {
|
||||||
|
app.session.config.allow_sliding = !app.session.config.allow_sliding;
|
||||||
|
Toast::new(
|
||||||
|
ToastTopic::System,
|
||||||
|
format!(
|
||||||
|
"Sliding is {}.",
|
||||||
|
ENABLED_DISABLED[app.session.config.allow_sliding as usize]
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
"".into(),
|
||||||
|
)
|
||||||
|
.submit(app);
|
||||||
|
}
|
||||||
SystemAction::ToggleAutoRealign => {
|
SystemAction::ToggleAutoRealign => {
|
||||||
app.session.config.realign_on_showhide = !app.session.config.realign_on_showhide;
|
app.session.config.realign_on_showhide = !app.session.config.realign_on_showhide;
|
||||||
Toast::new(
|
Toast::new(
|
||||||
ToastTopic::System,
|
ToastTopic::System,
|
||||||
format!(
|
format!(
|
||||||
"Auto realign is {}.",
|
"Auto realign is {}.",
|
||||||
if app.session.config.realign_on_showhide {
|
ENABLED_DISABLED[app.session.config.realign_on_showhide as usize]
|
||||||
"enabled"
|
|
||||||
} else {
|
|
||||||
"disabled"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
"".into(),
|
"".into(),
|
||||||
@@ -379,11 +391,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
|
|||||||
ToastTopic::System,
|
ToastTopic::System,
|
||||||
format!(
|
format!(
|
||||||
"Notifications are {}.",
|
"Notifications are {}.",
|
||||||
if app.session.config.notifications_enabled {
|
ENABLED_DISABLED[app.session.config.notifications_enabled as usize]
|
||||||
"enabled"
|
|
||||||
} else {
|
|
||||||
"disabled"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
"".into(),
|
"".into(),
|
||||||
@@ -397,11 +405,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
|
|||||||
ToastTopic::System,
|
ToastTopic::System,
|
||||||
format!(
|
format!(
|
||||||
"Notification sounds are {}.",
|
"Notification sounds are {}.",
|
||||||
if app.session.config.notifications_sound_enabled {
|
ENABLED_DISABLED[app.session.config.notifications_sound_enabled as usize]
|
||||||
"enabled"
|
|
||||||
} else {
|
|
||||||
"disabled"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
"".into(),
|
"".into(),
|
||||||
@@ -409,7 +413,7 @@ fn run_system(action: &SystemAction, app: &mut AppState) {
|
|||||||
.submit(app);
|
.submit(app);
|
||||||
}
|
}
|
||||||
SystemAction::PersistConfig => {
|
SystemAction::PersistConfig => {
|
||||||
if let Err(e) = save_settings(app) {
|
if let Err(e) = save_settings(&app.session.config) {
|
||||||
log::error!("Failed to save config: {:?}", e);
|
log::error!("Failed to save config: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,6 +589,20 @@ fn run_overlay(overlay: &OverlaySelector, action: &OverlayAction, app: &mut AppS
|
|||||||
o.show_hide = o.want_visible;
|
o.show_hide = o.want_visible;
|
||||||
o.reset(app, false);
|
o.reset(app, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut state_dirty = false;
|
||||||
|
if !o.want_visible {
|
||||||
|
state_dirty |= app.session.config.show_screens.arc_rm(o.name.as_ref());
|
||||||
|
} else if o.want_visible {
|
||||||
|
state_dirty |= app.session.config.show_screens.arc_ins(o.name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if state_dirty {
|
||||||
|
match save_state(&app.session.config) {
|
||||||
|
Ok(_) => log::debug!("Saved state"),
|
||||||
|
Err(e) => log::error!("Failed to save state: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -698,53 +716,3 @@ const THUMP_AUDIO_WAV: &[u8] = include_bytes!("../../res/380885.wav");
|
|||||||
fn audio_thump(app: &mut AppState) {
|
fn audio_thump(app: &mut AppState) {
|
||||||
app.audio.play(THUMP_AUDIO_WAV);
|
app.audio.play(THUMP_AUDIO_WAV);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct AutoSettings {
|
|
||||||
#[serde(default = "def_watch_pos")]
|
|
||||||
pub watch_pos: [f32; 3],
|
|
||||||
|
|
||||||
#[serde(default = "def_watch_rot")]
|
|
||||||
pub watch_rot: [f32; 4],
|
|
||||||
|
|
||||||
#[serde(default = "def_left")]
|
|
||||||
pub watch_hand: LeftRight,
|
|
||||||
|
|
||||||
#[serde(default = "def_half")]
|
|
||||||
pub watch_view_angle_min: f32,
|
|
||||||
|
|
||||||
#[serde(default = "def_point7")]
|
|
||||||
pub watch_view_angle_max: f32,
|
|
||||||
|
|
||||||
#[serde(default = "def_true")]
|
|
||||||
pub notifications_enabled: bool,
|
|
||||||
|
|
||||||
#[serde(default = "def_true")]
|
|
||||||
pub notifications_sound_enabled: bool,
|
|
||||||
|
|
||||||
#[serde(default = "def_true")]
|
|
||||||
pub realign_on_showhide: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_config_path() -> PathBuf {
|
|
||||||
let mut path = config_io::get_conf_d_path();
|
|
||||||
path.push("zz-saved-config.json5");
|
|
||||||
path
|
|
||||||
}
|
|
||||||
pub fn save_settings(app: &mut AppState) -> anyhow::Result<()> {
|
|
||||||
let conf = AutoSettings {
|
|
||||||
watch_pos: app.session.config.watch_pos,
|
|
||||||
watch_rot: app.session.config.watch_rot,
|
|
||||||
watch_hand: app.session.config.watch_hand,
|
|
||||||
watch_view_angle_min: app.session.config.watch_view_angle_min,
|
|
||||||
watch_view_angle_max: app.session.config.watch_view_angle_max,
|
|
||||||
notifications_enabled: app.session.config.notifications_enabled,
|
|
||||||
notifications_sound_enabled: app.session.config.notifications_sound_enabled,
|
|
||||||
realign_on_showhide: app.session.config.realign_on_showhide,
|
|
||||||
};
|
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
|
||||||
std::fs::write(get_config_path(), json)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use wlx_capture::{
|
|||||||
use {
|
use {
|
||||||
crate::config_io,
|
crate::config_io,
|
||||||
glam::Vec3,
|
glam::Vec3,
|
||||||
std::{collections::HashMap, error::Error, f32::consts::PI, ops::Deref, path::PathBuf},
|
std::{error::Error, f32::consts::PI, ops::Deref, path::PathBuf},
|
||||||
wlx_capture::{
|
wlx_capture::{
|
||||||
pipewire::{pipewire_select_screen, PipewireCapture},
|
pipewire::{pipewire_select_screen, PipewireCapture},
|
||||||
wayland::{wayland_client::protocol::wl_output, WlxClient, WlxOutput},
|
wayland::{wayland_client::protocol::wl_output, WlxClient, WlxOutput},
|
||||||
@@ -42,7 +42,7 @@ use crate::{
|
|||||||
input::{Haptics, InteractionHandler, PointerHit, PointerMode},
|
input::{Haptics, InteractionHandler, PointerHit, PointerMode},
|
||||||
overlay::{OverlayRenderer, OverlayState, SplitOverlayBackend},
|
overlay::{OverlayRenderer, OverlayState, SplitOverlayBackend},
|
||||||
},
|
},
|
||||||
config::def_pw_tokens,
|
config::{def_pw_tokens, AStrMapExt, PwTokenMap},
|
||||||
graphics::{fourcc_to_vk, WlxCommandBuffer, WlxPipeline, WlxPipelineLegacy},
|
graphics::{fourcc_to_vk, WlxCommandBuffer, WlxPipeline, WlxPipelineLegacy},
|
||||||
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
||||||
state::{AppSession, AppState, ScreenMeta},
|
state::{AppSession, AppState, ScreenMeta},
|
||||||
@@ -505,7 +505,7 @@ pub fn create_screen_renderer_wl(
|
|||||||
output: &WlxOutput,
|
output: &WlxOutput,
|
||||||
has_wlr_dmabuf: bool,
|
has_wlr_dmabuf: bool,
|
||||||
has_wlr_screencopy: bool,
|
has_wlr_screencopy: bool,
|
||||||
pw_token_store: &mut HashMap<String, String>,
|
pw_token_store: &mut PwTokenMap,
|
||||||
session: &AppSession,
|
session: &AppSession,
|
||||||
) -> Option<ScreenRenderer> {
|
) -> Option<ScreenRenderer> {
|
||||||
let mut capture: Option<ScreenRenderer> = None;
|
let mut capture: Option<ScreenRenderer> = None;
|
||||||
@@ -527,7 +527,7 @@ pub fn create_screen_renderer_wl(
|
|||||||
let display_name = output.name.deref();
|
let display_name = output.name.deref();
|
||||||
|
|
||||||
// Find existing token by display
|
// Find existing token by display
|
||||||
let token = pw_token_store.get(display_name).map(|s| s.as_str());
|
let token = pw_token_store.arc_get(display_name).map(|s| s.as_str());
|
||||||
|
|
||||||
if let Some(t) = token {
|
if let Some(t) = token {
|
||||||
log::info!(
|
log::info!(
|
||||||
@@ -542,10 +542,7 @@ pub fn create_screen_renderer_wl(
|
|||||||
capture = Some(renderer);
|
capture = Some(renderer);
|
||||||
|
|
||||||
if let Some(token) = restore_token {
|
if let Some(token) = restore_token {
|
||||||
if pw_token_store
|
if pw_token_store.arc_ins(display_name.into(), token.clone()) {
|
||||||
.insert(String::from(display_name), token.clone())
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
log::info!("Adding Pipewire token {}", token);
|
log::info!("Adding Pipewire token {}", token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,11 +604,6 @@ fn create_screen_state(
|
|||||||
|
|
||||||
OverlayState {
|
OverlayState {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
show_hide: session
|
|
||||||
.config
|
|
||||||
.show_screens
|
|
||||||
.iter()
|
|
||||||
.any(|s| s.as_ref() == name.as_ref()),
|
|
||||||
grabbable: true,
|
grabbable: true,
|
||||||
recenter: true,
|
recenter: true,
|
||||||
interactable: true,
|
interactable: true,
|
||||||
@@ -626,7 +618,7 @@ fn create_screen_state(
|
|||||||
#[derive(Deserialize, Serialize, Default)]
|
#[derive(Deserialize, Serialize, Default)]
|
||||||
pub struct TokenConf {
|
pub struct TokenConf {
|
||||||
#[serde(default = "def_pw_tokens")]
|
#[serde(default = "def_pw_tokens")]
|
||||||
pub pw_tokens: Vec<(String, String)>,
|
pub pw_tokens: PwTokenMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
@@ -637,31 +629,18 @@ fn get_pw_token_path() -> PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub fn save_pw_token_config(tokens: &HashMap<String, String>) -> Result<(), Box<dyn Error>> {
|
pub fn save_pw_token_config(tokens: &PwTokenMap) -> Result<(), Box<dyn Error>> {
|
||||||
let mut conf = TokenConf::default();
|
let yaml = serde_yaml::to_string(tokens)?;
|
||||||
|
|
||||||
for (name, token) in tokens {
|
|
||||||
conf.pw_tokens.push((name.clone(), token.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let yaml = serde_yaml::to_string(&conf)?;
|
|
||||||
std::fs::write(get_pw_token_path(), yaml)?;
|
std::fs::write(get_pw_token_path(), yaml)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub fn load_pw_token_config() -> Result<HashMap<String, String>, Box<dyn Error>> {
|
pub fn load_pw_token_config() -> Result<PwTokenMap, Box<dyn Error>> {
|
||||||
let mut map: HashMap<String, String> = HashMap::new();
|
|
||||||
|
|
||||||
let yaml = std::fs::read_to_string(get_pw_token_path())?;
|
let yaml = std::fs::read_to_string(get_pw_token_path())?;
|
||||||
let conf: TokenConf = serde_yaml::from_str(yaml.as_str())?;
|
let conf: TokenConf = serde_yaml::from_str(yaml.as_str())?;
|
||||||
|
Ok(conf.pw_tokens)
|
||||||
for (name, token) in conf.pw_tokens {
|
|
||||||
map.insert(name, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct ScreenCreateData {
|
pub(crate) struct ScreenCreateData {
|
||||||
@@ -681,13 +660,15 @@ pub fn create_screens_wayland(
|
|||||||
wl: &mut WlxClient,
|
wl: &mut WlxClient,
|
||||||
app: &mut AppState,
|
app: &mut AppState,
|
||||||
) -> anyhow::Result<ScreenCreateData> {
|
) -> anyhow::Result<ScreenCreateData> {
|
||||||
|
use crate::config::AStrMap;
|
||||||
|
|
||||||
let mut screens = vec![];
|
let mut screens = vec![];
|
||||||
|
|
||||||
// Load existing Pipewire tokens from file
|
// Load existing Pipewire tokens from file
|
||||||
let mut pw_tokens: HashMap<String, String> = if let Ok(conf) = load_pw_token_config() {
|
let mut pw_tokens: PwTokenMap = if let Ok(conf) = load_pw_token_config() {
|
||||||
conf
|
conf
|
||||||
} else {
|
} else {
|
||||||
HashMap::new()
|
AStrMap::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let pw_tokens_copy = pw_tokens.clone();
|
let pw_tokens_copy = pw_tokens.clone();
|
||||||
|
|||||||
@@ -565,6 +565,17 @@ elements:
|
|||||||
action: ToggleAutoRealign
|
action: ToggleAutoRealign
|
||||||
highlight: AutoRealign
|
highlight: AutoRealign
|
||||||
|
|
||||||
|
- type: Button
|
||||||
|
rect: [30, 555, 220, 30]
|
||||||
|
font_size: 12
|
||||||
|
fg_color: "#ffffff"
|
||||||
|
bg_color: "#401010"
|
||||||
|
text: "Grab+Scroll Slide"
|
||||||
|
click_down:
|
||||||
|
- type: System
|
||||||
|
action: ToggleAllowSliding
|
||||||
|
highlight: AllowSliding
|
||||||
|
|
||||||
####### Footer Section #######
|
####### Footer Section #######
|
||||||
|
|
||||||
- type: Panel
|
- type: Panel
|
||||||
|
|||||||
Reference in New Issue
Block a user