Merge origin/main into macos

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Will Temple
2026-05-16 16:38:10 -04:00
66 changed files with 9558 additions and 2326 deletions

View File

@@ -44,6 +44,9 @@ use wayland_protocols::wp::primary_selection::zv1::client::{
zwp_primary_selection_device_manager_v1, zwp_primary_selection_device_v1,
zwp_primary_selection_offer_v1, zwp_primary_selection_source_v1,
};
use wayland_protocols::xdg::decoration::zv1::client::{
zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
};
use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
use xkbcommon::xkb;
@@ -145,6 +148,7 @@ enum InternalBackendEvent {
struct State {
running: bool,
fixed_size_request: Option<(u32, u32)>,
_connection: Connection,
_compositor: wl_compositor::WlCompositor,
_surface: wl_surface::WlSurface,
@@ -156,6 +160,8 @@ struct State {
clipboard_device: Option<wl_data_device::WlDataDevice>,
cursor_shape_manager: Option<wp_cursor_shape_manager_v1::WpCursorShapeManagerV1>,
cursor_shape_device: Option<wp_cursor_shape_device_v1::WpCursorShapeDeviceV1>,
/// Server-side decoration handle. Kept alive so the compositor maintains decorations.
_decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
primary_selection_manager:
Option<zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1>,
primary_selection_device: Option<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1>,
@@ -227,6 +233,21 @@ impl State {
}
}
fn fixed_size_request_for_spec(spec: &WindowSpec) -> Option<(u32, u32)> {
if spec.resizable {
return None;
}
let size = spec
.requested_inner_size
.or(spec.min_inner_size)
.or(spec.max_inner_size)
.unwrap_or_else(|| UiSize::new(800.0, 500.0));
Some((
size.width.max(1.0).round() as u32,
size.height.max(1.0).round() as u32,
))
}
fn wayland_cursor_shape(icon: CursorIcon) -> wp_cursor_shape_device_v1::Shape {
match icon {
CursorIcon::Default => wp_cursor_shape_device_v1::Shape::Default,
@@ -318,6 +339,8 @@ impl WaylandWindow {
manager.get_device(&seat, &qh, ())
},
);
let decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1> =
globals.bind(&qh, 1..=1, ()).ok();
let surface = compositor.create_surface(&qh, ());
let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ());
let toplevel = xdg_surface.get_toplevel(&qh, ());
@@ -325,6 +348,11 @@ impl WaylandWindow {
if let Some(app_id) = spec.app_id.as_ref() {
toplevel.set_app_id(app_id.clone());
}
let decoration = decoration_manager.as_ref().map(|dm| {
let deco = dm.get_toplevel_decoration(&toplevel, &qh, ());
deco.set_mode(zxdg_toplevel_decoration_v1::Mode::ServerSide);
deco
});
apply_size_constraints(&toplevel, &spec);
if spec.maximized {
@@ -352,6 +380,7 @@ impl WaylandWindow {
surface_target,
state: State {
running: true,
fixed_size_request: fixed_size_request_for_spec(&spec),
_connection: connection,
_compositor: compositor,
_surface: surface,
@@ -395,6 +424,7 @@ impl WaylandWindow {
last_selection_serial: None,
last_pointer_enter_serial: None,
cursor_icon: CursorIcon::Default,
_decoration: decoration,
},
})
}
@@ -684,6 +714,7 @@ impl WaylandWindow {
}
pub fn apply_spec(&mut self, spec: &WindowSpec) -> Result<(), Box<dyn Error>> {
self.state.fixed_size_request = fixed_size_request_for_spec(spec);
self.state._toplevel.set_title(spec.title.clone());
if let Some(app_id) = spec.app_id.as_ref() {
self.state._toplevel.set_app_id(app_id.clone());
@@ -719,6 +750,14 @@ impl WaylandWindow {
}
if let Some((width, height)) = self.state.pending_size.take() {
trace!(
target: "ruin_ui_platform_wayland::resize",
width,
height,
previous_width = self.state.current_size.0,
previous_height = self.state.current_size.1,
"prepare_frame observed pending size change"
);
self.state.current_size = (width, height);
self.state.needs_redraw = false;
return Some(FrameRequest {
@@ -729,6 +768,12 @@ impl WaylandWindow {
}
if self.state.needs_redraw {
trace!(
target: "ruin_ui_platform_wayland::resize",
width = self.state.current_size.0,
height = self.state.current_size.1,
"prepare_frame servicing redraw without size change"
);
self.state.needs_redraw = false;
return Some(FrameRequest {
width: self.state.current_size.0,
@@ -1083,6 +1128,7 @@ fn spawn_window_worker(
spec.requested_inner_size
.unwrap_or_else(|| UiSize::new(800.0, 500.0))
.height as u32,
spec.base_color,
) {
Ok(renderer) => renderer,
Err(_) => {
@@ -1359,6 +1405,19 @@ fn spawn_window_worker(
state_ref
.pending_viewport_since
.get_or_insert_with(Instant::now);
// Deadlock prevention: if the app responded to the
// configure we sent (scene matches in_flight) but the
// viewport has since moved on, clear the in_flight
// marker so we can immediately send the next configure.
// Without this, viewport_request_in_flight stays set
// to the old size forever and the app is never told
// about the new viewport.
if state_ref.viewport_request_in_flight
== Some(scene.logical_size)
{
state_ref.viewport_request_in_flight = None;
maybe_request_pending_viewport(&mut state_ref);
}
state_ref.window.request_redraw();
} else if !state_ref.window.presentation_ready() {
// Correct scene is ready but the compositor
@@ -1589,6 +1648,8 @@ delegate_noop!(
State: ignore
zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1
);
delegate_noop!(State: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
delegate_noop!(State: ignore zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1);
impl Dispatch<wl_seat::WlSeat, ()> for State {
fn event(
state: &mut Self,
@@ -2079,6 +2140,14 @@ impl Dispatch<xdg_surface::XdgSurface, ()> for State {
if let xdg_surface::Event::Configure { serial } = event {
xdg_surface.ack_configure(serial);
state.configured = true;
trace!(
target: "ruin_ui_platform_wayland::resize",
serial,
current_width = state.current_size.0,
current_height = state.current_size.1,
has_pending_size = state.pending_size.is_some(),
"received xdg_surface configure"
);
state.request_redraw();
}
}
@@ -2131,7 +2200,37 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
height,
"received Wayland toplevel configure"
);
state.pending_size = Some((width, height));
let next_size = (width, height);
if let Some(expected_size) = state.fixed_size_request {
if next_size != expected_size {
trace!(
target: "ruin_ui_platform_wayland::resize",
width,
height,
expected_width = expected_size.0,
expected_height = expected_size.1,
"ignoring stale toplevel configure for fixed-size window"
);
state.request_redraw();
return;
}
}
let size_changed =
next_size != state.current_size && state.pending_size != Some(next_size);
trace!(
target: "ruin_ui_platform_wayland::resize",
width,
height,
current_width = state.current_size.0,
current_height = state.current_size.1,
pending_width = state.pending_size.map(|size| size.0),
pending_height = state.pending_size.map(|size| size.1),
size_changed,
"processed Wayland toplevel configure"
);
if size_changed {
state.pending_size = Some(next_size);
}
state.request_redraw();
}
_ => {}