Port config support from -x repo

This commit is contained in:
Aleksander 2024-01-28 17:35:00 +01:00
parent d66d39066c
commit b42e75bc2e
14 changed files with 338 additions and 55 deletions

7
Cargo.lock generated
View File

@ -4205,6 +4205,7 @@ dependencies = [
"vulkano-shaders",
"winit",
"wlx-capture",
"xdg",
]
[[package]]
@ -4256,6 +4257,12 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
[[package]]
name = "xdg"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
[[package]]
name = "xdg-home"
version = "1.0.0"

View File

@ -1,14 +1,17 @@
use std::{collections::VecDeque, time::Instant};
use glam::{Affine3A, Vec2, Vec3A};
use ovr_overlay::TrackedDeviceIndex;
use tinyvec::array_vec;
#[cfg(feature = "openvr")]
use ovr_overlay::TrackedDeviceIndex;
use crate::state::AppState;
use super::{common::OverlayContainer, overlay::OverlayData};
pub struct TrackedDevice {
#[cfg(feature = "openvr")]
pub index: TrackedDeviceIndex,
pub valid: bool,
pub soc: Option<f32>,

View File

@ -63,7 +63,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
};
let mut state = {
let graphics = WlxGraphics::new(instance_extensions, device_extensions_fn);
let graphics = WlxGraphics::new_openvr(instance_extensions, device_extensions_fn);
AppState::from_graphics(graphics)
};

View File

@ -220,7 +220,6 @@ impl OverlayData<OpenVrOverlayData> {
m_pQueue: graphics.queue.handle().as_raw() as *mut _,
m_nQueueFamilyIndex: graphics.queue.queue_family_index(),
};
log::info!(
"{}: UploadTex {:?}, {}x{}, {:?}",
self.state.name,
@ -228,10 +227,8 @@ impl OverlayData<OpenVrOverlayData> {
texture.m_nWidth,
texture.m_nHeight,
image.usage()
);
if let Err(e) = overlay.set_image_vulkan(handle, &mut texture) {
panic!("Failed to set overlay texture: {}", e);
}
log::info!("{}: Uploaded texture", self.state.name);
}
}

View File

@ -49,7 +49,7 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
log::info!("Using environment blend mode: {:?}", environment_blend_mode);
let mut app_state = {
let graphics = WlxGraphics::new_xr(xr_instance.clone(), system);
let graphics = WlxGraphics::new_openxr(xr_instance.clone(), system);
AppState::from_graphics(graphics)
};

121
src/config.rs Normal file
View File

@ -0,0 +1,121 @@
use crate::config_io;
use crate::config_io::get_conf_d_path;
use crate::load_with_fallback;
use crate::overlays::keyboard;
use log::error;
use serde::Deserialize;
use serde::Serialize;
pub fn def_pw_tokens() -> Vec<(String, String)> {
Vec::new()
}
fn def_grab_threshold() -> f32 {
0.6
}
fn def_trigger_threshold() -> f32 {
0.65
}
fn def_click_freeze_time_ms() -> u32 {
300
}
fn def_true() -> bool {
true
}
fn def_one() -> f32 {
1.0
}
#[derive(Deserialize, Serialize)]
pub struct GeneralConfig {
#[serde(default = "def_grab_threshold")]
pub grab_threshold: f32,
#[serde(default = "def_trigger_threshold")]
pub trigger_threshold: f32,
#[serde(default = "def_click_freeze_time_ms")]
pub click_freeze_time_ms: u32,
#[serde(default = "def_true")]
pub keyboard_sound_enabled: bool,
#[serde(default = "def_one")]
pub keyboard_scale: f32,
#[serde(default = "def_one")]
pub desktop_view_scale: f32,
#[serde(default = "def_one")]
pub watch_scale: f32,
#[serde(default = "def_pw_tokens")]
pub pw_tokens: Vec<(String, String)>,
}
impl GeneralConfig {
fn sanitize_range(name: &str, val: f32, from: f32, to: f32) {
if !val.is_normal() || val < from || val > to {
panic!(
"GeneralConfig: {} needs to be between {} and {}",
name, from, to
);
}
}
pub fn load_from_disk() -> GeneralConfig {
let config = load_general();
config.post_load();
config
}
fn post_load(&self) {
GeneralConfig::sanitize_range("grab_threshold", self.grab_threshold, 0.0, 1.0);
GeneralConfig::sanitize_range("trigger_threshold", self.trigger_threshold, 0.0, 1.0);
GeneralConfig::sanitize_range("keyboard_scale", self.keyboard_scale, 0.0, 5.0);
GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.0, 5.0);
GeneralConfig::sanitize_range("watch_scale", self.watch_scale, 0.0, 5.0);
}
}
pub fn load_keyboard() -> keyboard::Layout {
let yaml_data = load_with_fallback!("keyboard.yaml", "res/keyboard.yaml");
serde_yaml::from_str(&yaml_data).expect("Failed to parse keyboard.yaml")
}
pub fn load_general() -> GeneralConfig {
let mut yaml_data = String::new();
// Add files from conf.d directory
let path_conf_d = get_conf_d_path();
if let Ok(paths_unsorted) = std::fs::read_dir(path_conf_d) {
// Sort paths alphabetically
let mut paths: Vec<_> = paths_unsorted.map(|r| r.unwrap()).collect();
paths.sort_by_key(|dir| dir.path());
for path in paths {
if !path.file_type().unwrap().is_file() {
continue;
}
println!("Loading config file {}", path.path().to_string_lossy());
if let Ok(data) = std::fs::read_to_string(path.path()) {
yaml_data.push('\n'); // Just in case, if end of the config file was not newline
yaml_data.push_str(data.as_str());
} else {
// Shouldn't happen anyways
error!("Failed to load {}", path.path().to_string_lossy());
}
}
}
if yaml_data.is_empty() {
yaml_data.push_str(load_with_fallback!("config.yaml", "res/config.yaml").as_str());
}
serde_yaml::from_str(&yaml_data).expect("Failed to parse config.yaml")
}

71
src/config_io.rs Normal file
View File

@ -0,0 +1,71 @@
use log::error;
use once_cell::sync::Lazy;
use std::{
fs::{self, create_dir},
path::PathBuf,
};
const FALLBACK_CONFIG_PATH: &str = "/tmp/wlxoverlay";
pub static CONFIG_ROOT_PATH: Lazy<PathBuf> = Lazy::new(|| {
if let Ok(xdg_dirs) = xdg::BaseDirectories::new() {
let mut dir = xdg_dirs.get_config_home();
dir.push("wlxoverlay");
return dir;
}
//Return fallback config path
error!(
"Err: Failed to find config path, using {}",
FALLBACK_CONFIG_PATH
);
PathBuf::from(FALLBACK_CONFIG_PATH)
});
pub fn get_conf_d_path() -> PathBuf {
let mut config_root = CONFIG_ROOT_PATH.clone();
config_root.push("conf.d");
config_root
}
// Make sure config directory is present and return root config path
pub fn ensure_config_root() -> PathBuf {
let path = CONFIG_ROOT_PATH.clone();
let _ = create_dir(&path);
let path_conf_d = get_conf_d_path();
let _ = create_dir(path_conf_d);
path
}
fn get_config_file_path(filename: &str) -> PathBuf {
let mut config_root = CONFIG_ROOT_PATH.clone();
config_root.push(filename);
config_root
}
pub fn load(filename: &str) -> Option<String> {
let path = get_config_file_path(filename);
println!("Loading config {}", path.to_string_lossy());
if let Ok(data) = fs::read_to_string(path) {
Some(data)
} else {
None
}
}
#[macro_export]
macro_rules! load_with_fallback {
($filename: expr, $fallback: expr) => {
if let Some(data) = config_io::load($filename) {
data
} else {
println!(
"Config {}/{} does not exist, using internal fallback",
config_io::CONFIG_ROOT_PATH.to_string_lossy(),
$filename
);
include_str!($fallback).to_string()
}
};
}

View File

@ -26,8 +26,8 @@ use vulkano::{
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet,
},
device::{
physical::{PhysicalDevice, PhysicalDeviceType},
Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, QueueCreateInfo, QueueFlags,
physical::PhysicalDevice, Device, DeviceCreateInfo, DeviceExtensions, Queue,
QueueCreateInfo, QueueFlags,
},
format::Format,
image::{
@ -37,7 +37,7 @@ use vulkano::{
Image, ImageCreateInfo, ImageLayout, ImageTiling, ImageType, ImageUsage, SampleCount,
SubresourceLayout,
},
instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions},
instance::{Instance, InstanceCreateInfo},
memory::{
allocator::{
AllocationCreateInfo, GenericMemoryAllocatorCreateInfo, MemoryAllocator,
@ -108,7 +108,7 @@ pub struct WlxGraphics {
impl WlxGraphics {
#[cfg(feature = "openxr")]
pub fn new_xr(xr_instance: openxr::Instance, system: openxr::SystemId) -> Arc<Self> {
pub fn new_openxr(xr_instance: openxr::Instance, system: openxr::SystemId) -> Arc<Self> {
use std::ffi::{self, c_char, CString};
use vulkano::Handle;
@ -269,7 +269,8 @@ impl WlxGraphics {
Arc::new(me)
}
pub fn new(
#[cfg(feature = "openvr")]
pub fn new_openvr(
vk_instance_extensions: InstanceExtensions,
mut vk_device_extensions_fn: impl FnMut(&PhysicalDevice) -> DeviceExtensions,
) -> Arc<Self> {
@ -903,7 +904,7 @@ impl WlxPipeline {
) -> Self {
let render_pass_description = RenderPassCreateInfo {
attachments: vec![AttachmentDescription {
format: format,
format,
samples: SampleCount::Sample1,
load_op: AttachmentLoadOp::Clear,
store_op: AttachmentStoreOp::Store,

View File

@ -463,6 +463,7 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
fn view(&mut self) -> Option<Arc<ImageView>> {
Some(self.view_final.clone())
}
fn extent(&self) -> [u32; 3] {
self.view_final.image().extent().clone()
}

View File

@ -1,5 +1,7 @@
#[allow(dead_code)]
mod backend;
mod config;
mod config_io;
mod graphics;
mod gui;
mod hid;
@ -33,11 +35,12 @@ fn main() {
#[cfg(all(feature = "openxr", feature = "openvr"))]
auto_run(running);
// TODO: Handle error messages if using cherry-picked features
#[cfg(all(feature = "openvr", not(feature = "openxr")))]
crate::backend::openvr::openvr_run(running);
let _ = crate::backend::openvr::openvr_run(running);
#[cfg(all(feature = "openxr", not(feature = "openvr")))]
crate::backend::openxr::openxr_run(running);
let _ = crate::backend::openxr::openxr_run(running);
#[cfg(not(any(feature = "openxr", feature = "openvr")))]
compile_error!("You must enable at least one backend feature (openxr or openvr)");

View File

@ -1,9 +1,6 @@
use std::{
collections::HashMap,
env::var,
fs,
io::Cursor,
path::PathBuf,
process::{Child, Command},
str::FromStr,
sync::Arc,
@ -11,6 +8,7 @@ use std::{
use crate::{
backend::overlay::{OverlayData, OverlayState},
config,
gui::{color_parse, CanvasBuilder, Control},
hid::{KeyModifier, VirtualKey, KEYS_TO_MODS},
state::AppState,
@ -106,7 +104,7 @@ where
let canvas = canvas.build();
let width = LAYOUT.row_size * 0.05;
let width = LAYOUT.row_size * 0.05 * app.session.config.keyboard_scale;
let width_inv = 1.0 / width;
let aspect_ratio = size.x / size.y;
let t_w = width_inv;
@ -249,18 +247,13 @@ enum KeyButtonData {
},
}
static KEYBOARD_YAML: Lazy<PathBuf> = Lazy::new(|| {
let home = &var("HOME").unwrap();
[home, ".config/wlxoverlay/keyboard.yaml"].iter().collect() //TODO other paths
});
static LAYOUT: Lazy<Layout> = Lazy::new(Layout::load_from_disk);
static MACRO_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap());
#[derive(Debug, Deserialize, Serialize)]
struct Layout {
pub struct Layout {
name: String,
row_size: f32,
key_sizes: Vec<Vec<f32>>,
@ -272,16 +265,8 @@ struct Layout {
impl Layout {
fn load_from_disk() -> Layout {
let mut yaml = fs::read_to_string(KEYBOARD_YAML.as_path()).ok();
if yaml.is_none() {
yaml = Some(include_str!("../res/keyboard.yaml").to_string());
}
let mut layout: Layout =
serde_yaml::from_str(&yaml.unwrap()).expect("Failed to parse keyboard.yaml");
let mut layout = config::load_keyboard();
layout.post_load();
layout
}

View File

@ -1,11 +1,14 @@
use core::slice;
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
error::Error,
f32::consts::PI,
path::Path,
ops::Deref,
path::PathBuf,
ptr,
sync::{mpsc::Receiver, Arc},
time::{Duration, Instant},
usize,
};
use vulkano::{
buffer::Subbuffer,
@ -30,6 +33,8 @@ use crate::{
input::{InteractionHandler, PointerHit, PointerMode},
overlay::{OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend},
},
config::def_pw_tokens,
config_io,
graphics::{fourcc_to_vk, Vert2Uv, WlxGraphics, WlxPipeline},
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
state::{AppSession, AppState},
@ -341,7 +346,12 @@ impl OverlayRenderer for ScreenRenderer {
}
}
fn try_create_screen<O>(wl: &WlxClient, id: u32, session: &AppSession) -> Option<OverlayData<O>>
fn try_create_screen<O>(
wl: &WlxClient,
id: u32,
pw_token_store: &mut BTreeMap<String, String>,
session: &AppSession,
) -> Option<OverlayData<O>>
where
O: Default,
{
@ -365,9 +375,18 @@ where
if capture.is_none() {
log::info!("{}: Using Pipewire capture", &output.name);
let file_name = format!("{}.token", &output.name);
let full_path = Path::new(&session.config_path).join(file_name);
let token = std::fs::read_to_string(full_path).ok();
let display_name = output.name.deref();
// Find existing token by display
let token = pw_token_store.get(display_name).map(|s| s.as_str());
if let Some(t) = token {
println!(
"Found existing Pipewire token for display {}: {}",
display_name, t
);
}
capture = ScreenRenderer::new_pw(
output,
@ -430,6 +449,44 @@ where
}
}
#[derive(Deserialize, Serialize, Default)]
pub struct TokenConf {
#[serde(default = "def_pw_tokens")]
pub pw_tokens: Vec<(String, String)>,
}
fn get_pw_token_path() -> PathBuf {
let mut path = config_io::get_conf_d_path();
path.push("pw_tokens.yaml");
path
}
pub fn save_pw_token_config(tokens: &BTreeMap<String, String>) -> Result<(), Box<dyn Error>> {
let mut conf = TokenConf::default();
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)?;
Ok(())
}
pub fn load_pw_token_config() -> Result<BTreeMap<String, String>, Box<dyn Error>> {
let mut map: BTreeMap<String, String> = BTreeMap::new();
let yaml = std::fs::read_to_string(get_pw_token_path())?;
let conf: TokenConf = serde_yaml::from_str(yaml.as_str())?;
for (name, token) in conf.pw_tokens {
map.insert(name, token);
}
Ok(map)
}
pub fn get_screens_wayland<O>(session: &AppSession) -> (Vec<OverlayData<O>>, Vec2)
where
O: Default,
@ -437,11 +494,28 @@ where
let mut overlays = vec![];
let wl = WlxClient::new().unwrap();
// Load existing Pipewire tokens from file
let mut pw_tokens: BTreeMap<String, String> = if let Ok(conf) = load_pw_token_config() {
conf
} else {
BTreeMap::new()
};
let pw_tokens_copy = pw_tokens.clone();
for id in wl.outputs.keys() {
if let Some(overlay) = try_create_screen(&wl, *id, &session) {
if let Some(overlay) = try_create_screen(&wl, *id, &mut pw_tokens, session) {
overlays.push(overlay);
}
}
if pw_tokens_copy != pw_tokens {
// Token list changed, re-create token config file
if let Err(err) = save_pw_token_config(&pw_tokens) {
log::error!("Failed to save Pipewire token config: {}", err);
}
}
let extent = wl.get_desktop_extent();
(overlays, Vec2::new(extent.0 as f32, extent.1 as f32))
}

25
src/res/config.yaml Normal file
View File

@ -0,0 +1,25 @@
# Grab threshold for changing position of overlays
# Allowed values: 0.0 - 1.0
# Default - 0.6
#
# 1.0 - The hand must grab tighter to start dragging, recommended on Index controller
grab_threshold: 0.6
# Threshold for registering trigger (click) event
# Allowed values: 0.0 - 1.0
# Default - 0.65
trigger_threshold: 0.65
# For how much time mouse motion events should be stopped after clicking?
# Prevents accidental dragging various GUI elements or links, making it easier to click
# Default: 300
click_freeze_time_ms: 300
# Default: true
keyboard_sound_enabled: true
# Alter default scale of various overlays
# Default: 1.0
keyboard_scale: 1.0
desktop_view_scale: 1.0
watch_scale: 1.0

View File

@ -1,10 +1,12 @@
use std::{env::VarError, path::Path, sync::Arc};
use std::{path::PathBuf, sync::Arc};
use glam::{Quat, Vec3};
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
use crate::{
backend::{common::TaskContainer, input::InputState},
config::GeneralConfig,
config_io,
graphics::WlxGraphics,
gui::font::FontCache,
hid::HidProvider,
@ -74,7 +76,8 @@ impl AppState {
}
pub struct AppSession {
pub config_path: String,
pub config_root_path: PathBuf,
pub config: GeneralConfig,
pub show_screens: Vec<String>,
pub show_keyboard: bool,
@ -103,21 +106,13 @@ pub struct AppSession {
impl AppSession {
pub fn load() -> Self {
let config_path = std::env::var("XDG_CONFIG_HOME")
.or_else(|_| std::env::var("HOME").map(|home| format!("{}/.config", home)))
.or_else(|_| {
log::warn!("Err: $XDG_CONFIG_HOME and $HOME are not set, using /tmp/wlxoverlay");
Ok::<String, VarError>("/tmp".to_string())
})
.map(|config| Path::new(&config).join("wlxoverlay"))
.ok()
.and_then(|path| path.to_str().map(|path| path.to_string()))
.unwrap();
let _ = std::fs::create_dir(&config_path);
let config_root_path = config_io::ensure_config_root();
println!("Config root path: {}", config_root_path.to_string_lossy());
let config = GeneralConfig::load_from_disk();
AppSession {
config_path,
config_root_path,
config,
show_screens: vec!["DP-3".to_string()],
keyboard_volume: 0.5,
show_keyboard: false,