Repository: zng-ui/zng (language: rust)

Issue:
Upgrade winit to 0.30
See https://github.com/rust-windowing/winit/releases/tag/v0.30.0


Discussion / hints:

Fix patch:
diff --git a/crates/zng-ext-font/src/lib.rs b/crates/zng-ext-font/src/lib.rs
index 44e1a350e..598ab5d89 100644
--- a/crates/zng-ext-font/src/lib.rs
+++ b/crates/zng-ext-font/src/lib.rs
@@ -1104,7 +1104,7 @@ impl FontFace {
         let metrics = font.metrics();
         if metrics.units_per_em == 0 {
             // observed this in Noto Color Emoji
-            tracing::error!("font {:?} units_er_em 0", font.family_name());
+            tracing::error!("font {:?} units_per_em 0", font.family_name());
             return Err(FontLoadingError::UnknownFormat);
         }
 
diff --git a/crates/zng-view/Cargo.toml b/crates/zng-view/Cargo.toml
index e6ccc9a9f..0406ab963 100644
--- a/crates/zng-view/Cargo.toml
+++ b/crates/zng-view/Cargo.toml
@@ -53,11 +53,12 @@ zng-tp-licenses = { path = "../zng-tp-licenses", version = "0.2.5" }
 
 tracing = "0.1"
 gleam = "0.15.0" # matches webrender
-winit = { version = "0.29", default-features = false, features = [
+winit = { version = "0.30", default-features = false, features = [
     "x11",
     "wayland",
     "wayland-dlopen",
     "rwh_05",
+    "rwh_06",
 ] }
 glutin = "0.31"
 raw-window-handle = "0.5" # matches glutin
@@ -84,12 +85,19 @@ byteorder = "1.5"
 rustc-hash = "1.1"
 rayon = "1.10"
 serde = "1.0"
-rfd = { version = "0.12", default_features = false, features = ["xdg-portal"] }
-accesskit = "0.13"
-accesskit_winit = "0.19"
+accesskit = "0.14"
+accesskit_winit = "0.20"
+# rfd
+[target.'cfg(not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))'.dependencies.rfd]
+version = "0.14"
+default-features = false
+[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies.rfd]
+version = "0.14"
+default-features = false
+features = ["xdg-portal", "async-std"]
 
 [target.'cfg(windows)'.dependencies.windows-sys]
-version = "0.48.0" # matches winit, glutin, other crates
+version = "0.52.0" # matches winit, glutin, other crates
 features = [
     "Win32_UI_Shell",
     "Win32_System_Com",
@@ -105,7 +113,7 @@ features = [
     "Win32_System_Power",
 ]
 [target.'cfg(windows)'.dependencies.windows]
-version = "0.48.0"                                          # matches accesskit, other crates
+version = "0.56.0"
 features = ["Foundation_Collections", "System_UserProfile"]
 
 [target.'cfg(windows)'.dependencies]
diff --git a/crates/zng-view/src/gl.rs b/crates/zng-view/src/gl.rs
index e23ea1399..06caef065 100644
--- a/crates/zng-view/src/gl.rs
+++ b/crates/zng-view/src/gl.rs
@@ -9,7 +9,7 @@ use glutin::{
     surface::{Surface, SurfaceAttributesBuilder, WindowSurface},
 };
 use rustc_hash::FxHashSet;
-use winit::{dpi::PhysicalSize, event_loop::EventLoopWindowTarget};
+use winit::{dpi::PhysicalSize, event_loop::ActiveEventLoop};
 use zng_txt::ToTxt as _;
 use zng_view_api::window::{RenderMode, WindowId};
 
@@ -39,8 +39,8 @@ impl GlContextManager {
     pub(crate) fn create_headed(
         &mut self,
         id: WindowId,
-        window: winit::window::WindowBuilder,
-        window_target: &EventLoopWindowTarget<AppEvent>,
+        window: winit::window::WindowAttributes,
+        winit_loop: &ActiveEventLoop,
         render_mode: RenderMode,
         sender: &AppEventSender,
     ) -> (winit::window::Window, GlContext) {
@@ -52,7 +52,7 @@ impl GlContextManager {
                 continue;
             }
 
-            let window = window.clone().build(window_target).unwrap();
+            let window = winit_loop.create_window(window.clone()).unwrap();
 
             let r = util::catch_suppress(std::panic::AssertUnwindSafe(|| match config.mode {
                 RenderMode::Dedicated => self.create_headed_glutin(id, &window, config.hardware_acceleration),
@@ -97,7 +97,7 @@ impl GlContextManager {
     pub(crate) fn create_headless(
         &mut self,
         id: WindowId,
-        window_target: &EventLoopWindowTarget<AppEvent>,
+        winit_loop: &ActiveEventLoop,
         render_mode: RenderMode,
         sender: &AppEventSender,
     ) -> GlContext {
@@ -110,8 +110,8 @@ impl GlContextManager {
             }
 
             let r = util::catch_suppress(std::panic::AssertUnwindSafe(|| match config.mode {
-                RenderMode::Dedicated => self.create_headless_glutin(id, window_target, config.hardware_acceleration),
-                RenderMode::Integrated => self.create_headless_glutin(id, window_target, Some(false)),
+                RenderMode::Dedicated => self.create_headless_glutin(id, winit_loop, config.hardware_acceleration),
+                RenderMode::Integrated => self.create_headless_glutin(id, winit_loop, Some(false)),
                 RenderMode::Software => self.create_headless_swgl(id),
             }));
 
@@ -286,17 +286,17 @@ impl GlContextManager {
     fn create_headless_glutin(
         &mut self,
         id: WindowId,
-        window_target: &EventLoopWindowTarget<AppEvent>,
+        winit_loop: &ActiveEventLoop,
         hardware: Option<bool>,
     ) -> Result<GlContext, Box<dyn Error>> {
-        let hidden_window = winit::window::WindowBuilder::new()
+        let hidden_window = winit::window::WindowAttributes::default()
             .with_transparent(true)
             .with_inner_size(PhysicalSize::new(1u32, 1u32))
             .with_visible(false)
-            .with_decorations(false)
-            .build(window_target)?;
+            .with_decorations(false);
+        let hidden_window = winit_loop.create_window(hidden_window)?;
 
-        let display_handle = window_target.raw_display_handle();
+        let display_handle = winit_loop.raw_display_handle();
         let window_handle = hidden_window.raw_window_handle();
 
         #[cfg(windows)]
diff --git a/crates/zng-view/src/lib.rs b/crates/zng-view/src/lib.rs
index 3ef99bb08..3c222b146 100644
--- a/crates/zng-view/src/lib.rs
+++ b/crates/zng-view/src/lib.rs
@@ -115,7 +115,7 @@ use image_cache::ImageCache;
 use util::WinitToPx;
 use winit::{
     event::{DeviceEvent, WindowEvent},
-    event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
+    event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
     keyboard::ModifiersState,
     monitor::MonitorHandle,
     platform::modifier_supplement::KeyEventExtModifierSupplement,
@@ -293,7 +293,8 @@ pub(crate) struct App {
     exts: ViewExtensions,
 
     gl_manager: GlContextManager,
-    window_target: *const EventLoopWindowTarget<AppEvent>,
+    winit_loop: util::WinitEventLoop,
+    idle: IdleTrace,
     app_sender: AppEventSender,
     request_recv: flume::Receiver<RequestEvent>,
 
@@ -348,288 +349,10 @@ impl fmt::Debug for App {
             .finish_non_exhaustive()
     }
 }
-impl App {
-    fn init_device_events(&mut self, enabled: bool, t: Option<&EventLoopWindowTarget<AppEvent>>) {
-        self.device_events = enabled;
-
-        if let Some(t) = t {
-            if enabled {
-                t.listen_device_events(winit::event_loop::DeviceEvents::Always);
-            } else {
-                t.listen_device_events(winit::event_loop::DeviceEvents::Never);
-            }
-        } else {
-            self.device_events = false;
-        }
-    }
-
-    pub fn run_headless(c: ipc::ViewChannels, ext: ViewExtensions) {
-        tracing::info!("running headless view-process");
-
-        gl::warmup();
-
-        let (app_sender, app_receiver) = flume::unbounded();
-        let (request_sender, request_receiver) = flume::unbounded();
-        let mut app = App::new(
-            AppEventSender::Headless(app_sender, request_sender),
-            c.response_sender,
-            c.event_sender,
-            request_receiver,
-            ext,
-        );
-        app.headless = true;
-
-        let winit_span = tracing::trace_span!("winit::EventLoop::new").entered();
-        let event_loop = EventLoopBuilder::<AppEvent>::with_user_event().build().unwrap();
-
-        drop(winit_span);
-
-        let window_target: &EventLoopWindowTarget<AppEvent> = &event_loop;
-        app.window_target = window_target as *const _;
-
-        app.start_receiving(c.request_receiver);
-
-        'app_loop: while !app.exited {
-            match app_receiver.recv() {
-                Ok(app_ev) => match app_ev {
-                    AppEvent::Request => {
-                        while let Ok(request) = app.request_recv.try_recv() {
-                            match request {
-                                RequestEvent::Request(request) => {
-                                    let response = app.respond(request);
-                                    if response.must_be_send() && app.response_sender.send(response).is_err() {
-                                        app.exited = true;
-                                        break 'app_loop;
-                                    }
-                                }
-                                RequestEvent::FrameReady(id, msg) => {
-                                    let r = if let Some(s) = app.surfaces.iter_mut().find(|s| s.id() == id) {
-                                        Some(s.on_frame_ready(msg, &mut app.image_cache))
-                                    } else {
-                                        None
-                                    };
-                                    if let Some((frame_id, image)) = r {
-                                        app.notify(Event::FrameRendered(EventFrameRendered {
-                                            window: id,
-                                            frame: frame_id,
-                                            frame_image: image,
-                                        }));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    AppEvent::Notify(ev) => {
-                        if app.event_sender.send(ev).is_err() {
-                            app.exited = true;
-                            break 'app_loop;
-                        }
-                    }
-                    AppEvent::RefreshMonitors => {
-                        panic!("no monitor info in headless mode")
-                    }
-                    AppEvent::WinitFocused(_, _) => {
-                        panic!("no winit event loop in headless mode")
-                    }
-                    AppEvent::ParentProcessExited => {
-                        app.exited = true;
-                        break 'app_loop;
-                    }
-                    AppEvent::ImageLoaded(data) => {
-                        app.image_cache.loaded(data);
-                    }
-                    AppEvent::MonitorPowerChanged => {} // headless
-                    AppEvent::InitDeviceEvents(enabled) => {
-                        app.init_device_events(enabled, None);
-                    }
-                },
-                Err(_) => {
-                    app.exited = true;
-                    break;
-                }
-            }
-        }
-    }
-
-    pub fn run_headed(c: ipc::ViewChannels, ext: ViewExtensions) {
-        tracing::info!("running headed view-process");
-
-        gl::warmup();
-
-        let winit_span = tracing::trace_span!("winit::EventLoop::new").entered();
-        let event_loop = EventLoopBuilder::with_user_event().build().unwrap();
-        drop(winit_span);
-        let app_sender = event_loop.create_proxy();
-
-        let (request_sender, request_receiver) = flume::unbounded();
-        let mut app = App::new(
-            AppEventSender::Headed(app_sender, request_sender),
-            c.response_sender,
-            c.event_sender,
-            request_receiver,
-            ext,
-        );
-        app.start_receiving(c.request_receiver);
-
-        #[cfg(windows)]
-        config::spawn_listener(app.app_sender.clone());
-
-        struct IdleTrace(Option<tracing::span::EnteredSpan>);
-        impl IdleTrace {
-            pub fn enter(&mut self) {
-                self.0 = Some(tracing::trace_span!("<winit-idle>").entered());
-            }
-            pub fn exit(&mut self) {
-                self.0 = None;
-            }
-        }
-        let mut idle = IdleTrace(None);
-        idle.enter();
-
-        event_loop
-            .run(move |event, target| {
-                idle.exit();
-
-                app.window_target = target;
-                target.set_control_flow(ControlFlow::Wait);
-
-                if app.exited {
-                    target.exit();
-                } else {
-                    use winit::event::Event as WEvent;
-                    match event {
-                        WEvent::NewEvents(_) => {}
-                        WEvent::WindowEvent { window_id, event } => app.on_window_event(window_id, event),
-                        WEvent::DeviceEvent { device_id, event } => app.on_device_event(device_id, event),
-                        WEvent::UserEvent(ev) => match ev {
-                            AppEvent::Request => {
-                                while let Ok(req) = app.request_recv.try_recv() {
-                                    match req {
-                                        RequestEvent::Request(req) => {
-                                            let rsp = app.respond(req);
-                                            if rsp.must_be_send() && app.response_sender.send(rsp).is_err() {
-                                                // lost connection to app-process
-                                                app.exited = true;
-                                                target.exit();
-                                            }
-                                        }
-                                        RequestEvent::FrameReady(wid, msg) => app.on_frame_ready(wid, msg),
-                                    }
-                                }
-                            }
-                            AppEvent::Notify(ev) => app.notify(ev),
-                            AppEvent::WinitFocused(window_id, focused) => app.on_window_event(window_id, WindowEvent::Focused(focused)),
-                            AppEvent::RefreshMonitors => app.refresh_monitors(),
-                            AppEvent::ParentProcessExited => {
-                                app.exited = true;
-                                target.exit();
-                            }
-                            AppEvent::ImageLoaded(data) => {
-                                app.image_cache.loaded(data);
-                            }
-                            AppEvent::MonitorPowerChanged => {
-                                // if a window opens in power-off it is blank until redraw.
-                                for w in &mut app.windows {
-                                    w.redraw();
-                                }
-                            }
-                            AppEvent::InitDeviceEvents(enabled) => {
-                                app.init_device_events(enabled, Some(target));
-                            }
-                        },
-                        WEvent::Suspended => {}
-                        WEvent::Resumed => {}
-                        WEvent::AboutToWait => {
-                            app.finish_cursor_entered_move();
-                            app.update_modifiers();
-                            app.flush_coalesced();
-                            #[cfg(windows)]
-                            {
-                                app.skip_ralt = false;
-                            }
-                        }
-                        WEvent::MemoryWarning => {
-                            app.on_low_memory();
-                        }
-                        WEvent::LoopExiting => {}
-                    }
-                }
-
-                app.window_target = std::ptr::null();
+impl winit::application::ApplicationHandler<AppEvent> for App {
+    fn resumed(&mut self, _: &ActiveEventLoop) {}
 
-                idle.enter();
-            })
-            .unwrap();
-    }
-
-    fn new(
-        app_sender: AppEventSender,
-        response_sender: ipc::ResponseSender,
-        event_sender: ipc::EventSender,
-        request_recv: flume::Receiver<RequestEvent>,
-        mut ext: ViewExtensions,
-    ) -> Self {
-        ext.renderer("zng-view.webrender_debug", extensions::RendererDebugExt::new);
-        ext.init(&app_sender);
-        App {
-            headless: false,
-            started: false,
-            exts: ext,
-            gl_manager: GlContextManager::default(),
-            image_cache: ImageCache::new(app_sender.clone()),
-            app_sender,
-            request_recv,
-            response_sender,
-            event_sender,
-            window_target: std::ptr::null(),
-            gen: ViewProcessGen::INVALID,
-            device_events: false,
-            windows: vec![],
-            surfaces: vec![],
-            monitors: vec![],
-            monitor_id_gen: MonitorId::INVALID,
-            devices: vec![],
-            device_id_gen: DeviceId::INVALID,
-            dialog_id_gen: DialogId::INVALID,
-            resize_frame_wait_id_gen: FrameWaitId::INVALID,
-            coalescing_event: None,
-            cursor_entered_expect_move: Vec::with_capacity(1),
-            exited: false,
-            #[cfg(windows)]
-            skip_ralt: false,
-            pressed_modifiers: FxHashMap::default(),
-            pending_modifiers_update: None,
-            pending_modifiers_focus_clear: false,
-
-            #[cfg(not(windows))]
-            arboard: None,
-        }
-    }
-
-    fn start_receiving(&mut self, mut request_recv: ipc::RequestReceiver) {
-        let app_sender = self.app_sender.clone();
-        thread::spawn(move || {
-            while let Ok(r) = request_recv.recv() {
-                if let Err(ipc::Disconnected) = app_sender.request(r) {
-                    break;
-                }
-            }
-        });
-    }
-
-    fn on_low_memory(&mut self) {
-        self.image_cache.on_low_memory();
-        for w in &mut self.windows {
-            w.on_low_memory();
-        }
-        for s in &mut self.surfaces {
-            s.on_low_memory();
-        }
-        self.exts.on_low_memory();
-        self.notify(Event::LowMemory);
-    }
-
-    fn on_window_event(&mut self, window_id: winit::window::WindowId, event: WindowEvent) {
+    fn window_event(&mut self, winit_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: WindowEvent) {
         let i = if let Some((i, _)) = self.windows.iter_mut().enumerate().find(|(_, w)| w.window_id() == window_id) {
             i
         } else {
@@ -638,6 +361,8 @@ impl App {
 
         let _s = tracing::trace_span!("on_window_event", ?event).entered();
 
+        let mut winit_loop_guard = self.winit_loop.set(winit_loop);
+
         self.windows[i].pump_access(&event);
 
         let id = self.windows[i].id();
@@ -663,6 +388,7 @@ impl App {
         macro_rules! linux_modal_dialog_bail {
             () => {
                 if modal_dialog_active {
+                    winit_loop_guard.unset(&mut self.winit_loop);
                     return;
                 }
             };
@@ -684,6 +410,7 @@ impl App {
                 let size = if let Some(size) = self.windows[i].resized() {
                     size
                 } else {
+                    winit_loop_guard.unset(&mut self.winit_loop);
                     return;
                 };
 
@@ -764,6 +491,7 @@ impl App {
                             break;
                         }
                         Err(flume::RecvTimeoutError::Disconnected) => {
+                            winit_loop_guard.unset(&mut self.winit_loop);
                             unreachable!()
                         }
                     }
@@ -794,6 +522,7 @@ impl App {
                 let (global_position, position) = if let Some(p) = self.windows[i].moved() {
                     p
                 } else {
+                    winit_loop_guard.unset(&mut self.winit_loop);
                     return;
                 };
 
@@ -856,6 +585,7 @@ impl App {
                     #[cfg(windows)]
                     if self.skip_ralt {
                         if let winit::keyboard::PhysicalKey::Code(winit::keyboard::KeyCode::AltRight) = event.physical_key {
+                            winit_loop_guard.unset(&mut self.winit_loop);
                             return;
                         }
                     }
@@ -1072,12 +802,353 @@ impl App {
             }
             WindowEvent::Occluded(_) => {}
             WindowEvent::ActivationTokenDone { .. } => {}
-            WindowEvent::TouchpadMagnify { .. } => {}
-            WindowEvent::TouchpadRotate { .. } => {}
-            WindowEvent::SmartMagnify { .. } => {}
+            WindowEvent::PinchGesture { .. } => {}
+            WindowEvent::RotationGesture { .. } => {}
+            WindowEvent::DoubleTapGesture { .. } => {}
+            WindowEvent::PanGesture { .. } => {}
+        }
+
+        winit_loop_guard.unset(&mut self.winit_loop);
+    }
+
+    fn new_events(&mut self, _: &ActiveEventLoop, _: winit::event::StartCause) {
+        self.idle.exit();
+    }
+
+    fn user_event(&mut self, winit_loop: &ActiveEventLoop, ev: AppEvent) {
+        let mut winit_loop_guard = self.winit_loop.set(winit_loop);
+        match ev {
+            AppEvent::Request => {
+                while let Ok(req) = self.request_recv.try_recv() {
+                    match req {
+                        RequestEvent::Request(req) => {
+                            let rsp = self.respond(req);
+                            if rsp.must_be_send() && self.response_sender.send(rsp).is_err() {
+                                // lost connection to app-process
+                                self.exited = true;
+                                self.winit_loop.exit();
+                            }
+                        }
+                        RequestEvent::FrameReady(wid, msg) => self.on_frame_ready(wid, msg),
+                    }
+                }
+            }
+            AppEvent::Notify(ev) => self.notify(ev),
+            AppEvent::WinitFocused(window_id, focused) => self.window_event(winit_loop, window_id, WindowEvent::Focused(focused)),
+            AppEvent::RefreshMonitors => self.refresh_monitors(),
+            AppEvent::ParentProcessExited => {
+                self.exited = true;
+                self.winit_loop.exit();
+            }
+            AppEvent::ImageLoaded(data) => {
+                self.image_cache.loaded(data);
+            }
+            AppEvent::MonitorPowerChanged => {
+                // if a window opens in power-off it is blank until redraw.
+                for w in &mut self.windows {
+                    w.redraw();
+                }
+            }
+            AppEvent::InitDeviceEvents(enabled) => {
+                self.init_device_events(enabled, Some(winit_loop));
+            }
+        }
+        winit_loop_guard.unset(&mut self.winit_loop);
+    }
+
+    fn device_event(&mut self, winit_loop: &ActiveEventLoop, device_id: winit::event::DeviceId, event: DeviceEvent) {
+        if self.device_events {
+            let _s = tracing::trace_span!("on_device_event", ?event);
+
+            let mut winit_loop_guard = self.winit_loop.set(winit_loop);
+
+            let d_id = self.device_id(device_id);
+            match event {
+                DeviceEvent::Added => self.notify(Event::DeviceAdded(d_id)),
+                DeviceEvent::Removed => self.notify(Event::DeviceRemoved(d_id)),
+                DeviceEvent::MouseMotion { delta } => self.notify(Event::DeviceMouseMotion {
+                    device: d_id,
+                    delta: euclid::vec2(delta.0, delta.1),
+                }),
+                DeviceEvent::MouseWheel { delta } => self.notify(Event::DeviceMouseWheel {
+                    device: d_id,
+                    delta: util::winit_mouse_wheel_delta_to_zui(delta),
+                }),
+                DeviceEvent::Motion { axis, value } => self.notify(Event::DeviceMotion {
+                    device: d_id,
+                    axis: AxisId(axis),
+                    value,
+                }),
+                DeviceEvent::Button { button, state } => self.notify(Event::DeviceButton {
+                    device: d_id,
+                    button: ButtonId(button),
+                    state: util::element_state_to_button_state(state),
+                }),
+                DeviceEvent::Key(k) => self.notify(Event::DeviceKey {
+                    device: d_id,
+                    key_code: util::winit_physical_key_to_key_code(k.physical_key),
+                    state: util::element_state_to_key_state(k.state),
+                }),
+            }
+
+            winit_loop_guard.unset(&mut self.winit_loop);
+        }
+    }
+
+    fn about_to_wait(&mut self, winit_loop: &ActiveEventLoop) {
+        let mut winit_loop_guard = self.winit_loop.set(winit_loop);
+
+        self.finish_cursor_entered_move();
+        self.update_modifiers();
+        self.flush_coalesced();
+        #[cfg(windows)]
+        {
+            self.skip_ralt = false;
+        }
+        self.idle.enter();
+
+        winit_loop_guard.unset(&mut self.winit_loop);
+    }
+
+    fn suspended(&mut self, _: &ActiveEventLoop) {
+        unimplemented!()
+    }
+
+    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
+        let _ = event_loop;
+    }
+
+    fn memory_warning(&mut self, winit_loop: &ActiveEventLoop) {
+        let mut winit_loop_guard = self.winit_loop.set(winit_loop);
+
+        self.image_cache.on_low_memory();
+        for w in &mut self.windows {
+            w.on_low_memory();
+        }
+        for s in &mut self.surfaces {
+            s.on_low_memory();
+        }
+        self.exts.on_low_memory();
+        self.notify(Event::LowMemory);
+
+        winit_loop_guard.unset(&mut self.winit_loop);
+    }
+}
+struct IdleTrace(Option<tracing::span::EnteredSpan>);
+impl IdleTrace {
+    pub fn enter(&mut self) {
+        self.0 = Some(tracing::trace_span!("<winit-idle>").entered());
+    }
+    pub fn exit(&mut self) {
+        self.0 = None;
+    }
+}
+impl App {
+    fn init_device_events(&mut self, enabled: bool, t: Option<&ActiveEventLoop>) {
+        self.device_events = enabled;
+
+        if let Some(t) = t {
+            if enabled {
+                t.listen_device_events(winit::event_loop::DeviceEvents::Always);
+            } else {
+                t.listen_device_events(winit::event_loop::DeviceEvents::Never);
+            }
+        } else {
+            self.device_events = false;
+        }
+    }
+
+    pub fn run_headless(c: ipc::ViewChannels, ext: ViewExtensions) {
+        tracing::info!("running headless view-process");
+
+        gl::warmup();
+
+        let (app_sender, app_receiver) = flume::unbounded();
+        let (request_sender, request_receiver) = flume::unbounded();
+        let mut app = App::new(
+            AppEventSender::Headless(app_sender, request_sender),
+            c.response_sender,
+            c.event_sender,
+            request_receiver,
+            ext,
+        );
+        app.headless = true;
+
+        let winit_span = tracing::trace_span!("winit::EventLoop::new").entered();
+        let event_loop = EventLoop::new().unwrap();
+        drop(winit_span);
+
+        event_loop
+            .run_app(&mut HeadlessApp {
+                app,
+                request_receiver: Some(c.request_receiver),
+                app_receiver,
+            })
+            .unwrap();
+
+        struct HeadlessApp {
+            app: App,
+            request_receiver: Option<ipc::RequestReceiver>,
+            app_receiver: flume::Receiver<AppEvent>,
+        }
+        impl winit::application::ApplicationHandler<()> for HeadlessApp {
+            fn resumed(&mut self, winit_loop: &ActiveEventLoop) {
+                let mut winit_loop_guard = self.app.winit_loop.set(winit_loop);
+
+                self.app.start_receiving(self.request_receiver.take().unwrap());
+
+                'app_loop: while !self.app.exited {
+                    match self.app_receiver.recv() {
+                        Ok(app_ev) => match app_ev {
+                            AppEvent::Request => {
+                                while let Ok(request) = self.app.request_recv.try_recv() {
+                                    match request {
+                                        RequestEvent::Request(request) => {
+                                            let response = self.app.respond(request);
+                                            if response.must_be_send() && self.app.response_sender.send(response).is_err() {
+                                                self.app.exited = true;
+                                                break 'app_loop;
+                                            }
+                                        }
+                                        RequestEvent::FrameReady(id, msg) => {
+                                            let r = if let Some(s) = self.app.surfaces.iter_mut().find(|s| s.id() == id) {
+                                                Some(s.on_frame_ready(msg, &mut self.app.image_cache))
+                                            } else {
+                                                None
+                                            };
+                                            if let Some((frame_id, image)) = r {
+                                                self.app.notify(Event::FrameRendered(EventFrameRendered {
+                                                    window: id,
+                                                    frame: frame_id,
+                                                    frame_image: image,
+                                                }));
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            AppEvent::Notify(ev) => {
+                                if self.app.event_sender.send(ev).is_err() {
+                                    self.app.exited = true;
+                                    break 'app_loop;
+                                }
+                            }
+                            AppEvent::RefreshMonitors => {
+                                panic!("no monitor info in headless mode")
+                            }
+                            AppEvent::WinitFocused(_, _) => {
+                                panic!("no winit event loop in headless mode")
+                            }
+                            AppEvent::ParentProcessExited => {
+                                self.app.exited = true;
+                                break 'app_loop;
+                            }
+                            AppEvent::ImageLoaded(data) => {
+                                self.app.image_cache.loaded(data);
+                            }
+                            AppEvent::MonitorPowerChanged => {} // headless
+                            AppEvent::InitDeviceEvents(enabled) => {
+                                self.app.init_device_events(enabled, None);
+                            }
+                        },
+                        Err(_) => {
+                            self.app.exited = true;
+                            break;
+                        }
+                    }
+                }
+
+                winit_loop_guard.unset(&mut self.app.winit_loop);
+            }
+
+            fn window_event(&mut self, _: &ActiveEventLoop, _: winit::window::WindowId, _: WindowEvent) {}
+        }
+    }
+
+    pub fn run_headed(c: ipc::ViewChannels, ext: ViewExtensions) {
+        tracing::info!("running headed view-process");
+
+        gl::warmup();
+
+        let winit_span = tracing::trace_span!("winit::EventLoop::new").entered();
+        let event_loop = EventLoop::with_user_event().build().unwrap();
+        drop(winit_span);
+        let app_sender = event_loop.create_proxy();
+
+        let (request_sender, request_receiver) = flume::unbounded();
+        let mut app = App::new(
+            AppEventSender::Headed(app_sender, request_sender),
+            c.response_sender,
+            c.event_sender,
+            request_receiver,
+            ext,
+        );
+        app.start_receiving(c.request_receiver);
+
+        #[cfg(windows)]
+        config::spawn_listener(app.app_sender.clone());
+
+        event_loop.run_app(&mut app).unwrap();
+    }
+
+    fn new(
+        app_sender: AppEventSender,
+        response_sender: ipc::ResponseSender,
+        event_sender: ipc::EventSender,
+        request_recv: flume::Receiver<RequestEvent>,
+        mut ext: ViewExtensions,
+    ) -> Self {
+        ext.renderer("zng-view.webrender_debug", extensions::RendererDebugExt::new);
+        ext.init(&app_sender);
+        let mut idle = IdleTrace(None);
+        idle.enter();
+        App {
+            headless: false,
+            started: false,
+            exts: ext,
+            idle,
+            gl_manager: GlContextManager::default(),
+            image_cache: ImageCache::new(app_sender.clone()),
+            app_sender,
+            request_recv,
+            response_sender,
+            event_sender,
+            winit_loop: util::WinitEventLoop::default(),
+            gen: ViewProcessGen::INVALID,
+            device_events: false,
+            windows: vec![],
+            surfaces: vec![],
+            monitors: vec![],
+            monitor_id_gen: MonitorId::INVALID,
+            devices: vec![],
+            device_id_gen: DeviceId::INVALID,
+            dialog_id_gen: DialogId::INVALID,
+            resize_frame_wait_id_gen: FrameWaitId::INVALID,
+            coalescing_event: None,
+            cursor_entered_expect_move: Vec::with_capacity(1),
+            exited: false,
+            #[cfg(windows)]
+            skip_ralt: false,
+            pressed_modifiers: FxHashMap::default(),
+            pending_modifiers_update: None,
+            pending_modifiers_focus_clear: false,
+
+            #[cfg(not(windows))]
+            arboard: None,
         }
     }
 
+    fn start_receiving(&mut self, mut request_recv: ipc::RequestReceiver) {
+        let app_sender = self.app_sender.clone();
+        thread::spawn(move || {
+            while let Ok(r) = request_recv.recv() {
+                if let Err(ipc::Disconnected) = app_sender.request(r) {
+                    break;
+                }
+            }
+        });
+    }
+
     fn monitor_handle_to_id(&mut self, handle: &MonitorHandle) -> MonitorId {
         if let Some((id, _)) = self.monitors.iter().find(|(_, h)| h == handle) {
             *id
@@ -1170,9 +1241,7 @@ impl App {
 
         let mut changed = false;
 
-        let window_target = unsafe { &*self.window_target };
-
-        for (fresh_handle, (id, handle)) in window_target.available_monitors().zip(&self.monitors) {
+        for (fresh_handle, (id, handle)) in self.winit_loop.available_monitors().zip(&self.monitors) {
             let id = if &fresh_handle == handle {
                 *id
             } else {
@@ -1297,41 +1366,6 @@ impl App {
         }
     }
 
-    fn on_device_event(&mut self, device_id: winit::event::DeviceId, event: DeviceEvent) {
-        if self.device_events {
-            let _s = tracing::trace_span!("on_device_event", ?event);
-
-            let d_id = self.device_id(device_id);
-            match event {
-                DeviceEvent::Added => self.notify(Event::DeviceAdded(d_id)),
-                DeviceEvent::Removed => self.notify(Event::DeviceRemoved(d_id)),
-                DeviceEvent::MouseMotion { delta } => self.notify(Event::DeviceMouseMotion {
-                    device: d_id,
-                    delta: euclid::vec2(delta.0, delta.1),
-                }),
-                DeviceEvent::MouseWheel { delta } => self.notify(Event::DeviceMouseWheel {
-                    device: d_id,
-                    delta: util::winit_mouse_wheel_delta_to_zui(delta),
-                }),
-                DeviceEvent::Motion { axis, value } => self.notify(Event::DeviceMotion {
-                    device: d_id,
-                    axis: AxisId(axis),
-                    value,
-                }),
-                DeviceEvent::Button { button, state } => self.notify(Event::DeviceButton {
-                    device: d_id,
-                    button: ButtonId(button),
-                    state: util::element_state_to_button_state(state),
-                }),
-                DeviceEvent::Key(k) => self.notify(Event::DeviceKey {
-                    device: d_id,
-                    key_code: util::winit_physical_key_to_key_code(k.physical_key),
-                    state: util::element_state_to_key_state(k.state),
-                }),
-            }
-        }
-    }
-
     fn assert_started(&self) {
         if !self.started {
             panic!("not started")
@@ -1371,11 +1405,8 @@ impl App {
 
         self.assert_started();
 
-        let window_target = unsafe { &*self.window_target };
-
-        let primary = window_target.primary_monitor();
-
-        window_target
+        let primary = self.winit_loop.primary_monitor();
+        self.winit_loop
             .available_monitors()
             .map(|m| {
                 let id = self.monitor_id(&m);
@@ -1406,7 +1437,7 @@ impl App {
         let surf = Surface::open(
             self.gen,
             config,
-            unsafe { &*self.window_target },
+            &self.winit_loop,
             &mut self.gl_manager,
             self.exts.new_renderer(),
             self.app_sender.clone(),
@@ -1465,6 +1496,8 @@ impl Api for App {
         self.assert_started();
         self.started = false;
         self.exited = true;
+        // not really, but just to exit winit loop
+        let _ = self.app_sender.send(AppEvent::ParentProcessExited);
     }
 
     fn open_window(&mut self, mut config: WindowRequest) {
@@ -1505,12 +1538,11 @@ impl Api for App {
             self.assert_started();
 
             let id = config.id;
-
             let win = Window::open(
                 self.gen,
                 config.icon.and_then(|i| self.image_cache.get(i)).and_then(|i| i.icon()),
                 config,
-                unsafe { &*self.window_target },
+                &self.winit_loop,
                 &mut self.gl_manager,
                 self.exts.new_renderer(),
                 self.app_sender.clone(),
diff --git a/crates/zng-view/src/surface.rs b/crates/zng-view/src/surface.rs
index 6c399fccd..6b5a232e0 100644
--- a/crates/zng-view/src/surface.rs
+++ b/crates/zng-view/src/surface.rs
@@ -1,12 +1,11 @@
 use std::{collections::VecDeque, fmt};
 
-use winit::event_loop::EventLoopWindowTarget;
-
 use tracing::span::EnteredSpan;
 use webrender::{
     api::{DocumentId, DynamicProperties, FontInstanceKey, FontKey, FontVariation, PipelineId},
     RenderApi, Renderer, Transaction,
 };
+use winit::event_loop::ActiveEventLoop;
 use zng_unit::{DipSize, DipToPx, Factor, Px, PxRect, Rgba};
 use zng_view_api::{
     api_extension::{ApiExtensionId, ApiExtensionPayload},
@@ -26,7 +25,7 @@ use crate::{
     image_cache::{Image, ImageCache, ImageUseMap, WrImageCache},
     px_wr::PxToWr as _,
     util::{frame_render_reasons, frame_update_render_reasons, PxToWinit},
-    AppEvent, AppEventSender, FrameReadyMsg, WrNotifier,
+    AppEventSender, FrameReadyMsg, WrNotifier,
 };
 
 /// A headless "window".
@@ -65,14 +64,14 @@ impl Surface {
     pub fn open(
         gen: ViewProcessGen,
         mut cfg: HeadlessRequest,
-        window_target: &EventLoopWindowTarget<AppEvent>,
+        winit_loop: &ActiveEventLoop,
         gl_manager: &mut GlContextManager,
         mut renderer_exts: Vec<(ApiExtensionId, Box<dyn RendererExtension>)>,
         event_sender: AppEventSender,
     ) -> Self {
         let id = cfg.id;
 
-        let mut context = gl_manager.create_headless(id, window_target, cfg.render_mode, &event_sender);
+        let mut context = gl_manager.create_headless(id, winit_loop, cfg.render_mode, &event_sender);
         let size = cfg.size.to_px(cfg.scale_factor);
         context.resize(size.to_winit());
         let context = context;
diff --git a/crates/zng-view/src/util.rs b/crates/zng-view/src/util.rs
index f22a46322..e94fe5dde 100644
--- a/crates/zng-view/src/util.rs
+++ b/crates/zng-view/src/util.rs
@@ -1,11 +1,13 @@
 use std::any::Any;
 use std::backtrace::Backtrace;
 use std::cell::RefCell;
-use std::fmt;
+use std::marker::PhantomData;
 use std::{cell::Cell, sync::Arc};
+use std::{fmt, ops};
 
 use rayon::ThreadPoolBuilder;
 use webrender::api as wr;
+use winit::event_loop::ActiveEventLoop;
 use winit::{event::ElementState, monitor::MonitorHandle};
 use zng_txt::{ToTxt, Txt};
 use zng_unit::*;
@@ -299,7 +301,7 @@ pub(crate) fn monitor_handle_to_info(handle: &MonitorHandle) -> MonitorInfo {
     }
 }
 
-pub(crate) fn glutin_video_mode_to_video_mode(v: winit::monitor::VideoMode) -> VideoMode {
+pub(crate) fn glutin_video_mode_to_video_mode(v: winit::monitor::VideoModeHandle) -> VideoMode {
     let size = v.size();
     VideoMode {
         size: PxSize::new(Px(size.width as i32), Px(size.height as i32)),
@@ -1239,11 +1241,10 @@ pub(crate) fn accesskit_to_event(
 }
 
 pub(crate) fn access_tree_update_to_kit(update: zng_view_api::access::AccessTreeUpdate) -> accesskit::TreeUpdate {
-    let mut classes = accesskit::NodeClassSet::new();
     let mut nodes = Vec::with_capacity(update.updates.iter().map(|t| t.len()).sum());
 
     for update in update.updates {
-        access_node_to_kit(update.root(), &mut classes, &mut nodes);
+        access_node_to_kit(update.root(), &mut nodes);
     }
 
     accesskit::TreeUpdate {
@@ -1255,7 +1256,6 @@ pub(crate) fn access_tree_update_to_kit(update: zng_view_api::access::AccessTree
 
 fn access_node_to_kit(
     node: zng_view_api::access::AccessNodeRef,
-    class_set: &mut accesskit::NodeClassSet,
     output: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
 ) -> accesskit::NodeId {
     let node_id = access_id_to_kit(node.id);
@@ -1352,10 +1352,10 @@ fn access_node_to_kit(
                     builder.set_auto_complete(accesskit::AutoComplete::List)
                 }
             }
-            Checked(b) => builder.set_checked(match b {
-                Some(true) => accesskit::Checked::True,
-                Some(false) => accesskit::Checked::False,
-                None => accesskit::Checked::Mixed,
+            Checked(b) => builder.set_toggled(match b {
+                Some(true) => accesskit::Toggled::True,
+                Some(false) => accesskit::Toggled::False,
+                None => accesskit::Toggled::Mixed,
             }),
             Current(kind) => match kind {
                 access::CurrentKind::Page => builder.set_aria_current(accesskit::AriaCurrent::Page),
@@ -1385,7 +1385,7 @@ fn access_node_to_kit(
                 }
             }
             Label(s) => builder.set_name(s.clone().into_owned().into_boxed_str()),
-            Level(n) => builder.set_hierarchical_level(n.get() as usize),
+            Level(n) => builder.set_level(n.get() as usize),
             Modal => builder.set_modal(),
             MultiSelectable => builder.set_multiselectable(),
             Orientation(o) => match o {
@@ -1418,9 +1418,9 @@ fn access_node_to_kit(
                 }
             }
             ActiveDescendant(id) => builder.set_active_descendant(access_id_to_kit(*id)),
-            ColCount(c) => builder.set_table_column_count(*c),
-            ColIndex(i) => builder.set_table_column_index(*i),
-            ColSpan(s) => builder.set_table_cell_column_span(*s),
+            ColCount(c) => builder.set_column_count(*c),
+            ColIndex(i) => builder.set_column_index(*i),
+            ColSpan(s) => builder.set_column_span(*s),
             Controls(ids) => builder.set_controls(ids.iter().copied().map(access_id_to_kit).collect::<Vec<_>>()),
             DescribedBy(ids) => builder.set_described_by(ids.iter().copied().map(access_id_to_kit).collect::<Vec<_>>()),
             Details(ids) => builder.set_details(ids.iter().copied().map(access_id_to_kit).collect::<Vec<_>>()),
@@ -1440,9 +1440,9 @@ fn access_node_to_kit(
                 }
             }
             ItemIndex(p) => builder.set_position_in_set(*p),
-            RowCount(c) => builder.set_table_row_count(*c),
-            RowIndex(i) => builder.set_table_row_index(*i),
-            RowSpan(s) => builder.set_table_cell_row_span(*s),
+            RowCount(c) => builder.set_row_count(*c),
+            RowIndex(i) => builder.set_row_index(*i),
+            RowSpan(s) => builder.set_row_span(*s),
             ItemCount(s) => builder.set_size_of_set(*s),
             Lang(l) => builder.set_language(l.to_string()),
 
@@ -1463,7 +1463,7 @@ fn access_node_to_kit(
     // add descendants
     if node.children.is_empty() {
         for child in node.children() {
-            let child_id = access_node_to_kit(child, class_set, output);
+            let child_id = access_node_to_kit(child, output);
             builder.push_child(child_id);
         }
     } else {
@@ -1471,11 +1471,11 @@ fn access_node_to_kit(
             builder.push_child(access_id_to_kit(*id));
         }
         for child in node.children() {
-            let _ = access_node_to_kit(child, class_set, output);
+            let _ = access_node_to_kit(child, output);
         }
     }
 
-    let node = builder.build(class_set);
+    let node = builder.build();
     output.push((node_id, node));
     node_id
 }
@@ -1531,7 +1531,6 @@ fn access_role_to_kit(role: zng_view_api::access::AccessRole) -> accesskit::Role
         ListItem => Role::ListItem,
         Math => Role::Math,
         Note => Role::Note,
-        Column => Role::Column,
         Row => Role::Row,
         RowGroup => Role::RowGroup,
         RowHeader => Role::RowHeader,
@@ -1586,3 +1585,51 @@ pub(crate) fn frame_update_render_reasons(update: &FrameUpdateRequest) -> wr::Re
 
     reasons
 }
+
+#[must_use = "call .unset before drop"]
+pub(crate) struct WinitEventLoop(*const ActiveEventLoop);
+impl WinitEventLoop {
+    pub fn set<'l>(&mut self, winit_loop: &'l ActiveEventLoop) -> WinitEventLoopGuard<'l> {
+        self.0 = winit_loop;
+        WinitEventLoopGuard {
+            defused: false,
+            _loop_lifetime: PhantomData,
+        }
+    }
+}
+impl Default for WinitEventLoop {
+    fn default() -> Self {
+        Self(std::ptr::null())
+    }
+}
+impl ops::Deref for WinitEventLoop {
+    type Target = ActiveEventLoop;
+
+    fn deref(&self) -> &Self::Target {
+        assert!(!self.0.is_null(), "winit event loop not active");
+        // SAFETY: just checked, and can only set pointer with `set`
+        unsafe { &*self.0 }
+    }
+}
+pub(crate) struct WinitEventLoopGuard<'l> {
+    defused: bool,
+    _loop_lifetime: PhantomData<&'l ActiveEventLoop>,
+}
+impl<'l> WinitEventLoopGuard<'l> {
+    pub fn unset(&mut self, l: &mut WinitEventLoop) {
+        self.defused = true;
+        l.0 = std::ptr::null();
+    }
+}
+impl<'l> Drop for WinitEventLoopGuard<'l> {
+    fn drop(&mut self) {
+        if !self.defused {
+            let msg = "unsafe pointer to winit ActiveEventLoop not cleared";
+            if std::thread::panicking() {
+                tracing::error!("{msg}");
+            } else {
+                panic!("{msg}");
+            }
+        }
+    }
+}
diff --git a/crates/zng-view/src/window.rs b/crates/zng-view/src/window.rs
index 120c70964..11c95c564 100644
--- a/crates/zng-view/src/window.rs
+++ b/crates/zng-view/src/window.rs
@@ -17,9 +17,9 @@ use webrender::{
 };
 
 use winit::{
-    event_loop::EventLoopWindowTarget,
-    monitor::{MonitorHandle, VideoMode as GVideoMode},
-    window::{Fullscreen, Icon, Window as GWindow, WindowBuilder},
+    event_loop::ActiveEventLoop,
+    monitor::{MonitorHandle, VideoModeHandle as GVideoMode},
+    window::{Fullscreen, Icon, Window as GWindow, WindowAttributes},
 };
 use zng_txt::{ToTxt, Txt};
 use zng_unit::{DipPoint, DipRect, DipSize, DipToPx, Factor, Px, PxPoint, PxRect, PxToDip, PxVector, Rgba};
@@ -132,7 +132,7 @@ impl Window {
         gen: ViewProcessGen,
         icon: Option<Icon>,
         mut cfg: WindowRequest,
-        window_target: &EventLoopWindowTarget<AppEvent>,
+        winit_loop: &ActiveEventLoop,
         gl_manager: &mut GlContextManager,
         mut renderer_exts: Vec<(ApiExtensionId, Box<dyn RendererExtension>)>,
         event_sender: AppEventSender,
@@ -142,7 +142,7 @@ impl Window {
         let window_scope = tracing::trace_span!("glutin").entered();
 
         // create window and OpenGL context
-        let mut winit = WindowBuilder::new()
+        let mut winit = WindowAttributes::default()
             .with_title(cfg.title)
             .with_resizable(cfg.resizable)
             .with_transparent(cfg.transparent)
@@ -151,9 +151,9 @@ impl Window {
         let mut s = cfg.state;
         s.clamp_size();
 
-        let mut monitor = window_target.primary_monitor();
+        let mut monitor = winit_loop.primary_monitor();
         let mut monitor_is_primary = true;
-        for m in window_target.available_monitors() {
+        for m in winit_loop.available_monitors() {
             let pos = m.position();
             let size = m.size();
             let rect = PxRect::new(pos.to_px(), size.to_px());
@@ -227,7 +227,7 @@ impl Window {
             render_mode = RenderMode::Integrated;
         }
 
-        let (winit_window, context) = gl_manager.create_headed(id, winit, window_target, render_mode, &event_sender);
+        let (winit_window, context) = gl_manager.create_headed(id, winit, winit_loop, render_mode, &event_sender);
         render_mode = context.render_mode();
 
         // * Extend the winit Windows window to not block the Alt+F4 key press.
@@ -636,7 +636,13 @@ impl Window {
             return None;
         }
 
-        let new_pos = self.window.inner_position().unwrap().to_px();
+        let new_pos = match self.window.inner_position() {
+            Ok(p) => p.to_px(),
+            Err(e) => {
+                tracing::error!("cannot get inner_position, {e}");
+                PxPoint::zero()
+            }
+        };
         if self.prev_pos != new_pos {
             self.prev_pos = new_pos;
 
@@ -789,7 +795,7 @@ impl Window {
     /// Set cursor icon and visibility.
     pub fn set_cursor(&mut self, icon: Option<CursorIcon>) {
         if let Some(icon) = icon {
-            self.window.set_cursor_icon(icon.to_winit());
+            self.window.set_cursor(icon.to_winit());
             self.window.set_cursor_visible(true);
         } else {
             self.window.set_cursor_visible(false);
@@ -873,7 +879,13 @@ impl Window {
     fn probe_state(&self) -> WindowStateAll {
         let mut state = self.state.clone();
 
-        state.global_position = self.window.inner_position().unwrap().to_px();
+        state.global_position = match self.window.inner_position() {
+            Ok(p) => p.to_px(),
+            Err(e) => {
+                tracing::error!("cannot get inner_position, {e}");
+                PxPoint::zero()
+            }
+        };
 
         if self.is_minimized() {
             state.state = WindowState::Minimized;


Test patch: