keyboard progress & refactors
This commit is contained in:
@@ -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,
|
||||
}
|
||||
292
wlx-overlay-s/src/overlays/keyboard/builder.rs
Normal file
292
wlx-overlay-s/src/overlays/keyboard/builder.rs
Normal 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;
|
||||
}
|
||||
261
wlx-overlay-s/src/overlays/keyboard/layout.rs
Normal file
261
wlx-overlay-s/src/overlays/keyboard/layout.rs
Normal 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,
|
||||
}
|
||||
239
wlx-overlay-s/src/overlays/keyboard/mod.rs
Normal file
239
wlx-overlay-s/src/overlays/keyboard/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user