diff --git a/Cargo.lock b/Cargo.lock index ca363744..fd6c166c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,6 +173,19 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "bstr" version = "1.11.3" @@ -295,6 +308,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -587,6 +606,7 @@ dependencies = [ "ash", "backtrace", "bincode", + "blake3", "bstr", "byteorder", "cc", diff --git a/Cargo.toml b/Cargo.toml index 5a0d9519..0898817d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ regex = "1.11.1" cfg-if = "1.0.0" opera = "1.0.1" with_builtin_macros = "0.1.0" +blake3 = "1.8.2" [build-dependencies] repc = "0.1.1" diff --git a/src/backend.rs b/src/backend.rs index b8f15cf7..7e6b497e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -259,6 +259,10 @@ pub trait InputDevice { fn tablet_pad_info(&self) -> Option> { None } + + fn set_enabled_leds(&self, leds: Leds) { + let _ = leds; + } } #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] @@ -328,6 +332,15 @@ pub enum AxisSource { pub const AXIS_120: i32 = 120; +bitflags! { + Leds: u32; + LED_NUM_LOCK = 1 << 0, + LED_CAPS_LOCK = 1 << 1, + LED_SCROLL_LOCK = 1 << 2, + LED_COMPOSE = 1 << 3, + LED_KANA = 1 << 4, +} + #[derive(Debug)] pub enum InputEvent { Key { diff --git a/src/backends/metal.rs b/src/backends/metal.rs index ae9ce6e1..9c318349 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -9,7 +9,7 @@ use { async_engine::SpawnedFuture, backend::{ Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, - InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, + InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, Leds, TransformMatrix, transaction::BackendConnectorTransactionError, }, backends::metal::video::{ @@ -31,7 +31,7 @@ use { AccelProfile, ConfigClickMethod, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE, - LIBINPUT_DEVICE_CAP_TABLET_PAD, LIBINPUT_DEVICE_CAP_TABLET_TOOL, + LIBINPUT_DEVICE_CAP_TABLET_PAD, LIBINPUT_DEVICE_CAP_TABLET_TOOL, Led, }, device::{LibInputDevice, RegisteredDevice}, }, @@ -373,6 +373,7 @@ struct InputDeviceProperties { calibration_matrix: Cell>, click_method: Cell>, middle_button_emulation_enabled: Cell>, + enabled_leds: Cell>, } #[derive(Clone)] @@ -442,6 +443,9 @@ impl MetalInputDevice { if let Some(enabled) = self.desired.middle_button_emulation_enabled.get() { self.set_middle_button_emulation_enabled(enabled); } + if let Some(led) = self.desired.enabled_leds.get() { + self.set_enabled_leds_(led); + } self.fetch_effective(); } @@ -527,6 +531,14 @@ impl MetalInputDevice { .set(Some(dev.device().click_method())); } } + + fn set_enabled_leds_(&self, led: Led) { + self.desired.enabled_leds.set(Some(led)); + if let Some(dev) = self.inputdev.get() { + dev.device().led_update(led); + self.effective.enabled_leds.set(Some(led)); + } + } } impl InputDevice for MetalInputDevice { @@ -810,6 +822,11 @@ impl InputDevice for MetalInputDevice { groups, })) } + + fn set_enabled_leds(&self, leds: Leds) { + let led = Led(leds.0 as _); + self.set_enabled_leds_(led); + } } impl MetalInputDevice { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 87b5fd2e..5af33bdd 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -24,7 +24,7 @@ pub mod zwp_virtual_keyboard_v1; use { crate::{ async_engine::SpawnedFuture, - backend::KeyState, + backend::{KeyState, LED_CAPS_LOCK, LED_NUM_LOCK, Leds}, client::{Client, ClientError, ClientId}, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, ei::ei_ifs::ei_seat::EiSeat, @@ -75,7 +75,7 @@ use { xdg_toplevel_drag_v1::XdgToplevelDragV1, }, kbvm::{KbvmMap, KbvmMapId, KbvmState, PhysicalKeyboardState}, - keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd}, + keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd, ModifiersListener}, leaks::Tracker, object::{Object, Version}, rect::Rect, @@ -91,6 +91,7 @@ use { bindings::PerClientBindings, clonecell::CloneCell, copyhashmap::CopyHashMap, + event_listener::{EventListener, EventSource}, linkedlist::{LinkedList, LinkedNode, NodeRef}, numcell::NumCell, on_drop::OnDrop, @@ -106,7 +107,7 @@ use { }, ahash::AHashMap, jay_config::keyboard::syms::{KeySym, SYM_Escape}, - kbvm::Keycode, + kbvm::{Components, Keycode, ModifierMask}, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, @@ -235,6 +236,8 @@ pub struct WlSeatGlobal { focus_history_same_workspace: Cell, mark_mode: Cell>, marks: CopyHashMap>, + modifiers_listener: EventListener, + modifiers_forward: EventSource, } #[derive(Copy, Clone)] @@ -256,7 +259,7 @@ impl WlSeatGlobal { let cursor_user_group = CursorUserGroup::create(state); let cursor_user = cursor_user_group.create_user(); cursor_user.activate(); - let slf = Rc::new(Self { + let slf = Rc::new_cyclic(|slf: &Weak| Self { id: state.seat_ids.next(), name, state: state.clone(), @@ -322,8 +325,12 @@ impl WlSeatGlobal { focus_history_same_workspace: Cell::new(false), mark_mode: Default::default(), marks: Default::default(), + modifiers_listener: EventListener::new(slf.clone()), + modifiers_forward: Default::default(), }); slf.pointer_cursor.set_owner(slf.clone()); + slf.modifiers_listener + .attach(&seat_kb_state.borrow().kb_state.mods_changed); let seat = slf.clone(); let future = state.eng.spawn("seat handler", async move { loop { @@ -516,14 +523,27 @@ impl WlSeatGlobal { false } - pub fn set_seat_keymap(&self, keymap: &Rc) { + pub fn set_seat_keymap(self: &Rc, keymap: &Rc) { self.seat_kb_map.set(keymap.clone()); let new = self.get_kb_state(keymap); let old = self.seat_kb_state.set(new.clone()); if rc_eq(&old, &new) { return; } - self.kb_devices.lock().retain(|_, p| p.has_custom_map.get()); + let mut to_destroy = vec![]; + for (id, s) in self.kb_devices.lock().iter() { + if !s.has_custom_map.get() { + to_destroy.push(*id); + } + } + for dev in to_destroy { + self.destroy_physical_keyboard(dev); + } + { + let new = &*new.borrow(); + self.modifiers_listener.attach(&new.kb_state.mods_changed); + self.dispatch_seat_modifiers_listeners(&new.kb_state.mods); + } self.handle_keyboard_state_change(&old.borrow().kb_state, &new.borrow().kb_state); } @@ -558,6 +578,35 @@ impl WlSeatGlobal { s } + fn attach_modifiers_listener( + &self, + id: PhysicalKeyboardId, + listener: &EventListener, + map: Option<&Rc>, + ) { + let _ = self.get_physical_keyboard(id, map); + let state = match map { + None => { + listener.attach(&self.modifiers_forward); + self.seat_kb_state.get() + } + Some(m) => { + let state = self.get_kb_state(m); + listener.attach(&state.borrow().kb_state.mods_changed); + state + } + }; + if let Some(l) = listener.get() { + l.locked_mods(&state.borrow().kb_state.mods); + } + } + + fn dispatch_seat_modifiers_listeners(&self, mods: &Components) { + for listener in self.modifiers_forward.iter() { + listener.locked_mods(mods); + } + } + pub fn prepare_for_lock(self: &Rc) { self.pointer_owner.revert_to_default(self); self.kb_owner.ungrab(self); @@ -1639,17 +1688,32 @@ impl DeviceHandlerData { seat.update_capabilities(); } } + self.attach_event_listeners(); } fn destroy_physical_keyboard_state(&self) { + self.mods_listener.detach(); if let Some(seat) = self.seat.get() { seat.destroy_physical_keyboard(self.keyboard_id); }; } + fn attach_event_listeners(&self) { + if self.is_kb + && let Some(seat) = self.seat.get() + { + seat.attach_modifiers_listener( + self.keyboard_id, + &self.mods_listener, + self.keymap.get().as_ref(), + ); + }; + } + pub fn set_keymap(&self, keymap: Option>) { self.destroy_physical_keyboard_state(); self.keymap.set(keymap); + self.attach_event_listeners(); } pub fn set_output(&self, output: Option<&WlOutputGlobal>) { @@ -1675,6 +1739,25 @@ impl DeviceHandlerData { } } +impl ModifiersListener for DeviceHandlerData { + fn locked_mods(&self, mods: &Components) { + 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); + } +} + +impl ModifiersListener for WlSeatGlobal { + fn locked_mods(&self, mods: &Components) { + self.dispatch_seat_modifiers_listeners(mods); + } +} + pub struct PositionHintRequest { seat: Rc, node: NodeId, diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 7f2010e5..616c9d3b 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -890,7 +890,7 @@ impl WlSeatGlobal { let mut shortcuts = SmallVec::<[_; 1]>::new(); let mut components_changed = false; while let Some(event) = events.pop() { - components_changed |= kbvm_state.kb_state.mods.apply_event(event); + components_changed |= kbvm_state.kb_state.apply_event(event); let (key_state, kc) = match event { Event::KeyDown(kc) => (KeyState::Pressed, kc), Event::KeyUp(kc) => (KeyState::Released, kc), diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs index 88c3fe66..a7a4b3f6 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs @@ -90,6 +90,7 @@ impl ZwpVirtualKeyboardManagerV1RequestHandler for ZwpVirtualKeyboardManagerV1 { xwayland_map: seat_keymap.xwayland_map.clone(), pressed_keys: Default::default(), mods: Default::default(), + mods_changed: Default::default(), })), }); track!(self.client, kb); diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs index ad810d1b..eb849a7f 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs @@ -84,6 +84,7 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 { xwayland_map: map.xwayland_map.clone(), pressed_keys: Default::default(), mods: Default::default(), + mods_changed: Default::default(), }; Ok(()) } @@ -112,11 +113,15 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 { fn modifiers(&self, req: Modifiers, _slf: &Rc) -> Result<(), Self::Error> { 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_latched.0 = req.mods_latched; kb_state.mods.mods_locked.0 = req.mods_locked; kb_state.mods.group_locked.0 = req.group; kb_state.mods.update_effective(); + if locked_mods != kb_state.mods.mods_locked { + kb_state.dispatch_locked_mods_listeners(); + } self.for_each_kb(|serial, surface, kb| { kb.on_mods_changed(serial, surface.id, &kb_state); }); diff --git a/src/kbvm.rs b/src/kbvm.rs index d2c4a685..85c38689 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -33,21 +33,18 @@ pub enum KbvmError { pub struct KbvmContext { pub ctx: xkb::Context, - pub ids: KbvmMapIds, } impl Default for KbvmContext { fn default() -> Self { let mut ctx = xkb::Context::builder(); ctx.enable_environment(true); - Self { - ctx: ctx.build(), - ids: Default::default(), - } + Self { ctx: ctx.build() } } } -linear_ids!(KbvmMapIds, KbvmMapId, u64); +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct KbvmMapId([u8; 32]); pub struct KbvmMap { pub id: KbvmMapId, @@ -90,7 +87,7 @@ impl KbvmContext { .map_err(KbvmError::CouldNotParseKeymap)?; let builder = map.to_builder(); Ok(Rc::new(KbvmMap { - id: self.ids.next(), + id: KbvmMapId(*blake3::hash(keymap).as_bytes()), state_machine: builder.build_state_machine(), map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?, xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?, @@ -130,6 +127,7 @@ impl KbvmMap { xwayland_map: self.xwayland_map.clone(), pressed_keys: Default::default(), mods: Default::default(), + mods_changed: Default::default(), }, } } @@ -139,7 +137,7 @@ impl KbvmState { pub fn apply_events(&mut self, events: &SyncQueue) { let state = &mut self.kb_state; while let Some(event) = events.pop() { - state.mods.apply_event(event); + state.apply_event(event); match event { Event::KeyDown(kc) => { state.pressed_keys.insert(kc.to_evdev()); diff --git a/src/keyboard.rs b/src/keyboard.rs index b4f6090b..61dcd7b8 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,6 +1,6 @@ use { - crate::utils::{oserror::OsError, vecset::VecSet}, - kbvm::Components, + crate::utils::{event_listener::EventSource, oserror::OsError, vecset::VecSet}, + kbvm::{Components, state_machine::Event}, std::{ cell::{Ref, RefCell}, rc::Rc, @@ -25,6 +25,11 @@ pub struct KeyboardState { pub xwayland_map: KeymapFd, pub pressed_keys: VecSet, pub mods: Components, + pub mods_changed: EventSource, +} + +pub trait ModifiersListener { + fn locked_mods(&self, mods: &Components); } pub trait DynKeyboardState { @@ -37,6 +42,23 @@ impl DynKeyboardState for RefCell { } } +impl KeyboardState { + pub fn apply_event(&mut self, event: Event) -> bool { + let locked_mods = self.mods.mods_locked; + let changed = self.mods.apply_event(event); + if locked_mods != self.mods.mods_locked { + self.dispatch_locked_mods_listeners(); + } + changed + } + + pub fn dispatch_locked_mods_listeners(&self) { + for listener in self.mods_changed.iter() { + listener.locked_mods(&self.mods); + } + } +} + #[derive(Clone)] pub struct KeymapFd { pub map: Rc, diff --git a/src/libinput/consts.rs b/src/libinput/consts.rs index 616bf10e..10d4ee11 100644 --- a/src/libinput/consts.rs +++ b/src/libinput/consts.rs @@ -33,6 +33,8 @@ cenum! { LIBINPUT_LED_NUM_LOCK = 1 << 0, LIBINPUT_LED_CAPS_LOCK = 1 << 1, LIBINPUT_LED_SCROLL_LOCK = 1 << 2, + LIBINPUT_LED_COMPOSE = 1 << 3, + LIBINPUT_LED_KANA = 1 << 4, } cenum! { diff --git a/src/libinput/device.rs b/src/libinput/device.rs index 75ccc2d9..9d398880 100644 --- a/src/libinput/device.rs +++ b/src/libinput/device.rs @@ -7,7 +7,7 @@ use { LIBINPUT_CONFIG_DRAG_DISABLED, LIBINPUT_CONFIG_DRAG_ENABLED, LIBINPUT_CONFIG_DRAG_LOCK_DISABLED, LIBINPUT_CONFIG_DRAG_LOCK_ENABLED, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED, - LIBINPUT_CONFIG_TAP_DISABLED, LIBINPUT_CONFIG_TAP_ENABLED, + LIBINPUT_CONFIG_TAP_DISABLED, LIBINPUT_CONFIG_TAP_ENABLED, Led, }, sys::{ libinput_device, libinput_device_config_accel_get_profile, @@ -36,8 +36,9 @@ use { libinput_device_get_id_vendor, libinput_device_get_name, libinput_device_get_user_data, libinput_device_group, libinput_device_group_get_user_data, libinput_device_group_set_user_data, libinput_device_has_capability, - libinput_device_set_user_data, libinput_device_tablet_pad_get_mode_group, - libinput_device_tablet_pad_get_num_buttons, libinput_device_tablet_pad_get_num_dials, + libinput_device_led_update, libinput_device_set_user_data, + libinput_device_tablet_pad_get_mode_group, libinput_device_tablet_pad_get_num_buttons, + libinput_device_tablet_pad_get_num_dials, libinput_device_tablet_pad_get_num_mode_groups, libinput_device_tablet_pad_get_num_rings, libinput_device_tablet_pad_get_num_strips, libinput_device_unref, libinput_path_remove_device, libinput_tablet_pad_mode_group, @@ -343,6 +344,12 @@ impl<'a> LibInputDevice<'a> { } [[m[0], m[1], m[2]], [m[3], m[4], m[5]]] } + + pub fn led_update(&self, led: Led) { + unsafe { + libinput_device_led_update(self.dev, led.raw() as _); + } + } } impl<'a> LibInputDeviceGroup<'a> { diff --git a/src/libinput/sys.rs b/src/libinput/sys.rs index 2de8d416..9bacc30c 100644 --- a/src/libinput/sys.rs +++ b/src/libinput/sys.rs @@ -410,6 +410,8 @@ unsafe extern "C" { device: *mut libinput_device, matrix: *mut [f32; 6], ) -> c::c_int; + + pub fn libinput_device_led_update(device: *mut libinput_device, leds: libinput_led); } #[repr(C)] diff --git a/src/state.rs b/src/state.rs index 56cc29f8..cc7c768e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -83,7 +83,7 @@ use { }, io_uring::IoUring, kbvm::{KbvmContext, KbvmMap}, - keyboard::KeyboardStateIds, + keyboard::{KeyboardStateIds, ModifiersListener}, leaks::Tracker, logger::Logger, pr_caps::PrCapsThread, @@ -100,11 +100,21 @@ use { generic_node_visitor, }, utils::{ - activation_token::ActivationToken, asyncevent::AsyncEvent, bindings::Bindings, - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - event_listener::EventSource, fdcloser::FdCloser, hash_map_ext::HashMapExt, - linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, - run_toplevel::RunToplevel, toplevel_identifier::ToplevelIdentifier, + activation_token::ActivationToken, + asyncevent::AsyncEvent, + bindings::Bindings, + clonecell::CloneCell, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + event_listener::{EventListener, EventSource}, + fdcloser::FdCloser, + hash_map_ext::HashMapExt, + linkedlist::LinkedList, + numcell::NumCell, + queue::AsyncQueue, + refcounted::RefCounted, + run_toplevel::RunToplevel, + toplevel_identifier::ToplevelIdentifier, }, video::{ dmabuf::DmaBufIds, @@ -386,6 +396,8 @@ pub struct DeviceHandlerData { pub tablet_init: Option>, pub tablet_pad_init: Option>, pub is_touch: bool, + pub is_kb: bool, + pub mods_listener: EventListener, } pub struct ConnectorData { diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index 189e6d43..f9bf3f5a 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -4,10 +4,13 @@ use { ifs::wl_seat::PX_PER_SCROLL, state::{DeviceHandlerData, InputDeviceData, State}, tasks::udev_utils::{UdevProps, udev_props}, - utils::asyncevent::AsyncEvent, + utils::{asyncevent::AsyncEvent, event_listener::EventListener}, }, jay_config::_private::DEFAULT_SEAT_NAME, - std::{cell::Cell, rc::Rc}, + std::{ + cell::Cell, + rc::{Rc, Weak}, + }, }; pub fn handle(state: &Rc, dev: Rc) { @@ -15,7 +18,7 @@ pub fn handle(state: &Rc, dev: Rc) { None => UdevProps::default(), Some(dev_t) => udev_props(dev_t, 3), }; - let data = Rc::new(DeviceHandlerData { + let data = Rc::new_cyclic(|slf: &Weak| DeviceHandlerData { keyboard_id: state.physical_keyboard_ids.next(), seat: Default::default(), px_per_scroll_wheel: Cell::new(PX_PER_SCROLL), @@ -27,6 +30,8 @@ pub fn handle(state: &Rc, dev: Rc) { tablet_init: dev.tablet_info(), tablet_pad_init: dev.tablet_pad_info(), is_touch: dev.has_capability(InputDeviceCapability::Touch), + is_kb: dev.has_capability(InputDeviceCapability::Keyboard), + mods_listener: EventListener::new(slf.clone()), }); let ae = Rc::new(AsyncEvent::default()); let oh = DeviceHandler { diff --git a/src/utils/event_listener.rs b/src/utils/event_listener.rs index 2bed98b1..7e5e5964 100644 --- a/src/utils/event_listener.rs +++ b/src/utils/event_listener.rs @@ -78,4 +78,8 @@ impl EventListener { pub fn detach(&self) { self.link.detach(); } + + pub fn get(&self) -> Option> { + self.link.upgrade() + } }