Compare commits

...
29 Commits
Author SHA1 Message Date
galister 82374a60a0 Release v0.3.1 2024-03-19 18:19:32 +01:00
galister 0d41bf1a3e openvr: correct size for portrait overlays 2024-03-19 18:16:23 +01:00
galister 3907e089de openvr: re-upload line texs on show-hide to prevent disappearing lasers 2024-03-19 17:16:35 +01:00
Aleksander 72bac6465e Fix ui_transform (#17) 2024-03-19 16:14:56 +00:00
Stuart Irwin 271e037692 Save screen scaling changes (#16) 2024-03-17 21:22:18 +00:00
galister bc78450c6a openxr: use squeeze/force for knuckles 2024-03-17 17:11:25 +01:00
galister dce634edd1 openxr: click/grab/altclick sensitivity option 2024-03-17 16:41:32 +01:00
galister 69d14eebe5 fix when desktop origin other than (0,0) 2024-03-17 16:30:16 +01:00
galister 54dfb297ac improve error handling 2024-03-17 16:11:05 +01:00
galister 1d1230b1e7 chore: update wlx-capture & other deps 2024-03-17 13:08:49 +01:00
galister 67b6b90158 fonts: dont panic on load error 2024-03-17 11:25:11 +01:00
galister 31ec035db7 pipewire: log screen capture start failures 2024-03-16 11:27:10 +01:00
galister deb43f0d94 do not log toasts 2024-03-11 09:07:35 +01:00
Stuart Irwin 4434d82981 Add uptime timer module (#15) 2024-03-11 07:51:43 +00:00
libjared 8ba5d68e5a Update keyboard.yaml (#14)
- update virtual key list URL
- remove references to xmodmap
- ensure layouts are consistent with length
- remove alt_layout for now
2024-03-10 22:31:54 +00:00
galister 0c6e319fbe Release v0.3.0 2024-03-10 21:40:14 +01:00
galister 4679961cb2 openvr: smoother rotate 2024-03-10 20:56:13 +01:00
galister cb12357d68 openvr: dont overwrite bindings 2024-03-10 16:55:40 +01:00
galister b401fb28fd openvr: space rotate, recenter height adjust 2024-03-10 16:28:37 +01:00
galister 06478bbfc9 openvr: recenter rework 2024-03-10 14:40:50 +01:00
galister b57b77b06a openvr: reset offsets reloads from disk 2024-03-10 14:08:12 +01:00
galister 5a077e394c openvr: reload playspace on scene app change 2024-03-10 13:23:00 +01:00
galister a08c260898 openvr: chaperone movement fix 2024-03-10 13:19:08 +01:00
galister 45d08cd89b x11: fix cursor size 2024-03-10 00:10:28 +01:00
galister 49b9cc361d x11: fix screen gamma 2024-03-10 00:02:10 +01:00
galister 2f8dcaf110 use linked openxr 2024-03-09 21:21:48 +01:00
galister 473c6e2038 cleanup 2024-03-09 17:15:37 +01:00
galister 4c91470b5c Release v0.2.6 2024-03-09 16:54:52 +01:00
galister 85e8e80538 update ovr_overlay 2024-03-09 01:59:11 +01:00
22 changed files with 802 additions and 665 deletions

No files matched your search

Generated
+411 -496
View File
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -4,7 +4,7 @@ debug = true
[package]
name = "wlx-overlay-s"
version = "0.2.5"
version = "0.3.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -27,9 +27,9 @@ idmap = "0.2.21"
idmap-derive = "0.1.2"
input-linux = "0.6.0"
json = { version = "0.12.4", optional = true }
libc = "0.2.147"
log = "0.4.20"
once_cell = "1.18.0"
libc = "0.2.153"
log = "0.4.21"
once_cell = "1.19.0"
openxr = { version = "0.17.1", features = ["linked"], optional = true }
ovr_overlay = { features = ["ovr_input", "ovr_system"], git = "https://github.com/galister/ovr_overlay_oyasumi", optional = true }
regex = "1.9.5"
@@ -43,7 +43,7 @@ strum = { version = "0.25.0", features = ["derive"] }
thiserror = "1.0.56"
vulkano = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano", rev = "94f50f1" }
wlx-capture = { git = "https://github.com/galister/wlx-capture", default-features = false }
wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.3.0", default-features = false }
xdg = "2.5.2"
[features]
+4
View File
@@ -71,6 +71,9 @@ impl InputState {
if hand.now.space_drag != hand.before.space_drag {
log::debug!("Hand {}: space_drag {}", hand.idx, hand.now.space_drag);
}
if hand.now.space_rotate != hand.before.space_rotate {
log::debug!("Hand {}: space_rotate {}", hand.idx, hand.now.space_rotate);
}
if hand.now.click_modifier_right != hand.before.click_modifier_right {
log::debug!(
"Hand {}: click_modifier_right {}",
@@ -189,6 +192,7 @@ pub struct PointerState {
pub alt_click: bool,
pub show_hide: bool,
pub space_drag: bool,
pub space_rotate: bool,
pub click_modifier_right: bool,
pub click_modifier_middle: bool,
}
+10 -3
View File
@@ -36,6 +36,7 @@ const PATH_SCROLL: &str = "/actions/default/in/Scroll";
const PATH_ALT_CLICK: &str = "/actions/default/in/AltClick";
const PATH_SHOW_HIDE: &str = "/actions/default/in/ShowHide";
const PATH_SPACE_DRAG: &str = "/actions/default/in/SpaceDrag";
const PATH_SPACE_ROTATE: &str = "/actions/default/in/SpaceRotate";
const PATH_CLICK_MODIFIER_RIGHT: &str = "/actions/default/in/ClickModifierRight";
const PATH_CLICK_MODIFIER_MIDDLE: &str = "/actions/default/in/ClickModifierMiddle";
@@ -50,6 +51,7 @@ pub(super) struct OpenVrInputSource {
alt_click_hnd: ActionHandle,
show_hide_hnd: ActionHandle,
space_drag_hnd: ActionHandle,
space_rotate_hnd: ActionHandle,
click_modifier_right_hnd: ActionHandle,
click_modifier_middle_hnd: ActionHandle,
}
@@ -72,6 +74,7 @@ impl OpenVrInputSource {
let alt_click_hnd = input.get_action_handle(PATH_ALT_CLICK)?;
let show_hide_hnd = input.get_action_handle(PATH_SHOW_HIDE)?;
let space_drag_hnd = input.get_action_handle(PATH_SPACE_DRAG)?;
let space_rotate_hnd = input.get_action_handle(PATH_SPACE_ROTATE)?;
let click_modifier_right_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_RIGHT)?;
let click_modifier_middle_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_MIDDLE)?;
@@ -106,6 +109,7 @@ impl OpenVrInputSource {
alt_click_hnd,
show_hide_hnd,
space_drag_hnd,
space_rotate_hnd,
click_modifier_right_hnd,
click_modifier_middle_hnd,
hands,
@@ -193,6 +197,11 @@ impl OpenVrInputSource {
.map(|x| x.0.bState)
.unwrap_or(false);
app_hand.now.space_rotate = input
.get_digital_action_data(self.space_rotate_hnd, hand.input_hnd)
.map(|x| x.0.bState)
.unwrap_or(false);
app_hand.now.click_modifier_right = input
.get_digital_action_data(self.click_modifier_right_hnd, hand.input_hnd)
.map(|x| x.0.bState)
@@ -302,9 +311,7 @@ fn get_tracked_device(
pub fn set_action_manifest(input: &mut InputManager) -> anyhow::Result<()> {
let action_path = CONFIG_ROOT_PATH.join("actions.json");
if !action_path.is_file() {
File::create(&action_path)?.write_all(include_bytes!("../../res/actions.json"))?;
}
File::create(&action_path)?.write_all(include_bytes!("../../res/actions.json"))?;
let binding_path = CONFIG_ROOT_PATH.join("actions_binding_knuckles.json");
if !binding_path.is_file() {
+7
View File
@@ -75,6 +75,7 @@ impl LinePool {
}),
data: OpenVrOverlayData {
width: 0.002,
override_width: true,
..Default::default()
},
..Default::default()
@@ -160,6 +161,12 @@ impl LinePool {
}
Ok(())
}
pub fn mark_dirty(&mut self) {
for data in self.lines.values_mut() {
data.state.dirty = true;
}
}
}
struct StaticRenderer {
+4 -2
View File
@@ -158,7 +158,8 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
}
EVREventType::VREvent_SeatedZeroPoseReset
| EVREventType::VREvent_StandingZeroPoseReset
| EVREventType::VREvent_ChaperoneUniverseHasChanged => {
| EVREventType::VREvent_ChaperoneUniverseHasChanged
| EVREventType::VREvent_SceneApplicationChanged => {
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
}
_ => {}
@@ -213,7 +214,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
playspace.fix_floor(&mut chaperone_mgr, &state.input_state);
}
SystemTask::ResetPlayspace => {
playspace.reset_offset(&mut chaperone_mgr);
playspace.reset_offset(&mut chaperone_mgr, &state.input_state);
}
},
}
@@ -236,6 +237,7 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
.iter()
.any(|p| p.now.show_hide && !p.before.show_hide)
{
lines.mark_dirty(); // workaround to prevent lines from not showing
overlays.show_hide(&mut state);
}
+10
View File
@@ -24,6 +24,7 @@ pub(super) struct OpenVrOverlayData {
pub(super) curvature: f32,
pub(super) sort_order: u32,
pub(crate) width: f32,
pub(super) override_width: bool,
pub(super) relative_to: RelativeTo,
}
@@ -233,6 +234,15 @@ impl OverlayData<OpenVrOverlayData> {
}
let dimensions = image.extent();
if !self.data.override_width {
let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0);
if (new_width - self.data.width).abs() > f32::EPSILON {
log::info!("{}: New width {}", self.state.name, new_width);
self.data.width = new_width;
self.upload_width(overlay);
}
}
let format = image.format();
let mut texture = VRVulkanTextureData_t {
+138 -20
View File
@@ -1,4 +1,4 @@
use glam::{Affine3A, Vec3A};
use glam::{Affine3A, Quat, Vec3, Vec3A};
use ovr_overlay::{
chaperone_setup::ChaperoneSetupManager,
compositor::CompositorManager,
@@ -12,22 +12,24 @@ use crate::{
use super::{helpers::Affine3AConvert, overlay::OpenVrOverlayData};
struct DragData {
struct MoverData<T> {
pose: Affine3A,
hand: usize,
hand_pos: Vec3A,
hand_pose: T,
}
pub(super) struct PlayspaceMover {
universe: ETrackingUniverseOrigin,
last: Option<DragData>,
drag: Option<MoverData<Vec3A>>,
rotate: Option<MoverData<Quat>>,
}
impl PlayspaceMover {
pub fn new() -> Self {
Self {
universe: ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated,
last: None,
drag: None,
rotate: None,
}
}
@@ -38,10 +40,73 @@ impl PlayspaceMover {
state: &AppState,
) {
let universe = self.universe.clone();
if let Some(data) = self.last.as_mut() {
if let Some(data) = self.rotate.as_mut() {
let pointer = &state.input_state.pointers[data.hand];
if !pointer.now.space_rotate {
self.rotate = None;
log::info!("End space rotate");
return;
}
let new_hand =
Quat::from_affine3(&(data.pose * state.input_state.pointers[data.hand].raw_pose));
let dq = new_hand * data.hand_pose.conjugate();
let rel_y = f32::atan2(
2.0 * (dq.y * dq.w + dq.x * dq.z),
(2.0 * (dq.w * dq.w + dq.x * dq.x)) - 1.0,
);
let mut space_transform = Affine3A::from_rotation_y(rel_y);
let offset = (space_transform.transform_vector3a(state.input_state.hmd.translation)
- state.input_state.hmd.translation)
* -1.0;
let mut overlay_transform = Affine3A::from_rotation_y(-rel_y);
overlay_transform.translation = offset;
space_transform.translation = offset;
overlays.iter_mut().for_each(|overlay| {
if overlay.state.grabbable {
overlay.state.dirty = true;
overlay.state.transform.translation =
overlay_transform.transform_point3a(overlay.state.transform.translation);
}
});
data.pose = data.pose * space_transform;
data.hand_pose = new_hand;
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
apply_chaperone_transform(space_transform.inverse(), chaperone_mgr);
}
set_working_copy(&universe, chaperone_mgr, &data.pose);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
if pointer.now.space_rotate {
let Some(mat) = get_working_copy(&universe, chaperone_mgr) else {
log::warn!("Can't space rotate - failed to get zero pose");
return;
};
let hand_pose = Quat::from_affine3(&(mat * pointer.raw_pose));
self.rotate = Some(MoverData {
pose: mat,
hand: i,
hand_pose,
});
self.drag = None;
log::info!("Start space rotate");
return;
}
}
}
if let Some(data) = self.drag.as_mut() {
let pointer = &state.input_state.pointers[data.hand];
if !pointer.now.space_drag {
self.last = None;
self.drag = None;
log::info!("End space drag");
return;
}
@@ -49,23 +114,28 @@ impl PlayspaceMover {
let new_hand = data
.pose
.transform_point3a(state.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos = new_hand - data.hand_pos;
let relative_pos = new_hand - data.hand_pose;
if relative_pos.length_squared() > 1000.0 {
log::warn!("Space drag too fast, ignoring");
return;
}
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
overlays.iter_mut().for_each(|overlay| {
if overlay.state.grabbable {
overlay.state.dirty = true;
overlay.state.transform.translation -= relative_pos;
overlay.state.transform.translation += overlay_offset;
}
});
data.pose.translation += relative_pos;
data.hand_pos = new_hand;
data.hand_pose = new_hand;
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
apply_chaperone_offset(overlay_offset, chaperone_mgr);
}
set_working_copy(&universe, chaperone_mgr, &data.pose);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
} else {
@@ -76,25 +146,44 @@ impl PlayspaceMover {
return;
};
let hand_pos = mat.transform_point3a(pointer.raw_pose.translation);
self.last = Some(DragData {
self.drag = Some(MoverData {
pose: mat,
hand: i,
hand_pos,
hand_pose: hand_pos,
});
self.rotate = None;
log::info!("Start space drag");
break;
return;
}
}
}
}
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager) {
set_working_copy(&self.universe, chaperone_mgr, &Affine3A::IDENTITY);
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
let mut height = 1.6;
if let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) {
height = input.hmd.translation.y - mat.translation.y;
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
apply_chaperone_transform(mat, chaperone_mgr);
}
}
let xform = if self.universe == ETrackingUniverseOrigin::TrackingUniverseSeated {
Affine3A::from_translation(Vec3::NEG_Y * height)
} else {
Affine3A::IDENTITY
};
set_working_copy(&self.universe, chaperone_mgr, &xform);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
if self.last.is_some() {
if self.drag.is_some() {
log::info!("Space drag interrupted by manual reset");
self.last = None;
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by manual reset");
self.rotate = None;
}
}
@@ -105,7 +194,9 @@ impl PlayspaceMover {
log::warn!("Can't fix floor - failed to get zero pose");
return;
};
mat.translation.y += y1.min(y2) - 0.03;
let offset = y1.min(y2) - 0.03;
mat.translation.y += offset;
set_working_copy(&self.universe, chaperone_mgr, &mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
}
@@ -125,9 +216,13 @@ impl PlayspaceMover {
self.universe = new_universe;
}
if self.last.is_some() {
if self.drag.is_some() {
log::info!("Space drag interrupted by external change");
self.last = None;
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by external change");
self.rotate = None;
}
}
@@ -171,3 +266,26 @@ fn set_working_copy(
_ => chaperone_mgr.set_working_seated_zero_pose_to_raw_tracking_pose(&mat),
};
}
fn apply_chaperone_offset(offset: Vec3A, chaperone_mgr: &mut ChaperoneSetupManager) {
let mut quads = chaperone_mgr.get_live_collision_bounds_info();
quads.iter_mut().for_each(|quad| {
quad.vCorners.iter_mut().for_each(|corner| {
corner.v[0] += offset.x;
corner.v[2] += offset.z;
});
});
chaperone_mgr.set_working_collision_bounds_info(quads.as_mut_slice());
}
fn apply_chaperone_transform(transform: Affine3A, chaperone_mgr: &mut ChaperoneSetupManager) {
let mut quads = chaperone_mgr.get_live_collision_bounds_info();
quads.iter_mut().for_each(|quad| {
quad.vCorners.iter_mut().for_each(|corner| {
let coord = transform.transform_point3a(Vec3A::from_slice(&corner.v));
corner.v[0] = coord.x;
corner.v[2] = coord.z;
});
});
chaperone_mgr.set_working_collision_bounds_info(quads.as_mut_slice());
}
+1 -3
View File
@@ -4,9 +4,7 @@ use openxr as xr;
use xr::OverlaySessionCreateFlagsEXTX;
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
let Ok(entry) = (unsafe { xr::Entry::load() }) else {
bail!("OpenXR Loader not found.");
};
let entry = xr::Entry::linked();
let Ok(available_extensions) = entry.enumerate_extensions() else {
bail!("Failed to enumerate OpenXR extensions.");
+17 -23
View File
@@ -5,7 +5,7 @@ use openxr as xr;
use crate::{
backend::input::{Haptics, Pointer},
state::AppState,
state::{AppSession, AppState},
};
use super::XrState;
@@ -98,12 +98,7 @@ impl OpenXrInputSource {
xr.session.sync_actions(&[(&self.action_set).into()])?;
for i in 0..2 {
self.hands[i].update(
&mut state.input_state.pointers[i],
&xr.stage,
&xr.session,
xr.predicted_display_time,
)?;
self.hands[i].update(&mut state.input_state.pointers[i], xr, &state.session)?;
}
Ok(())
}
@@ -123,11 +118,10 @@ impl OpenXrHand {
pub(super) fn update(
&self,
pointer: &mut Pointer,
stage: &xr::Space,
session: &XrSession,
time: xr::Time,
xr: &XrState,
session: &AppSession,
) -> Result<(), xr::sys::Result> {
let location = self.space.locate(stage, time)?;
let location = self.space.locate(&xr.stage, xr.predicted_display_time)?;
if location
.location_flags
.contains(xr::SpaceLocationFlags::ORIENTATION_VALID)
@@ -140,46 +134,46 @@ impl OpenXrHand {
pointer.now.click = self
.source
.action_click
.state(session, xr::Path::NULL)?
.state(&xr.session, xr::Path::NULL)?
.current_state
> 0.7;
> session.config.xr_click_sensitivity;
pointer.now.grab = self
.source
.action_grab
.state(session, xr::Path::NULL)?
.state(&xr.session, xr::Path::NULL)?
.current_state
> 0.7;
> session.config.xr_grab_sensitivity;
pointer.now.scroll = self
.source
.action_scroll
.state(session, xr::Path::NULL)?
.state(&xr.session, xr::Path::NULL)?
.current_state;
pointer.now.alt_click = self
.source
.action_alt_click
.state(session, xr::Path::NULL)?
.state(&xr.session, xr::Path::NULL)?
.current_state
> 0.7;
> session.config.xr_alt_click_sensitivity;
pointer.now.show_hide = self
.source
.action_show_hide
.state(session, xr::Path::NULL)?
.state(&xr.session, xr::Path::NULL)?
.current_state;
pointer.now.click_modifier_right = self
.source
.action_click_modifier_right
.state(session, xr::Path::NULL)?
.state(&xr.session, xr::Path::NULL)?
.current_state;
pointer.now.click_modifier_middle = self
.source
.action_click_modifier_middle
.state(session, xr::Path::NULL)?
.state(&xr.session, xr::Path::NULL)?
.current_state;
Ok(())
@@ -380,11 +374,11 @@ fn suggest_bindings(
),
xr::Binding::new(
&hands[0].action_grab,
instance.string_to_path("/user/hand/left/input/squeeze/value")?,
instance.string_to_path("/user/hand/left/input/squeeze/force")?,
),
xr::Binding::new(
&hands[1].action_grab,
instance.string_to_path("/user/hand/right/input/squeeze/value")?,
instance.string_to_path("/user/hand/right/input/squeeze/force")?,
),
xr::Binding::new(
&hands[0].action_scroll,
+14 -5
View File
@@ -161,6 +161,7 @@ impl OverlayState {
}
let scale = self.transform.x_axis.length();
self.saved_scale = Some(scale);
let col_z = (self.transform.translation - hmd.translation).normalize();
let col_y = up_dir;
@@ -287,10 +288,18 @@ impl InteractionHandler for SplitOverlayBackend {
}
pub fn ui_transform(extent: &[u32; 2]) -> Affine2 {
let aspect = extent[0] as f32 / extent[1] as f32;
let scale = if aspect < 1.0 {
Vec2 {
x: 1.0 / aspect,
y: -1.0,
}
} else {
Vec2 {
x: 1.0,
y: -1.0 * aspect,
}
};
let center = Vec2 { x: 0.5, y: 0.5 };
Affine2::from_cols(
Vec2::X,
Vec2::NEG_Y * (extent[0] as f32 / extent[1] as f32),
center,
)
Affine2::from_scale_angle_translation(scale, 0.0, center)
}
+33 -5
View File
@@ -117,6 +117,15 @@ pub struct GeneralConfig {
#[serde(default = "def_auto")]
pub capture_method: Arc<str>,
#[serde(default = "def_point7")]
pub xr_grab_sensitivity: f32,
#[serde(default = "def_point7")]
pub xr_click_sensitivity: f32,
#[serde(default = "def_point7")]
pub xr_alt_click_sensitivity: f32,
}
impl GeneralConfig {
@@ -136,8 +145,8 @@ impl GeneralConfig {
}
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("keyboard_scale", self.keyboard_scale, 0.05, 5.0);
GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.05, 5.0);
}
}
@@ -192,12 +201,31 @@ pub fn load_general() -> GeneralConfig {
// 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) {
let mut paths: Vec<_> = paths_unsorted
.filter_map(|r| match r {
Ok(entry) => Some(entry),
Err(e) => {
error!("Failed to read conf.d directory: {}", e);
None
}
})
.collect();
// Sort paths alphabetically
let mut paths: Vec<_> = paths_unsorted.map(|r| r.unwrap()).collect(); // TODO safe unwrap?
paths.sort_by_key(|dir| dir.path());
for path in paths {
if !path.file_type().unwrap().is_file() {
// TODO safe unwrap?
let file_type = match path.file_type() {
Ok(file_type) => file_type,
Err(e) => {
error!(
"Failed to get file type of {}: {}",
path.path().to_string_lossy(),
e
);
continue;
}
};
if !file_type.is_file() {
continue;
}
+19 -14
View File
@@ -106,16 +106,14 @@ impl FontCache {
}
let pattern_str = format!("{PRIMARY_FONT}-{size}:style=bold:charset={cp:04x}");
let mut pattern =
OwnedPattern::from_str(&pattern_str).expect("Failed to create fontconfig pattern");
let mut pattern = OwnedPattern::from_str(&pattern_str).unwrap(); // safe because PRIMARY_FONT is const
self.fc
.substitute(&mut pattern, fontconfig::MatchKind::Pattern);
pattern.default_substitute();
let pattern = pattern.font_match(&mut self.fc);
if let Some(path) = pattern.filename() {
for path in pattern.filename().iter() {
log::debug!(
"Loading font: {} {}pt",
pattern.name().unwrap_or(path),
@@ -124,12 +122,20 @@ impl FontCache {
let font_idx = pattern.face_index().unwrap_or(0);
let face = self
.ft
.new_face(path, font_idx as _)
.expect("Failed to load font face");
face.set_char_size(size << 6, size << 6, 96, 96)
.expect("Failed to set font size");
let face = match self.ft.new_face(path, font_idx as _) {
Ok(face) => face,
Err(e) => {
log::warn!("Failed to load font at {}: {:?}", path, e);
break;
}
};
match face.set_char_size(size << 6, size << 6, 96, 96) {
Ok(_) => {}
Err(e) => {
log::warn!("Failed to set font size: {:?}", e);
break;
}
};
let idx = coll.fonts.len();
for cp in 0..0xFFFF {
@@ -156,11 +162,10 @@ impl FontCache {
let font = Font { face, glyphs };
coll.fonts.push(font);
idx
} else {
coll.cp_map.insert(cp, 0);
0
return idx;
}
coll.cp_map.insert(cp, 0);
0
}
fn get_glyph_for_cp(
+3 -8
View File
@@ -1,6 +1,5 @@
use std::{
f32::consts::PI,
io::Cursor,
ops::Add,
process::{self, Child},
sync::Arc,
@@ -8,7 +7,6 @@ use std::{
};
use glam::{Quat, Vec3A};
use rodio::{Decoder, Source};
use serde::Deserialize;
use crate::{
@@ -613,11 +611,8 @@ fn run_window(window: &Arc<str>, action: &WindowAction, app: &mut AppState) {
}
}
const THUMP_AUDIO_WAV: &'static [u8] = include_bytes!("../../res/380885.wav");
fn audio_thump(app: &mut AppState) {
if let Some(handle) = app.audio.get_handle() {
let wav = include_bytes!("../../res/380885.wav");
let cursor = Cursor::new(wav);
let source = Decoder::new_wav(cursor).unwrap();
let _ = handle.play_raw(source.convert_samples());
}
app.audio.play(THUMP_AUDIO_WAV);
}
+19
View File
@@ -29,6 +29,9 @@ pub enum LabelContent {
format: Arc<str>,
timezone: Option<Arc<str>>,
},
Timer {
format: Arc<str>,
},
Battery {
device: usize,
low_threshold: f32,
@@ -49,6 +52,10 @@ pub enum LabelData {
format: Arc<str>,
timezone: Option<Tz>,
},
Timer {
format: Arc<str>,
start: Instant,
},
Exec {
last_exec: Instant,
interval: f32,
@@ -85,6 +92,10 @@ pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent) {
timezone: tz,
})
}
LabelContent::Timer { format } => Some(LabelData::Timer {
format: format.clone(),
start: Instant::now(),
}),
LabelContent::Exec { command, interval } => Some(LabelData::Exec {
last_exec: Instant::now(),
interval: *interval,
@@ -155,6 +166,14 @@ pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut A
control.set_text(&format!("{}", &date.format(&format)));
}
}
LabelData::Timer { format, start } => {
let mut format = format.clone().to_lowercase();
let duration = start.elapsed().as_secs();
format = format.replace("%s", &format!("{:02}", (duration % 60)));
format = format.replace("%m", &format!("{:02}", ((duration / 60) % 60)));
format = format.replace("%h", &format!("{:02}", ((duration / 60) / 60)));
control.set_text(&format);
}
LabelData::Exec {
last_exec,
interval,
+18 -16
View File
@@ -1,6 +1,5 @@
use std::{
collections::HashMap,
io::Cursor,
process::{Child, Command},
str::FromStr,
};
@@ -18,7 +17,6 @@ use crate::{
use glam::{vec2, vec3a, Affine2, Vec4};
use once_cell::sync::Lazy;
use regex::Regex;
use rodio::{Decoder, Source};
use serde::{Deserialize, Serialize};
const PIXELS_PER_UNIT: f32 = 80.;
@@ -83,10 +81,14 @@ where
verbs: key_events_for_macro(macro_verbs),
});
} else if let Some(exec_args) = LAYOUT.exec_commands.get(key) {
if exec_args.is_empty() {
log::error!("Keyboard: EXEC args empty for {}", key);
continue;
}
maybe_state = Some(KeyButtonData::Exec {
program: exec_args
.first()
.expect("Keyboard: Invalid EXEC args")
.unwrap() // safe because we checked is_empty
.clone(),
args: exec_args.iter().skip(1).cloned().collect(),
});
@@ -227,18 +229,12 @@ struct KeyboardData {
processes: Vec<Child>,
}
const KEY_AUDIO_WAV: &'static [u8] = include_bytes!("../res/421581.wav");
impl KeyboardData {
fn key_click(&mut self, app: &mut AppState) {
if !app.session.config.keyboard_sound_enabled {
return;
}
if let Some(handle) = app.audio.get_handle() {
// https://freesound.org/people/UberBosser/sounds/421581/
let wav = include_bytes!("../res/421581.wav");
let cursor = Cursor::new(wav);
let source = Decoder::new_wav(cursor).unwrap();
let _ = handle.play_raw(source.convert_samples());
if app.session.config.keyboard_sound_enabled {
app.audio.play(KEY_AUDIO_WAV);
}
}
}
@@ -253,7 +249,7 @@ enum KeyButtonData {
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());
Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap()); // want panic
#[derive(Debug, Deserialize, Serialize)]
pub struct Layout {
@@ -314,11 +310,17 @@ impl Layout {
key = &key[3..];
}
if key.contains('_') {
key = key.split('_').next().unwrap();
key = key.split('_').next().unwrap_or_else(|| {
log::error!(
"keyboard.yaml: Key '{}' must not start or end with '_'!",
key
);
"???"
});
}
vec![format!(
"{}{}",
key.chars().next().unwrap().to_uppercase(),
key.chars().next().unwrap().to_uppercase(), // safe because we checked is_empty
&key[1..].to_lowercase()
)]
}
+45 -18
View File
@@ -48,6 +48,8 @@ use crate::{
state::{AppSession, AppState},
};
const CURSOR_SIZE: f32 = 16. / 1440.;
static DRM_FORMATS: once_cell::sync::OnceCell<Vec<DrmFormat>> = once_cell::sync::OnceCell::new();
pub struct ScreenInteractionHandler {
@@ -207,18 +209,21 @@ impl ScreenPipeline {
cmd.run_ref(&pass)?;
if let (Some(mouse), Some(mouse_view)) = (mouse, self.mouse.clone()) {
let size = CURSOR_SIZE * self.extentf[1];
let half_size = size * 0.5;
let vertex_buffer = app.graphics.upload_verts(
self.extentf[0],
self.extentf[1],
mouse.x * self.extentf[0] - 2.,
mouse.y * self.extentf[1] - 2.,
4.0,
4.0,
mouse.x * self.extentf[0] - half_size,
mouse.y * self.extentf[1] - half_size,
size,
size,
)?;
let set0 = self
.pipeline
.uniform_sampler(0, mouse_view.clone(), Filter::Linear)?;
.uniform_sampler(0, mouse_view.clone(), Filter::Nearest)?;
let pass = self.pipeline.create_pass(
self.extentf,
@@ -288,19 +293,18 @@ impl ScreenRenderer {
output: &WlxOutput,
token: Option<&str>,
session: &AppSession,
) -> Option<(
) -> anyhow::Result<(
ScreenRenderer,
Option<String>, /* pipewire restore token */
)> {
let name = output.name.clone();
let embed_mouse = !session.config.double_cursor_fix;
let select_screen_result =
futures::executor::block_on(pipewire_select_screen(token, embed_mouse, true, true))
.ok()?;
futures::executor::block_on(pipewire_select_screen(token, embed_mouse, true, true))?;
let capture = PipewireCapture::new(name, select_screen_result.node_id, 60);
Some((
Ok((
ScreenRenderer {
name: output.name.clone(),
capture: Box::new(capture),
@@ -546,17 +550,26 @@ where
);
}
if let Some((renderer, restore_token)) = ScreenRenderer::new_pw(output, token, session) {
capture = Some(renderer);
match ScreenRenderer::new_pw(output, token, session) {
Ok((renderer, restore_token)) => {
capture = Some(renderer);
if let Some(token) = restore_token {
if pw_token_store
.insert(String::from(display_name), token.clone())
.is_none()
{
log::info!("Adding Pipewire token {}", token);
if let Some(token) = restore_token {
if pw_token_store
.insert(String::from(display_name), token.clone())
.is_none()
{
log::info!("Adding Pipewire token {}", token);
}
}
}
Err(e) => {
log::warn!(
"{}: Failed to create Pipewire capture: {:?}",
&output.name,
e
);
}
}
}
if let Some(capture) = capture {
@@ -689,7 +702,7 @@ where
O: Default,
{
let mut overlays = vec![];
let wl = WlxClient::new().ok_or_else(|| anyhow::anyhow!("Failed to connect to Wayland"))?;
let mut wl = WlxClient::new().ok_or_else(|| anyhow::anyhow!("Failed to connect to Wayland"))?;
// Load existing Pipewire tokens from file
let mut pw_tokens: HashMap<String, String> = if let Ok(conf) = load_pw_token_config() {
@@ -700,6 +713,20 @@ where
let pw_tokens_copy = pw_tokens.clone();
let mut origin = (i32::MAX, i32::MAX);
for output in wl.outputs.values() {
origin.0 = origin.0.min(output.logical_pos.0);
origin.1 = origin.1.min(output.logical_pos.1);
}
log::info!("Desktop origin: {:?}", origin);
// adjust all outputs so that the top-left corner is at (0, 0)
for output in wl.outputs.values_mut() {
output.logical_pos.0 -= origin.0;
output.logical_pos.1 -= origin.1;
}
for id in wl.outputs.keys() {
if let Some(overlay) = try_create_screen(&wl, *id, &mut pw_tokens, session) {
overlays.push(overlay);
+2 -11
View File
@@ -1,12 +1,9 @@
use std::{
io::Cursor,
ops::Add,
sync::{atomic::AtomicUsize, Arc},
time::Instant,
};
use rodio::{Decoder, Source};
use glam::vec3a;
use crate::{
@@ -21,6 +18,7 @@ use crate::{
const FONT_SIZE: isize = 16;
const PADDING: (f32, f32) = (25., 7.);
const PIXELS_TO_METERS: f32 = 1. / 2000.;
const TOAST_AUDIO_WAV: &'static [u8] = include_bytes!("../res/557297.wav");
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
@@ -79,12 +77,7 @@ impl Toast {
.enqueue_at(TaskType::DropOverlay(selector), destroy_at);
if has_sound {
if let Some(handle) = app.audio.get_handle() {
let wav = include_bytes!("../res/557297.wav");
let cursor = Cursor::new(wav);
let source = Decoder::new_wav(cursor).unwrap();
let _ = handle.play_raw(source.convert_samples());
}
app.audio.play(TOAST_AUDIO_WAV);
}
}
}
@@ -136,7 +129,6 @@ fn new_toast(
canvas.panel(0., 0., size.0, size.1);
if toast.body.len() > 0 {
log::info!("{}: {}", title, toast.body);
canvas.label(PADDING.0, 54., og_width, size.1 - 54., toast.body);
canvas.fg_color = color_parse("#101010").unwrap(); // want panic
@@ -144,7 +136,6 @@ fn new_toast(
canvas.panel(0., 0., size.0, 30.);
canvas.label_centered(PADDING.0, 16., og_width, FONT_SIZE as f32 + 2., title);
} else {
log::info!("Toast: {}", title);
canvas.label_centered(PADDING.0, 0., og_width, size.1, title);
}
+5
View File
@@ -40,6 +40,11 @@
"type": "boolean",
"requirement": "optional"
},
{
"name": "/actions/default/in/SpaceRotate",
"type": "boolean",
"requirement": "optional"
},
{
"name": "/actions/default/in/LeftHand",
"type": "pose",
+12 -32
View File
@@ -22,47 +22,27 @@ row_size: 23
# Specifies the size of each key. The sum of any given row must equal RowSize
key_sizes:
- [1.5,0.5, 1, 1, 1, 1,0.5,1, 1, 1, 1,0.5,1, 1, 1, 1, 0.5, 1, 1, 1, 4.5]
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
- [1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.5, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
- [1.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.25, 4, 1, 1, 1, 1]
- [2.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1]
- [1.25, 1.25, 1.25, 6.25, 1.25, 1.25, 1.25, 1.25, 0.5, 1, 1, 1, 0.5, 2, 1, 1]
- [1.5,0.5, 1, 1, 1, 1,0.5,1, 1, 1, 1,0.5,1, 1, 1, 1, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
- [1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.5, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
- [1.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.25, 4, 1, 1, 1, 1]
- [2.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1]
- [1.25, 1.25, 1.25, 6.25, 1.25, 1.25, 1.25, 1.25, 0.5, 1, 1, 1, 0.5, 2, 1, 1]
# The main (blue) layout of the keyboard.
# Accepted are:
# - virtual keys, for a full list go to https://github.com/galister/X11Overlay/blob/master/Types/VirtualKey.cs
# - ExecCommands (defined below)
# - Macros (defined below)
# - virtual keys. For a full list, look at enum VirtualKey in https://github.com/galister/wlx-overlay-s/blob/main/src/hid.rs
# - exec_commands (defined below)
# - macros (defined below)
# - ~ (null) will leave an empty space with the corresponding size from key_sizes
main_layout:
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~]
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~, "COPY", "PASTE", ~, "KILL"]
- ["Oem3", "N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9", "N0", "Minus", "Plus", "BackSpace", ~, "Insert", "Home", "Prior", ~, "NumLock", "KP_Divide", "KP_Multiply", "KP_Subtract"]
- ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"]
- ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~]
- ["LShift", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"]
- ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", ~]
# When using the purple pointer...
# None - No special functionality when using purple pointer
# Shift - Use same functionality as the orange pointer
# Ctrl - Use Main layout with Ctrl modifier
# Super - Use Main layout with Super (WinKey) modifier
# Meta - Use Main layout with Meta (AltGr) modifier
# Layout - Use the alt_layout defined below
alt_layout_mode: Ctrl
# The alt (fn) layout of the keyboard. Only takes effect when alt_layout_mode: Layout
# Accepted values are the same as for MainLayout.
# The default layout here is a dummy. Change it to fit your liking.
alt_layout:
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~, "COPY", "PASTE"]
- ["Oem3", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Minus", "Plus", "BackSpace", ~, "Insert", "Home", "Prior", ~, "NumLock", "KP_Divide", "KP_Multiply", "KP_Subtract"]
- ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"]
- ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~]
- ["LShift", "Oem102", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"]
- ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", ~]
# Shell commands to be used in a layout.
# Value is an array of string arguments.
exec_commands:
@@ -70,7 +50,7 @@ exec_commands:
# Series of keypresses to be used in a layout.
# Format: keyName [DOWN|UP]
# keyName must be a valid key from `xmodmap -pke`
# keyName must be a valid virtual key from the VirtualKey enum (see above)
# DOWN|UP: can be omitted for an implicit "keyName DOWN, keyName UP"
macros:
KILL: [ "LSuper DOWN", "LCtrl DOWN", "Escape", "LCtrl UP", "LSuper UP" ]
@@ -78,7 +58,7 @@ macros:
PASTE: [ "LCtrl DOWN", "V", "LCtrl UP" ]
# Custom labels to use.
# Key: element of MainLayout / AltLayout
# Key: element of main_layout
# Value: Array of strings. 0th element is the upper row, 1st element is lower row.
# For empty labels, use [] (do not use ~)
labels:
+6
View File
@@ -94,6 +94,12 @@ pub mod frag_screen {
void main()
{
out_color = texture(in_texture, in_uv);
// linear to srgb
bvec4 cutoff = lessThan(out_color, vec4(0.0031308));
vec4 higher = (pow(out_color, vec4(0.41666)) * vec4(1.055)) - vec4(0.055);
vec4 lower = out_color*vec4(12.92);
out_color = mix(higher, lower, cutoff);
out_color.a = 1.0;
}
",
+19 -4
View File
@@ -1,8 +1,8 @@
use std::{path::PathBuf, sync::Arc};
use std::{io::Cursor, path::PathBuf, sync::Arc};
use anyhow::bail;
use glam::Vec3;
use rodio::{OutputStream, OutputStreamHandle};
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
use serde::{Deserialize, Serialize};
use smallvec::{smallvec, SmallVec};
use vulkano::format::Format;
@@ -126,18 +126,33 @@ impl AudioOutput {
}
}
pub fn get_handle(&mut self) -> Option<&OutputStreamHandle> {
fn get_handle(&mut self) -> Option<&OutputStreamHandle> {
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, handle));
} else {
log::error!("Failed to open audio stream");
log::error!("Failed to open audio stream. Audio will not work.");
return None;
}
}
self.audio_stream.as_ref().map(|(_, h)| h)
}
pub fn play(&mut self, wav_bytes: &'static [u8]) {
let Some(handle) = self.get_handle() else {
return;
};
let cursor = Cursor::new(wav_bytes);
let source = match Decoder::new_wav(cursor) {
Ok(source) => source,
Err(e) => {
log::error!("Failed to play sound: {:?}", e);
return;
}
};
let _ = handle.play_raw(source.convert_samples());
}
}
pub struct ScreenMeta {