Port changes from -x repo (#3)
* Port config support from -x repo * Port changes from -x repo
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -4205,6 +4205,7 @@ dependencies = [
|
|||||||
"vulkano-shaders",
|
"vulkano-shaders",
|
||||||
"winit",
|
"winit",
|
||||||
"wlx-capture",
|
"wlx-capture",
|
||||||
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4256,6 +4257,12 @@ version = "0.3.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
|
checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xdg"
|
||||||
|
version = "2.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xdg-home"
|
name = "xdg-home"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
|
|||||||
vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
|
vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
|
||||||
winit = "0.29.10"
|
winit = "0.29.10"
|
||||||
wlx-capture = { git = "https://github.com/galister/wlx-capture" }
|
wlx-capture = { git = "https://github.com/galister/wlx-capture" }
|
||||||
|
xdg = "2.5.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
openvr = ["dep:ovr_overlay"]
|
openvr = ["dep:ovr_overlay"]
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
use std::{collections::VecDeque, time::Instant};
|
use std::{collections::VecDeque, time::Instant};
|
||||||
|
|
||||||
use glam::{Affine3A, Vec2, Vec3A};
|
use glam::{Affine3A, Vec2, Vec3A};
|
||||||
use ovr_overlay::TrackedDeviceIndex;
|
|
||||||
use tinyvec::array_vec;
|
use tinyvec::array_vec;
|
||||||
|
|
||||||
|
#[cfg(feature = "openvr")]
|
||||||
|
use ovr_overlay::TrackedDeviceIndex;
|
||||||
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
use super::{common::OverlayContainer, overlay::OverlayData};
|
use super::{common::OverlayContainer, overlay::OverlayData};
|
||||||
|
|
||||||
pub struct TrackedDevice {
|
pub struct TrackedDevice {
|
||||||
|
#[cfg(feature = "openvr")]
|
||||||
pub index: TrackedDeviceIndex,
|
pub index: TrackedDeviceIndex,
|
||||||
pub valid: bool,
|
pub valid: bool,
|
||||||
pub soc: Option<f32>,
|
pub soc: Option<f32>,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut state = {
|
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)
|
AppState::from_graphics(graphics)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ impl OverlayData<OpenVrOverlayData> {
|
|||||||
m_pQueue: graphics.queue.handle().as_raw() as *mut _,
|
m_pQueue: graphics.queue.handle().as_raw() as *mut _,
|
||||||
m_nQueueFamilyIndex: graphics.queue.queue_family_index(),
|
m_nQueueFamilyIndex: graphics.queue.queue_family_index(),
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"{}: UploadTex {:?}, {}x{}, {:?}",
|
"{}: UploadTex {:?}, {}x{}, {:?}",
|
||||||
self.state.name,
|
self.state.name,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
log::info!("Using environment blend mode: {:?}", environment_blend_mode);
|
log::info!("Using environment blend mode: {:?}", environment_blend_mode);
|
||||||
|
|
||||||
let mut app_state = {
|
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)
|
AppState::from_graphics(graphics)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
105
src/config.rs
Normal file
105
src/config.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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_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_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("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
71
src/config_io.rs
Normal 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()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use std::{
|
|||||||
|
|
||||||
use ash::vk::{self, SubmitInfo};
|
use ash::vk::{self, SubmitInfo};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use vulkano::instance::InstanceCreateFlags;
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
buffer::{
|
buffer::{
|
||||||
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
|
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
|
||||||
@@ -25,9 +26,11 @@ use vulkano::{
|
|||||||
descriptor_set::{
|
descriptor_set::{
|
||||||
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet,
|
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet,
|
||||||
},
|
},
|
||||||
|
device::physical::PhysicalDeviceType,
|
||||||
|
device::Features,
|
||||||
device::{
|
device::{
|
||||||
physical::{PhysicalDevice, PhysicalDeviceType},
|
physical::PhysicalDevice, Device, DeviceCreateInfo, DeviceExtensions, Queue,
|
||||||
Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, QueueCreateInfo, QueueFlags,
|
QueueCreateInfo, QueueFlags,
|
||||||
},
|
},
|
||||||
format::Format,
|
format::Format,
|
||||||
image::{
|
image::{
|
||||||
@@ -37,7 +40,8 @@ use vulkano::{
|
|||||||
Image, ImageCreateInfo, ImageLayout, ImageTiling, ImageType, ImageUsage, SampleCount,
|
Image, ImageCreateInfo, ImageLayout, ImageTiling, ImageType, ImageUsage, SampleCount,
|
||||||
SubresourceLayout,
|
SubresourceLayout,
|
||||||
},
|
},
|
||||||
instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions},
|
instance::InstanceExtensions,
|
||||||
|
instance::{Instance, InstanceCreateInfo},
|
||||||
memory::{
|
memory::{
|
||||||
allocator::{
|
allocator::{
|
||||||
AllocationCreateInfo, GenericMemoryAllocatorCreateInfo, MemoryAllocator,
|
AllocationCreateInfo, GenericMemoryAllocatorCreateInfo, MemoryAllocator,
|
||||||
@@ -72,6 +76,7 @@ use vulkano::{
|
|||||||
},
|
},
|
||||||
DeviceSize, VulkanLibrary, VulkanObject,
|
DeviceSize, VulkanLibrary, VulkanObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
use wlx_capture::frame::{
|
use wlx_capture::frame::{
|
||||||
DmabufFrame, FourCC, DRM_FORMAT_ABGR8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888,
|
DmabufFrame, FourCC, DRM_FORMAT_ABGR8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888,
|
||||||
DRM_FORMAT_XRGB8888,
|
DRM_FORMAT_XRGB8888,
|
||||||
@@ -108,7 +113,7 @@ pub struct WlxGraphics {
|
|||||||
|
|
||||||
impl WlxGraphics {
|
impl WlxGraphics {
|
||||||
#[cfg(feature = "openxr")]
|
#[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 std::ffi::{self, c_char, CString};
|
||||||
|
|
||||||
use vulkano::Handle;
|
use vulkano::Handle;
|
||||||
@@ -269,7 +274,8 @@ impl WlxGraphics {
|
|||||||
Arc::new(me)
|
Arc::new(me)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
#[cfg(feature = "openvr")]
|
||||||
|
pub fn new_openvr(
|
||||||
vk_instance_extensions: InstanceExtensions,
|
vk_instance_extensions: InstanceExtensions,
|
||||||
mut vk_device_extensions_fn: impl FnMut(&PhysicalDevice) -> DeviceExtensions,
|
mut vk_device_extensions_fn: impl FnMut(&PhysicalDevice) -> DeviceExtensions,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
@@ -901,7 +907,7 @@ impl WlxPipeline {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let render_pass_description = RenderPassCreateInfo {
|
let render_pass_description = RenderPassCreateInfo {
|
||||||
attachments: vec![AttachmentDescription {
|
attachments: vec![AttachmentDescription {
|
||||||
format: format,
|
format,
|
||||||
samples: SampleCount::Sample1,
|
samples: SampleCount::Sample1,
|
||||||
load_op: AttachmentLoadOp::Clear,
|
load_op: AttachmentLoadOp::Clear,
|
||||||
store_op: AttachmentStoreOp::Store,
|
store_op: AttachmentStoreOp::Store,
|
||||||
|
|||||||
@@ -466,6 +466,7 @@ impl<D, S> OverlayRenderer for Canvas<D, S> {
|
|||||||
fn view(&mut self) -> Option<Arc<ImageView>> {
|
fn view(&mut self) -> Option<Arc<ImageView>> {
|
||||||
Some(self.view_final.clone())
|
Some(self.view_final.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extent(&self) -> [u32; 3] {
|
fn extent(&self) -> [u32; 3] {
|
||||||
self.view_final.image().extent().clone()
|
self.view_final.image().extent().clone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod backend;
|
mod backend;
|
||||||
|
mod config;
|
||||||
|
mod config_io;
|
||||||
mod graphics;
|
mod graphics;
|
||||||
mod gui;
|
mod gui;
|
||||||
mod hid;
|
mod hid;
|
||||||
@@ -33,11 +35,12 @@ fn main() {
|
|||||||
#[cfg(all(feature = "openxr", feature = "openvr"))]
|
#[cfg(all(feature = "openxr", feature = "openvr"))]
|
||||||
auto_run(running);
|
auto_run(running);
|
||||||
|
|
||||||
|
// TODO: Handle error messages if using cherry-picked features
|
||||||
#[cfg(all(feature = "openvr", not(feature = "openxr")))]
|
#[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")))]
|
#[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")))]
|
#[cfg(not(any(feature = "openxr", feature = "openvr")))]
|
||||||
compile_error!("You must enable at least one backend feature (openxr or openvr)");
|
compile_error!("You must enable at least one backend feature (openxr or openvr)");
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
env::var,
|
|
||||||
fs,
|
|
||||||
io::Cursor,
|
io::Cursor,
|
||||||
path::PathBuf,
|
|
||||||
process::{Child, Command},
|
process::{Child, Command},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -11,14 +8,15 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::overlay::{OverlayData, OverlayState},
|
backend::overlay::{OverlayData, OverlayState},
|
||||||
|
config,
|
||||||
gui::{color_parse, CanvasBuilder, Control},
|
gui::{color_parse, CanvasBuilder, Control},
|
||||||
hid::{KeyModifier, VirtualKey, KEYS_TO_MODS},
|
hid::{KeyModifier, VirtualKey, KEYS_TO_MODS},
|
||||||
state::AppState,
|
state::{AppSession, AppState},
|
||||||
};
|
};
|
||||||
use glam::{vec2, vec3a, Affine2};
|
use glam::{vec2, vec3a, Affine2};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rodio::{Decoder, OutputStream, Source};
|
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
const PIXELS_PER_UNIT: f32 = 80.;
|
const PIXELS_PER_UNIT: f32 = 80.;
|
||||||
@@ -37,6 +35,8 @@ where
|
|||||||
modifiers: 0,
|
modifiers: 0,
|
||||||
processes: vec![],
|
processes: vec![],
|
||||||
audio_stream: None,
|
audio_stream: None,
|
||||||
|
first_try: true,
|
||||||
|
audio_handle: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut canvas = CanvasBuilder::new(
|
let mut canvas = CanvasBuilder::new(
|
||||||
@@ -109,7 +109,7 @@ where
|
|||||||
let interaction_transform = Affine2::from_translation(vec2(0.5, 0.5))
|
let interaction_transform = Affine2::from_translation(vec2(0.5, 0.5))
|
||||||
* Affine2::from_scale(vec2(1., -size.x as f32 / size.y as f32));
|
* Affine2::from_scale(vec2(1., -size.x as f32 / size.y as f32));
|
||||||
|
|
||||||
let width = LAYOUT.row_size * 0.05;
|
let width = LAYOUT.row_size * 0.05 * app.session.config.keyboard_scale;
|
||||||
|
|
||||||
OverlayData {
|
OverlayData {
|
||||||
state: OverlayState {
|
state: OverlayState {
|
||||||
@@ -134,7 +134,7 @@ fn key_press(
|
|||||||
) {
|
) {
|
||||||
match control.state.as_mut() {
|
match control.state.as_mut() {
|
||||||
Some(KeyButtonData::Key { vk, pressed }) => {
|
Some(KeyButtonData::Key { vk, pressed }) => {
|
||||||
data.key_click();
|
data.key_click(&app.session);
|
||||||
app.hid_provider.send_key(*vk as _, true);
|
app.hid_provider.send_key(*vk as _, true);
|
||||||
*pressed = true;
|
*pressed = true;
|
||||||
}
|
}
|
||||||
@@ -145,12 +145,12 @@ fn key_press(
|
|||||||
}) => {
|
}) => {
|
||||||
*sticky = data.modifiers & *modifier == 0;
|
*sticky = data.modifiers & *modifier == 0;
|
||||||
data.modifiers |= *modifier;
|
data.modifiers |= *modifier;
|
||||||
data.key_click();
|
data.key_click(&app.session);
|
||||||
app.hid_provider.set_modifiers(data.modifiers);
|
app.hid_provider.set_modifiers(data.modifiers);
|
||||||
*pressed = true;
|
*pressed = true;
|
||||||
}
|
}
|
||||||
Some(KeyButtonData::Macro { verbs }) => {
|
Some(KeyButtonData::Macro { verbs }) => {
|
||||||
data.key_click();
|
data.key_click(&app.session);
|
||||||
for (vk, press) in verbs {
|
for (vk, press) in verbs {
|
||||||
app.hid_provider.send_key(*vk as _, *press);
|
app.hid_provider.send_key(*vk as _, *press);
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ fn key_press(
|
|||||||
data.processes
|
data.processes
|
||||||
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
|
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
|
||||||
|
|
||||||
data.key_click();
|
data.key_click(&app.session);
|
||||||
if let Ok(child) = Command::new(program).args(args).spawn() {
|
if let Ok(child) = Command::new(program).args(args).spawn() {
|
||||||
data.processes.push(child);
|
data.processes.push(child);
|
||||||
}
|
}
|
||||||
@@ -210,19 +210,31 @@ struct KeyboardData {
|
|||||||
modifiers: KeyModifier,
|
modifiers: KeyModifier,
|
||||||
processes: Vec<Child>,
|
processes: Vec<Child>,
|
||||||
audio_stream: Option<OutputStream>,
|
audio_stream: Option<OutputStream>,
|
||||||
|
audio_handle: Option<OutputStreamHandle>,
|
||||||
|
first_try: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyboardData {
|
impl KeyboardData {
|
||||||
fn key_click(&mut self) {
|
fn key_click(&mut self, session: &AppSession) {
|
||||||
|
if !session.config.keyboard_sound_enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.audio_stream.is_none() && self.first_try {
|
||||||
|
self.first_try = false;
|
||||||
|
if let Ok((stream, handle)) = OutputStream::try_default() {
|
||||||
|
self.audio_stream = Some(stream);
|
||||||
|
self.audio_handle = Some(handle);
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to open audio stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(handle) = &self.audio_handle {
|
||||||
let wav = include_bytes!("../res/421581.wav");
|
let wav = include_bytes!("../res/421581.wav");
|
||||||
let cursor = Cursor::new(wav);
|
let cursor = Cursor::new(wav);
|
||||||
let source = Decoder::new_wav(cursor).unwrap();
|
let source = Decoder::new_wav(cursor).unwrap();
|
||||||
self.audio_stream = None;
|
|
||||||
if let Ok((stream, handle)) = OutputStream::try_default() {
|
|
||||||
let _ = handle.play_raw(source.convert_samples());
|
let _ = handle.play_raw(source.convert_samples());
|
||||||
self.audio_stream = Some(stream);
|
|
||||||
} else {
|
|
||||||
log::error!("Failed to play key click");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,18 +258,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 LAYOUT: Lazy<Layout> = Lazy::new(Layout::load_from_disk);
|
||||||
|
|
||||||
static MACRO_REGEX: Lazy<Regex> =
|
static MACRO_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap());
|
Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap());
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
struct Layout {
|
pub struct Layout {
|
||||||
name: String,
|
name: String,
|
||||||
row_size: f32,
|
row_size: f32,
|
||||||
key_sizes: Vec<Vec<f32>>,
|
key_sizes: Vec<Vec<f32>>,
|
||||||
@@ -269,16 +276,8 @@ struct Layout {
|
|||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
fn load_from_disk() -> Layout {
|
fn load_from_disk() -> Layout {
|
||||||
let mut yaml = fs::read_to_string(KEYBOARD_YAML.as_path()).ok();
|
let mut layout = config::load_keyboard();
|
||||||
|
|
||||||
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");
|
|
||||||
layout.post_load();
|
layout.post_load();
|
||||||
|
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
use core::slice;
|
use core::slice;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
error::Error,
|
||||||
f32::consts::PI,
|
f32::consts::PI,
|
||||||
path::Path,
|
ops::Deref,
|
||||||
|
path::PathBuf,
|
||||||
ptr,
|
ptr,
|
||||||
sync::{mpsc::Receiver, Arc},
|
sync::{mpsc::Receiver, Arc},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
usize,
|
|
||||||
};
|
};
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
buffer::Subbuffer,
|
buffer::Subbuffer,
|
||||||
@@ -30,6 +33,8 @@ use crate::{
|
|||||||
input::{InteractionHandler, PointerHit, PointerMode},
|
input::{InteractionHandler, PointerHit, PointerMode},
|
||||||
overlay::{OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend},
|
overlay::{OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend},
|
||||||
},
|
},
|
||||||
|
config::def_pw_tokens,
|
||||||
|
config_io,
|
||||||
graphics::{fourcc_to_vk, Vert2Uv, WlxGraphics, WlxPipeline},
|
graphics::{fourcc_to_vk, Vert2Uv, WlxGraphics, WlxPipeline},
|
||||||
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
||||||
state::{AppSession, AppState},
|
state::{AppSession, AppState},
|
||||||
@@ -79,9 +84,6 @@ impl InteractionHandler for ScreenInteractionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
|
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
|
||||||
let pos = self.mouse_transform.transform_point2(hit.uv);
|
|
||||||
app.hid_provider.mouse_move(pos);
|
|
||||||
|
|
||||||
let btn = match hit.mode {
|
let btn = match hit.mode {
|
||||||
PointerMode::Right => MOUSE_RIGHT,
|
PointerMode::Right => MOUSE_RIGHT,
|
||||||
PointerMode::Middle => MOUSE_MIDDLE,
|
PointerMode::Middle => MOUSE_MIDDLE,
|
||||||
@@ -89,11 +91,14 @@ impl InteractionHandler for ScreenInteractionHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if pressed {
|
if pressed {
|
||||||
self.next_move =
|
self.next_move = Instant::now()
|
||||||
Instant::now() + Duration::from_millis(app.session.click_freeze_time_ms);
|
+ Duration::from_millis(app.session.config.click_freeze_time_ms as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.hid_provider.send_button(btn, pressed);
|
app.hid_provider.send_button(btn, pressed);
|
||||||
|
|
||||||
|
let pos = self.mouse_transform.transform_point2(hit.uv);
|
||||||
|
app.hid_provider.mouse_move(pos);
|
||||||
}
|
}
|
||||||
fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta: f32) {
|
fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta: f32) {
|
||||||
let millis = (1. - delta.abs()) * delta;
|
let millis = (1. - delta.abs()) * delta;
|
||||||
@@ -342,7 +347,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 HashMap<String, String>,
|
||||||
|
session: &AppSession,
|
||||||
|
) -> Option<OverlayData<O>>
|
||||||
where
|
where
|
||||||
O: Default,
|
O: Default,
|
||||||
{
|
{
|
||||||
@@ -366,9 +376,18 @@ where
|
|||||||
|
|
||||||
if capture.is_none() {
|
if capture.is_none() {
|
||||||
log::info!("{}: Using Pipewire capture", &output.name);
|
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 display_name = output.name.deref();
|
||||||
let token = std::fs::read_to_string(full_path).ok();
|
|
||||||
|
// 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(
|
capture = ScreenRenderer::new_pw(
|
||||||
output,
|
output,
|
||||||
@@ -416,7 +435,7 @@ where
|
|||||||
want_visible: session.show_screens.iter().any(|s| s == &*output.name),
|
want_visible: session.show_screens.iter().any(|s| s == &*output.name),
|
||||||
show_hide: true,
|
show_hide: true,
|
||||||
grabbable: true,
|
grabbable: true,
|
||||||
spawn_scale: 1.5,
|
spawn_scale: 1.5 * session.config.desktop_view_scale,
|
||||||
spawn_point: vec3a(0., 0.5, -1.),
|
spawn_point: vec3a(0., 0.5, -1.),
|
||||||
spawn_rotation: Quat::from_axis_angle(axis, angle),
|
spawn_rotation: Quat::from_axis_angle(axis, angle),
|
||||||
interaction_transform,
|
interaction_transform,
|
||||||
@@ -431,6 +450,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: &HashMap<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<HashMap<String, String>, Box<dyn Error>> {
|
||||||
|
let mut map: HashMap<String, String> = HashMap::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)
|
pub fn get_screens_wayland<O>(session: &AppSession) -> (Vec<OverlayData<O>>, Vec2)
|
||||||
where
|
where
|
||||||
O: Default,
|
O: Default,
|
||||||
@@ -438,11 +495,28 @@ where
|
|||||||
let mut overlays = vec![];
|
let mut overlays = vec![];
|
||||||
let wl = WlxClient::new().unwrap();
|
let wl = WlxClient::new().unwrap();
|
||||||
|
|
||||||
|
// Load existing Pipewire tokens from file
|
||||||
|
let mut pw_tokens: HashMap<String, String> = if let Ok(conf) = load_pw_token_config() {
|
||||||
|
conf
|
||||||
|
} else {
|
||||||
|
HashMap::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pw_tokens_copy = pw_tokens.clone();
|
||||||
|
|
||||||
for id in wl.outputs.keys() {
|
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);
|
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();
|
let extent = wl.get_desktop_extent();
|
||||||
(overlays, Vec2::new(extent.0 as f32, extent.1 as f32))
|
(overlays, Vec2::new(extent.0 as f32, extent.1 as f32))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ where
|
|||||||
name: "Watch".into(),
|
name: "Watch".into(),
|
||||||
size: (400, 200),
|
size: (400, 200),
|
||||||
want_visible: true,
|
want_visible: true,
|
||||||
spawn_scale: 0.065,
|
spawn_scale: 0.065 * state.session.config.watch_scale,
|
||||||
spawn_point: state.session.watch_pos.into(),
|
spawn_point: state.session.watch_pos.into(),
|
||||||
spawn_rotation: state.session.watch_rot,
|
spawn_rotation: state.session.watch_rot,
|
||||||
interaction_transform,
|
interaction_transform,
|
||||||
|
|||||||
13
src/res/config.yaml
Normal file
13
src/res/config.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 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
|
||||||
41
src/state.rs
41
src/state.rs
@@ -1,10 +1,12 @@
|
|||||||
use std::{env::VarError, path::Path, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use glam::{Quat, Vec3};
|
use glam::{Quat, Vec3};
|
||||||
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
|
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{common::TaskContainer, input::InputState},
|
backend::{common::TaskContainer, input::InputState},
|
||||||
|
config::GeneralConfig,
|
||||||
|
config_io,
|
||||||
graphics::WlxGraphics,
|
graphics::WlxGraphics,
|
||||||
gui::font::FontCache,
|
gui::font::FontCache,
|
||||||
hid::HidProvider,
|
hid::HidProvider,
|
||||||
@@ -74,16 +76,10 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppSession {
|
pub struct AppSession {
|
||||||
pub config_path: String,
|
pub config_root_path: PathBuf,
|
||||||
|
pub config: GeneralConfig,
|
||||||
|
|
||||||
pub show_screens: Vec<String>,
|
pub show_screens: Vec<String>,
|
||||||
pub show_keyboard: bool,
|
|
||||||
pub keyboard_volume: f32,
|
|
||||||
|
|
||||||
pub screen_flip_h: bool,
|
|
||||||
pub screen_flip_v: bool,
|
|
||||||
pub screen_invert_color: bool,
|
|
||||||
pub screen_max_res: [u32; 2],
|
|
||||||
|
|
||||||
pub watch_hand: usize,
|
pub watch_hand: usize,
|
||||||
pub watch_pos: Vec3,
|
pub watch_pos: Vec3,
|
||||||
@@ -97,34 +93,18 @@ pub struct AppSession {
|
|||||||
pub color_shift: Vec3,
|
pub color_shift: Vec3,
|
||||||
pub color_alt: Vec3,
|
pub color_alt: Vec3,
|
||||||
pub color_grab: Vec3,
|
pub color_grab: Vec3,
|
||||||
|
|
||||||
pub click_freeze_time_ms: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppSession {
|
impl AppSession {
|
||||||
pub fn load() -> Self {
|
pub fn load() -> Self {
|
||||||
let config_path = std::env::var("XDG_CONFIG_HOME")
|
let config_root_path = config_io::ensure_config_root();
|
||||||
.or_else(|_| std::env::var("HOME").map(|home| format!("{}/.config", home)))
|
println!("Config root path: {}", config_root_path.to_string_lossy());
|
||||||
.or_else(|_| {
|
let config = GeneralConfig::load_from_disk();
|
||||||
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);
|
|
||||||
|
|
||||||
AppSession {
|
AppSession {
|
||||||
config_path,
|
config_root_path,
|
||||||
|
config,
|
||||||
show_screens: vec!["DP-3".to_string()],
|
show_screens: vec!["DP-3".to_string()],
|
||||||
keyboard_volume: 0.5,
|
|
||||||
show_keyboard: false,
|
|
||||||
screen_flip_h: false,
|
|
||||||
screen_flip_v: false,
|
|
||||||
screen_invert_color: false,
|
|
||||||
screen_max_res: [2560, 1440],
|
|
||||||
capture_method: "auto".to_string(),
|
capture_method: "auto".to_string(),
|
||||||
primary_hand: 1,
|
primary_hand: 1,
|
||||||
watch_hand: 0,
|
watch_hand: 0,
|
||||||
@@ -150,7 +130,6 @@ impl AppSession {
|
|||||||
y: 0.,
|
y: 0.,
|
||||||
z: 0.,
|
z: 0.,
|
||||||
},
|
},
|
||||||
click_freeze_time_ms: 300,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user