use std::cell::{Cell, RefCell};
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::os::fd::OwnedFd;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use calloop::ping::Ping;
use rustix::event::{PollFd, PollFlags};
use rustix::pipe::{self, PipeFlags};
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use sctk::shell::xdg::popup::Popup;
use tracing::warn;
use wayland_protocols::xdg::shell::client::xdg_positioner;
use super::types::xdg_popup::{holds_topmost_grab, PopupId, PopupSettings, PopupState};
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{DndWindowEvent, Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::{
ActiveEventLoop as PlatformActiveEventLoop, OsError, PlatformCustomCursor,
};
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource};
mod proxy;
pub mod sink;
pub use proxy::EventLoopProxy;
use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState;
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
pub struct EventLoop<T: 'static> {
loop_running: bool,
buffer_sink: EventSink,
compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>,
user_events_sender: calloop::channel::Sender<T>,
pending_user_events: Rc<RefCell<Vec<T>>>,
wayland_dispatcher: WaylandDispatcher,
connection: Connection,
window_target: RootActiveEventLoop,
event_loop: calloop::EventLoop<'static, WinitState>,
pump_event_notifier: Option<PumpEventNotifier>,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, EventLoopError> {
macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
};
}
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
let (globals, mut event_queue) =
map_err!(globals::registry_queue_init(&connection), WaylandError::Global)?;
let queue_handle = event_queue.handle();
let event_loop =
map_err!(calloop::EventLoop::<WinitState>::try_new(), WaylandError::Calloop)?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
.map_err(|error| os_error!(error))?;
map_err!(event_queue.roundtrip(&mut winit_state), WaylandError::Dispatch)?;
let wayland_source = WaylandSource::new(connection.clone(), event_queue);
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| {
let result = queue.dispatch_pending(winit_state);
if result.is_ok()
&& (!winit_state.events_sink.is_empty()
|| !winit_state.window_compositor_updates.is_empty())
{
winit_state.dispatched_events = true;
}
result
});
map_err!(
event_loop.handle().register_dispatcher(wayland_dispatcher.clone()),
WaylandError::Calloop
)?;
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
let result = event_loop
.handle()
.insert_source(user_events_channel, move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
})
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
let (event_loop_awakener, event_loop_awakener_source) = map_err!(
calloop::ping::make_ping()
.map_err(|error| CalloopError::OtherError(Box::new(error).into())),
WaylandError::Calloop
)?;
let result = event_loop
.handle()
.insert_source(event_loop_awakener_source, move |_, _, winit_state: &mut WinitState| {
winit_state.dispatched_events = true;
})
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
let window_target = ActiveEventLoop {
connection: connection.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
event_loop_awakener,
queue_handle,
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
state: RefCell::new(winit_state),
};
let event_loop = Self {
loop_running: false,
compositor_updates: Vec::new(),
buffer_sink: EventSink::default(),
window_ids: Vec::new(),
connection,
wayland_dispatcher,
user_events_sender,
pending_user_events,
event_loop,
window_target: RootActiveEventLoop {
p: PlatformActiveEventLoop::Wayland(window_target),
_marker: PhantomData,
},
pump_event_notifier: None,
};
Ok(event_loop)
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let exit = loop {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
},
_ => {
continue;
},
}
};
let _ = self.roundtrip().map_err(EventLoopError::Os);
exit
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
if !self.loop_running {
self.loop_running = true;
self.single_iteration(&mut callback, StartCause::Init);
}
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
if timeout.is_some() && self.pump_event_notifier.is_none() {
let awakener = match &self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => {
window_target.event_loop_awakener.clone()
},
#[cfg(x11_platform)]
PlatformActiveEventLoop::X(_) => unreachable!(),
};
self.pump_event_notifier =
Some(PumpEventNotifier::spawn(self.connection.clone(), awakener));
}
if let Some(pump_event_notifier) = self.pump_event_notifier.as_ref() {
*pump_event_notifier.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
pump_event_notifier.control.1.notify_one();
}
PumpStatus::Continue
}
}
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let cause = loop {
let start = Instant::now();
timeout = {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
min_timeout(control_flow_timeout, timeout)
};
if let Err(err) = self.connection.flush() {
use sctk::reexports::client::backend::WaylandError as BackendError;
tracing::error!("Wayland flush error: {:?} (kind={:?})", err, match &err {
BackendError::Io(e) => Some(e.kind()),
_ => None,
});
match err {
BackendError::Io(ref e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
warn!("Wayland flush got broken pipe, retrying once");
if let Err(retry_err) = self.connection.flush() {
tracing::error!("Wayland flush retry error: {:?}", retry_err);
self.set_exit_code(1);
return;
}
},
_ => {
self.set_exit_code(1);
return;
},
}
}
let dispatch_start = Instant::now();
if let Err(error) = self.loop_dispatch(timeout) {
tracing::error!(
"loop_dispatch error: {:?} (os_error={:?})",
error,
error.raw_os_error()
);
let exit_code = error.raw_os_error().unwrap_or(1);
self.set_exit_code(exit_code);
return;
}
let dispatch_elapsed = dispatch_start.elapsed();
if dispatch_elapsed.as_millis() > 100 {
tracing::warn!("loop_dispatch took {:?}", dispatch_elapsed);
} else if dispatch_elapsed.as_millis() > 16 {
tracing::debug!("loop_dispatch took {:?}", dispatch_elapsed);
}
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled { start, requested_resume: Some(deadline) }
} else {
StartCause::ResumeTimeReached { start, requested_resume: deadline }
}
},
};
let dispatched_events = self.with_state(|state| state.dispatched_events);
if matches!(cause, StartCause::WaitCancelled { .. })
&& !dispatched_events
&& timeout.is_none()
{
continue;
}
break cause;
};
self.single_iteration(&mut callback, cause);
}
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let mut compositor_updates = std::mem::take(&mut self.compositor_updates);
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
callback(Event::NewEvents(cause), &self.window_target);
if cause == StartCause::Init {
callback(Event::Resumed, &self.window_target);
}
for user_event in self.pending_user_events.borrow_mut().drain(..) {
callback(Event::UserEvent(user_event), &self.window_target);
}
self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates));
for compositor_update in compositor_updates.iter_mut() {
if let Some(configure) = compositor_update.pending_configure.take() {
let window_id = compositor_update.window_id;
tracing::debug!(
"single_iteration: applying deferred configure for window={:?} \
size=({:?},{:?}) state={:?}",
window_id,
configure.new_size.0.map(|v| v.get()),
configure.new_size.1.map(|v| v.get()),
configure.state,
);
let (resized, occluded) = self.with_state(|state| {
let mut window = state
.windows
.get_mut()
.get_mut(&window_id)
.expect("got configure for dead window.")
.lock()
.unwrap();
let resized =
window.configure(configure, &state.shm, &state.subcompositor_state);
(resized, window.take_occluded())
});
tracing::debug!("single_iteration: configure applied, resized={}", resized);
compositor_update.resized |= resized;
compositor_update.occluded = occluded.or(compositor_update.occluded);
}
}
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if compositor_update.scale_changed {
let (physical_size, scale_factor) = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
(size, scale_factor)
});
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
},
&self.window_target,
);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
let new_logical_size: LogicalSize<f64> =
physical_size.to_logical(scale_factor);
window.request_inner_size(new_logical_size.into());
});
compositor_update.resized = true;
}
}
if compositor_update.resized || compositor_update.scale_changed {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
size
});
let resize_callback_start = Instant::now();
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
);
let resize_callback_elapsed = resize_callback_start.elapsed();
if resize_callback_elapsed.as_millis() > 1 {
tracing::warn!(
"single_iteration: Resized callback took {:?}",
resize_callback_elapsed,
);
}
}
if let Some(occluded) = compositor_update.occluded {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Occluded(occluded),
},
&self.window_target,
);
}
if compositor_update.close_window {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
);
}
}
self.with_state(|state| {
for (&window_id, window) in state.windows.get_mut().iter_mut() {
let mut window = window.lock().unwrap();
for event in window.take_special_action_events() {
buffer_sink.push_window_event(WindowEvent::SpecialAction(event), window_id);
}
for identity in window.take_identity_events() {
buffer_sink
.push_window_event(WindowEvent::IdentityChanged(identity), window_id);
}
for request in window.take_app_command_events() {
buffer_sink.push_window_event(WindowEvent::AppCommand(request), window_id);
}
}
});
self.with_state(|state| {
let mut guard = state.dnd_session.shared_offer.lock().unwrap();
let pending = std::mem::take(&mut guard.pending_data);
drop(guard);
if !pending.is_empty() {
let target_window = state
.dnd_session
.drop_window
.take()
.or(state.dnd_session.focused_window)
.or_else(|| state.windows.borrow().keys().next().copied());
if let Some(wid) = target_window {
for (mime_type, data) in pending {
buffer_sink.push_window_event(
WindowEvent::Dnd(DndWindowEvent::DataReceived { mime_type, data }),
wid,
);
}
}
}
});
self.with_state(|state| {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
self.with_state(|state| {
window_ids.extend(state.window_requests.get_mut().keys());
});
for window_id in window_ids.iter() {
let event = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(window_id));
mem::drop(state.windows.get_mut().remove(window_id));
return Some(WindowEvent::Destroyed);
}
let mut window =
state.windows.get_mut().get_mut(window_id).unwrap().lock().unwrap();
if window.frame_callback_state() == FrameCallbackState::Requested {
return None;
}
window.frame_callback_reset();
let mut redraw_requested =
window_requests.get(window_id).unwrap().take_redraw_requested();
redraw_requested |= window.refresh_frame();
redraw_requested.then_some(WindowEvent::RedrawRequested)
});
if let Some(event) = event {
callback(
Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event },
&self.window_target,
);
}
}
self.with_state(|state| {
state.dispatched_events = false;
});
callback(Event::AboutToWait, &self.window_target);
let mut wake_up = false;
for window_id in window_ids.drain(..) {
wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) {
Some(window) => {
let refresh = window.lock().unwrap().refresh_frame();
if refresh {
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
}
refresh
},
None => false,
});
}
if wake_up {
match &self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => {
window_target.event_loop_awakener.ping();
},
#[cfg(x11_platform)]
PlatformActiveEventLoop::X(_) => unreachable!(),
}
}
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
std::mem::swap(&mut self.window_ids, &mut window_ids);
}
#[inline]
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.user_events_sender.clone())
}
#[inline]
pub fn window_target(&self) -> &RootActiveEventLoop {
&self.window_target
}
fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U {
let state = match &mut self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(x11_platform)]
_ => unreachable!(),
};
callback(state)
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
let state = match &mut self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
self.event_loop.dispatch(timeout, state).map_err(|error| {
tracing::error!("Error dispatching event loop: {}", error);
error.into()
})
}
fn roundtrip(&mut self) -> Result<usize, RootOsError> {
let state = match &mut self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let event_queue = wayland_source.queue();
event_queue.roundtrip(state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})
}
fn control_flow(&self) -> ControlFlow {
self.window_target.p.control_flow()
}
fn exiting(&self) -> bool {
self.window_target.p.exiting()
}
fn set_exit_code(&self, code: i32) {
self.window_target.p.set_exit_code(code)
}
fn exit_code(&self) -> Option<i32> {
self.window_target.p.exit_code()
}
}
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
pub struct ActiveEventLoop {
pub event_loop_awakener: Ping,
pub queue_handle: QueueHandle<WinitState>,
pub(crate) control_flow: Cell<ControlFlow>,
pub(crate) exit: Cell<Option<i32>>,
pub state: RefCell<WinitState>,
pub wayland_dispatcher: WaylandDispatcher,
pub connection: Connection,
}
impl ActiveEventLoop {
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn clear_exit(&self) {
self.exit.set(None)
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
pub(crate) fn create_custom_cursor(&self, cursor: CustomCursorSource) -> RootCustomCursor {
RootCustomCursor {
inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))),
}
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
use sctk::reexports::client::Proxy;
let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
rwh_05::RawDisplayHandle::Wayland(display_handle)
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use sctk::reexports::client::Proxy;
Ok(rwh_06::WaylandDisplayHandle::new({
let ptr = self.connection.display().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null")
})
.into())
}
pub fn create_popup(&self, settings: PopupSettings) -> Option<PopupId> {
self.create_popup_under(settings, None)
}
pub fn create_child_popup(
&self,
parent_popup: PopupId,
settings: PopupSettings,
) -> Option<PopupId> {
self.create_popup_under(settings, Some(parent_popup))
}
fn create_popup_under(
&self,
settings: PopupSettings,
parent_popup: Option<PopupId>,
) -> Option<PopupId> {
use sctk::compositor::{Region, Surface};
use sctk::shell::xdg::{XdgPositioner, XdgSurface as XdgSurfaceTrait};
use sctk::shell::WaylandSurface;
let mut state = self.state.borrow_mut();
let internal_parent_id = WindowId(u64::from(settings.parent_id));
let (root_xdg_surface, root_wl_surface, parent_scale_factor) = {
let parent_window = state.windows.borrow().get(&internal_parent_id)?.clone();
let parent_guard = parent_window.lock().ok()?;
(
parent_guard.window.xdg_surface().clone(),
parent_guard.window.wl_surface().clone(),
parent_guard.scale_factor(),
)
};
let (parent_xdg_surface, parent_wl_surface, parent_holds_grab) = match parent_popup {
None => (root_xdg_surface, root_wl_surface, true),
Some(parent_popup) => {
let Some(parent) = state.popups.get(&parent_popup) else {
tracing::warn!("create_popup: parent popup {parent_popup:?} not found");
return None;
};
if !parent.configured {
tracing::warn!(
"create_popup: parent popup {parent_popup:?} not configured yet"
);
return None;
}
if parent.parent_id != internal_parent_id {
tracing::warn!(
"create_popup: parent popup {parent_popup:?} belongs to another window"
);
return None;
}
(
parent.popup.xdg_surface().clone(),
parent.popup.wl_surface().clone(),
holds_topmost_grab(
state.popups.iter().map(|(id, popup)| (*id, popup.grabbed)),
parent_popup,
),
)
},
};
tracing::debug!(
"create_popup: parent_popup={:?}, parent_scale_factor={}, size=({}, {}), \
anchor_rect=({}, {}, {}, {}), offset=({}, {}), anchor={:?}, gravity={:?}",
parent_popup,
parent_scale_factor,
settings.size.0,
settings.size.1,
settings.anchor_rect.0,
settings.anchor_rect.1,
settings.anchor_rect.2,
settings.anchor_rect.3,
settings.offset.0,
settings.offset.1,
settings.anchor,
settings.gravity
);
let surface = Surface::new(&*state.compositor_state, &self.queue_handle).ok()?;
let positioner = XdgPositioner::new(&state.xdg_shell).ok()?;
if let Some((_gx, _gy, gw, gh)) = settings.window_geometry {
positioner.set_size(gw, gh);
} else {
positioner.set_size(settings.size.0 as i32, settings.size.1 as i32);
}
positioner.set_anchor_rect(
settings.anchor_rect.0,
settings.anchor_rect.1,
settings.anchor_rect.2,
settings.anchor_rect.3,
);
positioner.set_anchor(settings.anchor.to_xdg());
positioner.set_gravity(settings.gravity.to_xdg());
positioner.set_offset(settings.offset.0, settings.offset.1);
positioner.set_reactive();
positioner.set_constraint_adjustment(
xdg_positioner::ConstraintAdjustment::from_bits_truncate(
settings.constraint_adjustment,
),
);
let popup = Popup::from_surface(
Some(&parent_xdg_surface),
&positioner,
&self.queue_handle,
surface,
&state.xdg_shell,
)
.ok()?;
let viewport = state
.viewporter_state
.as_ref()
.map(|vp_state| vp_state.get_viewport(popup.wl_surface(), &self.queue_handle));
let mut grabbed = false;
if settings.grab && !parent_holds_grab {
tracing::warn!(
"create_popup: parent popup isn't the topmost grab; creating this one without"
);
} else if settings.grab {
let grab_info = state.pointer_surfaces.values().next().and_then(|ptr| {
use sctk::reexports::client::Proxy;
let wl_ptr = ptr.pointer();
let winit_data =
wl_ptr.data::<crate::platform_impl::wayland::seat::WinitPointerData>()?;
let serial = winit_data.latest_button_serial();
if serial == 0 {
return None;
}
Some((winit_data.seat().clone(), serial))
});
if let Some((seat, serial)) = grab_info {
popup.xdg_popup().grab(&seat, serial);
grabbed = true;
}
}
if let Some((gx, gy, gw, gh)) = settings.window_geometry {
popup.xdg_surface().set_window_geometry(gx, gy, gw, gh);
} else {
popup.xdg_surface().set_window_geometry(
0,
0,
settings.size.0 as i32,
settings.size.1 as i32,
);
}
if let Some((gx, gy, gw, gh)) = settings.window_geometry {
if let Ok(region) = Region::new(&*state.compositor_state) {
region.add(gx, gy, gw, gh);
popup.wl_surface().set_input_region(Some(region.wl_region()));
}
}
let corner_radius = settings.corner_radius.and_then(|radius| {
let manager = state.corner_radius_manager.as_ref()?;
let controller = manager.get_corner_radius(popup.wl_surface(), &self.queue_handle);
controller.set_radius(radius, radius, radius, radius);
Some(controller)
});
let shadow = settings
.shadow
.then(|| {
let controller = state
.layer_shadow_manager
.as_ref()
.map(|manager| manager.get_shadow(popup.wl_surface(), &self.queue_handle));
if let Some(controller) = controller.as_ref() {
controller.enable();
} else {
tracing::info!("No shadow protocol available; the popup will have no shadow");
}
controller
})
.flatten();
let (background_effect, kwin_blur) = if settings.blur {
let blur_region = || {
Region::new(&*state.compositor_state).ok().map(|region| {
region.add(0, 0, settings.size.0 as i32, settings.size.1 as i32);
region
})
};
let effect = state.background_effect_manager.as_ref().map(|manager| {
let effect = manager.get_background_effect(popup.wl_surface(), &self.queue_handle);
if let Some(region) = blur_region() {
effect.set_blur_region(Some(region.wl_region()));
} else {
tracing::warn!("Could not build a blur region for the popup");
}
effect
});
let blur = state.kwin_blur_manager.as_ref().map(|manager| {
let blur = manager.blur(popup.wl_surface(), &self.queue_handle);
if let Some(region) = blur_region() {
blur.set_region(Some(region.wl_region()));
}
blur.commit();
blur
});
if effect.is_none() && blur.is_none() {
tracing::info!("No blur protocol available; the popup will not be blurred");
}
(effect, blur)
} else {
(None, None)
};
popup.wl_surface().commit();
let popup_id = PopupId::new();
let mut popup_state = PopupState::new(
popup,
internal_parent_id,
crate::dpi::LogicalSize::new(settings.size.0, settings.size.1),
viewport,
);
popup_state.background_effect = background_effect;
popup_state.blur = kwin_blur;
popup_state.shadow = shadow;
popup_state.corner_radius = corner_radius;
popup_state.parent_popup = parent_popup;
popup_state.grabbed = grabbed;
if let Some((offset_x, offset_y)) = settings.tooltip_offset {
if let Some(ref tooltip_manager) = state.tooltip_manager {
let popup_wl_surface = popup_state.popup.wl_surface();
let tooltip_handle = tooltip_manager.get_tooltip(
popup_wl_surface,
&parent_wl_surface,
&self.queue_handle,
);
tooltip_handle.set_offset(offset_x, offset_y);
if let Some(anchor) = settings.tooltip_anchor {
tooltip_handle.set_anchor(anchor);
}
if let Some(delay_ms) = settings.tooltip_delay_ms {
tooltip_handle.set_show_delay(delay_ms);
}
popup_state.tooltip = Some(tooltip_handle);
} else {
tracing::warn!(
"create_popup: tooltip_offset set but tooltip_manager is None! Protocol \
zcosmic_tooltip_manager_v1 may not be advertised by compositor."
);
}
}
state.popups.insert(popup_id, popup_state);
self.event_loop_awakener.ping();
Some(popup_id)
}
pub fn destroy_popup(&self, popup_id: PopupId) -> bool {
let mut state = self.state.borrow_mut();
let Some(children) = state.destroy_popup_tree(popup_id) else {
return false;
};
if !children.is_empty() {
use super::types::xdg_popup::PopupEvent;
state.popup_events.extend(children.into_iter().map(|id| PopupEvent::Done { id }));
self.event_loop_awakener.ping();
}
true
}
pub fn popup_wl_surface(
&self,
popup_id: PopupId,
) -> Option<std::ptr::NonNull<std::ffi::c_void>> {
use sctk::reexports::client::Proxy;
let state = self.state.borrow();
let popup_state = state.popups.get(&popup_id)?;
if !popup_state.configured {
return None;
}
let ptr = popup_state.popup.wl_surface().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _)
}
pub fn popup_raw_handles(
&self,
popup_id: PopupId,
) -> Option<(std::ptr::NonNull<std::ffi::c_void>, std::ptr::NonNull<std::ffi::c_void>)> {
use sctk::reexports::client::Proxy;
let state = self.state.borrow();
let popup_state = state.popups.get(&popup_id)?;
if !popup_state.configured {
return None;
}
let surface_ptr = popup_state.popup.wl_surface().id().as_ptr();
let display_ptr = self.connection.display().id().as_ptr();
Some((
std::ptr::NonNull::new(surface_ptr as *mut _)?,
std::ptr::NonNull::new(display_ptr as *mut _)?,
))
}
pub fn resize_popup(&self, popup_id: PopupId, width: u32, height: u32) -> bool {
let mut state = self.state.borrow_mut();
let Some(popup_state) = state.popups.get_mut(&popup_id) else {
return false;
};
popup_state.size = crate::dpi::LogicalSize::new(width, height);
if let Some(ref viewport) = popup_state.viewport {
viewport.set_destination(width as i32, height as i32);
}
popup_state.popup.wl_surface().commit();
self.event_loop_awakener.ping();
true
}
pub fn take_popup_events(&self) -> Vec<super::types::xdg_popup::PopupEvent> {
let mut state = self.state.borrow_mut();
state.release_dismissed_popups();
std::mem::take(&mut state.popup_events)
}
}
struct PumpEventNotifier {
control: Arc<(Mutex<PumpEventNotifierAction>, Condvar)>,
worker_waker: Option<OwnedFd>,
handle: Option<JoinHandle<()>>,
}
impl Drop for PumpEventNotifier {
fn drop(&mut self) {
if let Some(worker_waker) = self.worker_waker.as_ref() {
let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]);
}
*self.control.0.lock().unwrap() = PumpEventNotifierAction::Shutdown;
self.control.1.notify_one();
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
impl PumpEventNotifier {
fn spawn(connection: Connection, awakener: Ping) -> Self {
let control = Arc::new((Mutex::new(PumpEventNotifierAction::Pause), Condvar::new()));
let control_thread = Arc::clone(&control);
let (read, write) = match pipe::pipe_with(PipeFlags::CLOEXEC | PipeFlags::NONBLOCK) {
Ok((read, write)) => (read, write),
Err(_) => return Self { control, handle: None, worker_waker: None },
};
let handle =
std::thread::Builder::new().name(String::from("pump_events mon")).spawn(move || {
let (lock, cvar) = &*control_thread;
'outer: loop {
let mut wait = lock.lock().unwrap();
while *wait == PumpEventNotifierAction::Pause {
wait = cvar.wait(wait).unwrap();
}
if *wait == PumpEventNotifierAction::Shutdown {
break 'outer;
}
*wait = PumpEventNotifierAction::Pause;
drop(wait);
while let Some(read_guard) = connection.prepare_read() {
let _ = connection.flush();
let poll_fd = PollFd::from_borrowed_fd(connection.as_fd(), PollFlags::IN);
let pipe_poll_fd = PollFd::from_borrowed_fd(read.as_fd(), PollFlags::IN);
if Ok(1) == rustix::io::read(read.as_fd(), &mut [0u8; 1]) {
break 'outer;
}
let _ = rustix::event::poll(&mut [poll_fd, pipe_poll_fd], -1);
let _ = read_guard.read_without_dispatch();
}
awakener.ping();
}
});
if let Some(err) = handle.as_ref().err() {
warn!("failed to spawn pump_events wake-up thread: {err}");
}
PumpEventNotifier { control, handle: handle.ok(), worker_waker: Some(write) }
}
}
#[derive(Debug, PartialEq, Eq)]
enum PumpEventNotifierAction {
Monitor,
Pause,
Shutdown,
}