kbvm: use indicators to determine the active LEDs
This commit is contained in:
parent
0c7f7429db
commit
f5e04355d7
8 changed files with 101 additions and 57 deletions
|
|
@ -36,8 +36,8 @@ impl EiKeyboard {
|
||||||
self.client.event(Keymap {
|
self.client.event(Keymap {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
keymap_type: KEYMAP_TYPE_XKB,
|
keymap_type: KEYMAP_TYPE_XKB,
|
||||||
size: state.map.len as _,
|
size: state.map.map.len as _,
|
||||||
keymap: state.map.map.clone(),
|
keymap: state.map.map.map.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ pub mod zwp_virtual_keyboard_v1;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
async_engine::SpawnedFuture,
|
async_engine::SpawnedFuture,
|
||||||
backend::{KeyState, LED_CAPS_LOCK, LED_NUM_LOCK, Leds},
|
backend::{KeyState, Leds},
|
||||||
client::{Client, ClientError, ClientId},
|
client::{Client, ClientError, ClientId},
|
||||||
cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner},
|
cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner},
|
||||||
ei::ei_ifs::ei_seat::EiSeat,
|
ei::ei_ifs::ei_seat::EiSeat,
|
||||||
|
|
@ -75,7 +75,7 @@ use {
|
||||||
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
||||||
},
|
},
|
||||||
kbvm::{KbvmMap, KbvmMapId, KbvmState, PhysicalKeyboardState},
|
kbvm::{KbvmMap, KbvmMapId, KbvmState, PhysicalKeyboardState},
|
||||||
keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, ModifiersListener},
|
keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, LedsListener},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::{Object, Version},
|
object::{Object, Version},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
|
|
@ -107,7 +107,7 @@ use {
|
||||||
},
|
},
|
||||||
ahash::AHashMap,
|
ahash::AHashMap,
|
||||||
jay_config::keyboard::syms::{KeySym, SYM_Escape},
|
jay_config::keyboard::syms::{KeySym, SYM_Escape},
|
||||||
kbvm::{Components, Keycode, ModifierMask},
|
kbvm::Keycode,
|
||||||
smallvec::SmallVec,
|
smallvec::SmallVec,
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
|
@ -236,8 +236,8 @@ pub struct WlSeatGlobal {
|
||||||
focus_history_same_workspace: Cell<bool>,
|
focus_history_same_workspace: Cell<bool>,
|
||||||
mark_mode: Cell<Option<MarkMode>>,
|
mark_mode: Cell<Option<MarkMode>>,
|
||||||
marks: CopyHashMap<Keycode, Rc<dyn Node>>,
|
marks: CopyHashMap<Keycode, Rc<dyn Node>>,
|
||||||
modifiers_listener: EventListener<dyn ModifiersListener>,
|
modifiers_listener: EventListener<dyn LedsListener>,
|
||||||
modifiers_forward: EventSource<dyn ModifiersListener>,
|
modifiers_forward: EventSource<dyn LedsListener>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
|
@ -330,7 +330,7 @@ impl WlSeatGlobal {
|
||||||
});
|
});
|
||||||
slf.pointer_cursor.set_owner(slf.clone());
|
slf.pointer_cursor.set_owner(slf.clone());
|
||||||
slf.modifiers_listener
|
slf.modifiers_listener
|
||||||
.attach(&seat_kb_state.borrow().kb_state.mods_changed);
|
.attach(&seat_kb_state.borrow().kb_state.leds_changed);
|
||||||
let seat = slf.clone();
|
let seat = slf.clone();
|
||||||
let future = state.eng.spawn("seat handler", async move {
|
let future = state.eng.spawn("seat handler", async move {
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -541,8 +541,8 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let new = &*new.borrow();
|
let new = &*new.borrow();
|
||||||
self.modifiers_listener.attach(&new.kb_state.mods_changed);
|
self.modifiers_listener.attach(&new.kb_state.leds_changed);
|
||||||
self.dispatch_seat_modifiers_listeners(&new.kb_state.mods);
|
self.dispatch_seat_leds_listeners(new.kb_state.leds);
|
||||||
}
|
}
|
||||||
self.handle_keyboard_state_change(&old.borrow().kb_state, &new.borrow().kb_state);
|
self.handle_keyboard_state_change(&old.borrow().kb_state, &new.borrow().kb_state);
|
||||||
}
|
}
|
||||||
|
|
@ -581,7 +581,7 @@ impl WlSeatGlobal {
|
||||||
fn attach_modifiers_listener(
|
fn attach_modifiers_listener(
|
||||||
&self,
|
&self,
|
||||||
id: PhysicalKeyboardId,
|
id: PhysicalKeyboardId,
|
||||||
listener: &EventListener<dyn ModifiersListener>,
|
listener: &EventListener<dyn LedsListener>,
|
||||||
map: Option<&Rc<KbvmMap>>,
|
map: Option<&Rc<KbvmMap>>,
|
||||||
) {
|
) {
|
||||||
let _ = self.get_physical_keyboard(id, map);
|
let _ = self.get_physical_keyboard(id, map);
|
||||||
|
|
@ -592,18 +592,18 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
Some(m) => {
|
Some(m) => {
|
||||||
let state = self.get_kb_state(m);
|
let state = self.get_kb_state(m);
|
||||||
listener.attach(&state.borrow().kb_state.mods_changed);
|
listener.attach(&state.borrow().kb_state.leds_changed);
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(l) = listener.get() {
|
if let Some(l) = listener.get() {
|
||||||
l.locked_mods(&state.borrow().kb_state.mods);
|
l.leds(state.borrow().kb_state.leds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_seat_modifiers_listeners(&self, mods: &Components) {
|
fn dispatch_seat_leds_listeners(&self, leds: Leds) {
|
||||||
for listener in self.modifiers_forward.iter() {
|
for listener in self.modifiers_forward.iter() {
|
||||||
listener.locked_mods(mods);
|
listener.leds(leds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1514,8 +1514,8 @@ impl WlSeat {
|
||||||
|
|
||||||
pub fn keymap_fd(&self, state: &KeyboardState) -> Result<KeymapFd, WlKeyboardError> {
|
pub fn keymap_fd(&self, state: &KeyboardState) -> Result<KeymapFd, WlKeyboardError> {
|
||||||
let fd = match self.client.is_xwayland {
|
let fd = match self.client.is_xwayland {
|
||||||
true => &state.xwayland_map,
|
true => &state.map.xwayland_map,
|
||||||
_ => &state.map,
|
_ => &state.map.map,
|
||||||
};
|
};
|
||||||
if self.version >= READ_ONLY_KEYMAP_SINCE {
|
if self.version >= READ_ONLY_KEYMAP_SINCE {
|
||||||
return Ok(fd.clone());
|
return Ok(fd.clone());
|
||||||
|
|
@ -1739,22 +1739,15 @@ impl DeviceHandlerData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModifiersListener for DeviceHandlerData {
|
impl LedsListener for DeviceHandlerData {
|
||||||
fn locked_mods(&self, mods: &Components) {
|
fn leds(&self, leds: Leds) {
|
||||||
let mut leds = Leds::none();
|
|
||||||
if mods.mods_locked.contains(ModifierMask::NUM_LOCK) {
|
|
||||||
leds |= LED_NUM_LOCK;
|
|
||||||
}
|
|
||||||
if mods.mods_locked.contains(ModifierMask::LOCK) {
|
|
||||||
leds |= LED_CAPS_LOCK;
|
|
||||||
}
|
|
||||||
self.device.set_enabled_leds(leds);
|
self.device.set_enabled_leds(leds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModifiersListener for WlSeatGlobal {
|
impl LedsListener for WlSeatGlobal {
|
||||||
fn locked_mods(&self, mods: &Components) {
|
fn leds(&self, leds: Leds) {
|
||||||
self.dispatch_seat_modifiers_listeners(mods);
|
self.dispatch_seat_leds_listeners(leds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ impl ZwpInputMethodKeyboardGrabV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_keymap(&self, kb_state: &KeyboardState) {
|
fn send_keymap(&self, kb_state: &KeyboardState) {
|
||||||
let map = match kb_state.map.create_unprotected_fd() {
|
let map = match kb_state.map.map.create_unprotected_fd() {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Could not create new keymap fd: {}", ErrorFmt(e));
|
log::error!("Could not create new keymap fd: {}", ErrorFmt(e));
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,11 @@ impl ZwpVirtualKeyboardManagerV1RequestHandler for ZwpVirtualKeyboardManagerV1 {
|
||||||
version: self.version,
|
version: self.version,
|
||||||
kb_state: Rc::new(RefCell::new(KeyboardState {
|
kb_state: Rc::new(RefCell::new(KeyboardState {
|
||||||
id: self.client.state.keyboard_state_ids.next(),
|
id: self.client.state.keyboard_state_ids.next(),
|
||||||
map: seat_keymap.map.clone(),
|
map: seat_keymap.clone(),
|
||||||
xwayland_map: seat_keymap.xwayland_map.clone(),
|
|
||||||
pressed_keys: Default::default(),
|
pressed_keys: Default::default(),
|
||||||
mods: Default::default(),
|
mods: Default::default(),
|
||||||
mods_changed: Default::default(),
|
leds: Default::default(),
|
||||||
|
leds_changed: Default::default(),
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
track!(self.client, kb);
|
track!(self.client, kb);
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,11 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 {
|
||||||
.map_err(ZwpVirtualKeyboardV1Error::ParseKeymap)?;
|
.map_err(ZwpVirtualKeyboardV1Error::ParseKeymap)?;
|
||||||
*self.kb_state.borrow_mut() = KeyboardState {
|
*self.kb_state.borrow_mut() = KeyboardState {
|
||||||
id: self.client.state.keyboard_state_ids.next(),
|
id: self.client.state.keyboard_state_ids.next(),
|
||||||
map: map.map.clone(),
|
map: map.clone(),
|
||||||
xwayland_map: map.xwayland_map.clone(),
|
|
||||||
pressed_keys: Default::default(),
|
pressed_keys: Default::default(),
|
||||||
mods: Default::default(),
|
mods: Default::default(),
|
||||||
mods_changed: Default::default(),
|
leds: Default::default(),
|
||||||
|
leds_changed: Default::default(),
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -113,15 +113,12 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 {
|
||||||
|
|
||||||
fn modifiers(&self, req: Modifiers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn modifiers(&self, req: Modifiers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
let kb_state = &mut *self.kb_state.borrow_mut();
|
let kb_state = &mut *self.kb_state.borrow_mut();
|
||||||
let locked_mods = kb_state.mods.mods_locked;
|
|
||||||
kb_state.mods.mods_pressed.0 = req.mods_depressed;
|
kb_state.mods.mods_pressed.0 = req.mods_depressed;
|
||||||
kb_state.mods.mods_latched.0 = req.mods_latched;
|
kb_state.mods.mods_latched.0 = req.mods_latched;
|
||||||
kb_state.mods.mods_locked.0 = req.mods_locked;
|
kb_state.mods.mods_locked.0 = req.mods_locked;
|
||||||
kb_state.mods.group_locked.0 = req.group;
|
kb_state.mods.group_locked.0 = req.group;
|
||||||
kb_state.mods.update_effective();
|
kb_state.mods.update_effective();
|
||||||
if locked_mods != kb_state.mods.mods_locked {
|
kb_state.update_leds();
|
||||||
kb_state.dispatch_locked_mods_listeners();
|
|
||||||
}
|
|
||||||
self.for_each_kb(|serial, surface, kb| {
|
self.for_each_kb(|serial, surface, kb| {
|
||||||
kb.on_mods_changed(serial, surface.id, &kb_state);
|
kb.on_mods_changed(serial, surface.id, &kb_state);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
36
src/kbvm.rs
36
src/kbvm.rs
|
|
@ -12,6 +12,7 @@ use {
|
||||||
xkb::{
|
xkb::{
|
||||||
self, Keymap,
|
self, Keymap,
|
||||||
diagnostic::{Diagnostic, WriteToLog},
|
diagnostic::{Diagnostic, WriteToLog},
|
||||||
|
keymap::{Indicator, IndicatorMatcher},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
|
@ -52,6 +53,12 @@ pub struct KbvmMap {
|
||||||
pub lookup_table: LookupTable,
|
pub lookup_table: LookupTable,
|
||||||
pub map: KeymapFd,
|
pub map: KeymapFd,
|
||||||
pub xwayland_map: KeymapFd,
|
pub xwayland_map: KeymapFd,
|
||||||
|
pub has_indicators: bool,
|
||||||
|
pub num_lock: Option<IndicatorMatcher>,
|
||||||
|
pub caps_lock: Option<IndicatorMatcher>,
|
||||||
|
pub scroll_lock: Option<IndicatorMatcher>,
|
||||||
|
pub compose: Option<IndicatorMatcher>,
|
||||||
|
pub kana: Option<IndicatorMatcher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KbvmState {
|
pub struct KbvmState {
|
||||||
|
|
@ -85,6 +92,23 @@ impl KbvmContext {
|
||||||
.ctx
|
.ctx
|
||||||
.keymap_from_bytes(WriteToLog, None, keymap)
|
.keymap_from_bytes(WriteToLog, None, keymap)
|
||||||
.map_err(KbvmError::CouldNotParseKeymap)?;
|
.map_err(KbvmError::CouldNotParseKeymap)?;
|
||||||
|
let mut has_indicators = false;
|
||||||
|
let mut num_lock = None;
|
||||||
|
let mut caps_lock = None;
|
||||||
|
let mut scroll_lock = None;
|
||||||
|
let mut compose = None;
|
||||||
|
let mut kana = None;
|
||||||
|
for indicator in map.indicators() {
|
||||||
|
match indicator.name() {
|
||||||
|
Indicator::NUM_LOCK => num_lock = Some(indicator.matcher()),
|
||||||
|
Indicator::CAPS_LOCK => caps_lock = Some(indicator.matcher()),
|
||||||
|
Indicator::SCROLL_LOCK => scroll_lock = Some(indicator.matcher()),
|
||||||
|
Indicator::COMPOSE => compose = Some(indicator.matcher()),
|
||||||
|
Indicator::KANA => kana = Some(indicator.matcher()),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
has_indicators = true;
|
||||||
|
}
|
||||||
let builder = map.to_builder();
|
let builder = map.to_builder();
|
||||||
Ok(Rc::new(KbvmMap {
|
Ok(Rc::new(KbvmMap {
|
||||||
id: KbvmMapId(*blake3::hash(keymap).as_bytes()),
|
id: KbvmMapId(*blake3::hash(keymap).as_bytes()),
|
||||||
|
|
@ -92,6 +116,12 @@ impl KbvmContext {
|
||||||
map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?,
|
map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?,
|
||||||
xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?,
|
xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?,
|
||||||
lookup_table: builder.build_lookup_table(),
|
lookup_table: builder.build_lookup_table(),
|
||||||
|
has_indicators,
|
||||||
|
num_lock,
|
||||||
|
caps_lock,
|
||||||
|
scroll_lock,
|
||||||
|
compose,
|
||||||
|
kana,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,11 +153,11 @@ impl KbvmMap {
|
||||||
state: self.state_machine.create_state(),
|
state: self.state_machine.create_state(),
|
||||||
kb_state: KeyboardState {
|
kb_state: KeyboardState {
|
||||||
id,
|
id,
|
||||||
map: self.map.clone(),
|
map: self.clone(),
|
||||||
xwayland_map: self.xwayland_map.clone(),
|
|
||||||
pressed_keys: Default::default(),
|
pressed_keys: Default::default(),
|
||||||
mods: Default::default(),
|
mods: Default::default(),
|
||||||
mods_changed: Default::default(),
|
leds: Default::default(),
|
||||||
|
leds_changed: Default::default(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
use {
|
use {
|
||||||
crate::utils::{event_listener::EventSource, oserror::OsError, vecset::VecSet},
|
crate::{
|
||||||
|
backend::{LED_CAPS_LOCK, LED_COMPOSE, LED_KANA, LED_NUM_LOCK, LED_SCROLL_LOCK, Leds},
|
||||||
|
kbvm::KbvmMap,
|
||||||
|
utils::{event_listener::EventSource, oserror::OsError, vecset::VecSet},
|
||||||
|
},
|
||||||
kbvm::{Components, state_machine::Event},
|
kbvm::{Components, state_machine::Event},
|
||||||
std::{
|
std::{
|
||||||
cell::{Ref, RefCell},
|
cell::{Ref, RefCell},
|
||||||
|
|
@ -21,15 +25,15 @@ linear_ids!(KeyboardStateIds, KeyboardStateId, u64);
|
||||||
|
|
||||||
pub struct KeyboardState {
|
pub struct KeyboardState {
|
||||||
pub id: KeyboardStateId,
|
pub id: KeyboardStateId,
|
||||||
pub map: KeymapFd,
|
pub map: Rc<KbvmMap>,
|
||||||
pub xwayland_map: KeymapFd,
|
|
||||||
pub pressed_keys: VecSet<u32>,
|
pub pressed_keys: VecSet<u32>,
|
||||||
pub mods: Components,
|
pub mods: Components,
|
||||||
pub mods_changed: EventSource<dyn ModifiersListener>,
|
pub leds: Leds,
|
||||||
|
pub leds_changed: EventSource<dyn LedsListener>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ModifiersListener {
|
pub trait LedsListener {
|
||||||
fn locked_mods(&self, mods: &Components);
|
fn leds(&self, leds: Leds);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DynKeyboardState {
|
pub trait DynKeyboardState {
|
||||||
|
|
@ -44,17 +48,37 @@ impl DynKeyboardState for RefCell<KeyboardState> {
|
||||||
|
|
||||||
impl KeyboardState {
|
impl KeyboardState {
|
||||||
pub fn apply_event(&mut self, event: Event) -> bool {
|
pub fn apply_event(&mut self, event: Event) -> bool {
|
||||||
let locked_mods = self.mods.mods_locked;
|
|
||||||
let changed = self.mods.apply_event(event);
|
let changed = self.mods.apply_event(event);
|
||||||
if locked_mods != self.mods.mods_locked {
|
if changed && self.map.has_indicators {
|
||||||
self.dispatch_locked_mods_listeners();
|
self.update_leds();
|
||||||
}
|
}
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch_locked_mods_listeners(&self) {
|
pub fn update_leds(&mut self) {
|
||||||
for listener in self.mods_changed.iter() {
|
if !self.map.has_indicators {
|
||||||
listener.locked_mods(&self.mods);
|
return;
|
||||||
|
}
|
||||||
|
let mut new = Leds::none();
|
||||||
|
macro_rules! map_led {
|
||||||
|
($field:ident, $led:ident) => {
|
||||||
|
if let Some(m) = &self.map.$field
|
||||||
|
&& m.matches(&self.mods)
|
||||||
|
{
|
||||||
|
new |= $led;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
map_led!(num_lock, LED_NUM_LOCK);
|
||||||
|
map_led!(caps_lock, LED_CAPS_LOCK);
|
||||||
|
map_led!(scroll_lock, LED_SCROLL_LOCK);
|
||||||
|
map_led!(compose, LED_COMPOSE);
|
||||||
|
map_led!(kana, LED_KANA);
|
||||||
|
if new != self.leds {
|
||||||
|
self.leds = new;
|
||||||
|
for listener in self.leds_changed.iter() {
|
||||||
|
listener.leds(new);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ use {
|
||||||
},
|
},
|
||||||
io_uring::IoUring,
|
io_uring::IoUring,
|
||||||
kbvm::{KbvmContext, KbvmMap},
|
kbvm::{KbvmContext, KbvmMap},
|
||||||
keyboard::{KeyboardStateIds, ModifiersListener},
|
keyboard::{KeyboardStateIds, LedsListener},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
logger::Logger,
|
logger::Logger,
|
||||||
pr_caps::PrCapsThread,
|
pr_caps::PrCapsThread,
|
||||||
|
|
@ -397,7 +397,7 @@ pub struct DeviceHandlerData {
|
||||||
pub tablet_pad_init: Option<Box<TabletPadInit>>,
|
pub tablet_pad_init: Option<Box<TabletPadInit>>,
|
||||||
pub is_touch: bool,
|
pub is_touch: bool,
|
||||||
pub is_kb: bool,
|
pub is_kb: bool,
|
||||||
pub mods_listener: EventListener<dyn ModifiersListener>,
|
pub mods_listener: EventListener<dyn LedsListener>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ConnectorData {
|
pub struct ConnectorData {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue