Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
c8fe672
Wayland: support basic Drag&Drop
SludgePhD Aug 12, 2022
809ae2c
Address clippy lints
SludgePhD Aug 13, 2022
31a4de1
Use `make_wid` to simplify code
SludgePhD Aug 13, 2022
9d88e5e
Also emit all appropriate cursor events
SludgePhD Aug 13, 2022
2b0dd48
Read the pipe without blocking
SludgePhD Aug 19, 2022
2687654
Follow style
SludgePhD Aug 19, 2022
d95dd41
Address clippy lint
SludgePhD Aug 19, 2022
60749aa
Fix parsing of CRLF-delimited lines
SludgePhD Aug 23, 2022
2c15c5e
Fix merge conflicts in Cargo.toml
eira-fransham May 19, 2026
ccd7116
Fix merge conflicts in `platform_impl`, start implementing data trans…
eira-fransham May 19, 2026
ccaff27
Fix cargo fmt
eira-fransham May 20, 2026
d8fa184
Convert to use new data transfer system
eira-fransham May 20, 2026
3c01cb8
Add plaintext + URI helpers, remove MIME
eira-fransham May 20, 2026
b7fcfe0
Add more documentation
eira-fransham May 20, 2026
2234a27
Fix drag-and-drop on X11
eira-fransham May 21, 2026
8d3111b
Fix use of `XDndStatus`
eira-fransham May 21, 2026
e867501
Change comment
eira-fransham May 21, 2026
4dc8f6f
Fix conversion from type hint to "real" type
eira-fransham May 21, 2026
1fec92c
Fill out type hints for X11
eira-fransham May 21, 2026
61ab9c8
Fix transferring text and add an example
eira-fransham May 21, 2026
fb7779e
WIP: Implement drag-and-drop for AppKit using new API
eira-fransham May 22, 2026
98106b8
Remove `DataTransferEvent`
eira-fransham May 26, 2026
2e99f38
Implement dnd for appkit
eira-fransham May 26, 2026
1c5a76a
Fix `registerForDraggedTypes` on macOS
eira-fransham May 26, 2026
0e6bbf6
Fix clippy on macOS
eira-fransham May 26, 2026
9a058cb
Fix clippy
eira-fransham May 26, 2026
fbccd66
Fix 1.85 compat
eira-fransham May 26, 2026
d4b9c49
Taplo fmt
eira-fransham May 26, 2026
d00d415
X11 clippy
eira-fransham May 26, 2026
e52cae1
Explain `regsisterForDraggedTypes` usage
eira-fransham May 26, 2026
47dd3cf
Fix clippy issue
eira-fransham May 27, 2026
03eb54a
Small changes
eira-fransham May 27, 2026
a439436
Fix compilation issue
eira-fransham May 27, 2026
0c94650
WIP: Implement for Windows
eira-fransham May 27, 2026
fb92e36
X11 cleanup
eira-fransham May 27, 2026
aaa006a
X11 cleanup
eira-fransham May 27, 2026
72ed878
Fix clippy warnings, make `wait_for_data` X11-internal
eira-fransham May 27, 2026
1a6b0dd
Remove `wait_for_data` from appkit
eira-fransham May 27, 2026
fc55406
WIP: win32
eira-fransham May 27, 2026
4f22d27
Simpler API
eira-fransham May 28, 2026
ea24514
`drop_handler`->`dnd`
eira-fransham May 28, 2026
8028fc8
Fix unused Cow import
eira-fransham May 28, 2026
42c4ccf
Use `OsString` for paths
eira-fransham May 28, 2026
798c79b
WIP: Implement for Wayland
eira-fransham Jun 1, 2026
9966b83
Fix appkit
eira-fransham Jun 1, 2026
f3eec15
Fix clippy warnings, fix on wayland
eira-fransham Jun 1, 2026
3a2231f
WIP: Initiate drag
eira-fransham Jun 1, 2026
74a3383
WIP: Initiate drag
eira-fransham Jun 2, 2026
9f48597
WIP: Initiate drag
eira-fransham Jun 2, 2026
5d28920
WIP: Initiate drag
eira-fransham Jun 2, 2026
75ffbf5
Initiate drag on Wayland
eira-fransham Jun 2, 2026
4596833
Fix icon on drag
eira-fransham Jun 2, 2026
4d92ce5
Allow switching on requested type, allowing the application to be gen…
eira-fransham Jun 2, 2026
cce666a
Complete win32 drag-and-drop by reading dropped data
tronical May 28, 2026
708962e
Use the actual copied length when reading dropped file paths
tronical May 28, 2026
649ad6f
Receive PNG image drops on win32
tronical Jun 1, 2026
5fb6208
Accept drops on win32 via set_valid_actions
tronical Jun 1, 2026
eb7792c
Update win32 for new set_valid_actions signature
eira-fransham Jun 2, 2026
ed9e71d
Fix nitpicks
eira-fransham Jun 2, 2026
e99836b
Fix invalid panic over FFI boundary
eira-fransham Jun 2, 2026
41e1f5f
Fmt
eira-fransham Jun 2, 2026
b5375a9
WIP: Initiate drag on macOS
eira-fransham Jun 2, 2026
340c2cb
Initiate drag on macOS
eira-fransham Jun 3, 2026
098af43
Remove `cancel_drag`
eira-fransham Jun 3, 2026
b8e3eba
Fix buggy drag visuals
eira-fransham Jun 3, 2026
ea394ea
Clippy warnings
eira-fransham Jun 3, 2026
fcababb
Clippy warnings
eira-fransham Jun 3, 2026
54f695e
Fix comment formatting
eira-fransham Jun 3, 2026
3a4bb3d
Fix `DndActionMask` on macOS
eira-fransham Jun 3, 2026
16f2102
Initiate drags from win32 windows
tronical Jun 2, 2026
1e1c579
Emit DragLeft when the drop was rejected
tronical Jun 2, 2026
353fb87
Pair COM Release with an Acquire fence before drop
tronical Jun 2, 2026
dfd05ae
Implement DragIcon on win32
tronical Jun 2, 2026
2c6d5c5
fixup! Implement DragIcon on win32
tronical Jun 4, 2026
e4f341d
fixup! Implement DragIcon on win32
tronical Jun 4, 2026
463d72f
fixup! Implement DragIcon on win32
tronical Jun 4, 2026
f058957
fixup! Pair COM Release with an Acquire fence before drop
tronical Jun 4, 2026
bf5bcc1
fixup! Implement DragIcon on win32
tronical Jun 4, 2026
e35894d
fixup! Initiate drags from win32 windows
tronical Jun 4, 2026
b8ebe98
fixup! Initiate drags from win32 windows
tronical Jun 4, 2026
0b5b9b3
fixup! Initiate drags from win32 windows
tronical Jun 4, 2026
d8b2bab
fixup! Implement DragIcon on win32
tronical Jun 4, 2026
e1293a1
Plaintext is UTF-8 on Wayland
eira-fransham Jun 4, 2026
5e7d943
Merge branch 'drag-n-drop' into simon/win32-drag
eira-fransham Jun 4, 2026
2884d74
fixup! Initiate drags from win32 windows
tronical Jun 4, 2026
58ca72b
fixup! Implement DragIcon on win32
tronical Jun 4, 2026
8bce692
Fix dragging file paths on Windows
eira-fransham Jun 4, 2026
b5535e2
Format
eira-fransham Jun 4, 2026
e5ee07f
Merge pull request #3 from slint-ui/simon/win32-drag
eira-fransham Jun 4, 2026
5804690
Fix KDE not sending dropped event if data is not fetched
eira-fransham Jun 8, 2026
fc21fad
Respond to PR feedback
eira-fransham Jun 8, 2026
6f4a843
Respond to PR feedback
eira-fransham Jun 8, 2026
5978feb
Make Wayland transfer ID more consistent
eira-fransham Jun 8, 2026
f5d6ba3
Talkback to drag source when drag is completed, properly handle opera…
eira-fransham Jun 9, 2026
1a50668
Wayland drag talkback (TODO: re-format)
eira-fransham Jun 9, 2026
87decff
Format
eira-fransham Jun 9, 2026
af93270
Fix CI
eira-fransham Jun 10, 2026
948fce7
Taplo fmt
eira-fransham Jun 10, 2026
01a5ce2
Fix macOS
eira-fransham Jun 10, 2026
e98cdfd
Fix internal drag-and-drop on Windows, implement outgoing drag ended …
eira-fransham Jun 11, 2026
971e5f8
Remove explicit internal drag functionality (only available on Waylan…
eira-fransham Jun 11, 2026
9e3fd5d
`new_window`->`register_window`
eira-fransham Jun 11, 2026
e608cea
Remove unused import
eira-fransham Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions winit-core/src/data_transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! Types related to data transfer, used for clipboard and drag-and-drop.
//!
//! This module contains types and traits implementing cross-application data transfer.
//! While the precise implementation depends on platform, there are a set of types which
//! can be safely transferred between applications on all platforms (see [`TypeHint`]).
//!
//! On all platforms, the process looks something like this:
//!
//! - A data transfer advertises a set of types which the data can be interpreted as
//! - For example, if you copy or drag text from a web page, the browser may advertise the text
//! formatted using HTML, the text formatted as RTF, and the text with all formatting removed
//! simultaneously.
//! - An application receiving a data transfer chooses one or more types that it understands and
//! requests the data in those formats (in practice, it will usually only request a single
//! format).
//! - The source application converts the data stored in its memory to the requested format and
//! asynchronously sends it to the target application
//!
//! On some platforms, the data is sometimes available synchronously, but all platforms have at
//! least some method of sending the data asynchronously and some types of data that may _only_ be
//! sent using the asynchronous interface. Because of this, the API in winit must be asynchronous.
//!
//! The flow for a user application that implements drag-and-drop would look something like this:
//!
//! - The application receives a [`DragEnter`](crate::event::WindowEvent::DragEnter) event. This
//! event supplies a [`DataTransferId`] which can be used to request information or operations on
//! the dragged data by using methods on [`Window`](crate::window::Window).
//! - As the drag operation continues, the window will receive
//! [`DragMoved`](crate::event::WindowEvent::DragEnter) events.
//! - While `DragMoved` events are being received, the receiving application may mark the data as
//! being accepted or rejected. This will update the OS/compositor to display the correct UI to
//! the user. Accepting does not "finalize" the drag operation, nor does rejecting cancel it.
//! - At any point during this operation, the receiving application may request either the available
//! types or even the data being transferred. This may be useful in cases where the application
//! wants to preload the data. For example, an image editor may want to display the image on the
//! canvas during the drag operation.
//! - When the user tries to drop the data onto the window, that window will receive a
//! [`DragDropped`](crate::event::WindowEvent::DragDropped) event. In general, the receiving
//! application should assume that calling [`reject_drag`](crate::window::Window::reject_drag)
//! after `DragDropped` is received ends the lifecycle of the data transfer.
//!
//! If platform-dependent behavior is required, a platform may define internal types
//! implementing the traits in this module, which can then be accessed in an application
//! using the methods defined on [`dyn AsAny`]. See each platform's documentation for details.

use std::fmt::{self, Debug};
use std::io;

use crate::as_any::AsAny;

/// Unique identifier for a data transfer.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DataTransferId(i64);

impl DataTransferId {
/// Convert the [`DataTransferId`] into the underlying integer.
///
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
pub const fn into_raw(self) -> i64 {
self.0
}

/// Construct a [`DataTransferId`] from the underlying integer.
///
/// This should only be called with integers returned from [`DataTransferId::into_raw`].
pub const fn from_raw(id: i64) -> Self {
Self(id)
}
}

/// The set of types supported cross-platform.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TypeHint {
/// Plain UTF-8 text (see [`TypedData::try_as_plaintext`]).
///
/// **Note for platform implementations**: this hint is _only_ for UTF-8 text. If the platform
/// returns plaintext in some format other than UTF-8 by default, a [`TypedData`]
/// implementation marked with this type hint should convert to UTF-8.
Plaintext,
/// A list of URIs in the format defined by the `text/uri-list` MIME type, encoded as UTF-8 (see
/// [`TypedData::try_as_uris`]).
///
/// **Note for platform implementations**: this hint is _only_ for URIs encoded precisely in the
/// format specified above. If the platform uses a different format, a [`TypedData`]
/// implementation marked with this type hint should convert to that format.
UriList,
/// A HTML-formatted string
Html,
/// An RTF-formatted string
Rtf,
/// Audio
Audio {
/// An optional hint for the encoding of the supplied bytes, specified using the standard
/// file extension for that audio format, lowercase and without the leading `.`.
extension_hint: Option<&'static str>,
},
/// Image data
Image {
/// An optional hint for the encoding of the supplied bytes, specified using the standard
/// file extension for that audio format, lowercase and without the leading `.`.
Comment thread
eira-fransham marked this conversation as resolved.
Outdated
extension_hint: Option<&'static str>,
},
}

/// The type of a data transfer.
///
/// [`hint`](TransferType::hint) can be called to get the type in
/// a cross-platform format (see [`TypeHint`])
pub trait TransferType: AsAny + Send + Sync + fmt::Debug {
/// Get the cross-platform representation of this type.
///
/// If this returns `None`, then this is a platform-dependent type that has no cross-platform
/// equivalent.
fn hint(&self) -> Option<TypeHint>;
}

impl TransferType for TypeHint {
fn hint(&self) -> Option<TypeHint> {
Some(*self)
}
}

impl_dyn_casting!(TransferType);

/// Data that has been fetched from a data transfer
pub trait TypedData: AsAny + Send + Sync + fmt::Debug {
/// The type of this `TypedData`.
fn type_(&self) -> &dyn TransferType;

/// If this value is readable as bytes, return a reader than can be used to read those bytes.
fn try_read(&mut self) -> Option<Box<dyn io::BufRead + '_>>;

/// Read this value as a list of URIs.
///
/// If this value is not readable as URIs, return `None`.
///
/// The format of the returned URIs is simply a vector of strings. No validation is done
/// to ensure that the URIs are valid or in the format
fn try_as_uris(&mut self) -> Option<Vec<String>>;

/// Read this value as a plain text string.
///
/// If this value is not readable as a string, return `None`.
fn try_as_string(&mut self) -> Option<String>;
}

impl_dyn_casting!(TypedData);

/// Metadata about a data transfer. This does not allow actually receiving data, as that is an
/// asynchronous operation. To fetch the data from the source application, see
/// [`Window::fetch_data_transfer`](crate::window::Window::fetch_data_transfer)
/// and [`WindowEvent::DataTransferResult`](crate::event::WindowEvent::DataTransferResult).
pub trait DataTransfer: AsAny + Send + Sync + fmt::Debug {
/// Display the list of all available types.
///
/// This is useful if more-complex type matching is required, but for most cases
/// [`has_type`](DataTransfer::has_type) should be used.
// TODO: We should be able to do `&dyn TransferType`, but some implementation details in
// the platforms make that unnecessarily difficult right now. Specifically, use of `RwLock`.
fn available_types(&self) -> Vec<Box<dyn TransferType>>;

/// Check if the supplied type is provided by this [`DataTransfer`].
///
/// Supplying a [`TypeHint`] as the type is supported on all platforms, but if some
/// platform-specific type is required then that platform's implementation of `TransferType` can
/// be used.
fn has_type(&self, type_: &dyn TransferType) -> bool {
let available_types = self.available_types();
type_.hint().is_some_and(|hint| {
available_types.iter().any(|haystack| haystack.hint() == Some(hint))
})
}
}

impl_dyn_casting!(DataTransfer);
46 changes: 20 additions & 26 deletions winit-core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use std::cell::LazyCell;
use std::cmp::Ordering;
use std::f64;
use std::path::PathBuf;
use std::sync::{Mutex, Weak};

use dpi::{PhysicalPosition, PhysicalSize};
Expand All @@ -11,6 +10,7 @@ use serde::{Deserialize, Serialize};
use smol_str::SmolStr;

use crate::Instant;
use crate::data_transfer::DataTransferId;
use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
Expand Down Expand Up @@ -77,41 +77,32 @@ pub enum WindowEvent {

/// A file drag operation has entered the window.
DragEntered {
/// List of paths that are being dragged onto the window.
paths: Vec<PathBuf>,
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
/// Data transfer object specifying the ID and available types.
id: DataTransferId,
},
/// A file drag operation has moved over the window.
DragMoved {
DragPosition {
/// Data transfer object specifying the ID and available types.
id: DataTransferId,
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// The file drag operation has dropped file(s) on the window.
DragDropped {
/// List of paths that are being dragged onto the window.
paths: Vec<PathBuf>,
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
/// Data transfer object specifying the ID and available types.
id: DataTransferId,
},
/// The file drag operation has been cancelled or left the window.
DragLeft {
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
///
/// ## Platform-specific
///
/// - **Windows:** Always emits [`None`].
position: Option<PhysicalPosition<f64>>,
/// Data transfer object specifying the ID and available types.
id: DataTransferId,
},

/// The result of a data transfer is available.
DataTransferResult { id: DataTransferId, serial: AsyncRequestSerial },

/// The window gained or lost focus.
///
/// The parameter is true if the window has gained focus, and false if it has lost focus.
Expand Down Expand Up @@ -1560,16 +1551,19 @@ mod tests {
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
use crate::data_transfer::DataTransferId;

let dnd_data = DataTransferId::from_raw(123);

with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(SurfaceResized((0, 0).into()));
with_window_event(DragEntered { paths: vec!["x.txt".into()], position: (0, 0).into() });
with_window_event(DragMoved { position: (0, 0).into() });
with_window_event(DragDropped { paths: vec!["x.txt".into()], position: (0, 0).into() });
with_window_event(DragLeft { position: Some((0, 0).into()) });
with_window_event(DragEntered { id: dnd_data});
with_window_event(DragPosition { id: dnd_data, position: (0, 0).into() });
with_window_event(DragDropped { id: dnd_data });
with_window_event(DragLeft { id: dnd_data });
with_window_event(Ime(Enabled));
with_window_event(PointerMoved {
device_id: None,
Expand Down
105 changes: 103 additions & 2 deletions winit-core/src/event_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod pump_events;
pub mod register;
pub mod run_on_demand;

use std::fmt::{self, Debug};
use std::fmt::{self, Debug, Display};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
Expand All @@ -13,10 +13,24 @@ use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
use crate::Instant;
use crate::as_any::AsAny;
use crate::cursor::{CustomCursor, CustomCursorSource};
use crate::error::RequestError;
use crate::data_transfer::{DataTransfer, DataTransferId, TransferType, TypedData};
use crate::error::{NotSupportedError, RequestError};
use crate::monitor::MonitorHandle;
use crate::window::{Theme, Window, WindowAttributes};

/// An operation was attempted on a data transfer ID, but that ID was invalid.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct UnknownDataTransfer(pub DataTransferId);

impl Display for UnknownDataTransfer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let id = self.0.into_raw();
write!(f, "Unknown data transfer with ID {id}")
}
}

impl std::error::Error for UnknownDataTransfer {}

pub trait ActiveEventLoop: AsAny + fmt::Debug {
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
Expand Down Expand Up @@ -114,6 +128,93 @@ pub trait ActiveEventLoop: AsAny + fmt::Debug {

/// Get the raw-window-handle handle.
fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;

/// Request to fetch a type from a [data transfer](crate::data_transfer::DataTransfer).
///
/// This may be called multiple times on the same [`DataTransferId`] with different types.
fn fetch_data_transfer(
&self,
id: DataTransferId,
type_: &dyn TransferType,
) -> Result<AsyncRequestSerial, RequestError> {
let _ = id;
let _ = type_;
Err(RequestError::NotSupported(NotSupportedError::new(
"Cross-application data transfer (e.g. drag-and-drop, clipboard) is unsupported on \
this platform",
)))
}

/// Get the resolved data for a data transfer.
///
/// Requires first calling [`fetch_data_transfer`](ActiveEventLoop::fetch_data_transfer) and
/// waiting for the corresponding `DataTransferResult` event.
fn data_transfer_result(
&self,
serial: AsyncRequestSerial,
) -> Result<Box<dyn TypedData>, RequestError> {
let _ = serial;
Err(RequestError::NotSupported(NotSupportedError::new(
"Cross-application data transfer (e.g. drag-and-drop, clipboard) is unsupported on \
this platform",
)))
}

/// Get a [data transfer](DataTransfer) by its ID.
///
/// If the ID is invalid (e.g. if the lifetime of the data transfer has expired), this will
/// return an error.
fn data_transfer(
&self,
id: DataTransferId,
) -> Result<Box<dyn DataTransfer>, UnknownDataTransfer> {
Err(UnknownDataTransfer(id))
}

/// Mark a given data transfer ID as being accepted by the window. By default, a drag will be
/// rejected.
///
/// This allows the OS/compositor to display the correct UI, indicating that the dragged data
/// can be dropped.
///
/// Note that on some platforms (e.g. Wayland), accepting a data transfer requires specifying
/// one or more accepted types. For platforms that require specifying a type, `accept_drag` will
/// mark all available types as accepted.
///
/// For the most reliable cross-platform behaviour,
/// [`accept_drag_type`](Window::accept_drag_type) is preferred.
fn accept_drag(&self, id: DataTransferId) -> Result<(), UnknownDataTransfer> {
Err(UnknownDataTransfer(id))
}

/// Mark a single type of a given data transfer ID as being accepted by the window. By default,
/// a drag will be rejected.
///
/// This allows the OS/compositor to display the correct UI, indicating that the dragged data
/// can be dropped.
///
/// If the window may accept more than one of the advertised types, this method should be
/// called multiple times, once for each of the accepted types.
fn accept_drag_type(
&self,
id: DataTransferId,
type_: &dyn TransferType,
) -> Result<(), UnknownDataTransfer> {
let _ = type_;
self.accept_drag(id)
}

/// Mark a given data transfer ID as being rejected by the window. This is the default if
/// `accept_drag`/`accept_drag_type` is never called.
///
/// This allows the OS/compositor to display the correct UI, indicating that the dragged data
/// can _not_ be dropped.
///
/// This will ensure that the OS/compositor indicates to the user that dropping the dragged data
/// is not possible.
fn reject_drag(&self, id: DataTransferId) -> Result<(), UnknownDataTransfer> {
Err(UnknownDataTransfer(id))
}
}

impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
Expand Down
Loading
Loading