git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

src/mac/mod.rs (40.2K)

   1 //! The macOS shell: an [`Application`] run in an AppKit window, the way the
   2 //! Wayland shell runs one on a surface and the browser shell on a canvas. It
   3 //! is the third shell over the shared pieces — [`Driver`] routes what input
   4 //! means, [`Pacer::turn`] decides what a turn does, [`build_frame`] builds
   5 //! the frame — and the renderer is the Vulkan one, on Metal through
   6 //! MoltenVK (a `CAMetalLayer` is a [`SurfaceTarget::Metal`]). What is left
   7 //! here is AppKit's side:
   8 //!
   9 //! - **The window.** An `NSWindow` whose content is the app's root plate:
  10 //!   transparent, its titlebar transparent over full-size content, so the
  11 //!   plate fills the window and the traffic lights stand on its corner. The
  12 //!   window keeps AppKit's own resize edges, so the driver's CSD resize band
  13 //!   is the app's (`PressSite::own_edges`); a press it reads as the
  14 //!   window's (the titlebar band, a movable root plate) drags the window
  15 //!   (`performWindowDragWithEvent:`).
  16 //! - **Events in.** A layer-hosting `NSView` (flipped, so y runs down as
  17 //!   everywhere else in the toolkit) takes the mouse, scroll, magnify and
  18 //!   key events and hands them, mapped by `backend::appkit`, to the driver.
  19 //!   Command is the shortcut key (⌘Z is undo). AppKit's own key repeats
  20 //!   and scroll momentum are dropped: the driver repeats a held key and the
  21 //!   toolkit coasts a flick, as on Wayland.
  22 //! - **Turns.** Main-queue dispatches (`dispatch2`): any event, and any
  23 //!   `AppSender::send` from any thread (`set_wake`), asks for a turn at most
  24 //!   one ACTIVE frame after the last; between, the loop sleeps for whatever
  25 //!   the pacer said. A turn superseded by an earlier one is dropped by its
  26 //!   generation.
  27 //! - **Frames out.** [`build_frame`] at the view's size in points and the
  28 //!   window's backing scale, drawn by the [`VkRenderer`]; `stage_renderer`
  29 //!   (and so the portable `stage_3d`) runs before each draw, `renderer_init`
  30 //!   once.
  31 //!
  32 //! The menu is a minimal one (Quit, ⌘Q), whose quit — like the close
  33 //! button — asks the app to exit the way a compositor's close does.
  34 //!
  35 //! - **Input methods.** The view is an `NSTextInputClient`: while a widget
  36 //!   is editing text, a key press goes through `interpretKeyEvents:`, whose
  37 //!   marked text is the composition and whose inserted text the commit
  38 //!   (`crate::ime`); the candidate window is put under the caret the
  39 //!   widget reported. The clipboard is the general `NSPasteboard`
  40 //!   (`widget::clipboard`).
  41 //!
  42 //! Not there yet: drag and drop, the context menu in a popup window (it is
  43 //! drawn in the window and kept inside it, as on a layer surface), and blur
  44 //! behind the window (`NSVisualEffectView`).
  45 //!
  46 //! **This module has never been run.** It is written against objc2's
  47 //! AppKit bindings and type-checked for `aarch64-apple-darwin`
  48 //! (`scripts/check-mac`) on Linux, where nothing can be linked or run: it
  49 //! needs a Mac, with MoltenVK installed (the Vulkan SDK, or Homebrew's
  50 //! `molten-vk` and `vulkan-loader`).
  51 
  52 use std::cell::{Cell, RefCell};
  53 use std::sync::mpsc::Receiver;
  54 use std::time::Duration;
  55 
  56 use cursor_icon::CursorIcon;
  57 use dispatch2::{DispatchQueue, DispatchTime};
  58 use objc2::rc::Retained;
  59 use objc2::runtime::{AnyObject, NSObject, ProtocolObject, Sel};
  60 use objc2::{define_class, msg_send, sel, AnyThread, DefinedClass, MainThreadOnly};
  61 use objc2_app_kit::{
  62     NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSApplicationTerminateReply,
  63     NSBackingStoreType, NSColor, NSCursor, NSEvent, NSEventModifierFlags, NSEventType, NSMenu, NSMenuItem,
  64     NSTextInputClient, NSTrackingArea, NSTrackingAreaOptions, NSView, NSWindow, NSWindowDelegate, NSWindowStyleMask,
  65     NSWindowTitleVisibility,
  66 };
  67 use objc2_foundation::{
  68     ns_string, MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSNotFound, NSNotification,
  69     NSObjectProtocol, NSPoint, NSRange, NSRangePointer, NSRect, NSSize, NSString, NSUInteger,
  70 };
  71 use objc2_quartz_core::CAMetalLayer;
  72 use web_time::Instant;
  73 
  74 use crate::backend::app::{set_wake, AppSender, Application, LogicalPosition, LogicalSize};
  75 use crate::backend::appkit;
  76 use crate::backend::driver::{Driver, Press, PressSite, Turn};
  77 use crate::backend::frame::build_frame;
  78 use crate::backend::shell::{Pacer, Shell, Step, ACTIVE_DISPATCH};
  79 use crate::vk::{SurfaceTarget, VkRenderer};
  80 use crate::widget::{context_menu, ElementState, Key};
  81 use crate::backend::text::DlText;
  82 
  83 /// Run `A` in a window until it exits. Must be called on the main thread
  84 /// (a process's `main`), as AppKit requires.
  85 pub fn run<A: Application>() {
  86     let mtm = MainThreadMarker::new().expect("cce-ui's macOS shell runs on the main thread");
  87     // The window's interaction state (`crate::window_state`), current while the run loop
  88     // below runs this window's code — which is until `run` returns.
  89     let window_state = crate::window_state::WindowState::new();
  90     let _window = crate::window_state::enter(&window_state);
  91     let ns_app = NSApplication::sharedApplication(mtm);
  92     ns_app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
  93 
  94     let (tx, rx) = std::sync::mpsc::channel();
  95     // The scale is known before the app is built, as on the other shells: a
  96     // widget may read it as it is made.
  97     let main_scale = objc2_app_kit::NSScreen::mainScreen(mtm).map_or(2.0, |s| s.backingScaleFactor());
  98     crate::scale::set_scale_factor(main_scale as f32);
  99     let mut app = A::create(AppSender::from(tx));
 100     let settings = app.settings();
 101     crate::scale::set_app_id(settings.app_id.clone());
 102 
 103     let delegate = Delegate::new(mtm);
 104     ns_app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
 105     ns_app.setMainMenu(Some(&main_menu(mtm, &settings.title)));
 106 
 107     let style = NSWindowStyleMask::Titled
 108         | NSWindowStyleMask::Closable
 109         | NSWindowStyleMask::Miniaturizable
 110         | NSWindowStyleMask::Resizable
 111         | NSWindowStyleMask::FullSizeContentView;
 112     let rect = NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(settings.width as f64, settings.height as f64));
 113     // SAFETY: released-when-closed is turned off at once, as a window made
 114     // outside a window controller must be.
 115     let window = unsafe {
 116         NSWindow::initWithContentRect_styleMask_backing_defer(
 117             NSWindow::alloc(mtm),
 118             rect,
 119             style,
 120             NSBackingStoreType::Buffered,
 121             false,
 122         )
 123     };
 124     unsafe { window.setReleasedWhenClosed(false) };
 125     window.setTitle(&NSString::from_str(&settings.title));
 126     window.setTitlebarAppearsTransparent(true);
 127     window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
 128     window.setOpaque(false);
 129     window.setBackgroundColor(Some(&NSColor::clearColor()));
 130     window.setAcceptsMouseMovedEvents(true);
 131     if let Some((w, h)) = settings.min_size {
 132         window.setContentMinSize(NSSize::new(w as f64, h as f64));
 133     }
 134     window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
 135 
 136     let view = CceView::new(mtm, rect);
 137     let layer = CAMetalLayer::new();
 138     layer.setOpaque(false);
 139     let scale = window.backingScaleFactor();
 140     layer.setContentsScale(scale);
 141     // A layer-HOSTING view: the layer is set before the view wants one.
 142     view.setLayer(Some(&layer));
 143     view.setWantsLayer(true);
 144     window.setContentView(Some(&view));
 145     let _ = window.makeFirstResponder(Some(&view));
 146     window.center();
 147 
 148     let fs = if app.load_system_fonts() {
 149         crate::create_font_system_with_system_fonts()
 150     } else {
 151         crate::create_font_system()
 152     };
 153     let (pw, ph) = physical((settings.width as f32, settings.height as f32), scale);
 154     let layer_ptr: *const std::ffi::c_void = Retained::as_ptr(&layer).cast();
 155     // SAFETY: the layer is held by the shell, which drops the renderer first.
 156     let mut renderer = match unsafe { VkRenderer::try_new_for(SurfaceTarget::Metal { layer: layer_ptr }, pw, ph, 0.0) } {
 157         Ok(r) => r,
 158         Err(e) => {
 159             log::error!("[mac] no renderer for the window: {e}");
 160             return;
 161         }
 162     };
 163     app.renderer_init(&mut renderer);
 164 
 165     let shell = MacShell {
 166         renderer,
 167         app,
 168         driver: Driver::new(),
 169         redraw: true,
 170         exit: false,
 171         rx,
 172         fs,
 173         swash: cosmic_text::SwashCache::new(),
 174         items: Vec::new(),
 175         damage_owed: true,
 176         logical: (0.0, 0.0),
 177         scale,
 178         just_configured: true,
 179         cursor: CursorIcon::Default,
 180         pinch: 1.0,
 181         pacer: Pacer::new(settings.title),
 182         generation: 0,
 183         due: None,
 184         last_turn: Instant::now(),
 185         stopped: false,
 186         ime_caret: None,
 187         mtm,
 188         layer,
 189         view: view.clone(),
 190         window: window.clone(),
 191     };
 192     let shell = RefCell::new(shell);
 193     shell.borrow_mut().measure();
 194     SINK.with(|s| *s.borrow_mut() = Some(Box::new(move |ev| shell.borrow_mut().event(ev))));
 195     // A send from any thread hops to the main queue and asks for a turn.
 196     set_wake(Some(std::sync::Arc::new(|| DispatchQueue::main().exec_async(|| {
 197         send(Ev::Wake);
 198     }))));
 199 
 200     window.makeKeyAndOrderFront(None);
 201     #[allow(deprecated)]
 202     ns_app.activateIgnoringOtherApps(true);
 203     send(Ev::Wake);
 204     ns_app.run();
 205 
 206     // `stop:` returned the run loop: the app has exited. The shell — the
 207     // renderer before the layer it presents to — goes with the sink.
 208     set_wake(None);
 209     SINK.with(|s| s.borrow_mut().take());
 210     drop(view);
 211     drop(window);
 212 }
 213 
 214 /// What the view, the delegate and the loop's dispatches hand the shell, in
 215 /// plain values: the shell is generic over the app and the AppKit classes
 216 /// cannot be, so they reach it through [`SINK`].
 217 enum Ev {
 218     Moved { x: f32, y: f32 },
 219     Entered { x: f32, y: f32 },
 220     Exited,
 221     /// Answers true when the press is a window drag the view must start.
 222     Pressed { number: i64, flags: u64, x: f32, y: f32 },
 223     Released { number: i64, flags: u64, x: f32, y: f32 },
 224     Scrolled { dx: f64, dy: f64, precise: bool, inverted: bool, phase: u64, momentum: u64, flags: u64, x: f32, y: f32 },
 225     Magnified { phase: u64, magnification: f64 },
 226     Keyed { code: u16, chars: String, unmodified: String, flags: u64, down: bool },
 227     /// The input method's composition changed (marked text).
 228     Preedit(Option<crate::ime::Preedit>),
 229     /// The input method committed text.
 230     Commit(String),
 231     FlagsChanged { code: u16, flags: u64 },
 232     Focused(bool),
 233     /// The window's size or backing scale changed.
 234     Resized,
 235     /// The close button, or Quit: the app is asked to exit.
 236     CloseRequested,
 237     /// A turn scheduled under this generation is due.
 238     Turn(u64),
 239     /// Turn soon.
 240     Wake,
 241 }
 242 
 243 thread_local! {
 244     static SINK: RefCell<Option<Box<dyn FnMut(Ev) -> bool>>> = const { RefCell::new(None) };
 245 }
 246 
 247 /// Hand `ev` to the shell. An event raised from inside a dispatch (AppKit
 248 /// calling back while the app runs) finds the shell busy and is dropped,
 249 /// as the browser shell drops one; so is everything after the exit.
 250 fn send(ev: Ev) -> bool {
 251     SINK.with(|s| match s.try_borrow_mut() {
 252         Ok(mut sink) => sink.as_mut().is_some_and(|f| f(ev)),
 253         Err(_) => false,
 254     })
 255 }
 256 
 257 /// The window's side of the run loop, as the Wayland shell's `EngineState`
 258 /// is the compositor's. Fields drop in order: the renderer before the layer
 259 /// it presents to.
 260 struct MacShell<A: Application> {
 261     renderer: VkRenderer,
 262     app: A,
 263     driver: Driver,
 264     redraw: bool,
 265     exit: bool,
 266     rx: Receiver<A::Message>,
 267     fs: cosmic_text::FontSystem,
 268     swash: cosmic_text::SwashCache,
 269     items: Vec<DlText>,
 270     damage_owed: bool,
 271     /// The view's size in points, and the window's backing scale.
 272     logical: (f32, f32),
 273     scale: f64,
 274     just_configured: bool,
 275     cursor: CursorIcon,
 276     /// The pinch's cumulative scale (AppKit reports each step's change).
 277     pinch: f32,
 278     pacer: Pacer,
 279     /// The generation of the turn that is due; older dispatches are stale.
 280     generation: u64,
 281     due: Option<Instant>,
 282     last_turn: Instant,
 283     stopped: bool,
 284     /// The caret the input method was last told of.
 285     ime_caret: Option<[f32; 4]>,
 286     mtm: MainThreadMarker,
 287     layer: Retained<CAMetalLayer>,
 288     view: Retained<CceView>,
 289     window: Retained<NSWindow>,
 290 }
 291 
 292 impl<A: Application> MacShell<A> {
 293     fn event(&mut self, ev: Ev) -> bool {
 294         if self.stopped {
 295             return false;
 296         }
 297         let mut drag = false;
 298         match ev {
 299             Ev::Turn(g) => {
 300                 if g == self.generation {
 301                     self.take_turn();
 302                 }
 303                 return false;
 304             }
 305             Ev::Wake => {}
 306             Ev::Moved { x, y } => {
 307                 let pos = self.at(x, y);
 308                 let (driver, t) = self.turn();
 309                 driver.pointer_motion(t, pos);
 310                 self.update_cursor();
 311             }
 312             Ev::Entered { x, y } => {
 313                 let pos = self.at(x, y);
 314                 let (driver, t) = self.turn();
 315                 driver.pointer_enter(t, pos);
 316                 self.update_cursor();
 317             }
 318             Ev::Exited => {
 319                 let (driver, t) = self.turn();
 320                 driver.pointer_leave(t);
 321             }
 322             Ev::Pressed { number, flags, x, y } => {
 323                 let Some(btn) = appkit::button(number, flags) else { return false };
 324                 self.sync_mods(flags);
 325                 let pos = self.at(x, y);
 326                 // AppKit resizes from its own window edges; a move is a drag.
 327                 let site = PressSite { size: self.size(), on_popup: false, can_grab: true, own_edges: true };
 328                 let (driver, t) = self.turn();
 329                 drag = driver.pointer_press(t, btn, pos, site) == Press::Move;
 330             }
 331             Ev::Released { number, flags, x, y } => {
 332                 let Some(btn) = appkit::button(number, flags) else { return false };
 333                 let pos = self.at(x, y);
 334                 let (driver, t) = self.turn();
 335                 driver.pointer_release(t, btn, pos);
 336                 self.update_cursor();
 337             }
 338             Ev::Scrolled { dx, dy, precise, inverted, phase, momentum, flags, x, y } => {
 339                 // The system's natural-scrolling setting is the one in force
 340                 // here, not input.kdl's: value controls read it through this.
 341                 if precise {
 342                     crate::input::force_natural_scroll(Some(inverted));
 343                 }
 344                 let Some(frame) = appkit::scroll_frame(dx, dy, precise, inverted, phase, momentum) else { return false };
 345                 self.sync_mods(flags);
 346                 let pos = self.at(x, y);
 347                 let (driver, t) = self.turn();
 348                 driver.scroll(t, frame, pos);
 349             }
 350             Ev::Magnified { phase, magnification } => {
 351                 if phase & appkit::phase::BEGAN != 0 {
 352                     self.pinch = 1.0;
 353                     self.driver.pinch_begin();
 354                 }
 355                 if magnification != 0.0 {
 356                     self.pinch *= 1.0 + magnification as f32;
 357                     let pinch = self.pinch;
 358                     let (driver, t) = self.turn();
 359                     driver.pinch_update(t, pinch);
 360                 }
 361                 if phase & (appkit::phase::ENDED | appkit::phase::CANCELLED) != 0 {
 362                     self.driver.pinch_end();
 363                 }
 364             }
 365             Ev::Keyed { code, chars, unmodified, flags, down } => {
 366                 let accel = flags & (appkit::flags::COMMAND | appkit::flags::CONTROL) != 0;
 367                 let Some((key, text)) = appkit::map_key(code, &chars, &unmodified, accel) else { return false };
 368                 self.sync_mods(flags);
 369                 let state = if down { ElementState::Pressed } else { ElementState::Released };
 370                 let (driver, t) = self.turn();
 371                 driver.key(t, key, text, state);
 372             }
 373             Ev::Preedit(preedit) => {
 374                 let (driver, t) = self.turn();
 375                 driver.preedit(t, preedit);
 376             }
 377             Ev::Commit(text) => {
 378                 let (driver, t) = self.turn();
 379                 driver.preedit(t, None);
 380                 let (driver, t) = self.turn();
 381                 driver.commit_text(t, text);
 382             }
 383             Ev::FlagsChanged { code, flags } => {
 384                 // A modifier key went down or up: the new state, and the key
 385                 // itself as the other shells report it.
 386                 self.sync_mods(flags);
 387                 let bit = match code {
 388                     0x37 | 0x36 => appkit::flags::COMMAND,
 389                     0x38 | 0x3C => appkit::flags::SHIFT,
 390                     0x3A | 0x3D => appkit::flags::OPTION,
 391                     0x3B | 0x3E => appkit::flags::CONTROL,
 392                     _ => 0,
 393                 };
 394                 if let (true, Some((key @ Key::Named(_), _))) = (bit != 0, appkit::map_key(code, "", "", false)) {
 395                     let state = if flags & bit != 0 { ElementState::Pressed } else { ElementState::Released };
 396                     let (driver, t) = self.turn();
 397                     driver.key(t, key, None, state);
 398                 }
 399             }
 400             Ev::Focused(focused) => {
 401                 let (driver, t) = self.turn();
 402                 driver.keyboard_focus(t, focused);
 403             }
 404             Ev::Resized => {
 405                 if self.measure() {
 406                     self.just_configured = true;
 407                 }
 408             }
 409             Ev::CloseRequested => self.exit = true,
 410         }
 411         self.wake();
 412         drag
 413     }
 414 
 415     /// Ask for a turn one ACTIVE frame after the last at the latest, unless
 416     /// one is due sooner already.
 417     fn wake(&mut self) {
 418         let at = (self.last_turn + ACTIVE_DISPATCH).max(Instant::now());
 419         if self.due.is_some_and(|d| d <= at) {
 420             return;
 421         }
 422         self.schedule(at.saturating_duration_since(Instant::now()));
 423     }
 424 
 425     fn schedule(&mut self, after: Duration) {
 426         self.generation += 1;
 427         self.due = Some(Instant::now() + after);
 428         let g = self.generation;
 429         let when = DispatchTime::try_from(after).unwrap_or(DispatchTime::NOW);
 430         let _ = DispatchQueue::main().after(when, move || {
 431             send(Ev::Turn(g));
 432         });
 433     }
 434 
 435     fn take_turn(&mut self) {
 436         self.due = None;
 437         self.last_turn = Instant::now();
 438         while let Ok(msg) = self.rx.try_recv() {
 439             let mut rebuild = false;
 440             self.app.update(msg, &mut rebuild, &mut self.exit);
 441             self.redraw |= rebuild;
 442         }
 443         let mut pacer = std::mem::replace(&mut self.pacer, Pacer::new(String::new()));
 444         let step = pacer.turn(self);
 445         self.pacer = pacer;
 446         match step {
 447             Step::Exit => self.finish(),
 448             Step::Sleep(d) => {
 449                 if self.due.is_none() {
 450                     self.schedule(d);
 451                 }
 452             }
 453         }
 454     }
 455 
 456     /// The app exited: let it take its leave, then return the run loop.
 457     fn finish(&mut self) {
 458         self.stopped = true;
 459         self.app.on_exit();
 460         let ns_app = NSApplication::sharedApplication(self.mtm);
 461         ns_app.stop(None);
 462         // `stop:` takes effect after the next event; this is it.
 463         if let Some(ev) = NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
 464             NSEventType::ApplicationDefined,
 465             NSPoint::new(0.0, 0.0),
 466             NSEventModifierFlags::empty(),
 467             0.0,
 468             0,
 469             None,
 470             0,
 471             0,
 472             0,
 473         ) {
 474             ns_app.postEvent_atStart(&ev, true);
 475         }
 476     }
 477 
 478     /// Read the view's size and the backing scale, and size the drawable to
 479     /// them. Whether either changed.
 480     fn measure(&mut self) -> bool {
 481         let bounds = self.view.bounds();
 482         let (w, h) = (bounds.size.width as f32, bounds.size.height as f32);
 483         let scale = self.window.backingScaleFactor();
 484         if (w, h) == self.logical && scale == self.scale {
 485             return false;
 486         }
 487         self.logical = (w, h);
 488         if scale != self.scale {
 489             self.scale = scale;
 490             crate::scale::set_scale_factor(scale as f32);
 491         }
 492         // MoltenVK reports the layer's bounds times its contents scale as
 493         // the surface's extent.
 494         self.layer.setContentsScale(scale);
 495         let (pw, ph) = physical(self.logical, self.scale);
 496         self.renderer.resize(pw, ph);
 497         self.redraw = true;
 498         true
 499     }
 500 
 501     /// Keep the input method in step with the frame just drawn: a
 502     /// composition a widget dropped, or one with no widget editing any more,
 503     /// is discarded (the marked text cleared first, so the `unmarkText` this
 504     /// may call commits nothing); a caret that moved has the candidate
 505     /// window follow it.
 506     fn sync_input_method(&mut self) {
 507         let caret = crate::ime::caret();
 508         let drop = crate::ime::take_reset() || (caret.is_none() && !self.view.ivars().marked.borrow().is_empty());
 509         let context = self.view.inputContext();
 510         if drop {
 511             self.view.ivars().marked.borrow_mut().clear();
 512             crate::ime::set_preedit(None);
 513             if let Some(c) = &context {
 514                 c.discardMarkedText();
 515             }
 516         }
 517         if caret != self.ime_caret {
 518             self.ime_caret = caret;
 519             if let Some(c) = &context {
 520                 c.invalidateCharacterCoordinates();
 521             }
 522         }
 523     }
 524 
 525     fn size(&self) -> LogicalSize {
 526         LogicalSize::new(self.logical.0, self.logical.1)
 527     }
 528 
 529     fn at(&mut self, x: f32, y: f32) -> LogicalPosition {
 530         self.driver.cursor_pos = (x, y);
 531         LogicalPosition::new(x, y)
 532     }
 533 
 534     fn sync_mods(&mut self, flags: u64) {
 535         let mods = appkit::modifiers(flags);
 536         if mods != self.driver.mods {
 537             self.driver.set_modifiers(&mut self.app, mods);
 538         }
 539     }
 540 
 541     fn update_cursor(&mut self) {
 542         let (x, y) = self.driver.cursor_pos;
 543         let icon = self.driver.cursor_icon_at(&self.app, x, y, self.size());
 544         if icon != self.cursor {
 545             self.cursor = icon;
 546             ns_cursor(icon).set();
 547         }
 548     }
 549 }
 550 
 551 impl<A: Application> Shell for MacShell<A> {
 552     type App = A;
 553 
 554     fn turn(&mut self) -> (&mut Driver, Turn<'_, A>) {
 555         (&mut self.driver, Turn { app: &mut self.app, redraw: &mut self.redraw, exit: &mut self.exit })
 556     }
 557 
 558     fn app(&self) -> &A {
 559         &self.app
 560     }
 561 
 562     fn redraw(&mut self) -> &mut bool {
 563         &mut self.redraw
 564     }
 565 
 566     fn exit_requested(&self) -> bool {
 567         self.exit
 568     }
 569 
 570     fn take_just_configured(&mut self) -> bool {
 571         std::mem::replace(&mut self.just_configured, false)
 572     }
 573 
 574     fn request_size(&mut self, w: u32, h: u32) {
 575         if (w as f32, h as f32) != self.logical {
 576             self.window.setContentSize(NSSize::new(w as f64, h as f64));
 577             self.measure();
 578         }
 579     }
 580 
 581     fn sync(&mut self) {
 582         // No popup window to host the menu yet: it is drawn in the window
 583         // and kept inside it, frames asked for while a page turn animates.
 584         if context_menu::is_visible() {
 585             if context_menu::is_turning() {
 586                 self.redraw = true;
 587             }
 588             context_menu::set_hosted(false);
 589             context_menu::constrain_to(0.0, 0.0, self.logical.0, self.logical.1);
 590         }
 591     }
 592 
 593     fn set_title(&mut self, title: &str) {
 594         self.window.setTitle(&NSString::from_str(title));
 595     }
 596 
 597     fn frame_pending(&mut self) -> bool {
 598         false
 599     }
 600 
 601     fn configured(&self) -> bool {
 602         self.logical.0 > 0.0 && self.logical.1 > 0.0
 603     }
 604 
 605     fn present(&mut self, _fresh: bool) {
 606         let size = self.size();
 607         let frame = build_frame(&mut self.app, &mut self.fs, size, self.scale, &mut self.damage_owed, &mut self.items);
 608         if frame.dl_text {
 609             let spans = frame.text_spans(&self.items);
 610             self.renderer.prepare_text(&mut self.fs, &mut self.swash, &spans);
 611         }
 612         let (pw, ph) = physical(self.logical, self.scale);
 613         let e = self.renderer.pending_extent();
 614         if (e.width, e.height) != (pw, ph) {
 615             self.renderer.resize(pw, ph);
 616         }
 617         if self.app.stage_renderer(&mut self.renderer, size, self.scale) {
 618             self.redraw = true;
 619         }
 620         if self.renderer.draw_frame_2d(frame.frame2d()) {
 621             self.damage_owed = false;
 622         } else {
 623             self.redraw = true;
 624         }
 625         self.sync_input_method();
 626     }
 627 }
 628 
 629 fn physical(logical: (f32, f32), scale: f64) -> (u32, u32) {
 630     let s = scale as f32;
 631     ((logical.0 * s).round().max(1.0) as u32, (logical.1 * s).round().max(1.0) as u32)
 632 }
 633 
 634 /// The toolkit's cursor as AppKit's. AppKit has no public diagonal resize
 635 /// cursors before macOS 15, so those are the arrow; the two straight ones
 636 /// are deprecated there, for the directional cursors macOS 15 added, and
 637 /// still what every earlier release has.
 638 #[allow(deprecated)]
 639 fn ns_cursor(icon: CursorIcon) -> Retained<NSCursor> {
 640     match icon {
 641         CursorIcon::Pointer => NSCursor::pointingHandCursor(),
 642         CursorIcon::Text | CursorIcon::VerticalText => NSCursor::IBeamCursor(),
 643         CursorIcon::Crosshair | CursorIcon::Cell => NSCursor::crosshairCursor(),
 644         CursorIcon::Grab => NSCursor::openHandCursor(),
 645         CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => NSCursor::closedHandCursor(),
 646         CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
 647         CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize | CursorIcon::ColResize => {
 648             NSCursor::resizeLeftRightCursor()
 649         }
 650         CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize | CursorIcon::RowResize => {
 651             NSCursor::resizeUpDownCursor()
 652         }
 653         CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
 654         CursorIcon::Copy => NSCursor::dragCopyCursor(),
 655         CursorIcon::Alias => NSCursor::dragLinkCursor(),
 656         _ => NSCursor::arrowCursor(),
 657     }
 658 }
 659 
 660 /// An application menu with Quit (⌘Q) in it: every Mac app has one, and
 661 /// without it ⌘Q does nothing.
 662 fn main_menu(mtm: MainThreadMarker, title: &str) -> Retained<NSMenu> {
 663     let bar = NSMenu::new(mtm);
 664     let app_item = NSMenuItem::new(mtm);
 665     let app_menu = NSMenu::new(mtm);
 666     // SAFETY: `terminate:` is NSApplication's, which answers it through the
 667     // delegate's `applicationShouldTerminate:` below.
 668     let quit = unsafe {
 669         NSMenuItem::initWithTitle_action_keyEquivalent(
 670             NSMenuItem::alloc(mtm),
 671             &NSString::from_str(&format!("Quit {title}")),
 672             Some(sel!(terminate:)),
 673             ns_string!("q"),
 674         )
 675     };
 676     app_menu.addItem(&quit);
 677     app_item.setSubmenu(Some(&app_menu));
 678     bar.addItem(&app_item);
 679     bar
 680 }
 681 
 682 /// Where an event's pointer is, in the view's (flipped) coordinates.
 683 fn location(view: &NSView, event: &NSEvent) -> (f32, f32) {
 684     let p = view.convertPoint_fromView(event.locationInWindow(), None);
 685     (p.x as f32, p.y as f32)
 686 }
 687 
 688 /// The view's input-method state, for the `NSTextInputClient` methods.
 689 #[derive(Default)]
 690 struct ViewIme {
 691     /// The marked text (the composition) as the input method last set it.
 692     marked: RefCell<String>,
 693     /// Inside `interpretKeyEvents:` for a key press, whose characters are
 694     /// these; and whether the input method took the press.
 695     in_key: Cell<bool>,
 696     consumed: Cell<bool>,
 697     key_chars: RefCell<String>,
 698 }
 699 
 700 /// The text of what an input method hands over: an `NSString`, or an
 701 /// `NSAttributedString` around one.
 702 fn ns_text(obj: &AnyObject) -> String {
 703     if let Some(a) = obj.downcast_ref::<NSAttributedString>() {
 704         a.string().to_string()
 705     } else if let Some(s) = obj.downcast_ref::<NSString>() {
 706         s.to_string()
 707     } else {
 708         String::new()
 709     }
 710 }
 711 
 712 define_class!(
 713     // SAFETY: NSView has no subclassing requirements, and CceView no Drop.
 714     #[unsafe(super(NSView, objc2_app_kit::NSResponder, NSObject))]
 715     #[thread_kind = MainThreadOnly]
 716     #[name = "CceUiView"]
 717     #[ivars = ViewIme]
 718     struct CceView;
 719 
 720     unsafe impl NSObjectProtocol for CceView {}
 721 
 722     // The input method's side of the view: AppKit calls these from inside
 723     // `interpretKeyEvents:` (see `key`), and the candidate window asks where
 724     // the caret is.
 725     unsafe impl NSTextInputClient for CceView {
 726         #[unsafe(method(insertText:replacementRange:))]
 727         unsafe fn insert_text(&self, string: &AnyObject, _replacement: NSRange) {
 728             let text = ns_text(string);
 729             let ime = self.ivars();
 730             let was_marked = !std::mem::take(&mut *ime.marked.borrow_mut()).is_empty();
 731             // A plain key typing its own character is left to the key path,
 732             // which keeps its named keys and the driver's repeat.
 733             if ime.in_key.get() && !was_marked && text == *ime.key_chars.borrow() {
 734                 return;
 735             }
 736             ime.consumed.set(true);
 737             send(Ev::Commit(text));
 738         }
 739 
 740         #[unsafe(method(doCommandBySelector:))]
 741         unsafe fn do_command(&self, _selector: Sel) {
 742             // A key the input method does not take (Return, Backspace, an
 743             // arrow): left to the key path.
 744         }
 745 
 746         #[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
 747         unsafe fn set_marked_text(&self, string: &AnyObject, selected: NSRange, _replacement: NSRange) {
 748             let text = ns_text(string);
 749             let ime = self.ivars();
 750             ime.consumed.set(true);
 751             *ime.marked.borrow_mut() = text.clone();
 752             let cursor = crate::backend::dom::utf16_range_to_bytes(
 753                 &text,
 754                 Some(selected.location as u32),
 755                 Some((selected.location + selected.length) as u32),
 756             );
 757             send(Ev::Preedit((!text.is_empty()).then(|| crate::ime::Preedit { text, cursor })));
 758         }
 759 
 760         #[unsafe(method(unmarkText))]
 761         fn unmark_text(&self) {
 762             // Accept the composition as it stands.
 763             let text = std::mem::take(&mut *self.ivars().marked.borrow_mut());
 764             if !text.is_empty() {
 765                 send(Ev::Commit(text));
 766             }
 767         }
 768 
 769         #[unsafe(method(selectedRange))]
 770         fn selected_range(&self) -> NSRange {
 771             NSRange::new(NSNotFound as usize, 0)
 772         }
 773 
 774         #[unsafe(method(markedRange))]
 775         fn marked_range(&self) -> NSRange {
 776             let marked = self.ivars().marked.borrow();
 777             if marked.is_empty() {
 778                 NSRange::new(NSNotFound as usize, 0)
 779             } else {
 780                 NSRange::new(0, marked.encode_utf16().count())
 781             }
 782         }
 783 
 784         #[unsafe(method(hasMarkedText))]
 785         fn has_marked_text(&self) -> bool {
 786             !self.ivars().marked.borrow().is_empty()
 787         }
 788 
 789         #[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
 790         unsafe fn attributed_substring(&self, _range: NSRange, _actual: NSRangePointer) -> Option<Retained<NSAttributedString>> {
 791             None
 792         }
 793 
 794         #[unsafe(method_id(validAttributesForMarkedText))]
 795         fn valid_attributes(&self) -> Retained<NSArray<NSAttributedStringKey>> {
 796             NSArray::new()
 797         }
 798 
 799         /// Where the candidate window goes: under the editing widget's caret,
 800         /// in screen coordinates.
 801         #[unsafe(method(firstRectForCharacterRange:actualRange:))]
 802         unsafe fn first_rect(&self, _range: NSRange, _actual: NSRangePointer) -> NSRect {
 803             let [x, y, w, h] = crate::ime::caret().unwrap_or([0.0, 0.0, 1.0, 16.0]);
 804             let caret = NSRect::new(NSPoint::new(x as f64, y as f64), NSSize::new(w.max(1.0) as f64, h as f64));
 805             let in_window = self.convertRect_toView(caret, None);
 806             self.window().map_or(in_window, |w| w.convertRectToScreen(in_window))
 807         }
 808 
 809         #[unsafe(method(characterIndexForPoint:))]
 810         fn character_index(&self, _point: NSPoint) -> NSUInteger {
 811             NSNotFound as NSUInteger
 812         }
 813     }
 814 
 815     impl CceView {
 816         #[unsafe(method(isFlipped))]
 817         fn is_flipped(&self) -> bool {
 818             true
 819         }
 820 
 821         #[unsafe(method(acceptsFirstResponder))]
 822         fn accepts_first_responder(&self) -> bool {
 823             true
 824         }
 825 
 826         #[unsafe(method(acceptsFirstMouse:))]
 827         fn accepts_first_mouse(&self, _event: Option<&NSEvent>) -> bool {
 828             true
 829         }
 830 
 831         #[unsafe(method(mouseMoved:))]
 832         fn mouse_moved(&self, event: &NSEvent) {
 833             let (x, y) = location(self, event);
 834             send(Ev::Moved { x, y });
 835         }
 836 
 837         #[unsafe(method(mouseDragged:))]
 838         fn mouse_dragged(&self, event: &NSEvent) {
 839             let (x, y) = location(self, event);
 840             send(Ev::Moved { x, y });
 841         }
 842 
 843         #[unsafe(method(rightMouseDragged:))]
 844         fn right_mouse_dragged(&self, event: &NSEvent) {
 845             let (x, y) = location(self, event);
 846             send(Ev::Moved { x, y });
 847         }
 848 
 849         #[unsafe(method(otherMouseDragged:))]
 850         fn other_mouse_dragged(&self, event: &NSEvent) {
 851             let (x, y) = location(self, event);
 852             send(Ev::Moved { x, y });
 853         }
 854 
 855         #[unsafe(method(mouseEntered:))]
 856         fn mouse_entered(&self, event: &NSEvent) {
 857             let (x, y) = location(self, event);
 858             send(Ev::Entered { x, y });
 859         }
 860 
 861         #[unsafe(method(mouseExited:))]
 862         fn mouse_exited(&self, _event: &NSEvent) {
 863             send(Ev::Exited);
 864         }
 865 
 866         #[unsafe(method(mouseDown:))]
 867         fn mouse_down(&self, event: &NSEvent) {
 868             self.press(event);
 869         }
 870 
 871         #[unsafe(method(rightMouseDown:))]
 872         fn right_mouse_down(&self, event: &NSEvent) {
 873             self.press(event);
 874         }
 875 
 876         #[unsafe(method(otherMouseDown:))]
 877         fn other_mouse_down(&self, event: &NSEvent) {
 878             self.press(event);
 879         }
 880 
 881         #[unsafe(method(mouseUp:))]
 882         fn mouse_up(&self, event: &NSEvent) {
 883             self.release(event);
 884         }
 885 
 886         #[unsafe(method(rightMouseUp:))]
 887         fn right_mouse_up(&self, event: &NSEvent) {
 888             self.release(event);
 889         }
 890 
 891         #[unsafe(method(otherMouseUp:))]
 892         fn other_mouse_up(&self, event: &NSEvent) {
 893             self.release(event);
 894         }
 895 
 896         #[unsafe(method(scrollWheel:))]
 897         fn scroll_wheel(&self, event: &NSEvent) {
 898             let (x, y) = location(self, event);
 899             send(Ev::Scrolled {
 900                 dx: event.scrollingDeltaX(),
 901                 dy: event.scrollingDeltaY(),
 902                 precise: event.hasPreciseScrollingDeltas(),
 903                 inverted: event.isDirectionInvertedFromDevice(),
 904                 phase: event.phase().0 as u64,
 905                 momentum: event.momentumPhase().0 as u64,
 906                 flags: event.modifierFlags().0 as u64,
 907                 x,
 908                 y,
 909             });
 910         }
 911 
 912         #[unsafe(method(magnifyWithEvent:))]
 913         fn magnify(&self, event: &NSEvent) {
 914             send(Ev::Magnified { phase: event.phase().0 as u64, magnification: event.magnification() });
 915         }
 916 
 917         #[unsafe(method(keyDown:))]
 918         fn key_down(&self, event: &NSEvent) {
 919             self.key(event, true);
 920         }
 921 
 922         #[unsafe(method(keyUp:))]
 923         fn key_up(&self, event: &NSEvent) {
 924             self.key(event, false);
 925         }
 926 
 927         #[unsafe(method(flagsChanged:))]
 928         fn flags_changed(&self, event: &NSEvent) {
 929             send(Ev::FlagsChanged { code: event.keyCode(), flags: event.modifierFlags().0 as u64 });
 930         }
 931     }
 932 );
 933 
 934 impl CceView {
 935     fn new(mtm: MainThreadMarker, frame: NSRect) -> Retained<Self> {
 936         let this = Self::alloc(mtm).set_ivars(ViewIme::default());
 937         // SAFETY: NSView's designated initializer.
 938         let view: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] };
 939         // Enter, exit and motion wherever the view is, window key or not.
 940         let options = NSTrackingAreaOptions::MouseEnteredAndExited
 941             | NSTrackingAreaOptions::MouseMoved
 942             | NSTrackingAreaOptions::ActiveAlways
 943             | NSTrackingAreaOptions::InVisibleRect;
 944         // SAFETY: the view owns the area and outlives it; no user info.
 945         let area = unsafe {
 946             NSTrackingArea::initWithRect_options_owner_userInfo(
 947                 NSTrackingArea::alloc(),
 948                 NSRect::ZERO,
 949                 options,
 950                 Some(&view),
 951                 None,
 952             )
 953         };
 954         view.addTrackingArea(&area);
 955         view
 956     }
 957 
 958     fn press(&self, event: &NSEvent) {
 959         let (x, y) = location(self, event);
 960         let ev = Ev::Pressed { number: event.buttonNumber() as i64, flags: event.modifierFlags().0 as u64, x, y };
 961         if send(ev) {
 962             if let Some(window) = self.window() {
 963                 window.performWindowDragWithEvent(event);
 964             }
 965         }
 966     }
 967 
 968     fn release(&self, event: &NSEvent) {
 969         let (x, y) = location(self, event);
 970         send(Ev::Released { number: event.buttonNumber() as i64, flags: event.modifierFlags().0 as u64, x, y });
 971     }
 972 
 973     fn key(&self, event: &NSEvent, down: bool) {
 974         let text = |s: Option<Retained<NSString>>| s.map(|s| s.to_string()).unwrap_or_default();
 975         let flags = event.modifierFlags().0 as u64;
 976         // While a widget is editing text, a key press goes to the input
 977         // method first (`interpretKeyEvents:`, which calls back into the
 978         // `NSTextInputClient` methods above); what it takes is composition
 979         // or a commit, what it leaves takes the key path below. A ⌘
 980         // shortcut never goes, and nothing does while nothing is editing,
 981         // so an input method left on does not eat an app's single-key
 982         // commands.
 983         let ime = self.ivars();
 984         if down && flags & appkit::flags::COMMAND == 0 && crate::ime::caret().is_some() {
 985             ime.in_key.set(true);
 986             ime.consumed.set(false);
 987             *ime.key_chars.borrow_mut() = text(event.characters());
 988             self.interpretKeyEvents(&NSArray::from_slice(&[event]));
 989             ime.in_key.set(false);
 990             if ime.consumed.get() {
 991                 return;
 992             }
 993         }
 994         // The driver repeats a held key itself, as it does on Wayland.
 995         if event.isARepeat() {
 996             return;
 997         }
 998         let keyed = |down| Ev::Keyed {
 999             code: event.keyCode(),
1000             chars: text(event.characters()),
1001             unmodified: text(event.charactersIgnoringModifiers()),
1002             flags,
1003             down,
1004         };
1005         send(keyed(down));
1006         // AppKit sends no keyUp for a key pressed with Command held, so a ⌘
1007         // shortcut is released as it is pressed: otherwise the driver would
1008         // take the key as held, and repeat ⌘Z until the window lost focus.
1009         if down && flags & appkit::flags::COMMAND != 0 {
1010             send(keyed(false));
1011         }
1012     }
1013 }
1014 
1015 define_class!(
1016     // SAFETY: NSObject has no subclassing requirements, and Delegate no Drop.
1017     #[unsafe(super(NSObject))]
1018     #[thread_kind = MainThreadOnly]
1019     #[name = "CceUiDelegate"]
1020     struct Delegate;
1021 
1022     unsafe impl NSObjectProtocol for Delegate {}
1023 
1024     unsafe impl NSApplicationDelegate for Delegate {
1025         /// Quit asks the app to exit, as a close does; the shell stops the
1026         /// run loop once it has.
1027         #[unsafe(method(applicationShouldTerminate:))]
1028         fn should_terminate(&self, _sender: &NSApplication) -> NSApplicationTerminateReply {
1029             send(Ev::CloseRequested);
1030             NSApplicationTerminateReply::TerminateCancel
1031         }
1032     }
1033 
1034     unsafe impl NSWindowDelegate for Delegate {
1035         #[unsafe(method(windowShouldClose:))]
1036         fn window_should_close(&self, _sender: &NSWindow) -> bool {
1037             send(Ev::CloseRequested);
1038             false
1039         }
1040 
1041         #[unsafe(method(windowDidResize:))]
1042         fn window_did_resize(&self, _notification: &NSNotification) {
1043             send(Ev::Resized);
1044         }
1045 
1046         #[unsafe(method(windowDidChangeBackingProperties:))]
1047         fn window_did_change_backing(&self, _notification: &NSNotification) {
1048             send(Ev::Resized);
1049         }
1050 
1051         #[unsafe(method(windowDidBecomeKey:))]
1052         fn window_did_become_key(&self, _notification: &NSNotification) {
1053             send(Ev::Focused(true));
1054         }
1055 
1056         #[unsafe(method(windowDidResignKey:))]
1057         fn window_did_resign_key(&self, _notification: &NSNotification) {
1058             send(Ev::Focused(false));
1059         }
1060     }
1061 );
1062 
1063 impl Delegate {
1064     fn new(mtm: MainThreadMarker) -> Retained<Self> {
1065         let this = Self::alloc(mtm).set_ivars(());
1066         // SAFETY: NSObject's `init`.
1067         unsafe { msg_send![super(this), init] }
1068     }
1069 }