Prevent EventLoop from getting initialized outside the main thread in cross-platform functions (#1186)
* Prevent EventLoop from getting initialized outside the main thread This only applies to the cross-platform functions. We expose functions to do this in a platform-specific manner, when available. * Add CHANGELOG entry * Formatting has changed since the latest stable update... * Fix error spacing * Unix: Prevent initializing EventLoop outside main thread * Updates libc dependency to 0.2.64, as required by BSD platforms * Update CHANGELOG.md for Linux implementation * Finish sentence * Consolidate documentation
This commit is contained in:
parent
af3ef52252
commit
28e3c35547
8 changed files with 224 additions and 35 deletions
|
|
@ -520,14 +520,22 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
|
|||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
assert_is_main_thread("new_any_thread");
|
||||
|
||||
EventLoop::new_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_any_thread() -> EventLoop<T> {
|
||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
||||
match env_var.as_str() {
|
||||
"x11" => {
|
||||
// TODO: propagate
|
||||
return EventLoop::new_x11().expect("Failed to initialize X11 backend");
|
||||
return EventLoop::new_x11_any_thread()
|
||||
.expect("Failed to initialize X11 backend");
|
||||
}
|
||||
"wayland" => {
|
||||
return EventLoop::new_wayland().expect("Failed to initialize Wayland backend");
|
||||
return EventLoop::new_wayland_any_thread()
|
||||
.expect("Failed to initialize Wayland backend");
|
||||
}
|
||||
_ => panic!(
|
||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
||||
|
|
@ -536,12 +544,12 @@ impl<T: 'static> EventLoop<T> {
|
|||
}
|
||||
}
|
||||
|
||||
let wayland_err = match EventLoop::new_wayland() {
|
||||
let wayland_err = match EventLoop::new_wayland_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
let x11_err = match EventLoop::new_x11() {
|
||||
let x11_err = match EventLoop::new_x11_any_thread() {
|
||||
Ok(event_loop) => return event_loop,
|
||||
Err(err) => err,
|
||||
};
|
||||
|
|
@ -554,10 +562,22 @@ impl<T: 'static> EventLoop<T> {
|
|||
}
|
||||
|
||||
pub fn new_wayland() -> Result<EventLoop<T>, ConnectError> {
|
||||
assert_is_main_thread("new_wayland_any_thread");
|
||||
|
||||
EventLoop::new_wayland_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, ConnectError> {
|
||||
wayland::EventLoop::new().map(EventLoop::Wayland)
|
||||
}
|
||||
|
||||
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
|
||||
assert_is_main_thread("new_x11_any_thread");
|
||||
|
||||
EventLoop::new_x11_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
X11_BACKEND
|
||||
.lock()
|
||||
.as_ref()
|
||||
|
|
@ -672,3 +692,35 @@ fn sticky_exit_callback<T, F>(
|
|||
// user callback
|
||||
callback(evt, target, cf)
|
||||
}
|
||||
|
||||
fn assert_is_main_thread(suggested_method: &str) {
|
||||
if !is_main_thread() {
|
||||
panic!(
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
cross-platform compatibility hazard. If you really, absolutely need to create an \
|
||||
EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.",
|
||||
suggested_method
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::{c_long, getpid, syscall, SYS_gettid};
|
||||
|
||||
unsafe { syscall(SYS_gettid) == getpid() as c_long }
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::pthread_main_np;
|
||||
|
||||
unsafe { pthread_main_np() == 1 }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
fn is_main_thread() -> bool {
|
||||
use libc::_lwp_self;
|
||||
|
||||
unsafe { _lwp_self() == 1 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,10 +43,7 @@ lazy_static! {
|
|||
get_function!("user32.dll", EnableNonClientDpiScaling);
|
||||
}
|
||||
|
||||
pub fn become_dpi_aware(enable: bool) {
|
||||
if !enable {
|
||||
return;
|
||||
}
|
||||
pub fn become_dpi_aware() {
|
||||
static ENABLE_DPI_AWARENESS: Once = Once::new();
|
||||
ENABLE_DPI_AWARENESS.call_once(|| {
|
||||
unsafe {
|
||||
|
|
|
|||
|
|
@ -132,18 +132,40 @@ pub struct EventLoopWindowTarget<T> {
|
|||
pub(crate) runner_shared: EventLoopRunnerShared<T>,
|
||||
}
|
||||
|
||||
macro_rules! main_thread_check {
|
||||
($fn_name:literal) => {{
|
||||
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
|
||||
if thread_id != main_thread_id() {
|
||||
panic!(concat!(
|
||||
"Initializing the event loop outside of the main thread is a significant \
|
||||
cross-platform compatibility hazard. If you really, absolutely need to create an \
|
||||
EventLoop on a different thread, please use the `EventLoopExtWindows::",
|
||||
$fn_name,
|
||||
"` function."
|
||||
));
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> EventLoop<T> {
|
||||
Self::with_dpi_awareness(true)
|
||||
main_thread_check!("new_any_thread");
|
||||
|
||||
Self::new_any_thread()
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &RootELW<T> {
|
||||
&self.window_target
|
||||
pub fn new_any_thread() -> EventLoop<T> {
|
||||
become_dpi_aware();
|
||||
Self::new_dpi_unaware_any_thread()
|
||||
}
|
||||
|
||||
pub fn with_dpi_awareness(dpi_aware: bool) -> EventLoop<T> {
|
||||
become_dpi_aware(dpi_aware);
|
||||
pub fn new_dpi_unaware() -> EventLoop<T> {
|
||||
main_thread_check!("new_dpi_unaware_any_thread");
|
||||
|
||||
Self::new_dpi_unaware_any_thread()
|
||||
}
|
||||
|
||||
pub fn new_dpi_unaware_any_thread() -> EventLoop<T> {
|
||||
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
|
||||
let runner_shared = Rc::new(ELRShared {
|
||||
runner: RefCell::new(None),
|
||||
|
|
@ -166,6 +188,10 @@ impl<T: 'static> EventLoop<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &RootELW<T> {
|
||||
&self.window_target
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, event_handler: F) -> !
|
||||
where
|
||||
F: 'static + FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
|
|
@ -178,10 +204,6 @@ impl<T: 'static> EventLoop<T> {
|
|||
where
|
||||
F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
|
||||
{
|
||||
unsafe {
|
||||
winuser::IsGUIThread(1);
|
||||
}
|
||||
|
||||
let event_loop_windows_ref = &self.window_target;
|
||||
|
||||
let mut runner = unsafe {
|
||||
|
|
@ -280,6 +302,21 @@ impl<T> EventLoopWindowTarget<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn main_thread_id() -> DWORD {
|
||||
static mut MAIN_THREAD_ID: DWORD = 0;
|
||||
#[used]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[link_section = ".CRT$XCU"]
|
||||
static INIT_MAIN_THREAD_ID: unsafe fn() = {
|
||||
unsafe fn initer() {
|
||||
MAIN_THREAD_ID = processthreadsapi::GetCurrentThreadId();
|
||||
}
|
||||
initer
|
||||
};
|
||||
|
||||
unsafe { MAIN_THREAD_ID }
|
||||
}
|
||||
|
||||
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
|
||||
pub(crate) struct ELRShared<T> {
|
||||
runner: RefCell<Option<EventLoopRunner<T>>>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue