keyboard progress & refactors

This commit is contained in:
galister
2025-06-20 00:48:37 +09:00
parent e0e30dedfb
commit 44a9faac14
37 changed files with 1226 additions and 761 deletions

View File

@@ -1,3 +1,14 @@
[profile.dev]
opt-level = 0
debug = true
strip = "none"
debug-assertions = true
incremental = true
[profile.release-with-debug]
inherits = "release"
debug = true
[workspace]
members = ["wgui", "wgui/uidev-vk", "wlx-overlay-s", "wlx-capture"]
resolver = "3"

View File

@@ -27,10 +27,3 @@ smallvec = "1.15.0"
taffy = "0.8.1"
vulkano = { workspace = true }
vulkano-shaders = { workspace = true }
[profile.dev]
opt-level = 0
debug = true
strip = "none"
debug-assertions = true
incremental = true

View File

@@ -7,26 +7,44 @@ use crate::{
widget::{WidgetData, WidgetObj},
};
// TODO: mouse index
#[derive(Debug, Clone, Copy)]
pub enum MouseButton {
Left,
Right,
Middle,
}
pub struct MouseDownEvent {
pub pos: Vec2,
pub button: MouseButton,
pub device: usize,
}
pub struct MouseLeaveEvent {
pub device: usize,
}
pub struct MouseMotionEvent {
pub pos: Vec2,
pub device: usize,
}
pub struct MouseUpEvent {
pub pos: Vec2,
pub button: MouseButton,
pub device: usize,
}
pub struct MouseWheelEvent {
pub pos: Vec2,
pub shift: Vec2,
pub device: usize,
}
pub enum Event {
InternalStateChange,
MouseDown(MouseDownEvent),
MouseLeave(MouseLeaveEvent),
MouseMotion(MouseMotionEvent),
MouseUp(MouseUpEvent),
MouseWheel(MouseWheelEvent),
@@ -46,6 +64,7 @@ impl Event {
Event::MouseMotion(evt) => self.test_transform_pos(transform, &evt.pos),
Event::MouseUp(evt) => self.test_transform_pos(transform, &evt.pos),
Event::MouseWheel(evt) => self.test_transform_pos(transform, &evt.pos),
_ => false,
}
}
}
@@ -99,10 +118,14 @@ impl<'a> WidgetCallback<'a> for CallbackData<'a> {
pub type MouseEnterCallback = Box<dyn Fn(&mut CallbackData)>;
pub type MouseLeaveCallback = Box<dyn Fn(&mut CallbackData)>;
pub type MouseClickCallback = Box<dyn Fn(&mut CallbackData)>;
pub type MousePressCallback = Box<dyn Fn(&mut CallbackData, MouseButton)>;
pub type MouseReleaseCallback = Box<dyn Fn(&mut CallbackData, MouseButton)>;
pub type InternalStateChangeCallback = Box<dyn Fn(&mut CallbackData)>;
pub enum EventListener {
MouseEnter(MouseEnterCallback),
MouseLeave(MouseLeaveCallback),
MouseClick(MouseClickCallback),
MousePress(MousePressCallback),
MouseRelease(MouseReleaseCallback),
InternalStateChange(InternalStateChangeCallback),
}

View File

@@ -57,7 +57,7 @@ impl ParserResult {
}
pub fn process_template(
&self,
&mut self,
template_name: &str,
layout: &mut Layout,
widget_id: WidgetID,
@@ -90,6 +90,10 @@ impl ParserResult {
widget_id,
)?;
ctx.ids.into_iter().for_each(|(id, key)| {
self.ids.insert(id, key);
});
Ok(())
}
}

View File

@@ -17,11 +17,56 @@ pub mod text;
pub mod util;
pub struct WidgetData {
pub hovered: bool,
pub pressed: bool,
hovered: usize,
pressed: usize,
pub scrolling: Vec2, // normalized, 0.0-1.0. Not used in case if overflow != scroll
pub transform: glam::Mat4,
}
impl WidgetData {
pub fn set_device_pressed(&mut self, device: usize, pressed: bool) -> bool {
let bit = 1 << device;
let state_changed;
if pressed {
state_changed = self.pressed == 0;
self.pressed |= bit;
} else {
state_changed = self.pressed == bit;
self.pressed &= !bit;
}
state_changed
}
pub fn set_device_hovered(&mut self, device: usize, hovered: bool) -> bool {
let bit = 1 << device;
let state_changed;
if hovered {
state_changed = self.hovered == 0;
self.hovered |= bit;
} else {
state_changed = self.hovered == bit;
self.hovered &= !bit;
}
state_changed
}
pub fn get_pressed(&self, device: usize) -> bool {
self.pressed & (1 << device) != 0
}
pub fn get_hovered(&self, device: usize) -> bool {
self.hovered & (1 << device) != 0
}
pub fn is_pressed(&self) -> bool {
self.pressed != 0
}
pub fn is_hovered(&self) -> bool {
self.hovered != 0
}
}
pub struct WidgetState {
pub data: WidgetData,
pub obj: Box<dyn WidgetObj>,
@@ -32,8 +77,8 @@ impl WidgetState {
fn new(obj: Box<dyn WidgetObj>) -> anyhow::Result<WidgetState> {
Ok(Self {
data: WidgetData {
hovered: false,
pressed: false,
hovered: 0,
pressed: 0,
scrolling: Vec2::default(),
transform: glam::Mat4::IDENTITY,
},
@@ -252,32 +297,40 @@ impl WidgetState {
) -> EventResult {
let hovered = event.test_mouse_within_transform(params.transform_stack.get());
let mut just_clicked = false;
let mut pressed_changed_button = None;
let mut hovered_changed = false;
match &event {
Event::MouseDown(_) => {
if self.data.hovered {
self.data.pressed = true;
}
Event::MouseDown(e) => {
pressed_changed_button = self
.data
.set_device_pressed(e.device, true)
.then_some(e.button);
}
Event::MouseUp(_) => {
if self.data.pressed {
self.data.pressed = false;
just_clicked = self.data.hovered;
}
Event::MouseUp(e) => {
pressed_changed_button = self
.data
.set_device_pressed(e.device, false)
.then_some(e.button);
}
Event::MouseWheel(e) => {
if self.process_wheel(params, e) {
return EventResult::Consumed;
}
}
Event::MouseMotion(e) => {
hovered_changed |= self.data.set_device_hovered(e.device, hovered);
}
Event::MouseLeave(e) => {
hovered_changed |= self.data.set_device_hovered(e.device, false);
}
_ => {}
}
// TODO: simplify this behemoth, I gave up arguing with the compiler
for listener in &self.event_listeners {
match listener {
EventListener::MouseEnter(callback) => {
if hovered && !self.data.hovered {
if hovered_changed && self.data.is_hovered() {
let mut data = CallbackData {
obj: self.obj.as_mut(),
widget_data: &mut self.data,
@@ -295,7 +348,7 @@ impl WidgetState {
}
}
EventListener::MouseLeave(callback) => {
if !hovered && self.data.hovered {
if hovered_changed && !self.data.is_hovered() {
let mut data = CallbackData {
obj: self.obj.as_mut(),
widget_data: &mut self.data,
@@ -312,8 +365,8 @@ impl WidgetState {
}
}
}
EventListener::MouseClick(callback) => {
if just_clicked {
EventListener::MousePress(callback) => {
if let Some(button) = pressed_changed_button.filter(|_| self.data.is_pressed()) {
let mut data = CallbackData {
obj: self.obj.as_mut(),
widget_data: &mut self.data,
@@ -324,19 +377,49 @@ impl WidgetState {
node_id,
needs_redraw: false,
};
callback(&mut data);
callback(&mut data, button);
if data.needs_redraw {
*params.needs_redraw = true;
}
}
}
EventListener::MouseRelease(callback) => {
if let Some(button) = pressed_changed_button.filter(|_| !self.data.is_pressed()) {
let mut data = CallbackData {
obj: self.obj.as_mut(),
widget_data: &mut self.data,
widgets: params.widgets,
animations: params.animations,
dirty_nodes: params.dirty_nodes,
widget_id,
node_id,
needs_redraw: false,
};
callback(&mut data, button);
if data.needs_redraw {
*params.needs_redraw = true;
}
}
}
EventListener::InternalStateChange(callback) => {
let mut data = CallbackData {
obj: self.obj.as_mut(),
widget_data: &mut self.data,
widgets: params.widgets,
animations: params.animations,
dirty_nodes: params.dirty_nodes,
widget_id,
node_id,
needs_redraw: false,
};
callback(&mut data);
if data.needs_redraw {
*params.needs_redraw = true;
}
}
}
}
if self.data.hovered != hovered {
self.data.hovered = hovered;
}
EventResult::Pass
}
}

View File

@@ -13,10 +13,3 @@ wgui = { path = "../" }
winit = "0.30.10"
vulkano = { workspace = true }
vulkano-shaders = { workspace = true }
[profile.dev]
opt-level = 0
debug = true
strip = "none"
debug-assertions = true
incremental = true

View File

@@ -18,7 +18,7 @@ use vulkano::{
sync::GpuFuture,
};
use wgui::{
event::{MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
event::{MouseButton, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
gfx::WGfx,
renderer_vk::{self},
};
@@ -121,6 +121,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.push_event(&wgui::event::Event::MouseWheel(MouseWheelEvent {
shift: Vec2::new(x, y),
pos: mouse / scale,
device: 0,
}))
.unwrap(),
MouseScrollDelta::PixelDelta(pos) => testbed
@@ -128,6 +129,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.push_event(&wgui::event::Event::MouseWheel(MouseWheelEvent {
shift: Vec2::new(pos.x as f32 / 5.0, pos.y as f32 / 5.0),
pos: mouse / scale,
device: 0,
}))
.unwrap(),
},
@@ -141,6 +143,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.layout()
.push_event(&wgui::event::Event::MouseDown(MouseDownEvent {
pos: mouse / scale,
button: MouseButton::Left,
device: 0,
}))
.unwrap();
} else {
@@ -148,6 +152,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.layout()
.push_event(&wgui::event::Event::MouseUp(MouseUpEvent {
pos: mouse / scale,
button: MouseButton::Left,
device: 0,
}))
.unwrap();
}
@@ -162,6 +168,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.layout()
.push_event(&wgui::event::Event::MouseMotion(MouseMotionEvent {
pos: mouse / scale,
device: 0,
}))
.unwrap();
}

View File

@@ -58,7 +58,7 @@ impl TestbedGeneric {
let wid = widget_id.clone();
layout.add_event_listener(
button.body,
EventListener::MouseClick(Box::new(move |data| {
EventListener::MouseRelease(Box::new(move |data, _| {
button.set_text(data, "Congratulations!");
*wid.borrow_mut() = Some(data.widget_id);
})),

View File

@@ -1,7 +1,3 @@
[profile.release-with-debug]
inherits = "release"
debug = true
[package]
name = "wlx-overlay-s"
version = "25.4.2"

View File

@@ -0,0 +1,9 @@
<layout>
<theme>
<var key="menu_dark" value="#0A0A0ACC" />
<var key="background_dark" value="#0d131acc" />
<var key="background_light" value="#244179aa" />
<var key="bottom_panel" value="#141e28" />
</theme>
</layout>

View File

@@ -4,7 +4,7 @@
<!-- Used for special keys. -->
<template name="KeySpecial">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}">
<rectangle
<rectangle id="${id}"
margin="4" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
align_items="center"
@@ -17,8 +17,8 @@
<!-- Key cap with a single label. -->
<!-- Used for letter keys on layouts without AltGr. -->
<template name="KeyLetter">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}">
<rectangle
<div id="${id}" width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}">
<rectangle id="${id}"
margin="4" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
align_items="center"
@@ -32,7 +32,7 @@
<!-- Used for letter keys on layouts with AltGr. -->
<template name="KeyLetterAltGr">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}">
<rectangle
<rectangle id="${id}"
margin="4" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
gap="4"
@@ -49,7 +49,7 @@
<!-- Used for number & symbol keys on layouts without AltGr. -->
<template name="KeySymbol">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}">
<rectangle
<rectangle id="${id}"
margin="4" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
gap="4"
@@ -67,7 +67,7 @@
<!-- Used for number & symbol keys on layouts with AltGr. -->
<template name="KeySymbolAltGr">
<div width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}">
<rectangle
<rectangle id="${id}"
margin="4" width="100%" overflow="hidden" box_sizing="border_box"
border_color="#0044CC" border="2" round="8" color="#000A1C" color2="#000002" gradient="vertical"
flex_direction="row"

View File

@@ -10,14 +10,14 @@ use thiserror::Error;
use crate::{
config::AStrSetExt,
hid::{get_keymap_wl, get_keymap_x11},
overlays::{
anchor::create_anchor,
keyboard::{create_keyboard, KEYBOARD_NAME},
keyboard::{KEYBOARD_NAME, builder::create_keyboard},
screen::WlxClientAlias,
watch::{create_watch, WATCH_NAME},
watch::{WATCH_NAME, create_watch},
},
state::AppState,
subsystem::hid::{get_keymap_wl, get_keymap_x11},
};
use super::overlay::{OverlayData, OverlayID};
@@ -248,9 +248,12 @@ where
if extent_dirty && !create_ran {
let extent = wl.get_desktop_extent();
let origin = wl.get_desktop_origin();
app.hid_provider
let mut hid_provider = app.hid_provider.borrow_mut();
hid_provider
.inner
.set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
app.hid_provider
hid_provider
.inner
.set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
}

View File

@@ -4,13 +4,14 @@ use std::{collections::VecDeque, time::Instant};
use glam::{Affine3A, Vec2, Vec3, Vec3A, Vec3Swizzles};
use smallvec::{smallvec, SmallVec};
use smallvec::{SmallVec, smallvec};
use crate::backend::common::OverlaySelector;
use crate::backend::overlay::Positioning;
use crate::config::AStrMapExt;
use crate::overlays::anchor::ANCHOR_NAME;
use crate::state::{AppSession, AppState, KeyboardFocus};
use crate::state::{AppSession, AppState};
use crate::subsystem::input::KeyboardFocus;
use super::overlay::{OverlayID, OverlayState};
use super::task::{TaskContainer, TaskType};
@@ -402,7 +403,10 @@ where
log::trace!("Hit: {} {:?}", hovered.state.name, hit);
if pointer.now.grab && !pointer.before.grab && hovered.state.grabbable {
update_focus(&mut app.keyboard_focus, &hovered.state);
{
let mut hid_provider = app.hid_provider.borrow_mut();
update_focus(&mut hid_provider.keyboard_focus, &hovered.state);
}
pointer.start_grab(hovered, &mut app.tasks);
return (
hit.dist,
@@ -451,7 +455,10 @@ where
if pointer.now.click && !pointer.before.click {
pointer.interaction.clicked_id = Some(hit.overlay);
update_focus(&mut app.keyboard_focus, &hovered.state);
{
let mut hid_provider = app.hid_provider.borrow_mut();
update_focus(&mut hid_provider.keyboard_focus, &hovered.state);
}
hovered.backend.on_pointer(app, &hit, true);
} else if !pointer.now.click && pointer.before.click {
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {

View File

@@ -1,9 +1,5 @@
pub mod common;
pub mod input;
pub mod notifications;
#[allow(clippy::all)]
mod notifications_dbus;
#[cfg(feature = "openvr")]
pub mod openvr;
@@ -11,9 +7,6 @@ pub mod openvr;
#[cfg(feature = "openxr")]
pub mod openxr;
#[cfg(feature = "osc")]
pub mod osc;
#[cfg(feature = "wayvr")]
pub mod wayvr;

View File

@@ -19,7 +19,6 @@ use crate::{
backend::{
common::{BackendError, OverlayContainer},
input::interact,
notifications::NotificationManager,
openvr::{
helpers::adjust_gain,
input::{OpenVrInputSource, set_action_manifest},
@@ -36,6 +35,7 @@ use crate::{
watch::{WATCH_NAME, watch_fade},
},
state::AppState,
subsystem::notifications::NotificationManager,
};
#[cfg(feature = "wayvr")]
@@ -315,7 +315,7 @@ pub fn openvr_run(
}
}
state.hid_provider.commit();
state.hid_provider.borrow_mut().inner.commit();
let mut buffers = CommandBuffers::default();
lines.update(universe.clone(), &mut overlay_mgr, &mut state)?;

View File

@@ -19,7 +19,6 @@ use crate::{
backend::{
common::{BackendError, OverlayContainer},
input::interact,
notifications::NotificationManager,
openxr::{lines::LinePool, overlay::OpenXrOverlayData},
overlay::{OverlayData, ShouldRender},
task::{SystemTask, TaskType},
@@ -30,6 +29,7 @@ use crate::{
watch::{WATCH_NAME, watch_fade},
},
state::AppState,
subsystem::notifications::NotificationManager,
};
#[cfg(feature = "wayvr")]
@@ -352,7 +352,7 @@ pub fn openxr_run(
}
}
app.hid_provider.commit();
app.hid_provider.borrow_mut().inner.commit();
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
let watch_transform = watch.state.transform;

View File

@@ -1,8 +1,8 @@
use std::{
f32::consts::PI,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
atomic::{AtomicUsize, Ordering},
},
};
@@ -12,9 +12,7 @@ use serde::Deserialize;
use vulkano::{format::Format, image::view::ImageView};
use crate::{
config::AStrMapExt,
graphics::CommandBuffers,
state::{AppState, KeyboardFocus},
config::AStrMapExt, graphics::CommandBuffers, state::AppState, subsystem::input::KeyboardFocus,
};
use super::{

View File

@@ -20,9 +20,9 @@ use smallvec::SmallVec;
use smithay::{
backend::{
egl,
renderer::{gles::GlesRenderer, ImportDma},
renderer::{ImportDma, gles::GlesRenderer},
},
input::{keyboard::XkbConfig, SeatState},
input::{SeatState, keyboard::XkbConfig},
output::{Mode, Output},
reexports::wayland_server::{self, backend::ClientId},
wayland::{
@@ -42,7 +42,7 @@ use std::{
use time::get_millis;
use wayvr_ipc::{packet_client, packet_server};
use crate::{hid::MODS_TO_KEYS, state::AppState};
use crate::{state::AppState, subsystem::hid::MODS_TO_KEYS};
const STR_INVALID_HANDLE_DISP: &str = "Invalid display handle";
@@ -373,9 +373,9 @@ impl WayVR {
process::find_by_pid(&self.state.processes, client.pid)
else {
log::error!(
"WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
client.pid
);
"WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
client.pid
);
continue;
};

View File

@@ -3,14 +3,17 @@ use std::sync::Arc;
use glam::vec2;
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
use wgui::{
event::{Event as WguiEvent, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
event::{
Event as WguiEvent, MouseButton, MouseDownEvent, MouseLeaveEvent, MouseMotionEvent,
MouseUpEvent, MouseWheelEvent,
},
layout::Layout,
renderer_vk::context::Context as WguiContext,
};
use crate::{
backend::{
input::{Haptics, InteractionHandler, PointerHit},
input::{Haptics, InteractionHandler, PointerHit, PointerMode},
overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
},
graphics::{CommandBuffers, ExtentExt},
@@ -66,30 +69,51 @@ impl InteractionHandler for GuiPanel {
.push_event(&WguiEvent::MouseWheel(MouseWheelEvent {
shift: vec2(delta_x, delta_y),
pos: hit.uv,
device: hit.pointer,
}))
.unwrap();
}
fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
self.layout
.push_event(&WguiEvent::MouseMotion(MouseMotionEvent { pos: hit.uv }))
.push_event(&WguiEvent::MouseMotion(MouseMotionEvent {
pos: hit.uv,
device: hit.pointer,
}))
.unwrap();
None
}
fn on_left(&mut self, _app: &mut AppState, _pointer: usize) {
//TODO: is this needed?
fn on_left(&mut self, _app: &mut AppState, pointer: usize) {
self.layout
.push_event(&WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }))
.unwrap();
}
fn on_pointer(&mut self, _app: &mut AppState, hit: &PointerHit, pressed: bool) {
let button = match hit.mode {
PointerMode::Left => MouseButton::Left,
PointerMode::Right => MouseButton::Right,
PointerMode::Middle => MouseButton::Middle,
_ => return,
};
if pressed {
self.layout
.push_event(&WguiEvent::MouseDown(MouseDownEvent { pos: hit.uv }))
.push_event(&WguiEvent::MouseDown(MouseDownEvent {
pos: hit.uv,
button,
device: hit.pointer,
}))
.unwrap();
} else {
self.layout
.push_event(&WguiEvent::MouseUp(MouseUpEvent { pos: hit.uv }))
.push_event(&WguiEvent::MouseUp(MouseUpEvent {
pos: hit.uv,
button,
device: hit.pointer,
}))
.unwrap();
}
}

View File

@@ -18,10 +18,10 @@ mod config;
mod config_io;
mod graphics;
mod gui;
mod hid;
mod overlays;
mod shaders;
mod state;
mod subsystem;
#[cfg(feature = "wayvr")]
mod config_wayvr;
@@ -34,8 +34,8 @@ use std::{
},
};
use backend::notifications::DbusNotificationSender;
use clap::Parser;
use subsystem::notifications::DbusNotificationSender;
use sysinfo::Pid;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};

View File

@@ -1,582 +0,0 @@
use std::{
collections::HashMap,
process::Child,
rc::Rc,
str::FromStr,
sync::{Arc, LazyLock},
};
use crate::{
backend::{
input::InteractionHandler,
overlay::{
FrameMeta, OverlayBackend, OverlayData, OverlayRenderer, OverlayState, Positioning,
ShouldRender,
},
},
config::{self, ConfigType},
graphics::CommandBuffers,
gui::{self, panel::GuiPanel},
hid::{
ALT, CTRL, KEYS_TO_MODS, KeyModifier, KeyType, META, NUM_LOCK, SHIFT, SUPER, VirtualKey,
XkbKeymap, get_key_type,
},
state::{AppState, KeyboardFocus},
};
use glam::{Affine2, vec2, vec3a};
use regex::Regex;
use serde::{Deserialize, Serialize};
use vulkano::image::view::ImageView;
use wgui::{
taffy::{self, prelude::length},
widget::{
div::Div,
rectangle::{Rectangle, RectangleParams},
util::WLength,
},
};
const PIXELS_PER_UNIT: f32 = 80.;
const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META];
const SPECIAL_KEYS: [VirtualKey; 13] = [
VirtualKey::BackSpace,
VirtualKey::Down,
VirtualKey::Left,
VirtualKey::Menu,
VirtualKey::Return,
VirtualKey::KP_Enter,
VirtualKey::Right,
VirtualKey::LShift,
VirtualKey::RShift,
VirtualKey::LSuper,
VirtualKey::RSuper,
VirtualKey::Tab,
VirtualKey::Up,
];
pub const KEYBOARD_NAME: &str = "kbd";
fn send_key(app: &mut AppState, key: VirtualKey, down: bool) {
match app.keyboard_focus {
KeyboardFocus::PhysicalScreen => {
app.hid_provider.send_key(key, down);
}
KeyboardFocus::WayVR =>
{
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &app.wayvr {
wayvr.borrow_mut().data.state.send_key(key as u32, down);
}
}
}
}
fn set_modifiers(app: &mut AppState, mods: u8) {
match app.keyboard_focus {
KeyboardFocus::PhysicalScreen => {
app.hid_provider.set_modifiers(mods);
}
KeyboardFocus::WayVR =>
{
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &app.wayvr {
wayvr.borrow_mut().data.state.set_modifiers(mods);
}
}
}
}
#[allow(clippy::too_many_lines)]
pub fn create_keyboard<O>(
app: &AppState,
mut keymap: Option<XkbKeymap>,
) -> anyhow::Result<OverlayData<O>>
where
O: Default,
{
let size = vec2(
LAYOUT.row_size * PIXELS_PER_UNIT,
(LAYOUT.main_layout.len() as f32) * PIXELS_PER_UNIT,
);
let data = KeyboardData {
modifiers: 0,
alt_modifier: match LAYOUT.alt_modifier {
AltModifier::Shift => SHIFT,
AltModifier::Ctrl => CTRL,
AltModifier::Alt => ALT,
AltModifier::Super => SUPER,
AltModifier::Meta => META,
_ => 0,
},
processes: vec![],
};
let padding = 4f32;
let mut panel = GuiPanel::new_blank(app, 2048)?;
let (background, _) = panel.layout.add_child(
panel.layout.root_widget,
Rectangle::create(RectangleParams {
color: wgui::drawing::Color::new(0., 0., 0., 0.6),
round: WLength::Units(4.0),
..Default::default()
})
.unwrap(),
taffy::Style {
flex_direction: taffy::FlexDirection::Column,
padding: length(padding),
..Default::default()
},
)?;
let has_altgr = keymap
.as_ref()
.is_some_and(super::super::hid::XkbKeymap::has_altgr);
if !LAYOUT.auto_labels.unwrap_or(true) {
keymap = None;
}
let (_gui_layout_key, gui_state_key) =
wgui::parser::new_layout_from_assets(Box::new(gui::asset::GuiAsset {}), "keyboard.xml")?;
for row in 0..LAYOUT.key_sizes.len() {
let (div, _) = panel.layout.add_child(
background,
Div::create().unwrap(),
taffy::Style {
flex_direction: taffy::FlexDirection::Row,
..Default::default()
},
)?;
for col in 0..LAYOUT.key_sizes[row].len() {
let my_size_f32 = LAYOUT.key_sizes[row][col];
let key_width = PIXELS_PER_UNIT * my_size_f32;
let key_height = PIXELS_PER_UNIT;
let taffy_size = taffy::Size {
width: length(key_width),
height: length(PIXELS_PER_UNIT),
};
let Some(key) = LAYOUT.main_layout[row][col].as_ref() else {
let _ = panel.layout.add_child(
div,
Div::create()?,
taffy::Style {
size: taffy_size,
min_size: taffy_size,
max_size: taffy_size,
..Default::default()
},
)?;
continue;
};
let mut label = Vec::with_capacity(3);
let mut maybe_state: Option<KeyButtonData> = None;
let mut cap_type = KeyCapType::Letter;
if let Ok(vk) = VirtualKey::from_str(key) {
if let Some(keymap) = keymap.as_ref() {
match get_key_type(vk) {
KeyType::Symbol => {
let label0 = keymap.label_for_key(vk, 0);
let label1 = keymap.label_for_key(vk, SHIFT);
if label0.chars().next().is_some_and(char::is_alphabetic) {
label.push(label1);
if has_altgr {
cap_type = KeyCapType::LetterAltGr;
label.push(keymap.label_for_key(vk, META));
} else {
cap_type = KeyCapType::Letter;
}
} else {
label.push(label0);
label.push(label1);
if has_altgr {
label.push(keymap.label_for_key(vk, META));
cap_type = KeyCapType::SymbolAltGr;
} else {
cap_type = KeyCapType::Symbol;
}
}
}
KeyType::NumPad => {
label.push(keymap.label_for_key(vk, NUM_LOCK));
}
KeyType::Other => {
if SPECIAL_KEYS.contains(&vk) {
cap_type = KeyCapType::Special;
match vk {
VirtualKey::RShift | VirtualKey::LShift => {
label.push("shift".into());
}
VirtualKey::RSuper | VirtualKey::LSuper => {
label.push("super".into());
}
VirtualKey::KP_Enter => {
label.push("return".into());
}
_ => label.push(format!("{vk:?}").to_lowercase()),
}
}
}
}
}
if let Some(mods) = KEYS_TO_MODS.get(vk) {
maybe_state = Some(KeyButtonData::Modifier {
modifier: *mods,
sticky: false,
});
} else {
maybe_state = Some(KeyButtonData::Key { vk, pressed: false });
}
} else if let Some(macro_verbs) = LAYOUT.macros.get(key) {
maybe_state = Some(KeyButtonData::Macro {
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}");
} else {
let mut iter = exec_args.iter().cloned();
if let Some(program) = iter.next() {
maybe_state = Some(KeyButtonData::Exec {
program,
args: iter.by_ref().take_while(|arg| arg[..] != *"null").collect(),
release_program: iter.next(),
release_args: iter.collect(),
});
}
}
} else {
log::error!("Unknown key: {key}");
}
if let Some(state) = maybe_state {
if label.is_empty() {
label = LAYOUT.label_for_key(key);
}
// todo: make this easier to maintain somehow
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("width"), Rc::from(key_width.to_string()));
params.insert(Rc::from("height"), Rc::from(key_height.to_string()));
let mut label = label.into_iter();
label
.next()
.and_then(|s| params.insert("text".into(), s.into()));
match cap_type {
KeyCapType::LetterAltGr => {
label
.next()
.and_then(|s| params.insert("text_altgr".into(), s.into()));
}
KeyCapType::Symbol => {
label
.next()
.and_then(|s| params.insert("text_shift".into(), s.into()));
}
KeyCapType::SymbolAltGr => {
label
.next()
.and_then(|s| params.insert("text_shift".into(), s.into()));
label
.next()
.and_then(|s| params.insert("text_altgr".into(), s.into()));
}
_ => {}
}
let template_key = format!("Key{cap_type:?}");
gui_state_key.process_template(&template_key, &mut panel.layout, div, params)?;
} else {
let _ = panel.layout.add_child(
div,
Div::create()?,
taffy::Style {
size: taffy_size,
min_size: taffy_size,
max_size: taffy_size,
..Default::default()
},
)?;
}
}
}
let interaction_transform = Affine2::from_translation(vec2(0.5, 0.5))
* Affine2::from_scale(vec2(1., -size.x as f32 / size.y as f32));
let width = LAYOUT.row_size * 0.05 * app.session.config.keyboard_scale;
Ok(OverlayData {
state: OverlayState {
name: KEYBOARD_NAME.into(),
grabbable: true,
recenter: true,
positioning: Positioning::Anchored,
interactable: true,
spawn_scale: width,
spawn_point: vec3a(0., -0.5, 0.),
interaction_transform,
..Default::default()
},
backend: Box::new(KeyboardBackend { panel }),
..Default::default()
})
}
struct KeyboardData {
modifiers: KeyModifier,
alt_modifier: KeyModifier,
processes: Vec<Child>,
}
const KEY_AUDIO_WAV: &[u8] = include_bytes!("../res/421581.wav");
fn key_click(app: &mut AppState) {
if app.session.config.keyboard_sound_enabled {
app.audio.play(KEY_AUDIO_WAV);
}
}
enum KeyButtonData {
Key {
vk: VirtualKey,
pressed: bool,
},
Modifier {
modifier: KeyModifier,
sticky: bool,
},
Macro {
verbs: Vec<(VirtualKey, bool)>,
},
Exec {
program: String,
args: Vec<String>,
release_program: Option<String>,
release_args: Vec<String>,
},
}
static LAYOUT: LazyLock<Layout> = LazyLock::new(Layout::load_from_disk);
static MACRO_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^([A-Za-z0-9_-]+)(?: +(UP|DOWN))?$").unwrap()); // want panic
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize)]
#[repr(usize)]
pub enum AltModifier {
#[default]
None,
Shift,
Ctrl,
Alt,
Super,
Meta,
}
#[derive(Debug, Deserialize, Serialize)]
#[allow(clippy::struct_field_names)]
pub struct Layout {
name: String,
row_size: f32,
key_sizes: Vec<Vec<f32>>,
main_layout: Vec<Vec<Option<String>>>,
alt_modifier: AltModifier,
exec_commands: HashMap<String, Vec<String>>,
macros: HashMap<String, Vec<String>>,
labels: HashMap<String, Vec<String>>,
auto_labels: Option<bool>,
}
impl Layout {
fn load_from_disk() -> Self {
let mut layout = config::load_known_yaml::<Self>(ConfigType::Keyboard);
layout.post_load();
layout
}
fn post_load(&mut self) {
for i in 0..self.key_sizes.len() {
let row = &self.key_sizes[i];
let width: f32 = row.iter().sum();
assert!(
(width - self.row_size).abs() < 0.001,
"Row {} has a width of {}, but the row size is {}",
i,
width,
self.row_size
);
}
for i in 0..self.main_layout.len() {
let row = &self.main_layout[i];
let width = row.len();
assert!(
(width == self.key_sizes[i].len()),
"Row {} has {} keys, needs to have {} according to key_sizes",
i,
width,
self.key_sizes[i].len()
);
}
}
fn label_for_key(&self, key: &str) -> Vec<String> {
if let Some(label) = self.labels.get(key) {
return label.clone();
}
if key.is_empty() {
return vec![];
}
if key.len() == 1 {
return vec![key.to_string().to_lowercase()];
}
let mut key = key;
if key.starts_with("KP_") {
key = &key[3..];
}
if key.contains('_') {
key = key.split('_').next().unwrap_or_else(|| {
log::error!("keyboard.yaml: Key '{key}' must not start or end with '_'!");
"???"
});
}
vec![format!(
"{}{}",
key.chars().next().unwrap().to_uppercase(), // safe because we checked is_empty
&key[1..].to_lowercase()
)]
}
}
fn key_events_for_macro(macro_verbs: &Vec<String>) -> Vec<(VirtualKey, bool)> {
let mut key_events = vec![];
for verb in macro_verbs {
if let Some(caps) = MACRO_REGEX.captures(verb) {
if let Ok(virtual_key) = VirtualKey::from_str(&caps[1]) {
if let Some(state) = caps.get(2) {
if state.as_str() == "UP" {
key_events.push((virtual_key, false));
} else if state.as_str() == "DOWN" {
key_events.push((virtual_key, true));
} else {
log::error!(
"Unknown key state in macro: {}, looking for UP or DOWN.",
state.as_str()
);
return vec![];
}
} else {
key_events.push((virtual_key, true));
key_events.push((virtual_key, false));
}
} else {
log::error!("Unknown virtual key: {}", &caps[1]);
return vec![];
}
}
}
key_events
}
struct KeyboardBackend {
panel: GuiPanel,
}
impl OverlayBackend for KeyboardBackend {
fn set_interaction(&mut self, interaction: Box<dyn crate::backend::input::InteractionHandler>) {
self.panel.set_interaction(interaction);
}
fn set_renderer(&mut self, renderer: Box<dyn crate::backend::overlay::OverlayRenderer>) {
self.panel.set_renderer(renderer);
}
}
impl InteractionHandler for KeyboardBackend {
fn on_pointer(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
pressed: bool,
) {
self.panel.on_pointer(app, hit, pressed);
}
fn on_scroll(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
delta_y: f32,
delta_x: f32,
) {
self.panel.on_scroll(app, hit, delta_y, delta_x);
}
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.panel.on_left(app, pointer);
}
fn on_hover(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
) -> Option<crate::backend::input::Haptics> {
self.panel.on_hover(app, hit)
}
}
impl OverlayRenderer for KeyboardBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.panel.init(app)
}
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.panel.should_render(app)
}
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.panel.render(app, tgt, buf, alpha)
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
self.panel.frame_meta()
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
set_modifiers(app, 0);
self.panel.pause(app)
}
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.panel.resume(app)
}
}
#[derive(Debug)]
pub enum KeyCapType {
/// Label an SVG
Special,
/// Label is in center of keycap
Letter,
/// Label on the top
/// AltGr symbol on bottom
LetterAltGr,
/// Primary symbol on bottom
/// Shift symbol on top
Symbol,
/// Primary symbol on bottom-left
/// Shift symbol on top-left
/// AltGr symbol on bottom-right
SymbolAltGr,
}

View File

@@ -0,0 +1,292 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use glam::{Affine2, vec2, vec3a};
use wgui::{
animation::{Animation, AnimationEasing},
event::{self, EventListener},
taffy::{self, prelude::length},
widget::{
div::Div,
rectangle::{Rectangle, RectangleParams},
util::WLength,
},
};
use crate::{
backend::overlay::{OverlayData, OverlayState, Positioning},
gui::{self, panel::GuiPanel},
state::AppState,
subsystem::hid::{ALT, CTRL, META, SHIFT, SUPER, XkbKeymap},
};
use super::{
KEYBOARD_NAME, KeyState, KeyboardBackend, KeyboardState, handle_press, handle_release,
layout::{self, AltModifier, KeyCapType},
};
const BACKGROUND_PADDING: f32 = 4.;
const PIXELS_PER_UNIT: f32 = 80.;
#[allow(clippy::too_many_lines, clippy::significant_drop_tightening)]
pub fn create_keyboard<O>(
app: &AppState,
mut keymap: Option<XkbKeymap>,
) -> anyhow::Result<OverlayData<O>>
where
O: Default,
{
let layout = layout::Layout::load_from_disk();
let state = Rc::new(RefCell::new(KeyboardState {
hid: app.hid_provider.clone(),
audio: app.audio_provider.clone(),
modifiers: 0,
alt_modifier: match layout.alt_modifier {
AltModifier::Shift => SHIFT,
AltModifier::Ctrl => CTRL,
AltModifier::Alt => ALT,
AltModifier::Super => SUPER,
AltModifier::Meta => META,
_ => 0,
},
processes: vec![],
}));
let size = vec2(
layout.row_size * PIXELS_PER_UNIT,
(layout.main_layout.len() as f32) * PIXELS_PER_UNIT,
);
let mut panel = GuiPanel::new_blank(app, 2048)?;
let (background, _) = panel.layout.add_child(
panel.layout.root_widget,
Rectangle::create(RectangleParams {
color: wgui::drawing::Color::new(0., 0., 0., 0.6),
round: WLength::Units(4.0),
..Default::default()
})
.unwrap(),
taffy::Style {
flex_direction: taffy::FlexDirection::Column,
padding: length(BACKGROUND_PADDING),
..Default::default()
},
)?;
let has_altgr = keymap.as_ref().is_some_and(XkbKeymap::has_altgr);
if !layout.auto_labels.unwrap_or(true) {
keymap = None;
}
let (_, mut gui_state_key) =
wgui::parser::new_layout_from_assets(Box::new(gui::asset::GuiAsset {}), "keyboard.xml")?;
for row in 0..layout.key_sizes.len() {
let (div, _) = panel.layout.add_child(
background,
Div::create().unwrap(),
taffy::Style {
flex_direction: taffy::FlexDirection::Row,
..Default::default()
},
)?;
for col in 0..layout.key_sizes[row].len() {
let my_size_f32 = layout.key_sizes[row][col];
let key_width = PIXELS_PER_UNIT * my_size_f32;
let key_height = PIXELS_PER_UNIT;
let taffy_size = taffy::Size {
width: length(key_width),
height: length(PIXELS_PER_UNIT),
};
let Some(key) = layout.get_key_data(keymap.as_ref(), has_altgr, col, row) else {
let _ = panel.layout.add_child(
div,
Div::create()?,
taffy::Style {
size: taffy_size,
min_size: taffy_size,
max_size: taffy_size,
..Default::default()
},
)?;
continue;
};
let my_id: Rc<str> = Rc::from(format!("key-{row}-{col}"));
// todo: make this easier to maintain somehow
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("id"), my_id.clone());
params.insert(Rc::from("width"), Rc::from(key_width.to_string()));
params.insert(Rc::from("height"), Rc::from(key_height.to_string()));
let mut label = key.label.into_iter();
label
.next()
.and_then(|s| params.insert("text".into(), s.into()));
match key.cap_type {
KeyCapType::LetterAltGr => {
label
.next()
.and_then(|s| params.insert("text_altgr".into(), s.into()));
}
KeyCapType::Symbol => {
label
.next()
.and_then(|s| params.insert("text_shift".into(), s.into()));
}
KeyCapType::SymbolAltGr => {
label
.next()
.and_then(|s| params.insert("text_shift".into(), s.into()));
label
.next()
.and_then(|s| params.insert("text_altgr".into(), s.into()));
}
_ => {}
}
let template_key = format!("Key{:?}", key.cap_type);
gui_state_key.process_template(&template_key, &mut panel.layout, div, params)?;
if let Some(widget_id) = gui_state_key.ids.get(&*my_id) {
let key_state = {
let widget = panel
.layout
.widget_states
.get(*widget_id)
.unwrap() // want panic
.lock()
.unwrap(); // want panic
let rect = widget.obj.get_as::<Rectangle>();
Rc::new(KeyState {
button_state: key.button_state,
color: rect.params.color,
color2: rect.params.color2,
border_color: rect.params.border_color,
})
};
panel.layout.add_event_listener(
*widget_id,
EventListener::MouseEnter(Box::new({
let (k, kb) = (key_state.clone(), state.clone());
move |data| {
on_enter_anim(k.clone(), kb.clone(), data);
}
})),
);
panel.layout.add_event_listener(
*widget_id,
EventListener::MouseLeave(Box::new({
let (k, kb) = (key_state.clone(), state.clone());
move |data| {
on_leave_anim(k.clone(), kb.clone(), data);
}
})),
);
panel.layout.add_event_listener(
*widget_id,
EventListener::MousePress(Box::new({
let (k, kb) = (key_state.clone(), state.clone());
move |data, button| {
on_press_anim(k.clone(), data);
handle_press(k.clone(), kb.clone(), button);
}
})),
);
panel.layout.add_event_listener(
*widget_id,
EventListener::MouseRelease(Box::new({
let (k, kb) = (key_state.clone(), state.clone());
move |data, button| {
on_release_anim(k.clone(), data);
handle_release(k.clone(), kb.clone(), button);
}
})),
);
}
}
}
let interaction_transform = Affine2::from_translation(vec2(0.5, 0.5))
* Affine2::from_scale(vec2(1., -size.x as f32 / size.y as f32));
let width = layout.row_size * 0.05 * app.session.config.keyboard_scale;
Ok(OverlayData {
state: OverlayState {
name: KEYBOARD_NAME.into(),
grabbable: true,
recenter: true,
positioning: Positioning::Anchored,
interactable: true,
spawn_scale: width,
spawn_point: vec3a(0., -0.5, 0.),
interaction_transform,
..Default::default()
},
backend: Box::new(KeyboardBackend { panel, state }),
..Default::default()
})
}
fn on_enter_anim(
key_state: Rc<KeyState>,
_keyboard_state: Rc<RefCell<KeyboardState>>,
data: &mut event::CallbackData,
) {
data.animations.push(Animation::new(
data.widget_id,
5,
AnimationEasing::OutQuad,
Box::new(move |data| {
let rect = data.obj.get_as_mut::<Rectangle>();
let brightness = data.pos * 0.5;
rect.params.color.r = key_state.color.r + brightness;
rect.params.color.g = key_state.color.g + brightness;
rect.params.color.b = key_state.color.b + brightness;
data.needs_redraw = true;
}),
));
}
fn on_leave_anim(
key_state: Rc<KeyState>,
_keyboard_state: Rc<RefCell<KeyboardState>>,
data: &mut event::CallbackData,
) {
data.animations.push(Animation::new(
data.widget_id,
5,
AnimationEasing::OutQuad,
Box::new(move |data| {
let rect = data.obj.get_as_mut::<Rectangle>();
let brightness = (1.0 - data.pos) * 0.5;
rect.params.color.r = key_state.color.r + brightness;
rect.params.color.g = key_state.color.g + brightness;
rect.params.color.b = key_state.color.b + brightness;
data.needs_redraw = true;
}),
));
}
fn on_press_anim(key_state: Rc<KeyState>, data: &mut event::CallbackData) {
let rect = data.obj.get_as_mut::<Rectangle>();
rect.params.border_color = key_state.border_color;
data.needs_redraw = true;
}
fn on_release_anim(key_state: Rc<KeyState>, data: &mut event::CallbackData) {
let rect = data.obj.get_as_mut::<Rectangle>();
rect.params.border_color = key_state.border_color;
data.needs_redraw = true;
}

View File

@@ -0,0 +1,261 @@
use std::{collections::HashMap, str::FromStr, sync::LazyLock};
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::{
config::{ConfigType, load_known_yaml},
subsystem::hid::{
KEYS_TO_MODS, KeyType, META, NUM_LOCK, SHIFT, VirtualKey, XkbKeymap, get_key_type,
},
};
use super::KeyButtonData;
static MACRO_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^([A-Za-z0-9_-]+)(?: +(UP|DOWN))?$").unwrap()); // want panic
#[derive(Debug, Deserialize, Serialize)]
#[allow(clippy::struct_field_names)]
pub struct Layout {
pub(super) name: String,
pub(super) row_size: f32,
pub(super) key_sizes: Vec<Vec<f32>>,
pub(super) main_layout: Vec<Vec<Option<String>>>,
pub(super) alt_modifier: AltModifier,
pub(super) exec_commands: HashMap<String, Vec<String>>,
pub(super) macros: HashMap<String, Vec<String>>,
pub(super) labels: HashMap<String, Vec<String>>,
pub(super) auto_labels: Option<bool>,
}
impl Layout {
pub(super) fn load_from_disk() -> Self {
let mut layout = load_known_yaml::<Self>(ConfigType::Keyboard);
layout.post_load();
layout
}
fn post_load(&mut self) {
for i in 0..self.key_sizes.len() {
let row = &self.key_sizes[i];
let width: f32 = row.iter().sum();
assert!(
(width - self.row_size).abs() < 0.001,
"Row {} has a width of {}, but the row size is {}",
i,
width,
self.row_size
);
}
for i in 0..self.main_layout.len() {
let row = &self.main_layout[i];
let width = row.len();
assert!(
(width == self.key_sizes[i].len()),
"Row {} has {} keys, needs to have {} according to key_sizes",
i,
width,
self.key_sizes[i].len()
);
}
}
pub(super) fn get_key_data(
&self,
keymap: Option<&XkbKeymap>,
has_altgr: bool,
col: usize,
row: usize,
) -> Option<KeyData> {
let key = self.main_layout[row][col].as_ref()?;
let mut label = Vec::with_capacity(3);
let mut cap_type = KeyCapType::Letter;
let button_state: KeyButtonData;
if let Ok(vk) = VirtualKey::from_str(key) {
if let Some(keymap) = keymap.as_ref() {
match get_key_type(vk) {
KeyType::Symbol => {
let label0 = keymap.label_for_key(vk, 0);
let label1 = keymap.label_for_key(vk, SHIFT);
if label0.chars().next().is_some_and(char::is_alphabetic) {
label.push(label1);
if has_altgr {
cap_type = KeyCapType::LetterAltGr;
label.push(keymap.label_for_key(vk, META));
} else {
cap_type = KeyCapType::Letter;
}
} else {
label.push(label0);
label.push(label1);
if has_altgr {
label.push(keymap.label_for_key(vk, META));
cap_type = KeyCapType::SymbolAltGr;
} else {
cap_type = KeyCapType::Symbol;
}
}
}
KeyType::NumPad => {
label.push(keymap.label_for_key(vk, NUM_LOCK));
}
KeyType::Special => {
cap_type = KeyCapType::Special;
match vk {
VirtualKey::RShift | VirtualKey::LShift => {
label.push("shift".into());
}
VirtualKey::RSuper | VirtualKey::LSuper => {
label.push("super".into());
}
VirtualKey::KP_Enter => {
label.push("return".into());
}
_ => label.push(format!("{vk:?}").to_lowercase()),
}
}
KeyType::Other => {}
}
}
if let Some(mods) = KEYS_TO_MODS.get(vk) {
button_state = KeyButtonData::Modifier {
modifier: *mods,
sticky: false.into(),
};
} else {
button_state = KeyButtonData::Key {
vk,
pressed: false.into(),
};
}
} else if let Some(macro_verbs) = self.macros.get(key) {
button_state = KeyButtonData::Macro {
verbs: key_events_for_macro(macro_verbs),
};
} else if let Some(exec_args) = self.exec_commands.get(key) {
let mut iter = exec_args.iter().cloned();
if let Some(program) = iter.next() {
button_state = KeyButtonData::Exec {
program,
args: iter.by_ref().take_while(|arg| arg[..] != *"null").collect(),
release_program: iter.next(),
release_args: iter.collect(),
};
} else {
log::error!("Keyboard: EXEC args empty for {key}");
return None;
}
} else {
log::error!("Unknown key: {key}");
return None;
}
if label.is_empty() {
label = self.label_for_key(key);
}
Some(KeyData {
label,
button_state,
cap_type,
})
}
fn label_for_key(&self, key: &str) -> Vec<String> {
if let Some(label) = self.labels.get(key) {
return label.clone();
}
if key.is_empty() {
return vec![];
}
if key.len() == 1 {
return vec![key.to_string().to_lowercase()];
}
let mut key = key;
if key.starts_with("KP_") {
key = &key[3..];
}
if key.contains('_') {
key = key.split('_').next().unwrap_or_else(|| {
log::error!("keyboard.yaml: Key '{key}' must not start or end with '_'!");
"???"
});
}
vec![format!(
"{}{}",
key.chars().next().unwrap().to_uppercase(), // safe because we checked is_empty
&key[1..].to_lowercase()
)]
}
}
pub(super) fn key_events_for_macro(macro_verbs: &Vec<String>) -> Vec<(VirtualKey, bool)> {
let mut key_events = vec![];
for verb in macro_verbs {
if let Some(caps) = MACRO_REGEX.captures(verb) {
if let Ok(virtual_key) = VirtualKey::from_str(&caps[1]) {
if let Some(state) = caps.get(2) {
if state.as_str() == "UP" {
key_events.push((virtual_key, false));
} else if state.as_str() == "DOWN" {
key_events.push((virtual_key, true));
} else {
log::error!(
"Unknown key state in macro: {}, looking for UP or DOWN.",
state.as_str()
);
return vec![];
}
} else {
key_events.push((virtual_key, true));
key_events.push((virtual_key, false));
}
} else {
log::error!("Unknown virtual key: {}", &caps[1]);
return vec![];
}
}
}
key_events
}
pub(super) struct KeyData {
pub(super) label: Vec<String>,
pub(super) button_state: KeyButtonData,
pub(super) cap_type: KeyCapType,
}
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize)]
#[repr(usize)]
pub enum AltModifier {
#[default]
None,
Shift,
Ctrl,
Alt,
Super,
Meta,
}
#[derive(Debug)]
pub enum KeyCapType {
/// Label an SVG
Special,
/// Label is in center of keycap
Letter,
/// Label on the top
/// AltGr symbol on bottom
LetterAltGr,
/// Primary symbol on bottom
/// Shift symbol on top
Symbol,
/// Primary symbol on bottom-left
/// Shift symbol on top-left
/// AltGr symbol on bottom-right
SymbolAltGr,
}

View File

@@ -0,0 +1,239 @@
use std::{
cell::{Cell, RefCell},
process::{Child, Command},
rc::Rc,
sync::Arc,
};
use vulkano::image::view::ImageView;
use wgui::{drawing, event::MouseButton};
use crate::{
backend::{
input::InteractionHandler,
overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
},
graphics::CommandBuffers,
gui::panel::GuiPanel,
state::AppState,
subsystem::{
audio::{AudioOutput, AudioRole},
hid::{ALT, CTRL, KeyModifier, META, SHIFT, SUPER, VirtualKey},
input::HidWrapper,
},
};
pub mod builder;
mod layout;
pub const KEYBOARD_NAME: &str = "kbd";
const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META];
struct KeyboardBackend {
panel: GuiPanel,
state: Rc<RefCell<KeyboardState>>,
}
impl OverlayBackend for KeyboardBackend {
fn set_interaction(&mut self, interaction: Box<dyn crate::backend::input::InteractionHandler>) {
self.panel.set_interaction(interaction);
}
fn set_renderer(&mut self, renderer: Box<dyn crate::backend::overlay::OverlayRenderer>) {
self.panel.set_renderer(renderer);
}
}
impl InteractionHandler for KeyboardBackend {
fn on_pointer(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
pressed: bool,
) {
self.panel.on_pointer(app, hit, pressed);
}
fn on_scroll(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
delta_y: f32,
delta_x: f32,
) {
self.panel.on_scroll(app, hit, delta_y, delta_x);
}
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.panel.on_left(app, pointer);
}
fn on_hover(
&mut self,
app: &mut AppState,
hit: &crate::backend::input::PointerHit,
) -> Option<crate::backend::input::Haptics> {
self.panel.on_hover(app, hit)
}
}
impl OverlayRenderer for KeyboardBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.panel.init(app)
}
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
self.panel.should_render(app)
}
fn render(
&mut self,
app: &mut AppState,
tgt: Arc<ImageView>,
buf: &mut CommandBuffers,
alpha: f32,
) -> anyhow::Result<bool> {
self.panel.render(app, tgt, buf, alpha)
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
self.panel.frame_meta()
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.state.borrow_mut().modifiers = 0;
app.hid_provider.borrow_mut().set_modifiers_routed(0);
self.panel.pause(app)
}
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.panel.resume(app)
}
}
struct KeyboardState {
hid: Rc<RefCell<HidWrapper>>,
audio: Rc<RefCell<AudioOutput>>,
modifiers: KeyModifier,
alt_modifier: KeyModifier,
processes: Vec<Child>,
}
const KEY_AUDIO_WAV: &[u8] = include_bytes!("../../res/421581.wav");
struct KeyState {
button_state: KeyButtonData,
color: drawing::Color,
color2: drawing::Color,
border_color: drawing::Color,
}
enum KeyButtonData {
Key {
vk: VirtualKey,
pressed: Cell<bool>,
},
Modifier {
modifier: KeyModifier,
sticky: Cell<bool>,
},
Macro {
verbs: Vec<(VirtualKey, bool)>,
},
Exec {
program: String,
args: Vec<String>,
release_program: Option<String>,
release_args: Vec<String>,
},
}
fn play_key_click(keyboard: &KeyboardState) {
keyboard
.audio
.borrow_mut()
.play(AudioRole::Keyboard, KEY_AUDIO_WAV);
}
fn handle_press(key: Rc<KeyState>, keyboard: Rc<RefCell<KeyboardState>>, button: MouseButton) {
let mut keyboard = keyboard.borrow_mut();
match &key.button_state {
KeyButtonData::Key { vk, pressed } => {
keyboard.modifiers |= match button {
MouseButton::Right => SHIFT,
MouseButton::Middle => keyboard.alt_modifier,
_ => 0,
};
{
let mut hid = keyboard.hid.borrow_mut();
hid.set_modifiers_routed(keyboard.modifiers);
hid.send_key_routed(*vk, true);
}
pressed.set(true);
play_key_click(&keyboard);
}
KeyButtonData::Modifier { modifier, sticky } => {
sticky.set(keyboard.modifiers & *modifier == 0);
keyboard.modifiers |= *modifier;
keyboard
.hid
.borrow_mut()
.set_modifiers_routed(keyboard.modifiers);
play_key_click(&keyboard);
}
KeyButtonData::Macro { verbs } => {
let hid = keyboard.hid.borrow_mut();
for (vk, press) in verbs {
hid.send_key_routed(*vk, *press);
}
play_key_click(&keyboard);
}
KeyButtonData::Exec { program, args, .. } => {
// Reap previous processes
keyboard
.processes
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
if let Ok(child) = Command::new(program).args(args).spawn() {
keyboard.processes.push(child);
}
play_key_click(&keyboard);
}
}
}
fn handle_release(key: Rc<KeyState>, keyboard: Rc<RefCell<KeyboardState>>, _button: MouseButton) {
let mut keyboard = keyboard.borrow_mut();
match &key.button_state {
KeyButtonData::Key { vk, pressed } => {
pressed.set(false);
for m in &AUTO_RELEASE_MODS {
if keyboard.modifiers & *m != 0 {
keyboard.modifiers &= !*m;
}
}
let mut hid = keyboard.hid.borrow_mut();
hid.send_key_routed(*vk, false);
hid.set_modifiers_routed(keyboard.modifiers);
}
KeyButtonData::Modifier { modifier, sticky } => {
if !sticky.get() {
keyboard.modifiers &= !*modifier;
keyboard
.hid
.borrow_mut()
.set_modifiers_routed(keyboard.modifiers);
}
}
KeyButtonData::Exec {
release_program,
release_args,
..
} => {
// Reap previous processes
keyboard
.processes
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
if let Some(program) = release_program {
if let Ok(child) = Command::new(program).args(release_args).spawn() {
keyboard.processes.push(child);
}
}
}
_ => {}
}
}

View File

@@ -63,8 +63,11 @@ use crate::{
dmabuf::{WGfxDmabuf, fourcc_to_vk},
upload_quad_vertices,
},
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
state::{AppSession, AppState, KeyboardFocus, ScreenMeta},
state::{AppSession, AppState, ScreenMeta},
subsystem::{
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
input::KeyboardFocus,
},
};
#[cfg(feature = "wayland")]
@@ -128,7 +131,7 @@ impl InteractionHandler for ScreenInteractionHandler {
|| app.input_state.pointers[hit.pointer].now.move_mouse)
{
let pos = self.mouse_transform.transform_point2(hit.uv);
app.hid_provider.mouse_move(pos);
app.hid_provider.borrow_mut().inner.mouse_move(pos);
set_next_move(u64::from(app.session.config.mouse_move_interval_ms));
}
None
@@ -144,16 +147,20 @@ impl InteractionHandler for ScreenInteractionHandler {
set_next_move(u64::from(app.session.config.click_freeze_time_ms));
}
app.hid_provider.send_button(btn, pressed);
let mut hid_provider = app.hid_provider.borrow_mut();
hid_provider.inner.send_button(btn, pressed);
if !pressed {
return;
}
let pos = self.mouse_transform.transform_point2(hit.uv);
app.hid_provider.mouse_move(pos);
hid_provider.inner.mouse_move(pos);
}
fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta_y: f32, delta_x: f32) {
app.hid_provider
.borrow_mut()
.inner
.wheel((delta_y * 64.) as i32, (delta_x * 64.) as i32);
}
fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
@@ -201,7 +208,7 @@ impl ScreenPipeline {
app.gfx_extras.quad_verts.clone(),
0..4,
0..1,
vec![set0.clone(), set1],
vec![set0, set1],
)?;
Ok(Self {
@@ -885,9 +892,12 @@ pub fn create_screens_wayland(wl: &mut WlxClientAlias, app: &mut AppState) -> Sc
let extent = wl.get_desktop_extent();
let origin = wl.get_desktop_origin();
app.hid_provider
let mut hid_provider = app.hid_provider.borrow_mut();
hid_provider
.inner
.set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
app.hid_provider
hid_provider
.inner
.set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
ScreenCreateData { screens }
@@ -985,8 +995,9 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
})
.collect();
app.hid_provider.set_desktop_extent(extent);
app.hid_provider.set_desktop_origin(vec2(0.0, 0.0));
let mut hid_provider = app.hid_provider.borrow_mut();
hid_provider.inner.set_desktop_extent(extent);
hid_provider.inner.set_desktop_origin(vec2(0.0, 0.0));
Ok(ScreenCreateData { screens })
}
@@ -1043,8 +1054,9 @@ pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result<ScreenCreateDat
})
.collect();
app.hid_provider.set_desktop_extent(extent);
app.hid_provider.set_desktop_origin(vec2(0.0, 0.0));
let mut hid_provider = app.hid_provider.borrow_mut();
hid_provider.inner.set_desktop_extent(extent);
hid_provider.inner.set_desktop_origin(vec2(0.0, 0.0));
Ok(ScreenCreateData { screens })
}
@@ -1154,7 +1166,7 @@ fn select_pw_screen(
persist: bool,
multiple: bool,
) -> Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError> {
use crate::backend::notifications::DbusNotificationSender;
use crate::subsystem::notifications::DbusNotificationSender;
use std::time::Duration;
use wlx_capture::pipewire::pipewire_select_screen;

View File

@@ -30,6 +30,7 @@ use crate::{
},
gui::panel::GuiPanel,
state::{AppState, LeftRight},
subsystem::audio::AudioRole,
};
const FONT_SIZE: isize = 16;
@@ -93,9 +94,10 @@ impl Toast {
let destroy_at = instant.add(std::time::Duration::from_secs_f32(self.timeout));
let has_sound = self.sound && app.session.config.notifications_sound_enabled;
if has_sound {
app.audio.play(app.toast_sound);
if self.sound {
app.audio_provider
.borrow_mut()
.play(AudioRole::Notification, app.toast_sound);
}
// drop any toast that was created before us.

View File

@@ -27,7 +27,8 @@ use crate::{
},
config_wayvr,
graphics::{CommandBuffers, Vert2Uv, dmabuf::WGfxDmabuf},
state::{self, AppState, KeyboardFocus},
state::{self, AppState},
subsystem::input::KeyboardFocus,
};
use super::toast::error_toast;

View File

@@ -1,9 +1,8 @@
use glam::Affine3A;
use idmap::IdMap;
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
use serde::{Deserialize, Serialize};
use smallvec::{smallvec, SmallVec};
use std::{io::Cursor, sync::Arc};
use smallvec::{SmallVec, smallvec};
use std::sync::Arc;
use vulkano::image::view::ImageView;
use wgui::gfx::WGfx;
@@ -15,39 +14,30 @@ use {
};
#[cfg(feature = "osc")]
use crate::backend::osc::OscSender;
use crate::subsystem::osc::OscSender;
use crate::{
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
config::{AStrMap, GeneralConfig},
config_io,
graphics::WGfxExtras,
hid::HidProvider,
overlays::toast::{DisplayMethod, ToastTopic},
subsystem::{audio::AudioOutput, input::HidWrapper},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum KeyboardFocus {
PhysicalScreen,
#[allow(dead_code)] // Not available if "wayvr" feature is disabled
WayVR, // (for now without wayland window id data, it's handled internally),
}
pub struct AppState {
pub session: AppSession,
pub tasks: TaskContainer,
pub gfx: Arc<WGfx>,
pub gfx_extras: WGfxExtras,
pub hid_provider: Rc<RefCell<HidWrapper>>,
pub audio_provider: Rc<RefCell<AudioOutput>>,
pub input_state: InputState,
pub hid_provider: Box<dyn HidProvider>,
pub audio: AudioOutput,
pub screens: SmallVec<[ScreenMeta; 8]>,
pub anchor: Affine3A,
pub sprites: AStrMap<Arc<ImageView>>,
pub keyboard_focus: KeyboardFocus,
pub toast_sound: &'static [u8],
#[cfg(feature = "osc")]
@@ -74,25 +64,26 @@ impl AppState {
.post_load(&session.config, &mut tasks)?;
#[cfg(feature = "osc")]
let osc_sender = crate::backend::osc::OscSender::new(session.config.osc_out_port).ok();
let osc_sender = crate::subsystem::osc::OscSender::new(session.config.osc_out_port).ok();
let toast_sound_wav = Self::try_load_bytes(
&session.config.notification_sound,
include_bytes!("res/557297.wav"),
);
let audio_provider = AudioOutput::new(&session.config);
Ok(Self {
session,
tasks,
gfx,
gfx_extras,
hid_provider: Rc::new(RefCell::new(HidWrapper::new())),
audio_provider: Rc::new(RefCell::new(audio_provider)),
input_state: InputState::new(),
hid_provider: crate::hid::initialize(),
audio: AudioOutput::new(),
screens: smallvec![],
anchor: Affine3A::IDENTITY,
sprites: AStrMap::new(),
keyboard_focus: KeyboardFocus::PhysicalScreen,
toast_sound: toast_sound_wav,
#[cfg(feature = "osc")]
@@ -177,48 +168,6 @@ impl AppSession {
}
}
pub struct AudioOutput {
audio_stream: Option<(OutputStream, OutputStreamHandle)>,
first_try: bool,
}
impl AudioOutput {
pub const fn new() -> Self {
Self {
audio_stream: None,
first_try: true,
}
}
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. 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 {
pub name: Arc<str>,
pub id: OverlayID,

View File

@@ -0,0 +1,64 @@
use std::io::Cursor;
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
use strum::EnumCount;
use crate::config::GeneralConfig;
#[derive(Debug, Clone, Copy, EnumCount)]
#[repr(usize)]
pub enum AudioRole {
Notification,
Keyboard,
}
pub struct AudioOutput {
muted_roles: [bool; AudioRole::COUNT],
audio_stream: Option<(OutputStream, OutputStreamHandle)>,
first_try: bool,
}
impl AudioOutput {
pub const fn new(config: &GeneralConfig) -> Self {
Self {
muted_roles: [
//TODO: improve this
!config.keyboard_sound_enabled,
!config.notifications_sound_enabled,
],
audio_stream: None,
first_try: true,
}
}
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. Audio will not work.");
return None;
}
}
self.audio_stream.as_ref().map(|(_, h)| h)
}
pub fn play(&mut self, role: AudioRole, wav_bytes: &'static [u8]) {
if self.muted_roles[role as usize] {
return;
}
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());
}
}

View File

@@ -10,7 +10,7 @@ use serde::Deserialize;
use std::mem::transmute;
use std::sync::LazyLock;
use std::{fs::File, sync::atomic::AtomicBool};
use strum::{AsRefStr, EnumIter, EnumString, IntoEnumIterator};
use strum::{EnumIter, EnumString, IntoEnumIterator};
use xkbcommon::xkb;
#[cfg(feature = "wayland")]
@@ -21,7 +21,7 @@ mod x11;
pub static USE_UINPUT: AtomicBool = AtomicBool::new(true);
pub fn initialize() -> Box<dyn HidProvider> {
pub(super) fn initialize() -> Box<dyn HidProvider> {
if !USE_UINPUT.load(std::sync::atomic::Ordering::Relaxed) {
log::info!("Uinput disabled by user.");
return Box::new(DummyProvider {});
@@ -40,7 +40,7 @@ pub fn initialize() -> Box<dyn HidProvider> {
Box::new(DummyProvider {})
}
pub trait HidProvider {
pub trait HidProvider: Sync + Send {
fn mouse_move(&mut self, pos: Vec2);
fn send_button(&mut self, button: u16, down: bool);
fn wheel(&mut self, delta_y: i32, delta_x: i32);
@@ -506,6 +506,7 @@ pub static MODS_TO_KEYS: LazyLock<IdMap<KeyModifier, Vec<VirtualKey>>> = LazyLoc
pub enum KeyType {
Symbol,
NumPad,
Special,
Other,
}
@@ -534,6 +535,23 @@ pub const fn get_key_type(key: VirtualKey) -> KeyType {
&& !key_is!(key, VirtualKey::KP_Add)
{
KeyType::NumPad
} else if matches!(
key,
VirtualKey::BackSpace
| VirtualKey::Down
| VirtualKey::Left
| VirtualKey::Menu
| VirtualKey::Return
| VirtualKey::KP_Enter
| VirtualKey::Right
| VirtualKey::LShift
| VirtualKey::RShift
| VirtualKey::LSuper
| VirtualKey::RSuper
| VirtualKey::Tab
| VirtualKey::Up
) {
KeyType::Special
} else {
KeyType::Other
}

View File

@@ -0,0 +1,54 @@
use std::{cell::RefCell, rc::Rc};
use super::hid::{self, HidProvider, VirtualKey};
use crate::overlays::wayvr::WayVRData;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum KeyboardFocus {
PhysicalScreen,
#[allow(dead_code)] // Not available if "wayvr" feature is disabled
WayVR, // (for now without wayland window id data, it's handled internally),
}
pub struct HidWrapper {
pub keyboard_focus: KeyboardFocus,
pub inner: Box<dyn HidProvider>,
pub wayvr: Option<Rc<RefCell<WayVRData>>>, // Dynamically created if requested
}
impl HidWrapper {
pub fn new() -> Self {
Self {
keyboard_focus: KeyboardFocus::PhysicalScreen,
inner: hid::initialize(),
wayvr: None,
}
}
pub fn send_key_routed(&self, key: VirtualKey, down: bool) {
match self.keyboard_focus {
KeyboardFocus::PhysicalScreen => self.inner.send_key(key, down),
KeyboardFocus::WayVR =>
{
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &self.wayvr {
wayvr.borrow_mut().data.state.send_key(key as u32, down);
}
}
}
}
pub fn set_modifiers_routed(&mut self, mods: u8) {
match self.keyboard_focus {
KeyboardFocus::PhysicalScreen => self.inner.set_modifiers(mods),
KeyboardFocus::WayVR =>
{
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &self.wayvr {
wayvr.borrow_mut().data.state.set_modifiers(mods);
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
pub mod audio;
pub mod hid;
pub mod input;
pub mod notifications;
#[cfg(feature = "osc")]
pub mod osc;

View File

@@ -1,9 +1,13 @@
#[allow(clippy::all)]
mod notifications_dbus;
use dbus::{
arg::{PropMap, Variant},
blocking::Connection,
channel::MatchingReceiver,
message::MatchRule,
};
use notifications_dbus::OrgFreedesktopNotifications;
use serde::Deserialize;
use std::{
sync::{
@@ -15,7 +19,6 @@ use std::{
};
use crate::{
backend::notifications_dbus::OrgFreedesktopNotifications,
overlays::toast::{Toast, ToastTopic},
state::AppState,
};

View File

@@ -6,12 +6,13 @@ use std::{
use anyhow::bail;
use rosc::{OscMessage, OscPacket, OscType};
use crate::overlays::{keyboard::KEYBOARD_NAME, watch::WATCH_NAME};
use crate::{
backend::{common::OverlayContainer, input::TrackedDevice},
overlays::{keyboard::KEYBOARD_NAME, watch::WATCH_NAME},
};
use crate::backend::input::TrackedDeviceRole;
use super::{common::OverlayContainer, input::TrackedDevice};
pub struct OscSender {
last_sent_overlay: Instant,
last_sent_battery: Instant,