(
- rect: Rect,
- scale: f64,
-) -> PhysicalRect {
- PhysicalRect {
- position: rect.position.to_physical(scale),
- size: rect.size.to_physical(scale),
- }
+pub(crate) fn rect_to_physical(rect: Rect, scale: f64) -> PhysicalRect {
+ PhysicalRect {
+ position: rect.position.to_physical(scale),
+ size: rect.size.to_physical(scale),
+ }
}
#[cfg(target_os = "macos")]
-pub(crate) fn rect_to_logical(
- rect: Rect,
- scale: f64,
-) -> LogicalRect {
- LogicalRect {
- position: rect.position.to_logical(scale),
- size: rect.size.to_logical(scale),
- }
+pub(crate) fn rect_to_logical(rect: Rect, scale: f64) -> LogicalRect {
+ LogicalRect {
+ position: rect.position.to_logical(scale),
+ size: rect.size.to_logical(scale),
+ }
}
// feat/cef adds Icon::into_owned.
pub(crate) fn icon_into_owned(icon: Icon<'_>) -> Icon<'static> {
- Icon {
- rgba: Cow::Owned(icon.rgba.into_owned()),
- width: icon.width,
- height: icon.height,
- }
+ Icon {
+ rgba: Cow::Owned(icon.rgba.into_owned()),
+ width: icon.width,
+ height: icon.height,
+ }
}
diff --git a/src/config.rs b/src/config.rs
index 9d2d56d..94aee13 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -27,41 +27,41 @@ use std::sync::OnceLock;
/// process) and at custom-scheme registration (all processes).
#[derive(Debug, Clone)]
pub struct CefConfig {
- /// Application identifier; the CEF disk cache defaults to
- /// `{user cache dir}/{identifier}/cef`. Falls back to the executable name.
- pub identifier: String,
- /// Extra Chromium command line switches, `(name, optional value)`.
- pub command_line_args: Vec<(String, Option)>,
- /// Overrides the CEF disk cache directory (`Settings::cache_path`).
- pub cache_path: Option,
- /// Deep link schemes to register as protocol handlers.
- pub deep_link_schemes: Vec,
- /// Tauri custom protocol schemes to register with Chromium as standard,
- /// secure, CORS- and fetch-enabled schemes so published tauri's native URL
- /// forms (`tauri://localhost`, `fetch("ipc://localhost/…")`) work under
- /// CEF. Must cover every scheme the app or its plugins register.
- pub custom_schemes: Vec,
- /// Custom schemes whose origins get a cookie jar
- /// (`cef::Settings::cookieable_schemes_list`). CEF cookies only http/https
- /// by default, so `document.cookie` and `Set-Cookie` are inert on custom
- /// schemes not listed here. The http/https defaults are preserved.
- pub cookieable_schemes: Vec,
+ /// Application identifier; the CEF disk cache defaults to
+ /// `{user cache dir}/{identifier}/cef`. Falls back to the executable name.
+ pub identifier: String,
+ /// Extra Chromium command line switches, `(name, optional value)`.
+ pub command_line_args: Vec<(String, Option)>,
+ /// Overrides the CEF disk cache directory (`Settings::cache_path`).
+ pub cache_path: Option,
+ /// Deep link schemes to register as protocol handlers.
+ pub deep_link_schemes: Vec,
+ /// Tauri custom protocol schemes to register with Chromium as standard,
+ /// secure, CORS- and fetch-enabled schemes so published tauri's native URL
+ /// forms (`tauri://localhost`, `fetch("ipc://localhost/…")`) work under
+ /// CEF. Must cover every scheme the app or its plugins register.
+ pub custom_schemes: Vec,
+ /// Custom schemes whose origins get a cookie jar
+ /// (`cef::Settings::cookieable_schemes_list`). CEF cookies only http/https
+ /// by default, so `document.cookie` and `Set-Cookie` are inert on custom
+ /// schemes not listed here. The http/https defaults are preserved.
+ pub cookieable_schemes: Vec,
}
impl Default for CefConfig {
- fn default() -> Self {
- Self {
- identifier: std::env::current_exe()
- .ok()
- .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
- .unwrap_or_else(|| "tauri-app".into()),
- command_line_args: Vec::new(),
- cache_path: None,
- deep_link_schemes: Vec::new(),
- custom_schemes: vec!["tauri".into(), "ipc".into(), "asset".into()],
- cookieable_schemes: Vec::new(),
+ fn default() -> Self {
+ Self {
+ identifier: std::env::current_exe()
+ .ok()
+ .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
+ .unwrap_or_else(|| "tauri-app".into()),
+ command_line_args: Vec::new(),
+ cache_path: None,
+ deep_link_schemes: Vec::new(),
+ custom_schemes: vec!["tauri".into(), "ipc".into(), "asset".into()],
+ cookieable_schemes: Vec::new(),
+ }
}
- }
}
static CONFIG: OnceLock = OnceLock::new();
@@ -70,9 +70,9 @@ static CONFIG: OnceLock = OnceLock::new();
/// `main`, before [`crate::run_cef_helper_process`] and before building the
/// tauri app. Later calls are ignored.
pub fn configure(config: CefConfig) {
- let _ = CONFIG.set(config);
+ let _ = CONFIG.set(config);
}
pub(crate) fn config() -> &'static CefConfig {
- CONFIG.get_or_init(CefConfig::default)
+ CONFIG.get_or_init(CefConfig::default)
}
diff --git a/src/external_message_pump/linux.rs b/src/external_message_pump/linux.rs
index dd4e2df..09d57fa 100644
--- a/src/external_message_pump/linux.rs
+++ b/src/external_message_pump/linux.rs
@@ -4,93 +4,295 @@
//! Linux/BSD backend for the CEF external message pump.
//!
-//! cefclient owns the GLib loop and drives a GLib timeout from it
-//! (`main_message_loop_external_pump_linux.cc`). Here winit owns an X11 loop
-//! instead, so the timeout is attached to the default GLib main context and the
-//! winit loop services that context every iteration (see the runtime's
-//! `service_glib`), waking at [`PlatformPump::deadline`] via
-//! `ControlFlow::WaitUntil`. The timeout still fires inside nested GLib loops
-//! (e.g. GTK menus/dialogs) that winit cannot observe, keeping CEF pumping
-//! there — the same property the Windows/macOS backends get from `WM_TIMER` /
-//! `NSTimer`.
+//! Upstream cefclient:
+//!
//!
-//! Reference:
-//!
+//! Mirrors cefclient's `main_message_loop_external_pump_linux.cc`: a custom
+//! low-priority `GSource` is attached to the default GLib context, delayed work
+//! is tracked as an absolute deadline, and cross-thread scheduling wakes the
+//! GLib poll through a pipe.
-use std::sync::Weak;
-use std::time::{Duration, Instant};
+use std::{
+ mem,
+ os::raw::{c_int, c_uint},
+ sync::Weak,
+ time::{Duration, Instant},
+};
-use gtk::glib;
-use winit::event_loop::EventLoopProxy;
+use gtk::glib::{self, ffi, translate::ToGlibPtr};
use super::PumpState;
+#[repr(C)]
+struct WorkSource {
+ source: ffi::GSource,
+ source_state: *mut SourceState,
+}
+
+struct SourceState {
+ state: Weak,
+
+ // The time when we need to do delayed work.
+ delayed_work_time: Option,
+
+ // We use a wakeup pipe to make sure we'll get out of the glib polling phase
+ // when another thread has scheduled us to do some work. There is a glib
+ // mechanism g_main_context_wakeup, but this won't guarantee that our event's
+ // Dispatch() will be called.
+ wakeup_pipe_read: c_int,
+ wakeup_pipe_write: c_int,
+
+ // Boxed to keep the GPollFD address stable while it is registered with the
+ // work source.
+ wakeup_gpollfd: Box,
+}
+
pub(super) struct PlatformPump {
- state: Weak,
- proxy: EventLoopProxy,
- timer: Option,
- deadline: Option,
+ // The work source. It is destroyed when the message pump is destroyed.
+ work_source: *mut ffi::GSource,
+ source_state: Box,
}
+// SAFETY: `on_schedule_message_pump_work` is the only method called from other
+// threads, and it only writes to the wakeup pipe. GLib source/timer state is
+// mutated on the owner thread when GLib dispatches the attached source.
+unsafe impl Send for PlatformPump {}
+
impl PlatformPump {
- pub(super) fn new(state: Weak, proxy: EventLoopProxy) -> Self {
- Self {
- state,
- proxy,
- timer: None,
- deadline: None,
+ pub(super) fn new(state: Weak) -> Self {
+ // The runtime services callbacks from GLib's default MainContext.
+ let context = glib::MainContext::default();
+
+ // Create our wakeup pipe, which is used to flag when work was scheduled.
+ let mut fds = [0; 2];
+ let ret = unsafe { libc::pipe(fds.as_mut_ptr()) };
+ assert_eq!(ret, 0, "failed to create CEF message pump wakeup pipe");
+
+ let mut source_state = Box::new(SourceState {
+ state,
+ delayed_work_time: None,
+ wakeup_pipe_read: fds[0],
+ wakeup_pipe_write: fds[1],
+ wakeup_gpollfd: Box::new(ffi::GPollFD {
+ fd: fds[0],
+ events: ffi::G_IO_IN as _,
+ revents: 0,
+ }),
+ });
+
+ let work_source = unsafe {
+ let source = ffi::g_source_new(
+ &raw mut WORK_SOURCE_FUNCS,
+ mem::size_of::() as c_uint,
+ );
+ assert!(
+ !source.is_null(),
+ "failed to create CEF message pump GSource"
+ );
+
+ (*(source as *mut WorkSource)).source_state = &mut *source_state;
+ ffi::g_source_add_poll(source, &mut *source_state.wakeup_gpollfd);
+ // Use a low priority so that we let other events in the queue go first.
+ ffi::g_source_set_priority(source, ffi::G_PRIORITY_DEFAULT_IDLE);
+ // This is needed to allow Run calls inside Dispatch.
+ ffi::g_source_set_can_recurse(source, ffi::GTRUE);
+ ffi::g_source_attach(source, context.to_glib_none().0);
+ source
+ };
+
+ Self {
+ work_source,
+ source_state,
+ }
+ }
+
+ pub(super) fn on_schedule_message_pump_work(&mut self, delay_ms: i64) {
+ // This can be called on any thread, so we don't want to touch any state
+ // variables as we would then need locks all over. This ensures that if we
+ // are sleeping in a poll that we will wake up.
+ let written = retry_eintr(|| unsafe {
+ libc::write(
+ self.source_state.wakeup_pipe_write,
+ (&delay_ms as *const i64).cast(),
+ mem::size_of::(),
+ )
+ });
+ if written != mem::size_of::() as isize {
+ log::error!("could not write to the CEF message pump wakeup pipe");
+ }
+ }
+
+ pub(super) fn set_timer(&mut self, delay_ms: i64) {
+ debug_assert!(delay_ms > 0);
+
+ let now = Instant::now();
+ self.source_state.delayed_work_time = Some(now + Duration::from_millis(delay_ms as u64));
+ }
+
+ pub(super) fn kill_timer(&mut self) {
+ self.source_state.delayed_work_time = None;
+ }
+
+ pub(super) fn is_timer_pending(&self) -> bool {
+ get_time_interval_milliseconds(self.source_state.delayed_work_time) > 0
}
- }
-
- pub(super) fn post_schedule_work(&mut self, delay_ms: i64) {
- // May be called on any thread. Marshal the request onto the default GLib
- // main context, which the winit loop services on the main thread; wake winit
- // so it does so promptly.
- let state = self.state.clone();
- glib::idle_add_once(move || {
- if let Some(state) = state.upgrade() {
- state.on_schedule_work(delay_ms);
- }
- });
- self.proxy.wake_up();
- }
-
- pub(super) fn set_timer(&mut self, delay_ms: i64) {
- debug_assert!(self.timer.is_none());
- debug_assert!(delay_ms > 0);
-
- let delay = Duration::from_millis(delay_ms as u64);
- let state = self.state.clone();
- let source = glib::timeout_add_once(delay, move || {
- let Some(state) = state.upgrade() else {
- return;
- };
- // This one-shot source removes itself after firing, so forget it before
- // `on_timer_timeout` runs `kill_timer` — `SourceId::remove` would panic on
- // an already-removed source.
- if let Ok(mut platform) = state.platform.lock() {
- platform.timer = None;
- platform.deadline = None;
- }
- state.on_timer_timeout();
- });
-
- self.timer = Some(source);
- self.deadline = Some(Instant::now() + delay);
- }
-
- pub(super) fn kill_timer(&mut self) {
- if let Some(source) = self.timer.take() {
- source.remove();
+
+ pub(super) fn deadline(&self) -> Option {
+ self.source_state.delayed_work_time
}
- self.deadline = None;
- }
+}
+
+impl Drop for PlatformPump {
+ fn drop(&mut self) {
+ unsafe {
+ ffi::g_source_destroy(self.work_source);
+ ffi::g_source_unref(self.work_source);
+ libc::close(self.source_state.wakeup_pipe_read);
+ libc::close(self.source_state.wakeup_pipe_write);
+ }
+ }
+}
+
+// Return a timeout suitable for the glib loop, -1 to block forever,
+// 0 to return right away, or a timeout in milliseconds from now.
+fn get_time_interval_milliseconds(from: Option) -> c_int {
+ let Some(from) = from else {
+ return -1;
+ };
- pub(super) fn is_timer_pending(&self) -> bool {
- self.timer.is_some()
- }
+ // Be careful here. Instant has finer precision than milliseconds, but GLib
+ // wants a value in milliseconds. If there are 5.5ms left, should the delay be
+ // 5 or 6? It should be 6 to avoid executing delayed work too early.
+ let now = Instant::now();
+ let delay = from
+ .checked_duration_since(now)
+ .map(|duration| (duration.as_secs_f64() * 1000.0).ceil() as c_int)
+ .unwrap_or(-1);
- pub(super) fn deadline(&self) -> Option {
- self.deadline
- }
+ // If this value is negative, then we need to run delayed work soon.
+ if delay < 0 { 0 } else { delay }
}
+
+// From base/posix/eintr_wrapper.h.
+// This provides a wrapper around system calls which may be interrupted by a
+// signal and return EINTR. See man 7 signal.
+fn retry_eintr(mut f: F) -> isize
+where
+ F: FnMut() -> isize,
+{
+ loop {
+ let result = f();
+ if result != -1 || std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR) {
+ return result;
+ }
+ }
+}
+
+unsafe fn source_state(source: *mut ffi::GSource) -> *mut SourceState {
+ unsafe { (*(source as *mut WorkSource)).source_state }
+}
+
+// Return the timeout we want passed to poll.
+unsafe fn handle_prepare(source_state: *mut SourceState) -> c_int {
+ // We don't think we have work to do, but make sure not to block longer than
+ // the next time we need to run delayed work.
+ let delayed_work_time = unsafe { (*source_state).delayed_work_time };
+ get_time_interval_milliseconds(delayed_work_time)
+}
+
+unsafe fn handle_check(source_state: *mut SourceState) -> bool {
+ // We usually have a single message on the wakeup pipe, since we are only
+ // signaled when the queue went from empty to non-empty, but there can be
+ // two messages if a task posted a task, hence we read at most two bytes.
+ // The glib poll will tell us whether there was data, so this read shouldn't
+ // block.
+ let have_wakeup = {
+ let wakeup_gpollfd = unsafe { &*(*source_state).wakeup_gpollfd };
+ (wakeup_gpollfd.revents & ffi::G_IO_IN as u16) != 0
+ };
+ if have_wakeup {
+ let mut delay_ms = [0_i64; 2];
+ let num_bytes = retry_eintr(|| unsafe {
+ libc::read(
+ (*source_state).wakeup_pipe_read,
+ delay_ms.as_mut_ptr().cast(),
+ mem::size_of::() * 2,
+ )
+ });
+
+ if num_bytes < mem::size_of::() as isize {
+ log::error!("error reading from the CEF message pump wakeup pipe");
+ }
+ if num_bytes == mem::size_of::() as isize {
+ if let Some(state) = unsafe { (*source_state).state.upgrade() } {
+ state.on_schedule_work(delay_ms[0]);
+ }
+ }
+ if num_bytes == (mem::size_of::() * 2) as isize {
+ if let Some(state) = unsafe { (*source_state).state.upgrade() } {
+ state.on_schedule_work(delay_ms[1]);
+ }
+ }
+ }
+
+ let delayed_work_time = unsafe { (*source_state).delayed_work_time };
+ if get_time_interval_milliseconds(delayed_work_time) == 0 {
+ // The timer has expired. That condition will stay true until we process
+ // that delayed work, so we don't need to record this differently.
+ return true;
+ }
+
+ false
+}
+
+unsafe fn handle_dispatch(source_state: *mut SourceState) {
+ if let Some(state) = unsafe { (*source_state).state.upgrade() } {
+ state.on_timer_timeout();
+ }
+}
+
+unsafe extern "C" fn work_source_prepare(
+ source: *mut ffi::GSource,
+ timeout_ms: *mut c_int,
+) -> ffi::gboolean {
+ if !timeout_ms.is_null() {
+ let source_state = unsafe { source_state(source) };
+ unsafe { *timeout_ms = handle_prepare(source_state) };
+ }
+
+ // We always return FALSE, so that our timeout is honored. If we were
+ // to return TRUE, the timeout would be considered to be 0 and the poll
+ // would never block. Once the poll is finished, Check will be called.
+ ffi::GFALSE
+}
+
+unsafe extern "C" fn work_source_check(source: *mut ffi::GSource) -> ffi::gboolean {
+ let source_state = unsafe { source_state(source) };
+ // Only return TRUE if Dispatch should be called.
+ if unsafe { handle_check(source_state) } {
+ ffi::GTRUE
+ } else {
+ ffi::GFALSE
+ }
+}
+
+unsafe extern "C" fn work_source_dispatch(
+ source: *mut ffi::GSource,
+ _callback: ffi::GSourceFunc,
+ _user_data: ffi::gpointer,
+) -> ffi::gboolean {
+ let source_state = unsafe { source_state(source) };
+ unsafe { handle_dispatch(source_state) };
+ // Always return TRUE so our source stays registered.
+ ffi::GTRUE
+}
+
+// I wish these could be const, but g_source_new wants non-const.
+static mut WORK_SOURCE_FUNCS: ffi::GSourceFuncs = ffi::GSourceFuncs {
+ prepare: Some(work_source_prepare),
+ check: Some(work_source_check),
+ dispatch: Some(work_source_dispatch),
+ finalize: None,
+ closure_callback: None,
+ closure_marshal: None,
+};
diff --git a/src/external_message_pump/macos.rs b/src/external_message_pump/macos.rs
index fdc7376..7c168d1 100644
--- a/src/external_message_pump/macos.rs
+++ b/src/external_message_pump/macos.rs
@@ -4,27 +4,27 @@
//! macOS backend for the CEF external message pump.
//!
+//! Upstream cefclient:
+//!
+//!
//! Mirrors cefclient's `main_message_loop_external_pump_mac.mm`: scheduling
//! requests are posted back onto the owning AppKit thread with
//! `performSelector:onThread:`, and delayed work is driven by an `NSTimer`
//! installed in the common and event-tracking run-loop modes so it keeps firing
//! while AppKit spins a nested menu/tracking loop (e.g. a webview context menu)
//! that winit's callbacks never observe.
-//!
-//! Reference:
-//!
-
use std::sync::Weak;
use objc2::{AnyThread, DefinedClass, define_class, msg_send, rc::Retained, sel};
use objc2_app_kit::NSEventTrackingRunLoopMode;
use objc2_foundation::{
- NSNumber, NSObject, NSObjectNSThreadPerformAdditions, NSObjectProtocol, NSRunLoop,
- NSRunLoopCommonModes, NSThread, NSTimer,
+ NSNumber, NSObject, NSObjectNSThreadPerformAdditions, NSObjectProtocol, NSRunLoop,
+ NSRunLoopCommonModes, NSThread, NSTimer,
};
use super::PumpState;
+// Object that handles event callbacks on the owner thread.
define_class! {
#[unsafe(super(NSObject))]
#[ivars = Weak]
@@ -32,7 +32,7 @@ define_class! {
impl EventHandler {
#[unsafe(method(scheduleWork:))]
- fn schedule_work(&self, delay_ms: &NSNumber) {
+ fn handle_schedule_work(&self, delay_ms: &NSNumber) {
let Some(state) = self.ivars().upgrade() else {
return;
};
@@ -40,7 +40,7 @@ define_class! {
}
#[unsafe(method(timerTimeout:))]
- fn timer_timeout(&self, _: &NSTimer) {
+ fn handle_timer_timeout(&self, _: &NSTimer) {
let Some(state) = self.ivars().upgrade() else {
return;
};
@@ -52,76 +52,82 @@ define_class! {
}
impl EventHandler {
- fn new(state: Weak) -> Retained {
- let this = Self::alloc().set_ivars(state);
- unsafe { msg_send![super(this), init] }
- }
+ fn new(state: Weak) -> Retained {
+ let this = Self::alloc().set_ivars(state);
+ unsafe { msg_send![super(this), init] }
+ }
}
pub(super) struct PlatformPump {
- owner_thread: Retained,
- event_handler: Retained,
- timer: Option>,
+ // Owner thread that will run events.
+ owner_thread: Retained,
+
+ // Used to handle event callbacks on the owner thread.
+ event_handler: Retained,
+
+ // Pending work timer.
+ timer: Option>,
}
// SAFETY: the owner thread and timer are only touched on the AppKit thread that
-// constructed the pump; `post_schedule_work` marshals back to it before use.
+// constructed the pump; `on_schedule_message_pump_work` marshals back to it
+// before use.
unsafe impl Send for PlatformPump {}
impl PlatformPump {
- pub(super) fn new(state: Weak) -> Self {
- Self {
- owner_thread: NSThread::currentThread(),
- event_handler: EventHandler::new(state),
- timer: None,
+ pub(super) fn new(state: Weak) -> Self {
+ Self {
+ owner_thread: NSThread::currentThread(),
+ event_handler: EventHandler::new(state),
+ timer: None,
+ }
}
- }
- pub(super) fn post_schedule_work(&mut self, delay_ms: i64) {
- let delay_ms = isize::try_from(delay_ms).unwrap_or(isize::MAX);
- let delay_ms = NSNumber::new_isize(delay_ms);
- unsafe {
- self
- .event_handler
- .performSelector_onThread_withObject_waitUntilDone(
- sel!(scheduleWork:),
- &self.owner_thread,
- Some(&delay_ms),
- false,
- );
+ pub(super) fn on_schedule_message_pump_work(&mut self, delay_ms: i64) {
+ // This method may be called on any thread.
+ let delay_ms = NSNumber::new_i32(delay_ms as i32);
+ unsafe {
+ self.event_handler
+ .performSelector_onThread_withObject_waitUntilDone(
+ sel!(scheduleWork:),
+ &self.owner_thread,
+ Some(&delay_ms),
+ false,
+ );
+ }
}
- }
- pub(super) fn set_timer(&mut self, delay_ms: i64) {
- debug_assert!(delay_ms > 0);
- debug_assert!(self.timer.is_none());
-
- let timer = unsafe {
- NSTimer::timerWithTimeInterval_target_selector_userInfo_repeats(
- delay_ms as f64 / 1000.0,
- &self.event_handler,
- sel!(timerTimeout:),
- None,
- false,
- )
- };
-
- let run_loop = NSRunLoop::currentRunLoop();
- unsafe {
- run_loop.addTimer_forMode(&timer, NSRunLoopCommonModes);
- run_loop.addTimer_forMode(&timer, NSEventTrackingRunLoopMode);
+ pub(super) fn set_timer(&mut self, delay_ms: i64) {
+ debug_assert!(delay_ms > 0);
+ debug_assert!(self.timer.is_none());
+
+ let timer = unsafe {
+ NSTimer::timerWithTimeInterval_target_selector_userInfo_repeats(
+ delay_ms as f64 / 1000.0,
+ &self.event_handler,
+ sel!(timerTimeout:),
+ None,
+ false,
+ )
+ };
+
+ // Add the timer to default and tracking runloop modes.
+ let run_loop = NSRunLoop::currentRunLoop();
+ unsafe {
+ run_loop.addTimer_forMode(&timer, NSRunLoopCommonModes);
+ run_loop.addTimer_forMode(&timer, NSEventTrackingRunLoopMode);
+ }
+
+ self.timer = Some(timer);
}
- self.timer = Some(timer);
- }
-
- pub(super) fn kill_timer(&mut self) {
- if let Some(timer) = self.timer.take() {
- timer.invalidate();
+ pub(super) fn kill_timer(&mut self) {
+ if let Some(timer) = self.timer.take() {
+ timer.invalidate();
+ }
}
- }
- pub(super) fn is_timer_pending(&self) -> bool {
- self.timer.is_some()
- }
+ pub(super) fn is_timer_pending(&self) -> bool {
+ self.timer.is_some()
+ }
}
diff --git a/src/external_message_pump/mod.rs b/src/external_message_pump/mod.rs
index 5140d2e..870ca40 100644
--- a/src/external_message_pump/mod.rs
+++ b/src/external_message_pump/mod.rs
@@ -9,6 +9,10 @@
//! [`cef::do_message_loop_work`] by invoking `OnScheduleMessagePumpWork(delay)`
//! whenever it has work pending.
//!
+//! Upstream cefclient:
+//! -
+//! -
+//!
//! This is a port of upstream cefclient's external pump — same semantics and
//! logic, adapted to Rust. The platform-independent scheduling/reentrancy logic
//! lives here; each platform supplies a [`PlatformPump`] backend that drives a
@@ -16,29 +20,25 @@
//!
//! - Windows: a `WM_TIMER` on a message-only window.
//! - macOS: an `NSTimer` in the common and event-tracking run-loop modes.
-//! - Linux/BSD: a GLib timeout serviced by the winit loop.
+//! - Linux/BSD: a custom low-priority GLib source with a wakeup pipe.
//!
//! On Windows and macOS the timer lives on the same native loop winit already
//! runs, so CEF keeps painting and processing IPC even while the OS spins a
//! nested modal loop winit cannot observe (window move/resize on Windows, menu
-//! and event tracking on macOS). On Linux/BSD the GLib timeout still fires
+//! and event tracking on macOS). On Linux/BSD the GLib source still dispatches
//! inside nested GLib loops (e.g. GTK menus/dialogs) for the same reason.
-//!
-//! Reference implementation (cefclient base class):
-//! -
-//! -
use std::sync::{
- Arc, Mutex,
- atomic::{AtomicBool, Ordering},
+ Arc, Mutex,
+ atomic::{AtomicBool, Ordering},
};
#[cfg(any(
- target_os = "linux",
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
))]
mod linux;
#[cfg(target_os = "macos")]
@@ -47,11 +47,11 @@ mod macos;
mod windows;
#[cfg(any(
- target_os = "linux",
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
))]
use linux::PlatformPump;
#[cfg(target_os = "macos")]
@@ -59,173 +59,153 @@ use macos::PlatformPump;
#[cfg(windows)]
use windows::PlatformPump;
-#[cfg(any(
- target_os = "linux",
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"
-))]
-use winit::event_loop::EventLoopProxy;
+// Special timer delay placeholder value. Intentionally 32-bit for Windows and
+// OS X platform API compatibility.
+const K_TIMER_DELAY_PLACEHOLDER: i64 = i32::MAX as i64;
-/// Sentinel delay used to (re)arm the fallback "max delay" tick. Kept within 32
-/// bits for Win32/AppKit timer API compatibility, matching cefclient's
-/// `kTimerDelayPlaceholder`.
-const TIMER_DELAY_PLACEHOLDER: i64 = i32::MAX as i64;
-
-/// Upper bound on how long we wait between [`cef::do_message_loop_work`] calls
-/// (~30fps), matching cefclient's `kMaxTimerDelay`.
-const MAX_TIMER_DELAY_MS: i64 = 1000 / 30;
+// The maximum number of milliseconds we're willing to wait between calls to
+// DoWork().
+const K_MAX_TIMER_DELAY: i64 = 1000 / 30; // 30fps
/// Handle to the external message pump. Cloning shares the same underlying
/// state; the backing platform resources are released when the last clone drops.
#[derive(Clone)]
pub(crate) struct CefExternalPump {
- state: Arc,
+ state: Arc,
}
impl CefExternalPump {
- pub(crate) fn new(
+ pub(crate) fn new() -> Self {
+ let state = Arc::new_cyclic(|weak| PumpState {
+ is_active: AtomicBool::new(false),
+ reentrancy_detected: AtomicBool::new(false),
+ platform: Mutex::new(PlatformPump::new(weak.clone())),
+ });
+
+ Self { state }
+ }
+
+ /// Called from CEF's `OnScheduleMessagePumpWork`. May run on any thread.
+ pub(crate) fn on_schedule_message_pump_work(&self, delay_ms: i64) {
+ self.state.on_schedule_message_pump_work(delay_ms);
+ }
+
+ /// Explicit tick, used to drive CEF before winit's loop is running (startup)
+ /// and after winit processes a batch of events. Must run on the owner thread.
+ pub(crate) fn do_work(&self) {
+ self.state.do_work();
+ }
+
+ /// When the platform timer is next due. This is only needed by event loops
+ /// that do not block in the GLib main context themselves.
#[cfg(any(
- target_os = "linux",
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
))]
- proxy: EventLoopProxy,
- ) -> Self {
- let state = Arc::new_cyclic(|weak| PumpState {
- is_active: AtomicBool::new(false),
- reentrancy_detected: AtomicBool::new(false),
- platform: Mutex::new(PlatformPump::new(
- weak.clone(),
- #[cfg(any(
- target_os = "linux",
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"
- ))]
- proxy,
- )),
- });
-
- Self { state }
- }
-
- /// Called from CEF's `OnScheduleMessagePumpWork`. May run on any thread.
- pub(crate) fn schedule_message_pump_work(&self, delay_ms: i64) {
- self.state.schedule_message_pump_work(delay_ms);
- }
-
- /// Explicit tick, used to drive CEF before winit's loop is running (startup)
- /// and after winit processes a batch of events. Must run on the owner thread.
- pub(crate) fn do_message_loop_work(&self) {
- self.state.do_work();
- }
-
- /// When the GLib timer is next due, so the winit loop can wake to service
- /// GLib (see [`linux`]).
- #[cfg(any(
- target_os = "linux",
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"
- ))]
- pub(crate) fn next_deadline(&self) -> Option {
- self.state.platform.lock().ok().and_then(|p| p.deadline())
- }
+ pub(crate) fn next_deadline(&self) -> Option {
+ self.state.platform.lock().ok().and_then(|p| p.deadline())
+ }
}
/// Platform-independent pump state, shared with the [`PlatformPump`] backend.
struct PumpState {
- is_active: AtomicBool,
- reentrancy_detected: AtomicBool,
- platform: Mutex,
+ is_active: AtomicBool,
+ reentrancy_detected: AtomicBool,
+ platform: Mutex,
}
impl PumpState {
- /// Post a scheduling request onto the owner thread. The platform backend is
- /// responsible for delivering it there, where it lands back in
- /// [`Self::on_schedule_work`]. Mirrors the platform `OnScheduleMessagePumpWork`.
- fn schedule_message_pump_work(&self, delay_ms: i64) {
- if let Ok(mut platform) = self.platform.lock() {
- platform.post_schedule_work(delay_ms);
- }
- }
-
- /// Runs on the owner thread once a scheduling request is delivered. Mirrors
- /// cefclient's `OnScheduleWork`.
- fn on_schedule_work(&self, delay_ms: i64) {
- {
- let Ok(mut platform) = self.platform.lock() else {
- return;
- };
-
- // An already-pending timer covers the fallback tick; don't reset it.
- if delay_ms == TIMER_DELAY_PLACEHOLDER && platform.is_timer_pending() {
- return;
- }
-
- platform.kill_timer();
+ /// Post a scheduling request onto the owner thread. The platform backend is
+ /// responsible for delivering it there, where it lands back in
+ /// [`Self::on_schedule_work`]. Mirrors the platform `OnScheduleMessagePumpWork`.
+ fn on_schedule_message_pump_work(&self, delay_ms: i64) {
+ if let Ok(mut platform) = self.platform.lock() {
+ platform.on_schedule_message_pump_work(delay_ms);
+ }
}
- if delay_ms <= 0 {
- self.do_work();
- return;
+ /// Runs on the owner thread once a scheduling request is delivered. Mirrors
+ /// cefclient's `OnScheduleWork`.
+ fn on_schedule_work(&self, mut delay_ms: i64) {
+ {
+ let Ok(mut platform) = self.platform.lock() else {
+ return;
+ };
+
+ if delay_ms == K_TIMER_DELAY_PLACEHOLDER && platform.is_timer_pending() {
+ // Don't set the maximum timer requested from DoWork() if a timer event is
+ // currently pending.
+ return;
+ }
+
+ platform.kill_timer();
+ }
+
+ if delay_ms <= 0 {
+ // Execute the work immediately.
+ self.do_work();
+ } else if let Ok(mut platform) = self.platform.lock() {
+ // Never wait longer than the maximum allowed time.
+ if delay_ms > K_MAX_TIMER_DELAY {
+ delay_ms = K_MAX_TIMER_DELAY;
+ }
+
+ // Results in call to OnTimerTimeout() after the specified delay.
+ platform.set_timer(delay_ms);
+ }
}
- if let Ok(mut platform) = self.platform.lock() {
- platform.set_timer(delay_ms.min(MAX_TIMER_DELAY_MS));
+ /// Runs on the owner thread when the platform timer fires. Mirrors cefclient's
+ /// `OnTimerTimeout`.
+ fn on_timer_timeout(&self) {
+ if let Ok(mut platform) = self.platform.lock() {
+ platform.kill_timer();
+ }
+ self.do_work();
}
- }
- /// Runs on the owner thread when the platform timer fires. Mirrors cefclient's
- /// `OnTimerTimeout`.
- fn on_timer_timeout(&self) {
- if let Ok(mut platform) = self.platform.lock() {
- platform.kill_timer();
- }
- self.do_work();
- }
-
- /// Mirrors cefclient's `DoWork`.
- fn do_work(&self) {
- let was_reentrant = self.perform_message_loop_work();
- if was_reentrant {
- // The work was discarded because we were already inside
- // do_message_loop_work; repost so it runs on the next clean turn.
- self.schedule_message_pump_work(0);
- return;
+ /// Mirrors cefclient's `DoWork`.
+ fn do_work(&self) {
+ let was_reentrant = self.perform_message_loop_work();
+ if was_reentrant {
+ // Execute the remaining work as soon as possible.
+ self.on_schedule_message_pump_work(0);
+ } else if !self.is_timer_pending() {
+ // Schedule a timer event at the maximum allowed time. This may be dropped
+ // in OnScheduleWork() if another timer event is already in-flight.
+ self.on_schedule_message_pump_work(K_TIMER_DELAY_PLACEHOLDER);
+ }
}
- // Arm the fallback tick so CEF work it didn't explicitly announce (e.g. via
- // its own internal timers) still runs within the max delay.
- let timer_pending = self
- .platform
- .lock()
- .map(|platform| platform.is_timer_pending())
- .unwrap_or(true);
- if !timer_pending {
- self.schedule_message_pump_work(TIMER_DELAY_PLACEHOLDER);
- }
- }
-
- /// Mirrors cefclient's `PerformMessageLoopWork`.
- fn perform_message_loop_work(&self) -> bool {
- if self.is_active.swap(true, Ordering::SeqCst) {
- // do_message_loop_work can trigger CEF callbacks (paint, IPC) that
- // re-enter this method. Record it so the caller reschedules the work.
- self.reentrancy_detected.store(true, Ordering::SeqCst);
- return false;
+ fn is_timer_pending(&self) -> bool {
+ self.platform
+ .lock()
+ .map(|platform| platform.is_timer_pending())
+ .unwrap_or(true)
}
- self.reentrancy_detected.store(false, Ordering::SeqCst);
- cef::do_message_loop_work();
- self.is_active.store(false, Ordering::SeqCst);
-
- self.reentrancy_detected.load(Ordering::SeqCst)
- }
+ /// Mirrors cefclient's `PerformMessageLoopWork`.
+ fn perform_message_loop_work(&self) -> bool {
+ if self.is_active.load(Ordering::SeqCst) {
+ // When CefDoMessageLoopWork() is called there may be various callbacks
+ // (such as paint and IPC messages) that result in additional calls to this
+ // method. If re-entrancy is detected we must repost a request again to the
+ // owner thread to ensure that the discarded call is executed in the future.
+ self.reentrancy_detected.store(true, Ordering::SeqCst);
+ return false;
+ }
+
+ self.reentrancy_detected.store(false, Ordering::SeqCst);
+
+ self.is_active.store(true, Ordering::SeqCst);
+ cef::do_message_loop_work();
+ self.is_active.store(false, Ordering::SeqCst);
+
+ // |reentrancy_detected_| may have changed due to re-entrant calls to this
+ // method.
+ self.reentrancy_detected.load(Ordering::SeqCst)
+ }
}
diff --git a/src/external_message_pump/windows.rs b/src/external_message_pump/windows.rs
index 69bf451..c52a8eb 100644
--- a/src/external_message_pump/windows.rs
+++ b/src/external_message_pump/windows.rs
@@ -4,6 +4,9 @@
//! Windows backend for the CEF external message pump.
//!
+//! Upstream cefclient:
+//!
+//!
//! Mirrors cefclient's `main_message_loop_external_pump_win.cc`: a message-only
//! window owns a `WM_TIMER`, and `OnScheduleMessagePumpWork` posts a private
//! `WM_HAVE_WORK` message to it. winit runs the thread's own
@@ -11,153 +14,164 @@
//! window procedure — so CEF is pumped from the same loop, including while
//! Windows runs a modal move/resize loop that winit's `ApplicationHandler`
//! callbacks never observe.
-//!
-//! Reference:
-//!
-
use std::sync::Weak;
use windows::{
- Win32::{
- Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
- System::LibraryLoader::GetModuleHandleW,
- UI::WindowsAndMessaging::{
- CreateWindowExW, DefWindowProcW, DestroyWindow, GWLP_USERDATA, GetWindowLongPtrW,
- HWND_MESSAGE, KillTimer, PostMessageW, RegisterClassExW, SetTimer, SetWindowLongPtrW,
- WINDOW_EX_STYLE, WM_TIMER, WM_USER, WNDCLASSEXW, WS_OVERLAPPEDWINDOW,
+ Win32::{
+ Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
+ System::LibraryLoader::GetModuleHandleW,
+ UI::WindowsAndMessaging::{
+ CreateWindowExW, DefWindowProcW, DestroyWindow, GWLP_USERDATA, GetWindowLongPtrW,
+ HWND_MESSAGE, KillTimer, PostMessageW, RegisterClassExW, SetTimer, SetWindowLongPtrW,
+ WINDOW_EX_STYLE, WM_TIMER, WM_USER, WNDCLASSEXW, WS_OVERLAPPEDWINDOW,
+ },
},
- },
- core::{PCWSTR, w},
+ core::{PCWSTR, w},
};
use super::PumpState;
-/// Window class for the pump's message-only window.
-const WINDOW_CLASS: PCWSTR = w!("TauriCefExternalMessagePump");
-/// Private message posted by `OnScheduleMessagePumpWork`; matches cefclient's
-/// `kMsgHaveWork` (`WM_USER + 1`). The `LPARAM` carries the delay in ms.
-const WM_HAVE_WORK: u32 = WM_USER + 1;
-/// Timer id used with `SetTimer`/`KillTimer` on the pump window.
+const K_CLASS_NAME: PCWSTR = w!("TauriCEFMainTargetHWND");
+
+// Message sent to get an additional time slice for pumping (processing) another
+// task (a series of such messages creates a continuous task pump).
+const K_MSG_HAVE_WORK: u32 = WM_USER + 1;
+
const TIMER_ID: usize = 1;
pub(super) struct PlatformPump {
- hwnd: HWND,
- timer_pending: bool,
+ // HWND owned by the thread that CefDoMessageLoopWork should be invoked on.
+ main_thread_target: HWND,
+
+ // True if a timer event is currently pending.
+ timer_pending: bool,
}
-// SAFETY: `hwnd` is created on, and its timer is only armed/disarmed from, the
-// main (winit) thread. The sole cross-thread use is `PostMessageW` in
-// `post_schedule_work`, which Win32 explicitly permits from any thread.
+// SAFETY: `main_thread_target` is created on, and its timer is only
+// armed/disarmed from, the main (winit) thread. The sole cross-thread use is
+// `PostMessageW` in `on_schedule_message_pump_work`, which Win32 explicitly
+// permits from any thread.
unsafe impl Send for PlatformPump {}
impl PlatformPump {
- pub(super) fn new(state: Weak) -> Self {
- let hinstance: HINSTANCE = unsafe { GetModuleHandleW(None) }
- .map(|module| HINSTANCE(module.0))
- .unwrap_or_default();
-
- let class = WNDCLASSEXW {
- cbSize: std::mem::size_of::() as u32,
- lpfnWndProc: Some(wndproc),
- hInstance: hinstance,
- lpszClassName: WINDOW_CLASS,
- ..Default::default()
- };
- // Registering an already-registered class fails harmlessly; this also lets a
- // second runtime in the same process reuse the class.
- unsafe { RegisterClassExW(&class) };
-
- let hwnd = unsafe {
- CreateWindowExW(
- WINDOW_EX_STYLE::default(),
- WINDOW_CLASS,
- PCWSTR::null(),
- WS_OVERLAPPEDWINDOW,
- 0,
- 0,
- 0,
- 0,
- Some(HWND_MESSAGE),
- None,
- Some(hinstance),
- None,
- )
+ pub(super) fn new(state: Weak) -> Self {
+ let hinstance: HINSTANCE = unsafe { GetModuleHandleW(None) }
+ .map(|module| HINSTANCE(module.0))
+ .unwrap_or_default();
+
+ let class = WNDCLASSEXW {
+ cbSize: std::mem::size_of::() as u32,
+ lpfnWndProc: Some(wnd_proc),
+ hInstance: hinstance,
+ lpszClassName: K_CLASS_NAME,
+ ..Default::default()
+ };
+ unsafe { RegisterClassExW(&class) };
+
+ // Create the message handling window.
+ let hwnd = unsafe {
+ CreateWindowExW(
+ WINDOW_EX_STYLE::default(),
+ K_CLASS_NAME,
+ PCWSTR::null(),
+ WS_OVERLAPPEDWINDOW,
+ 0,
+ 0,
+ 0,
+ 0,
+ Some(HWND_MESSAGE),
+ None,
+ Some(hinstance),
+ None,
+ )
+ }
+ .expect("failed to create CEF external message pump window");
+
+ let state = Box::into_raw(Box::new(state));
+ unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, state as isize) };
+
+ Self {
+ main_thread_target: hwnd,
+ timer_pending: false,
+ }
}
- .expect("failed to create CEF external message pump window");
- // Store a Weak back-reference for the window procedure; freed in `Drop`. No
- // timer is armed and no message is posted yet, so the window cannot be
- // dispatched anything that reads this slot before it is set.
- let state = Box::into_raw(Box::new(state));
- unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, state as isize) };
+ pub(super) fn on_schedule_message_pump_work(&mut self, delay_ms: i64) {
+ // This method may be called on any thread.
+ let _ = unsafe {
+ PostMessageW(
+ Some(self.main_thread_target),
+ K_MSG_HAVE_WORK,
+ WPARAM(0),
+ LPARAM(delay_ms as isize),
+ )
+ };
+ }
- Self {
- hwnd,
- timer_pending: false,
+ pub(super) fn set_timer(&mut self, delay_ms: i64) {
+ debug_assert!(!self.timer_pending);
+ debug_assert!(delay_ms > 0);
+ self.timer_pending = true;
+ unsafe {
+ SetTimer(
+ Some(self.main_thread_target),
+ TIMER_ID,
+ delay_ms as u32,
+ None,
+ )
+ };
}
- }
-
- pub(super) fn post_schedule_work(&mut self, delay_ms: i64) {
- // Thread-safe; lands in `wndproc` (WM_HAVE_WORK) on the owner thread.
- let _ = unsafe {
- PostMessageW(
- Some(self.hwnd),
- WM_HAVE_WORK,
- WPARAM(0),
- LPARAM(delay_ms as isize),
- )
- };
- }
-
- pub(super) fn set_timer(&mut self, delay_ms: i64) {
- debug_assert!(!self.timer_pending);
- debug_assert!(delay_ms > 0);
- self.timer_pending = true;
- unsafe { SetTimer(Some(self.hwnd), TIMER_ID, delay_ms as u32, None) };
- }
-
- pub(super) fn kill_timer(&mut self) {
- if self.timer_pending {
- let _ = unsafe { KillTimer(Some(self.hwnd), TIMER_ID) };
- self.timer_pending = false;
+
+ pub(super) fn kill_timer(&mut self) {
+ if self.timer_pending {
+ let _ = unsafe { KillTimer(Some(self.main_thread_target), TIMER_ID) };
+ self.timer_pending = false;
+ }
}
- }
- pub(super) fn is_timer_pending(&self) -> bool {
- self.timer_pending
- }
+ pub(super) fn is_timer_pending(&self) -> bool {
+ self.timer_pending
+ }
}
impl Drop for PlatformPump {
- fn drop(&mut self) {
- unsafe {
- if self.timer_pending {
- let _ = KillTimer(Some(self.hwnd), TIMER_ID);
- }
-
- let state = SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, 0) as *mut Weak;
- if !state.is_null() {
- drop(Box::from_raw(state));
- }
-
- let _ = DestroyWindow(self.hwnd);
+ fn drop(&mut self) {
+ unsafe {
+ if self.timer_pending {
+ let _ = KillTimer(Some(self.main_thread_target), TIMER_ID);
+ }
+
+ let state = SetWindowLongPtrW(self.main_thread_target, GWLP_USERDATA, 0)
+ as *mut Weak;
+ if !state.is_null() {
+ drop(Box::from_raw(state));
+ }
+
+ let _ = DestroyWindow(self.main_thread_target);
+ }
}
- }
}
-unsafe extern "system" fn wndproc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
- if msg == WM_TIMER || msg == WM_HAVE_WORK {
- let state = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) } as *const Weak;
- if !state.is_null()
- && let Some(state) = unsafe { &*state }.upgrade()
- {
- if msg == WM_HAVE_WORK {
- state.on_schedule_work(lparam.0 as i64);
- } else {
- state.on_timer_timeout();
- }
+unsafe extern "system" fn wnd_proc(
+ hwnd: HWND,
+ msg: u32,
+ wparam: WPARAM,
+ lparam: LPARAM,
+) -> LRESULT {
+ if msg == WM_TIMER || msg == K_MSG_HAVE_WORK {
+ let state = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) } as *const Weak;
+ if !state.is_null()
+ && let Some(state) = unsafe { &*state }.upgrade()
+ {
+ if msg == K_MSG_HAVE_WORK {
+ // OnScheduleMessagePumpWork() request.
+ state.on_schedule_work(lparam.0 as i64);
+ } else {
+ // Timer timed out.
+ state.on_timer_timeout();
+ }
+ }
}
- }
- unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
+ unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
}
diff --git a/src/lib.rs b/src/lib.rs
index f1196ee..4cd51f2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -18,16 +18,16 @@ mod window;
mod window_builder;
mod window_handle;
-pub use config::{configure, CefConfig};
-pub use streaming::{
- register_streaming_scheme_handler, InitiatorOrigin, StreamClosed, StreamResponder, StreamWriter,
-};
+pub use config::{CefConfig, configure};
pub use policy::{
- DEFAULT_PROMPT_TIMEOUT, DeferredResponder, DenyReason, NormalizedOrigin, PermissionAudit,
- PermissionKind, PermissionRequest, PermissionResponder, PopupRequest, RequestSource, Verdict,
- set_permission_audit, set_permission_policy, set_popup_policy,
+ DEFAULT_PROMPT_TIMEOUT, DeferredResponder, DenyReason, NormalizedOrigin, PermissionAudit,
+ PermissionKind, PermissionRequest, PermissionResponder, PopupRequest, RequestSource, Verdict,
+ set_permission_audit, set_permission_policy, set_popup_policy,
};
pub use runtime::*;
+pub use streaming::{
+ InitiatorOrigin, StreamClosed, StreamResponder, StreamWriter, register_streaming_scheme_handler,
+};
pub use webview::*;
pub use window::CefWindowDispatcher;
pub use window_builder::WindowBuilderWrapper;
diff --git a/src/platform/linux/event_loop.rs b/src/platform/linux/event_loop.rs
index 2664b04..eeb03e3 100644
--- a/src/platform/linux/event_loop.rs
+++ b/src/platform/linux/event_loop.rs
@@ -11,39 +11,39 @@ use crate::platform::EventLoopExt;
use super::{taskbar, utils::with_x11};
impl EventLoopExt for dyn ActiveEventLoop + '_ {
- fn set_badge_count(&self, count: Option, desktop_filename: Option) {
- taskbar::set_badge_count(count, desktop_filename);
- }
-
- fn set_badge_label(&self, _label: Option) {
- // Unsupported on Linux/BSD
- }
-
- fn cursor_position(&self) -> Result> {
- with_x11(None, |xlib, display| unsafe {
- let root = (xlib.XDefaultRootWindow)(display);
- let mut root_return: c_ulong = 0;
- let mut child_return: c_ulong = 0;
- let mut root_x = 0;
- let mut root_y = 0;
- let mut win_x = 0;
- let mut win_y = 0;
- let mut mask: c_uint = 0;
-
- let ok = (xlib.XQueryPointer)(
- display,
- root,
- &mut root_return,
- &mut child_return,
- &mut root_x,
- &mut root_y,
- &mut win_x,
- &mut win_y,
- &mut mask,
- );
-
- (ok != 0).then_some(PhysicalPosition::new(root_x as f64, root_y as f64))
- })
- .ok_or(Error::FailedToGetCursorPosition)
- }
+ fn set_badge_count(&self, count: Option, desktop_filename: Option) {
+ taskbar::set_badge_count(count, desktop_filename);
+ }
+
+ fn set_badge_label(&self, _label: Option) {
+ // Unsupported on Linux/BSD
+ }
+
+ fn cursor_position(&self) -> Result> {
+ with_x11(None, |xlib, display| unsafe {
+ let root = (xlib.XDefaultRootWindow)(display);
+ let mut root_return: c_ulong = 0;
+ let mut child_return: c_ulong = 0;
+ let mut root_x = 0;
+ let mut root_y = 0;
+ let mut win_x = 0;
+ let mut win_y = 0;
+ let mut mask: c_uint = 0;
+
+ let ok = (xlib.XQueryPointer)(
+ display,
+ root,
+ &mut root_return,
+ &mut child_return,
+ &mut root_x,
+ &mut root_y,
+ &mut win_x,
+ &mut win_y,
+ &mut mask,
+ );
+
+ (ok != 0).then_some(PhysicalPosition::new(root_x as f64, root_y as f64))
+ })
+ .ok_or(Error::FailedToGetCursorPosition)
+ }
}
diff --git a/src/platform/linux/monitor.rs b/src/platform/linux/monitor.rs
index c7f1c31..9c92b4d 100644
--- a/src/platform/linux/monitor.rs
+++ b/src/platform/linux/monitor.rs
@@ -12,122 +12,120 @@ use crate::platform::{MonitorExt, monitor_bounds};
use super::utils::{atom, with_x11};
impl MonitorExt for MonitorHandle {
- fn work_area(&self) -> PhysicalRect {
- let bounds = monitor_bounds(self);
- x11_work_area(bounds).unwrap_or(bounds)
- }
+ fn work_area(&self) -> PhysicalRect {
+ let bounds = monitor_bounds(self);
+ x11_work_area(bounds).unwrap_or(bounds)
+ }
}
fn x11_work_area(monitor_bounds: PhysicalRect) -> Option> {
- with_x11(None, |xlib, display| unsafe {
- let root = (xlib.XDefaultRootWindow)(display);
- let workareas = get_cardinal_property(xlib, display, root, "_NET_WORKAREA")?;
-
- let desktop = get_cardinal_property(xlib, display, root, "_NET_CURRENT_DESKTOP")
- .and_then(|desktops| desktops.first().copied())
- .and_then(|desktop| usize::try_from(desktop).ok())
- .unwrap_or(0);
-
- let offset = desktop.checked_mul(4)?;
- let workarea = workareas
- .get(offset..offset + 4)
- .or_else(|| workareas.get(0..4))?;
-
- let workarea = PhysicalRect {
- position: PhysicalPosition::new(
- i32::try_from(workarea[0]).ok()?,
- i32::try_from(workarea[1]).ok()?,
- ),
- size: PhysicalSize::new(
- u32::try_from(workarea[2]).ok()?,
- u32::try_from(workarea[3]).ok()?,
- ),
- };
-
- intersect_rects(monitor_bounds, workarea)
- })
+ with_x11(None, |xlib, display| unsafe {
+ let root = (xlib.XDefaultRootWindow)(display);
+ let workareas = get_cardinal_property(xlib, display, root, "_NET_WORKAREA")?;
+
+ let desktop = get_cardinal_property(xlib, display, root, "_NET_CURRENT_DESKTOP")
+ .and_then(|desktops| desktops.first().copied())
+ .and_then(|desktop| usize::try_from(desktop).ok())
+ .unwrap_or(0);
+
+ let offset = desktop.checked_mul(4)?;
+ let workarea = workareas
+ .get(offset..offset + 4)
+ .or_else(|| workareas.get(0..4))?;
+
+ let workarea = PhysicalRect {
+ position: PhysicalPosition::new(
+ i32::try_from(workarea[0]).ok()?,
+ i32::try_from(workarea[1]).ok()?,
+ ),
+ size: PhysicalSize::new(
+ u32::try_from(workarea[2]).ok()?,
+ u32::try_from(workarea[3]).ok()?,
+ ),
+ };
+
+ intersect_rects(monitor_bounds, workarea)
+ })
}
fn get_cardinal_property(
- xlib: &xlib::Xlib,
- display: *mut xlib::Display,
- window: xlib::Window,
- name: &str,
+ xlib: &xlib::Xlib,
+ display: *mut xlib::Display,
+ window: xlib::Window,
+ name: &str,
) -> Option> {
- let property = atom(xlib, display, name);
- if property == 0 {
- return None;
- }
-
- let mut actual_type: c_ulong = 0;
- let mut actual_format: c_int = 0;
- let mut nitems: c_ulong = 0;
- let mut bytes_after: c_ulong = 0;
- let mut data: *mut c_uchar = std::ptr::null_mut();
-
- let status = unsafe {
- (xlib.XGetWindowProperty)(
- display,
- window,
- property,
- 0,
- 256,
- 0,
- xlib::XA_CARDINAL,
- &mut actual_type,
- &mut actual_format,
- &mut nitems,
- &mut bytes_after,
- &mut data,
- )
- };
-
- if status != xlib::Success as c_int || data.is_null() {
- return None;
- }
-
- let value = if actual_type == xlib::XA_CARDINAL && actual_format == 32 && bytes_after == 0 {
- let values = unsafe { std::slice::from_raw_parts(data.cast::(), nitems as usize) };
- Some(values.to_vec())
- } else {
- None
- };
-
- unsafe {
- (xlib.XFree)(data.cast());
- }
- value
+ let property = atom(xlib, display, name);
+ if property == 0 {
+ return None;
+ }
+
+ let mut actual_type: c_ulong = 0;
+ let mut actual_format: c_int = 0;
+ let mut nitems: c_ulong = 0;
+ let mut bytes_after: c_ulong = 0;
+ let mut data: *mut c_uchar = std::ptr::null_mut();
+
+ let status = unsafe {
+ (xlib.XGetWindowProperty)(
+ display,
+ window,
+ property,
+ 0,
+ 256,
+ 0,
+ xlib::XA_CARDINAL,
+ &mut actual_type,
+ &mut actual_format,
+ &mut nitems,
+ &mut bytes_after,
+ &mut data,
+ )
+ };
+
+ if status != xlib::Success as c_int || data.is_null() {
+ return None;
+ }
+
+ let value = if actual_type == xlib::XA_CARDINAL && actual_format == 32 && bytes_after == 0 {
+ let values = unsafe { std::slice::from_raw_parts(data.cast::(), nitems as usize) };
+ Some(values.to_vec())
+ } else {
+ None
+ };
+
+ unsafe {
+ (xlib.XFree)(data.cast());
+ }
+ value
}
fn intersect_rects(
- a: PhysicalRect,
- b: PhysicalRect,
+ a: PhysicalRect,
+ b: PhysicalRect,
) -> Option> {
- let left = a.position.x.max(b.position.x);
- let top = a.position.y.max(b.position.y);
- let right = rect_right(a).min(rect_right(b));
- let bottom = rect_bottom(a).min(rect_bottom(b));
-
- if right <= left || bottom <= top {
- return None;
- }
-
- Some(PhysicalRect {
- position: PhysicalPosition::new(left, top),
- size: PhysicalSize::new((right - left) as u32, (bottom - top) as u32),
- })
+ let left = a.position.x.max(b.position.x);
+ let top = a.position.y.max(b.position.y);
+ let right = rect_right(a).min(rect_right(b));
+ let bottom = rect_bottom(a).min(rect_bottom(b));
+
+ if right <= left || bottom <= top {
+ return None;
+ }
+
+ Some(PhysicalRect {
+ position: PhysicalPosition::new(left, top),
+ size: PhysicalSize::new((right - left) as u32, (bottom - top) as u32),
+ })
}
fn rect_right(rect: PhysicalRect) -> i32 {
- rect
- .position
- .x
- .saturating_add(i32::try_from(rect.size.width).unwrap_or(i32::MAX))
+ rect.position
+ .x
+ .saturating_add(i32::try_from(rect.size.width).unwrap_or(i32::MAX))
}
fn rect_bottom(rect: PhysicalRect) -> i32 {
- rect
- .position
- .y
- .saturating_add(i32::try_from(rect.size.height).unwrap_or(i32::MAX))
+ rect.position
+ .y
+ .saturating_add(i32::try_from(rect.size.height).unwrap_or(i32::MAX))
}
diff --git a/src/platform/linux/taskbar.rs b/src/platform/linux/taskbar.rs
index f5ff835..17c3d34 100644
--- a/src/platform/linux/taskbar.rs
+++ b/src/platform/linux/taskbar.rs
@@ -7,35 +7,36 @@ use std::{cell::RefCell, ffi::CString, os::raw::c_char};
use tauri_runtime::{ProgressBarState, ProgressBarStatus};
pub(super) fn set_progress_bar(state: ProgressBarState) {
- TASKBAR_INDICATOR.with_borrow_mut(|taskbar| taskbar.update(state));
+ TASKBAR_INDICATOR.with_borrow_mut(|taskbar| taskbar.update(state));
}
pub(super) fn set_badge_count(count: Option, desktop_filename: Option) {
- TASKBAR_INDICATOR.with_borrow_mut(|taskbar| taskbar.update_count(count, desktop_filename));
+ TASKBAR_INDICATOR.with_borrow_mut(|taskbar| taskbar.update_count(count, desktop_filename));
}
#[derive(WrapperApi)]
struct UnityLib {
- unity_launcher_entry_get_for_desktop_id: unsafe extern "C" fn(id: *const c_char) -> *const isize,
- unity_inspector_get_default: unsafe extern "C" fn() -> *const isize,
- unity_inspector_get_unity_running: unsafe extern "C" fn(inspector: *const isize) -> i32,
- unity_launcher_entry_set_progress: unsafe extern "C" fn(entry: *const isize, value: f64) -> i32,
- unity_launcher_entry_set_progress_visible:
- unsafe extern "C" fn(entry: *const isize, value: i32) -> i32,
- unity_launcher_entry_set_count: unsafe extern "C" fn(entry: *const isize, value: i64) -> i32,
- unity_launcher_entry_set_count_visible:
- unsafe extern "C" fn(entry: *const isize, value: bool) -> bool,
+ unity_launcher_entry_get_for_desktop_id:
+ unsafe extern "C" fn(id: *const c_char) -> *const isize,
+ unity_inspector_get_default: unsafe extern "C" fn() -> *const isize,
+ unity_inspector_get_unity_running: unsafe extern "C" fn(inspector: *const isize) -> i32,
+ unity_launcher_entry_set_progress: unsafe extern "C" fn(entry: *const isize, value: f64) -> i32,
+ unity_launcher_entry_set_progress_visible:
+ unsafe extern "C" fn(entry: *const isize, value: i32) -> i32,
+ unity_launcher_entry_set_count: unsafe extern "C" fn(entry: *const isize, value: i64) -> i32,
+ unity_launcher_entry_set_count_visible:
+ unsafe extern "C" fn(entry: *const isize, value: bool) -> bool,
}
struct TaskbarIndicator {
- desktop_filename: Option,
- desktop_filename_c_str: Option,
+ desktop_filename: Option,
+ desktop_filename_c_str: Option,
- unity_lib: Option>,
- attempted_load: bool,
+ unity_lib: Option>,
+ attempted_load: bool,
- unity_inspector: Option<*const isize>,
- unity_entry: Option<*const isize>,
+ unity_inspector: Option<*const isize>,
+ unity_entry: Option<*const isize>,
}
thread_local! {
@@ -43,136 +44,138 @@ thread_local! {
}
impl TaskbarIndicator {
- fn new() -> Self {
- Self {
- desktop_filename: None,
- desktop_filename_c_str: None,
+ fn new() -> Self {
+ Self {
+ desktop_filename: None,
+ desktop_filename_c_str: None,
- unity_lib: None,
- attempted_load: false,
+ unity_lib: None,
+ attempted_load: false,
- unity_inspector: None,
- unity_entry: None,
+ unity_inspector: None,
+ unity_entry: None,
+ }
}
- }
- fn ensure_lib_load(&mut self) {
- if self.attempted_load {
- return;
- }
-
- self.attempted_load = true;
-
- self.unity_lib = unsafe {
- Container::load("libunity.so.4")
- .or_else(|_| Container::load("libunity.so.6"))
- .or_else(|_| Container::load("libunity.so.9"))
- .ok()
- };
-
- if let Some(unity_lib) = &self.unity_lib {
- let handle = unsafe { unity_lib.unity_inspector_get_default() };
- if !handle.is_null() {
- self.unity_inspector = Some(handle);
- }
- }
- }
-
- fn ensure_entry_load(&mut self) {
- if let Some(unity_lib) = &self.unity_lib
- && let Some(id) = &self.desktop_filename_c_str
- {
- let handle = unsafe { unity_lib.unity_launcher_entry_get_for_desktop_id(id.as_ptr()) };
- if !handle.is_null() {
- self.unity_entry = Some(handle);
- }
- }
- }
-
- fn is_unity_running(&self) -> bool {
- if let Some(inspector) = self.unity_inspector
- && let Some(unity_lib) = &self.unity_lib
- {
- return unsafe { unity_lib.unity_inspector_get_unity_running(inspector) } == 1;
- }
-
- false
- }
-
- fn update(&mut self, progress: ProgressBarState) {
- if let Some(uri) = progress.desktop_filename {
- self.desktop_filename = Some(uri);
- self.desktop_filename_c_str = None;
- self.unity_entry = None;
- }
-
- self.ensure_lib_load();
-
- if !self.is_unity_running() {
- return;
- }
-
- if self.desktop_filename_c_str.is_none()
- && let Some(uri) = &self.desktop_filename
- {
- self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default());
- }
+ fn ensure_lib_load(&mut self) {
+ if self.attempted_load {
+ return;
+ }
- if self.unity_entry.is_none() {
- self.ensure_entry_load();
- }
+ self.attempted_load = true;
- if let Some(unity_lib) = &self.unity_lib
- && let Some(unity_entry) = self.unity_entry
- {
- if let Some(progress) = progress.progress {
- let progress = progress.min(100) as f64 / 100.0;
- unsafe { unity_lib.unity_launcher_entry_set_progress(unity_entry, progress) };
- }
-
- if let Some(status) = progress.status {
- let is_visible = !matches!(status, ProgressBarStatus::None);
- unsafe {
- unity_lib
- .unity_launcher_entry_set_progress_visible(unity_entry, if is_visible { 1 } else { 0 })
+ self.unity_lib = unsafe {
+ Container::load("libunity.so.4")
+ .or_else(|_| Container::load("libunity.so.6"))
+ .or_else(|_| Container::load("libunity.so.9"))
+ .ok()
};
- }
- }
- }
- fn update_count(&mut self, count: Option, desktop_filename: Option) {
- if let Some(uri) = desktop_filename {
- self.desktop_filename = Some(uri);
- self.desktop_filename_c_str = None;
- self.unity_entry = None;
+ if let Some(unity_lib) = &self.unity_lib {
+ let handle = unsafe { unity_lib.unity_inspector_get_default() };
+ if !handle.is_null() {
+ self.unity_inspector = Some(handle);
+ }
+ }
}
- self.ensure_lib_load();
-
- if !self.is_unity_running() {
- return;
+ fn ensure_entry_load(&mut self) {
+ if let Some(unity_lib) = &self.unity_lib
+ && let Some(id) = &self.desktop_filename_c_str
+ {
+ let handle = unsafe { unity_lib.unity_launcher_entry_get_for_desktop_id(id.as_ptr()) };
+ if !handle.is_null() {
+ self.unity_entry = Some(handle);
+ }
+ }
}
- if self.desktop_filename_c_str.is_none()
- && let Some(uri) = &self.desktop_filename
- {
- self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default());
+ fn is_unity_running(&self) -> bool {
+ if let Some(inspector) = self.unity_inspector
+ && let Some(unity_lib) = &self.unity_lib
+ {
+ return unsafe { unity_lib.unity_inspector_get_unity_running(inspector) } == 1;
+ }
+
+ false
}
- if self.unity_entry.is_none() {
- self.ensure_entry_load();
+ fn update(&mut self, progress: ProgressBarState) {
+ if let Some(uri) = progress.desktop_filename {
+ self.desktop_filename = Some(uri);
+ self.desktop_filename_c_str = None;
+ self.unity_entry = None;
+ }
+
+ self.ensure_lib_load();
+
+ if !self.is_unity_running() {
+ return;
+ }
+
+ if self.desktop_filename_c_str.is_none()
+ && let Some(uri) = &self.desktop_filename
+ {
+ self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default());
+ }
+
+ if self.unity_entry.is_none() {
+ self.ensure_entry_load();
+ }
+
+ if let Some(unity_lib) = &self.unity_lib
+ && let Some(unity_entry) = self.unity_entry
+ {
+ if let Some(progress) = progress.progress {
+ let progress = progress.min(100) as f64 / 100.0;
+ unsafe { unity_lib.unity_launcher_entry_set_progress(unity_entry, progress) };
+ }
+
+ if let Some(status) = progress.status {
+ let is_visible = !matches!(status, ProgressBarStatus::None);
+ unsafe {
+ unity_lib.unity_launcher_entry_set_progress_visible(
+ unity_entry,
+ if is_visible { 1 } else { 0 },
+ )
+ };
+ }
+ }
}
- if let Some(unity_lib) = &self.unity_lib
- && let Some(unity_entry) = self.unity_entry
- {
- if let Some(count) = count {
- unsafe { unity_lib.unity_launcher_entry_set_count(unity_entry, count) };
- unsafe { unity_lib.unity_launcher_entry_set_count_visible(unity_entry, true) };
- } else {
- unsafe { unity_lib.unity_launcher_entry_set_count(unity_entry, 0) };
- unsafe { unity_lib.unity_launcher_entry_set_count_visible(unity_entry, false) };
- }
+ fn update_count(&mut self, count: Option, desktop_filename: Option) {
+ if let Some(uri) = desktop_filename {
+ self.desktop_filename = Some(uri);
+ self.desktop_filename_c_str = None;
+ self.unity_entry = None;
+ }
+
+ self.ensure_lib_load();
+
+ if !self.is_unity_running() {
+ return;
+ }
+
+ if self.desktop_filename_c_str.is_none()
+ && let Some(uri) = &self.desktop_filename
+ {
+ self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default());
+ }
+
+ if self.unity_entry.is_none() {
+ self.ensure_entry_load();
+ }
+
+ if let Some(unity_lib) = &self.unity_lib
+ && let Some(unity_entry) = self.unity_entry
+ {
+ if let Some(count) = count {
+ unsafe { unity_lib.unity_launcher_entry_set_count(unity_entry, count) };
+ unsafe { unity_lib.unity_launcher_entry_set_count_visible(unity_entry, true) };
+ } else {
+ unsafe { unity_lib.unity_launcher_entry_set_count(unity_entry, 0) };
+ unsafe { unity_lib.unity_launcher_entry_set_count_visible(unity_entry, false) };
+ }
+ }
}
- }
}
diff --git a/src/platform/linux/utils.rs b/src/platform/linux/utils.rs
index fe9b4e6..88e6ab5 100644
--- a/src/platform/linux/utils.rs
+++ b/src/platform/linux/utils.rs
@@ -3,10 +3,10 @@
// SPDX-License-Identifier: MIT
use std::{
- cell::RefCell,
- ffi::CString,
- os::raw::{c_long, c_ulong},
- sync::LazyLock,
+ cell::RefCell,
+ ffi::CString,
+ os::raw::{c_long, c_ulong},
+ sync::LazyLock,
};
use x11_dl::xlib;
@@ -25,84 +25,84 @@ thread_local! {
}
pub(super) fn with_cef_display(
- default: R,
- f: impl FnOnce(&xlib::Xlib, *mut xlib::Display) -> R,
+ default: R,
+ f: impl FnOnce(&xlib::Xlib, *mut xlib::Display) -> R,
) -> R {
- let Some(xlib) = XLIB.as_ref() else {
- return default;
- };
- let display = cef::get_xdisplay() as *mut xlib::Display;
- if display.is_null() {
- return default;
- }
-
- let result = f(xlib, display);
- unsafe {
- (xlib.XFlush)(display);
- }
- result
-}
-
-pub(super) fn with_x11(default: R, f: impl FnOnce(&xlib::Xlib, *mut xlib::Display) -> R) -> R {
- let Some(xlib) = XLIB.as_ref() else {
- return default;
- };
-
- DISPLAY.with(|cell| {
- let mut guard = cell.borrow_mut();
- if guard.is_none() {
- let display = unsafe { (xlib.XOpenDisplay)(std::ptr::null()) };
- if display.is_null() {
+ let Some(xlib) = XLIB.as_ref() else {
+ return default;
+ };
+ let display = cef::get_xdisplay() as *mut xlib::Display;
+ if display.is_null() {
return default;
- }
- *guard = Some(Display(display));
}
- let display = guard.as_ref().unwrap().0;
let result = f(xlib, display);
unsafe {
- (xlib.XFlush)(display);
+ (xlib.XFlush)(display);
}
result
- })
+}
+
+pub(super) fn with_x11(default: R, f: impl FnOnce(&xlib::Xlib, *mut xlib::Display) -> R) -> R {
+ let Some(xlib) = XLIB.as_ref() else {
+ return default;
+ };
+
+ DISPLAY.with(|cell| {
+ let mut guard = cell.borrow_mut();
+ if guard.is_none() {
+ let display = unsafe { (xlib.XOpenDisplay)(std::ptr::null()) };
+ if display.is_null() {
+ return default;
+ }
+ *guard = Some(Display(display));
+ }
+
+ let display = guard.as_ref().unwrap().0;
+ let result = f(xlib, display);
+ unsafe {
+ (xlib.XFlush)(display);
+ }
+ result
+ })
}
pub(super) fn atom(xlib: &xlib::Xlib, display: *mut xlib::Display, name: &str) -> c_ulong {
- let cname = CString::new(name).unwrap();
- unsafe { (xlib.XInternAtom)(display, cname.as_ptr(), 0) }
+ let cname = CString::new(name).unwrap();
+ unsafe { (xlib.XInternAtom)(display, cname.as_ptr(), 0) }
}
pub(super) fn set_wm_state(xid: c_ulong, add: bool, atom1: &str, atom2: Option<&str>) {
- with_x11((), |xlib, display| {
- let wm_state = atom(xlib, display, "_NET_WM_STATE");
- let a1 = atom(xlib, display, atom1);
- let a2 = atom2.map(|name| atom(xlib, display, name)).unwrap_or(0);
- let action = if add {
- NET_WM_STATE_ADD
- } else {
- NET_WM_STATE_REMOVE
- };
+ with_x11((), |xlib, display| {
+ let wm_state = atom(xlib, display, "_NET_WM_STATE");
+ let a1 = atom(xlib, display, atom1);
+ let a2 = atom2.map(|name| atom(xlib, display, name)).unwrap_or(0);
+ let action = if add {
+ NET_WM_STATE_ADD
+ } else {
+ NET_WM_STATE_REMOVE
+ };
- unsafe {
- let root = (xlib.XDefaultRootWindow)(display);
- let mut event: xlib::XEvent = std::mem::zeroed();
- event.client_message = xlib::XClientMessageEvent {
- type_: CLIENT_MESSAGE,
- serial: 0,
- send_event: 1,
- display,
- window: xid,
- message_type: wm_state,
- format: 32,
- data: xlib::ClientMessageData::from([action, a1 as c_long, a2 as c_long, 1, 0]),
- };
- (xlib.XSendEvent)(
- display,
- root,
- 0,
- SUBSTRUCTURE_REDIRECT_MASK | SUBSTRUCTURE_NOTIFY_MASK,
- &mut event,
- );
- }
- });
+ unsafe {
+ let root = (xlib.XDefaultRootWindow)(display);
+ let mut event: xlib::XEvent = std::mem::zeroed();
+ event.client_message = xlib::XClientMessageEvent {
+ type_: CLIENT_MESSAGE,
+ serial: 0,
+ send_event: 1,
+ display,
+ window: xid,
+ message_type: wm_state,
+ format: 32,
+ data: xlib::ClientMessageData::from([action, a1 as c_long, a2 as c_long, 1, 0]),
+ };
+ (xlib.XSendEvent)(
+ display,
+ root,
+ 0,
+ SUBSTRUCTURE_REDIRECT_MASK | SUBSTRUCTURE_NOTIFY_MASK,
+ &mut event,
+ );
+ }
+ });
}
diff --git a/src/platform/linux/webview.rs b/src/platform/linux/webview.rs
index b32e629..a5992be 100644
--- a/src/platform/linux/webview.rs
+++ b/src/platform/linux/webview.rs
@@ -13,120 +13,127 @@ use crate::{webview::AppWebview, window::AppWindow};
use super::utils::{atom, with_cef_display};
impl AppWebview {
- fn xid(&self) -> xlib::Window {
- let xid = self.host.window_handle();
- assert_ne!(xid, 0, "failed to get XID");
- xid as xlib::Window
- }
-
- pub(crate) fn set_background_color(&self, color: Option) {
- let _ = (self, color);
- // Native child-window background is not equivalent to Chromium's rendered
- // background. Creation still applies BrowserSettings.
- }
-
- pub(crate) fn bounds(&self) -> Option {
- let xid = self.xid();
-
- with_cef_display(None, |xlib, display| unsafe {
- let mut root: xlib::Window = 0;
- let mut x: i32 = 0;
- let mut y: i32 = 0;
- let mut width: u32 = 0;
- let mut height: u32 = 0;
- let mut border_width: u32 = 0;
- let mut depth: u32 = 0;
-
- if (xlib.XGetGeometry)(
- display,
- xid,
- &mut root,
- &mut x,
- &mut y,
- &mut width,
- &mut height,
- &mut border_width,
- &mut depth,
- ) == 0
- {
- return None;
- }
-
- Some(Rect {
- position: PhysicalPosition::new(x, y).into(),
- size: PhysicalSize::new(width, height).into(),
- })
- })
- }
-
- pub(crate) fn reparent(&self, parent: &AppWindow) {
- let xid = self.xid();
- let parent_xid = parent.xid();
-
- with_cef_display((), |xlib, display| unsafe {
- (xlib.XReparentWindow)(display, xid, parent_xid as xlib::Window, 0, 0);
- (xlib.XMapRaised)(display, xid);
- });
- }
-
- pub(crate) fn apply_visible(&self, visible: bool) {
- let xid = self.xid();
-
- with_cef_display((), |xlib, display| unsafe {
- let net_wm_state = atom(xlib, display, "_NET_WM_STATE");
- const PROP_MODE_REPLACE: i32 = 0;
-
- if visible {
- (xlib.XChangeProperty)(
- display,
- xid,
- net_wm_state,
- xlib::XA_ATOM,
- 32,
- PROP_MODE_REPLACE,
- std::ptr::null(),
- 0,
- );
- (xlib.XMapWindow)(display, xid);
- } else {
- let hidden: [c_ulong; 1] = [atom(xlib, display, "_NET_WM_STATE_HIDDEN")];
- (xlib.XChangeProperty)(
- display,
- xid,
- net_wm_state,
- xlib::XA_ATOM,
- 32,
- PROP_MODE_REPLACE,
- hidden.as_ptr() as *const u8,
- 1,
- );
- (xlib.XUnmapWindow)(display, xid);
- }
- });
- }
-
- pub(crate) fn destroy_native(&self) {
- let xid = self.xid();
- with_cef_display((), |xlib, display| unsafe {
- (xlib.XDestroyWindow)(display, xid);
- (xlib.XFlush)(display);
- });
- }
-
- pub(crate) fn apply_physical_bounds(&self, _scale: f64, x: i32, y: i32, width: i32, height: i32) {
- let xid = self.xid();
-
- with_cef_display((), |xlib, display| unsafe {
- (xlib.XMoveResizeWindow)(
- display,
- xid,
- x,
- y,
- width.max(1) as u32,
- height.max(1) as u32,
- );
- // `with_cef_display` issues an `XFlush` once the closure returns, so a
- // blocking `XSync` round-trip here just stalls every resize frame.
- });
- }
+ fn xid(&self) -> xlib::Window {
+ let xid = self.host.window_handle();
+ assert_ne!(xid, 0, "failed to get XID");
+ xid as xlib::Window
+ }
+
+ pub(crate) fn set_background_color(&self, color: Option) {
+ let _ = (self, color);
+ // Native child-window background is not equivalent to Chromium's rendered
+ // background. Creation still applies BrowserSettings.
+ }
+
+ pub(crate) fn bounds(&self) -> Option {
+ let xid = self.xid();
+
+ with_cef_display(None, |xlib, display| unsafe {
+ let mut root: xlib::Window = 0;
+ let mut x: i32 = 0;
+ let mut y: i32 = 0;
+ let mut width: u32 = 0;
+ let mut height: u32 = 0;
+ let mut border_width: u32 = 0;
+ let mut depth: u32 = 0;
+
+ if (xlib.XGetGeometry)(
+ display,
+ xid,
+ &mut root,
+ &mut x,
+ &mut y,
+ &mut width,
+ &mut height,
+ &mut border_width,
+ &mut depth,
+ ) == 0
+ {
+ return None;
+ }
+
+ Some(Rect {
+ position: PhysicalPosition::new(x, y).into(),
+ size: PhysicalSize::new(width, height).into(),
+ })
+ })
+ }
+
+ pub(crate) fn reparent(&self, parent: &AppWindow) {
+ let xid = self.xid();
+ let parent_xid = parent.xid();
+
+ with_cef_display((), |xlib, display| unsafe {
+ (xlib.XReparentWindow)(display, xid, parent_xid as xlib::Window, 0, 0);
+ (xlib.XMapRaised)(display, xid);
+ });
+ }
+
+ pub(crate) fn apply_visible(&self, visible: bool) {
+ let xid = self.xid();
+
+ with_cef_display((), |xlib, display| unsafe {
+ let net_wm_state = atom(xlib, display, "_NET_WM_STATE");
+ const PROP_MODE_REPLACE: i32 = 0;
+
+ if visible {
+ (xlib.XChangeProperty)(
+ display,
+ xid,
+ net_wm_state,
+ xlib::XA_ATOM,
+ 32,
+ PROP_MODE_REPLACE,
+ std::ptr::null(),
+ 0,
+ );
+ (xlib.XMapWindow)(display, xid);
+ } else {
+ let hidden: [c_ulong; 1] = [atom(xlib, display, "_NET_WM_STATE_HIDDEN")];
+ (xlib.XChangeProperty)(
+ display,
+ xid,
+ net_wm_state,
+ xlib::XA_ATOM,
+ 32,
+ PROP_MODE_REPLACE,
+ hidden.as_ptr() as *const u8,
+ 1,
+ );
+ (xlib.XUnmapWindow)(display, xid);
+ }
+ });
+ }
+
+ pub(crate) fn destroy_native(&self) {
+ let xid = self.xid();
+ with_cef_display((), |xlib, display| unsafe {
+ (xlib.XDestroyWindow)(display, xid);
+ (xlib.XFlush)(display);
+ });
+ }
+
+ pub(crate) fn apply_physical_bounds(
+ &self,
+ _scale: f64,
+ x: i32,
+ y: i32,
+ width: i32,
+ height: i32,
+ ) {
+ let xid = self.xid();
+
+ with_cef_display((), |xlib, display| unsafe {
+ (xlib.XMoveResizeWindow)(
+ display,
+ xid,
+ x,
+ y,
+ width.max(1) as u32,
+ height.max(1) as u32,
+ );
+ // `with_cef_display` issues an `XFlush` once the closure returns, so a
+ // blocking `XSync` round-trip here just stalls every resize frame.
+ });
+ }
}
diff --git a/src/platform/linux/window.rs b/src/platform/linux/window.rs
index 1f39b25..2e26cf4 100644
--- a/src/platform/linux/window.rs
+++ b/src/platform/linux/window.rs
@@ -12,67 +12,67 @@ use crate::window::AppWindow;
use super::{taskbar, utils::set_wm_state};
impl AppWindow {
- pub(crate) fn raw_cef_handle(&self) -> cef::sys::cef_window_handle_t {
- self.xid() as cef::sys::cef_window_handle_t
- }
+ pub(crate) fn raw_cef_handle(&self) -> cef::sys::cef_window_handle_t {
+ self.xid() as cef::sys::cef_window_handle_t
+ }
- pub(crate) fn xid(&self) -> c_ulong {
- let handle = self
- .window
- .window_handle()
- .expect("failed to get window handle");
- match handle.as_raw() {
- RawWindowHandle::Xlib(handle) => handle.window as c_ulong,
- RawWindowHandle::Xcb(handle) => handle.window.get() as c_ulong,
- other => panic!("expected X11 window handle, got {other:?}"),
+ pub(crate) fn xid(&self) -> c_ulong {
+ let handle = self
+ .window
+ .window_handle()
+ .expect("failed to get window handle");
+ match handle.as_raw() {
+ RawWindowHandle::Xlib(handle) => handle.window as c_ulong,
+ RawWindowHandle::Xcb(handle) => handle.window.get() as c_ulong,
+ other => panic!("expected X11 window handle, got {other:?}"),
+ }
}
- }
- pub(crate) fn set_enabled(&self, enabled: bool) {
- let _ = (self, enabled);
- // TODO: implement native window enabled state on Linux/BSD.
- }
+ pub(crate) fn set_enabled(&self, enabled: bool) {
+ let _ = (self, enabled);
+ // TODO: implement native window enabled state on Linux/BSD.
+ }
- pub(crate) fn is_enabled(&self) -> bool {
- let _ = self;
- // TODO: query native window enabled state on Linux/BSD.
- true
- }
+ pub(crate) fn is_enabled(&self) -> bool {
+ let _ = self;
+ // TODO: query native window enabled state on Linux/BSD.
+ true
+ }
- pub(crate) fn set_background_color(&self, color: Option) {
- let xid = self.xid();
- let Some(color) = color else {
- return;
- };
+ pub(crate) fn set_background_color(&self, color: Option) {
+ let xid = self.xid();
+ let Some(color) = color else {
+ return;
+ };
- super::utils::with_x11((), |xlib, display| unsafe {
- let screen = (xlib.XDefaultScreen)(display);
- let colormap = (xlib.XDefaultColormap)(display, screen);
- let mut xcolor = x11_dl::xlib::XColor {
- pixel: 0,
- red: u16::from(color.0) * 257,
- green: u16::from(color.1) * 257,
- blue: u16::from(color.2) * 257,
- flags: x11_dl::xlib::DoRed | x11_dl::xlib::DoGreen | x11_dl::xlib::DoBlue,
- pad: 0,
- };
+ super::utils::with_x11((), |xlib, display| unsafe {
+ let screen = (xlib.XDefaultScreen)(display);
+ let colormap = (xlib.XDefaultColormap)(display, screen);
+ let mut xcolor = x11_dl::xlib::XColor {
+ pixel: 0,
+ red: u16::from(color.0) * 257,
+ green: u16::from(color.1) * 257,
+ blue: u16::from(color.2) * 257,
+ flags: x11_dl::xlib::DoRed | x11_dl::xlib::DoGreen | x11_dl::xlib::DoBlue,
+ pad: 0,
+ };
- if (xlib.XAllocColor)(display, colormap, &mut xcolor) != 0 {
- (xlib.XSetWindowBackground)(display, xid, xcolor.pixel);
- (xlib.XClearWindow)(display, xid);
- }
- });
- }
+ if (xlib.XAllocColor)(display, colormap, &mut xcolor) != 0 {
+ (xlib.XSetWindowBackground)(display, xid, xcolor.pixel);
+ (xlib.XClearWindow)(display, xid);
+ }
+ });
+ }
- pub(crate) fn set_skip_taskbar(&self, skip: bool) {
- set_wm_state(self.xid(), skip, "_NET_WM_STATE_SKIP_TASKBAR", None);
- }
+ pub(crate) fn set_skip_taskbar(&self, skip: bool) {
+ set_wm_state(self.xid(), skip, "_NET_WM_STATE_SKIP_TASKBAR", None);
+ }
- pub(crate) fn set_visible_on_all_workspaces(&self, visible: bool) {
- set_wm_state(self.xid(), visible, "_NET_WM_STATE_STICKY", None);
- }
+ pub(crate) fn set_visible_on_all_workspaces(&self, visible: bool) {
+ set_wm_state(self.xid(), visible, "_NET_WM_STATE_STICKY", None);
+ }
- pub(crate) fn set_progress_bar(&self, state: ProgressBarState) {
- taskbar::set_progress_bar(state);
- }
+ pub(crate) fn set_progress_bar(&self, state: ProgressBarState) {
+ taskbar::set_progress_bar(state);
+ }
}
diff --git a/src/platform/macos/appkit_state.rs b/src/platform/macos/appkit_state.rs
index e285b05..42f714c 100644
--- a/src/platform/macos/appkit_state.rs
+++ b/src/platform/macos/appkit_state.rs
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT
use std::{
- ffi::c_void,
- sync::{Arc, RwLock},
+ ffi::c_void,
+ sync::{Arc, RwLock},
};
use objc2_app_kit::NSWindow;
@@ -14,20 +14,20 @@ use super::utils;
#[derive(Debug, Default)]
pub(crate) struct AppkitState {
- pub(crate) traffic_light_position: Option,
+ pub(crate) traffic_light_position: Option,
}
impl AppkitState {
- pub(super) fn associate(state: &Arc>, nswindow: &NSWindow) {
- utils::set_associated_data(nswindow, Self::key(), Arc::as_ptr(state));
- }
+ pub(super) fn associate(state: &Arc>, nswindow: &NSWindow) {
+ utils::set_associated_data(nswindow, Self::key(), Arc::as_ptr(state));
+ }
- pub(super) fn from_window(nswindow: &NSWindow) -> Option<&RwLock> {
- unsafe { utils::associated_data(nswindow, Self::key()) }
- }
+ pub(super) fn from_window(nswindow: &NSWindow) -> Option<&RwLock> {
+ unsafe { utils::associated_data(nswindow, Self::key()) }
+ }
- fn key() -> *const c_void {
- static APPKIT_STATE_KEY: u8 = 0;
- &APPKIT_STATE_KEY as *const u8 as *const c_void
- }
+ fn key() -> *const c_void {
+ static APPKIT_STATE_KEY: u8 = 0;
+ &APPKIT_STATE_KEY as *const u8 as *const c_void
+ }
}
diff --git a/src/platform/macos/application.rs b/src/platform/macos/application.rs
index f40c094..6eaf04a 100644
--- a/src/platform/macos/application.rs
+++ b/src/platform/macos/application.rs
@@ -3,20 +3,20 @@
// SPDX-License-Identifier: MIT
use std::{
- cell::Cell,
- time::{Duration, Instant},
+ cell::Cell,
+ time::{Duration, Instant},
};
use cef::application_mac::{CefAppProtocol, CrAppControlProtocol, CrAppProtocol};
use objc2::{
- ClassType, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, extern_methods,
- msg_send,
- rc::Retained,
- runtime::{AnyObject, Bool, ProtocolObject},
+ ClassType, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, extern_methods,
+ msg_send,
+ rc::Retained,
+ runtime::{AnyObject, Bool, ProtocolObject},
};
use objc2_app_kit::{
- NSApp, NSApplication, NSApplicationActivationOptions, NSApplicationDelegate,
- NSApplicationTerminateReply, NSEvent, NSRunningApplication,
+ NSApp, NSApplication, NSApplicationActivationOptions, NSApplicationDelegate,
+ NSApplicationTerminateReply, NSEvent, NSRunningApplication,
};
use objc2_application_services::kProcessTransformToForegroundApplication;
use objc2_foundation::{NSArray, NSObject, NSObjectProtocol, NSString, NSURL};
@@ -25,20 +25,20 @@ use super::utils;
#[derive(Default)]
pub(crate) struct CefWinitApplicationIvars {
- handling_send_event: Cell,
- last_dock_show_ms: Cell,
- delegate: Cell<*const AppDelegate>,
+ handling_send_event: Cell,
+ last_dock_show_ms: Cell,
+ delegate: Cell<*const AppDelegate>,
}
pub(crate) enum AppDelegateEvent {
- TryTerminate,
- Reopen { has_visible_windows: bool },
- AccessibilityChanged { enabled: bool },
- OpenURLs { urls: Vec },
+ TryTerminate,
+ Reopen { has_visible_windows: bool },
+ AccessibilityChanged { enabled: bool },
+ OpenURLs { urls: Vec },
}
pub(crate) struct CefAppDelegateIvars {
- on_event: Box,
+ on_event: Box,
}
define_class!(
@@ -178,66 +178,65 @@ define_class!(
);
impl AppDelegate {
- fn new(mtm: MainThreadMarker, on_event: Box) -> Retained {
- let this = Self::alloc(mtm).set_ivars(CefAppDelegateIvars { on_event });
- unsafe { msg_send![super(this), init] }
- }
+ fn new(mtm: MainThreadMarker, on_event: Box) -> Retained {
+ let this = Self::alloc(mtm).set_ivars(CefAppDelegateIvars { on_event });
+ unsafe { msg_send![super(this), init] }
+ }
- fn emit(&self, event: AppDelegateEvent) {
- (self.ivars().on_event)(event);
- }
+ fn emit(&self, event: AppDelegateEvent) {
+ (self.ivars().on_event)(event);
+ }
}
impl CefWinitApplication {
- extern_methods! {
- #[unsafe(method(sharedApplication))]
- pub fn shared_application() -> Retained;
- }
+ extern_methods! {
+ #[unsafe(method(sharedApplication))]
+ pub fn shared_application() -> Retained;
+ }
- pub fn last_dock_show(&self) -> Option {
- match self.ivars().last_dock_show_ms.get() {
- 0 => None,
- // Store elapsed milliseconds + 1 so the zero-initialized ivar can mean
- // "not set" for AppKit-created NSApplication instances.
- milliseconds => Some(utils::instant_epoch() + Duration::from_millis(milliseconds - 1)),
+ pub fn last_dock_show(&self) -> Option {
+ match self.ivars().last_dock_show_ms.get() {
+ 0 => None,
+ // Store elapsed milliseconds + 1 so the zero-initialized ivar can mean
+ // "not set" for AppKit-created NSApplication instances.
+ milliseconds => Some(utils::instant_epoch() + Duration::from_millis(milliseconds - 1)),
+ }
}
- }
- pub fn set_last_dock_show(&self, instant: Instant) {
- let milliseconds = instant
- .saturating_duration_since(utils::instant_epoch())
- .as_millis()
- .try_into()
- .unwrap_or(u64::MAX);
- self
- .ivars()
- .last_dock_show_ms
- // Offset by one so `0` remains the zero-initialized "not set" sentinel.
- .set(milliseconds.saturating_add(1));
- }
+ pub fn set_last_dock_show(&self, instant: Instant) {
+ let milliseconds = instant
+ .saturating_duration_since(utils::instant_epoch())
+ .as_millis()
+ .try_into()
+ .unwrap_or(u64::MAX);
+ self.ivars()
+ .last_dock_show_ms
+ // Offset by one so `0` remains the zero-initialized "not set" sentinel.
+ .set(milliseconds.saturating_add(1));
+ }
- fn delegate(&self) -> Option<&AppDelegate> {
- unsafe { self.ivars().delegate.get().as_ref() }
- }
+ fn delegate(&self) -> Option<&AppDelegate> {
+ unsafe { self.ivars().delegate.get().as_ref() }
+ }
}
pub fn setup_application() {
- let _ = CefWinitApplication::shared_application();
- let mtm = MainThreadMarker::new().expect("macOS application must start on the main thread");
- assert!(NSApp(mtm).isKindOfClass(CefWinitApplication::class()));
+ let _ = CefWinitApplication::shared_application();
+ let mtm = MainThreadMarker::new().expect("macOS application must start on the main thread");
+ assert!(NSApp(mtm).isKindOfClass(CefWinitApplication::class()));
}
pub(crate) fn set_application_event_handler(
- on_event: Box,
+ on_event: Box,
) -> Retained {
- let mtm = MainThreadMarker::new().expect("macOS application must start on the main thread");
- let delegate = AppDelegate::new(mtm, on_event);
- let app = CefWinitApplication::shared_application();
-
- // `NSApplication.delegate` is weak. The runtime owns the retained delegate;
- // the app stores only a non-owning pointer because AppKit creates the
- // NSApplication singleton and its ivars must stay zero-initialized/no-drop.
- app.ivars().delegate.set(&*delegate as *const AppDelegate);
- app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
- delegate
+ let mtm = MainThreadMarker::new().expect("macOS application must start on the main thread");
+ let delegate = AppDelegate::new(mtm, on_event);
+ let app = CefWinitApplication::shared_application();
+
+ // `NSApplication.delegate` is weak. The runtime owns the retained delegate;
+ // the app stores only a non-owning pointer because AppKit creates the
+ // NSApplication singleton and its ivars must stay zero-initialized/no-drop.
+ app.ivars().delegate.set(&*delegate as *const AppDelegate);
+ app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
+ delegate
}
diff --git a/src/platform/macos/dock.rs b/src/platform/macos/dock.rs
index bb07709..b4876b9 100644
--- a/src/platform/macos/dock.rs
+++ b/src/platform/macos/dock.rs
@@ -7,7 +7,7 @@ use std::time::{Duration, Instant};
use objc2::{msg_send, runtime::AnyObject, sel};
use objc2_app_kit::{NSApplicationActivationOptions, NSRunningApplication};
use objc2_application_services::{
- kProcessTransformToForegroundApplication, kProcessTransformToUIElementApplication,
+ kProcessTransformToForegroundApplication, kProcessTransformToUIElementApplication,
};
use objc2_foundation::NSString;
@@ -17,77 +17,78 @@ const DOCK_SHOW_TIMEOUT: Duration = Duration::from_secs(1);
const DOCK_BUNDLE_IDENTIFIER: &str = "com.apple.dock";
impl CefWinitApplication {
- pub fn set_dock_visibility(&self, visible: bool) {
- if visible {
- self.set_dock_show();
- } else {
- self.set_dock_hide();
+ pub fn set_dock_visibility(&self, visible: bool) {
+ if visible {
+ self.set_dock_show();
+ } else {
+ self.set_dock_hide();
+ }
}
- }
- fn set_dock_hide(&self) {
- let now = Instant::now();
- if let Some(last_dock_show_time) = self.last_dock_show() {
- // TransformProcessType from UIElement back to foreground is asynchronous
- // and does not expose a completion signal. Electron found that rapid
- // hide/show cycles can race the macOS Dock and leave duplicate app icons
- // behind, so it ignores hide requests immediately after showing.
- // https://github.com/electron/electron/blob/88cd4b418618424fbcd11917fffee489f534ad72/shell/browser/browser_mac.mm#L2376-L2408
- if now.duration_since(last_dock_show_time) < DOCK_SHOW_TIMEOUT {
- return;
- }
- }
+ fn set_dock_hide(&self) {
+ let now = Instant::now();
+ if let Some(last_dock_show_time) = self.last_dock_show() {
+ // TransformProcessType from UIElement back to foreground is asynchronous
+ // and does not expose a completion signal. Electron found that rapid
+ // hide/show cycles can race the macOS Dock and leave duplicate app icons
+ // behind, so it ignores hide requests immediately after showing.
+ // https://github.com/electron/electron/blob/88cd4b418618424fbcd11917fffee489f534ad72/shell/browser/browser_mac.mm#L2376-L2408
+ if now.duration_since(last_dock_show_time) < DOCK_SHOW_TIMEOUT {
+ return;
+ }
+ }
- self.set_windows_can_hide(false);
- utils::transform_process_type(kProcessTransformToUIElementApplication);
- }
+ self.set_windows_can_hide(false);
+ utils::transform_process_type(kProcessTransformToUIElementApplication);
+ }
- fn set_dock_show(&self) {
- self.set_last_dock_show(Instant::now());
- self.set_windows_can_hide(true);
+ fn set_dock_show(&self) {
+ self.set_last_dock_show(Instant::now());
+ self.set_windows_can_hide(true);
- if NSRunningApplication::currentApplication().isActive() {
- // TransformProcessType is buggy when bringing an active UIElement app
- // back to foreground. Electron works around it by activating Dock first,
- // then delaying the foreground transform and app reactivation:
- // https://github.com/electron/electron/blob/88cd4b418618424fbcd11917fffee489f534ad72/shell/browser/browser_mac.mm#L2424-L2475
- activate_dock();
- self.perform_delayed_dock_show();
- } else {
- utils::transform_process_type(kProcessTransformToForegroundApplication);
+ if NSRunningApplication::currentApplication().isActive() {
+ // TransformProcessType is buggy when bringing an active UIElement app
+ // back to foreground. Electron works around it by activating Dock first,
+ // then delaying the foreground transform and app reactivation:
+ // https://github.com/electron/electron/blob/88cd4b418618424fbcd11917fffee489f534ad72/shell/browser/browser_mac.mm#L2424-L2475
+ activate_dock();
+ self.perform_delayed_dock_show();
+ } else {
+ utils::transform_process_type(kProcessTransformToForegroundApplication);
+ }
}
- }
- fn set_windows_can_hide(&self, can_hide: bool) {
- let windows = self.windows();
- for idx in 0..windows.count() {
- windows.objectAtIndex(idx).setCanHide(can_hide);
+ fn set_windows_can_hide(&self, can_hide: bool) {
+ let windows = self.windows();
+ for idx in 0..windows.count() {
+ windows.objectAtIndex(idx).setCanHide(can_hide);
+ }
}
- }
- fn perform_delayed_dock_show(&self) {
- unsafe {
- let _: () = msg_send![
- self,
- performSelector: sel!(tauriTransformProcessToForeground),
- withObject: None::<&AnyObject>,
- afterDelay: 1.0f64,
- ];
- let _: () = msg_send![
- self,
- performSelector: sel!(tauriActivateCurrentApplication),
- withObject: None::<&AnyObject>,
- afterDelay: 2.0f64,
- ];
+ fn perform_delayed_dock_show(&self) {
+ unsafe {
+ let _: () = msg_send![
+ self,
+ performSelector: sel!(tauriTransformProcessToForeground),
+ withObject: None::<&AnyObject>,
+ afterDelay: 1.0f64,
+ ];
+ let _: () = msg_send![
+ self,
+ performSelector: sel!(tauriActivateCurrentApplication),
+ withObject: None::<&AnyObject>,
+ afterDelay: 2.0f64,
+ ];
+ }
}
- }
}
fn activate_dock() {
- let dock_id = NSString::from_str(DOCK_BUNDLE_IDENTIFIER);
- let dock_apps = NSRunningApplication::runningApplicationsWithBundleIdentifier(&dock_id);
- if dock_apps.count() > 0 {
- let app = dock_apps.objectAtIndex(0);
- app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps);
- }
+ let dock_id = NSString::from_str(DOCK_BUNDLE_IDENTIFIER);
+ let dock_apps = NSRunningApplication::runningApplicationsWithBundleIdentifier(&dock_id);
+ if dock_apps.count() > 0 {
+ let app = dock_apps.objectAtIndex(0);
+ #[allow(deprecated)]
+ app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps);
+ }
}
diff --git a/src/platform/macos/event_loop.rs b/src/platform/macos/event_loop.rs
index 7b6c844..86adf3b 100644
--- a/src/platform/macos/event_loop.rs
+++ b/src/platform/macos/event_loop.rs
@@ -6,8 +6,8 @@ use objc2::MainThreadMarker;
use objc2_app_kit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSScreen};
use objc2_foundation::{NSPoint, NSString};
use tauri_runtime::{
- Error, ProgressBarState, Result,
- dpi::{LogicalPosition, PhysicalPosition},
+ Error, ProgressBarState, Result,
+ dpi::{LogicalPosition, PhysicalPosition},
};
use winit::event_loop::ActiveEventLoop;
@@ -16,83 +16,86 @@ use crate::platform::EventLoopExt;
use super::{application::CefWinitApplication, progress};
impl EventLoopExt for dyn ActiveEventLoop + '_ {
- fn set_activation_policy(&self, policy: tauri_runtime::ActivationPolicy) {
- let Some(mtm) = MainThreadMarker::new() else {
- return;
- };
-
- let app = NSApplication::sharedApplication(mtm);
- let policy = match policy {
- tauri_runtime::ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
- tauri_runtime::ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
- tauri_runtime::ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
- _ => NSApplicationActivationPolicy::Regular,
- };
- app.setActivationPolicy(policy);
- }
-
- fn set_dock_visibility(&self, visible: bool) {
- let Some(_mtm) = MainThreadMarker::new() else {
- return;
- };
-
- let app = CefWinitApplication::shared_application();
- app.set_dock_visibility(visible);
- }
-
- fn show_application(&self) {
- let Some(mtm) = MainThreadMarker::new() else {
- return;
- };
-
- NSApp(mtm).unhide(None);
- }
-
- fn hide_application(&self) {
- let Some(mtm) = MainThreadMarker::new() else {
- return;
- };
-
- NSApp(mtm).hide(None);
- }
-
- fn set_progress_bar(&self, state: ProgressBarState) {
- progress::set_dock_progress_bar(state);
- }
-
- fn set_badge_count(&self, count: Option, _desktop_filename: Option) {
- self.set_badge_label(count.map(|count| count.to_string()));
- }
-
- fn set_badge_label(&self, label: Option) {
- let Some(mtm) = MainThreadMarker::new() else {
- return;
- };
-
- let app = NSApplication::sharedApplication(mtm);
- let dock_tile = app.dockTile();
- let ns_label = label.map(|label| NSString::from_str(&label));
- dock_tile.setBadgeLabel(ns_label.as_deref());
- }
-
- fn cursor_position(&self) -> Result> {
- let Some(mtm) = MainThreadMarker::new() else {
- return Err(Error::FailedToGetCursorPosition);
- };
-
- // `NSEvent::mouseLocation` is in global coordinates with a bottom-left
- // origin, in logical points. The global origin is the bottom-left of the
- // primary screen, so flip Y against the primary screen height, then scale to
- // physical pixels to satisfy the trait contract (the Windows/Linux backends
- // and wry/tao all return physical pixels — returning logical here is off by
- // the scale factor on HiDPI/Retina displays).
- let location: NSPoint = NSEvent::mouseLocation();
- let primary =
- unsafe { NSScreen::screens(mtm).firstObject() }.ok_or(Error::FailedToGetCursorPosition)?;
- let screen_height = primary.frame().size.height;
- let scale = primary.backingScaleFactor();
-
- let logical = LogicalPosition::new(location.x, screen_height - location.y);
- Ok(logical.to_physical(scale))
- }
+ fn set_activation_policy(&self, policy: tauri_runtime::ActivationPolicy) {
+ let Some(mtm) = MainThreadMarker::new() else {
+ return;
+ };
+
+ let app = NSApplication::sharedApplication(mtm);
+ let policy = match policy {
+ tauri_runtime::ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
+ tauri_runtime::ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
+ tauri_runtime::ActivationPolicy::Prohibited => {
+ NSApplicationActivationPolicy::Prohibited
+ }
+ _ => NSApplicationActivationPolicy::Regular,
+ };
+ app.setActivationPolicy(policy);
+ }
+
+ fn set_dock_visibility(&self, visible: bool) {
+ let Some(_mtm) = MainThreadMarker::new() else {
+ return;
+ };
+
+ let app = CefWinitApplication::shared_application();
+ app.set_dock_visibility(visible);
+ }
+
+ fn show_application(&self) {
+ let Some(mtm) = MainThreadMarker::new() else {
+ return;
+ };
+
+ NSApp(mtm).unhide(None);
+ }
+
+ fn hide_application(&self) {
+ let Some(mtm) = MainThreadMarker::new() else {
+ return;
+ };
+
+ NSApp(mtm).hide(None);
+ }
+
+ fn set_progress_bar(&self, state: ProgressBarState) {
+ progress::set_dock_progress_bar(state);
+ }
+
+ fn set_badge_count(&self, count: Option, _desktop_filename: Option