crcbl-shell 0.1.0

Crucible windowing shell: own Wayland, X11, Web/canvas and headless backends
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
//! The window class this backend builds at runtime, window creation, the
//! `CAMetalLayer`, and the borderless round trip.
//!
//! # Decision: an `NSWindow` subclass, because a borderless one cannot take the
//! keyboard
//!
//! `-[NSWindow canBecomeKeyWindow]` returns `NO` for a window whose style mask
//! has neither `NSWindowStyleMaskTitled` nor `NSWindowStyleMaskResizable` — and
//! `NSWindowStyleMaskBorderless` is zero, so a borderless window is exactly that
//! case. A game that went borderless would stop receiving keystrokes, with
//! nothing anywhere reporting why.
//!
//! Overriding it is the documented answer and needs a subclass, which on this
//! runtime means a second `objc_allocateClassPair` beside
//! [`app`](super::app)'s. `canBecomeMainWindow` is overridden with it: *main* is
//! what the menu bar acts on, and a borderless window that is key but not main
//! leaves the previous application's menus on screen.
//!
//! # Decision: borderless is a style swap, and the way back has to be exact
//!
//! The windowing rules (`docs/notes/backends.md`) keep two display modes and drop exclusive
//! fullscreen, and — separately — this backend does **not** use
//! `toggleFullScreen:`. Spaces fullscreen is a third mode with its own
//! animation, its own space, its own `NSWindowDelegate` callbacks and its own
//! ways to fail; what the plan asks for is a frameless window at screen size,
//! which is what this does.
//!
//! So: remember the style mask and the **frame** rectangle, drop to borderless,
//! and `setFrame:` the target screen's own rectangle. The way back restores both,
//! in that order — mask first, because `setFrame:` positions the window for the
//! mask it has and restoring the rectangle under a borderless mask would put a
//! titled window where a frameless one fitted. The desktop's display mode is
//! never touched, which is the whole reason the mode exists.
//!
//! # The ordering rule: style mask, then presentation options, then frame
//!
//! **Changing an application's `NSApplicationPresentationOptions` repositions
//! every window of that application back to its creation frame.** Not the
//! window it is called about — there is no such window, the property is on
//! `NSApplication` — and not "constrains it to the screen": it puts windows back
//! where they were created, origin on the way in and origin *and size* on the
//! way out.
//!
//! That is a fact about AppKit rather than about this codebase, it is documented
//! nowhere Apple publishes, and it cost eight CI round trips to corner. It was
//! measured, on a real runner, by bracketing
//! [`apply_mode`](AppKitShell::apply_mode) statement by statement:
//!
//! ```text
//! after the Borderless arm                 [0,0,1024,768]
//! after size_layer                         [0,0,1024,768]
//! after refresh_presentation (options 0x5) [192,160,1024,768]   ← moved
//!
//! after the Windowed arm                   [192,256,512,416]
//! after size_layer                         [192,256,512,416]
//! after refresh_presentation (options 0x0) [192,160,640,512]    ← moved and resized
//! ```
//!
//! `[192,160,640,512]` is exactly the frame the window was created at. One
//! mechanism, both directions.
//!
//! **There are three positions, not two, and the middle one was paid for
//! separately.** The obvious repair — hoist the options to the very front — put
//! `setPresentationOptions:` before the style mask ever changed, and AppKit
//! raised `NSInvalidArgumentException`. An Objective-C exception unwinding
//! through a Rust frame is undefined behaviour: the process aborted, `SIGTRAP`,
//! with the whole injected-input suite already green behind it and nothing
//! logged after the statement before. **The options are not accepted while the
//! window is still in the style it is leaving.**
//!
//! So the sequence is: **`setStyleMask:`, then `refresh_presentation`, then
//! `setFrame:`.** Both of the first two reposition the window and the third has
//! to survive, which is why the frame is last; the mask is first because the
//! options will not take before it. `apply_mode` hoists both the target mask and
//! the target frame out of what used to be two `match` arms so that this reads
//! as one ordered block — buried in a `match`, the rule was expressible twice
//! and enforceable neither time. The `apply_mode:` readings around each step
//! exist to keep it that way: anything that repositions the window after the
//! frame shows up immediately as a frame that changed after it was set.
//!
//! The value being sent is **not** the thing that raised, and this is provable
//! rather than assumed: [`geometry::presentation_options`] is a `const fn` whose
//! only input is a two-variant enum and whose only outputs are
//! `PRESENTATION_DEFAULT` (`0x0`) and `PRESENTATION_BORDERLESS`
//! (`AutoHideDock | AutoHideMenuBar`, `0x5`). The illegal lone-menu-bar
//! combination this crate warns about cannot be constructed by it at any call
//! site, at any time.
//!
//! Three things follow that are worth knowing before reordering anything else.
//!
//! * The borderless frame is computed from `-[NSScreen frame]` rather than
//!   `visibleFrame`, so it does not depend on what the options do — the ordering
//!   is free.
//! * The effective mode has to be settled before the options, because
//!   `refresh_presentation` decides from it. That is why the target screen is
//!   resolved up front by
//!   [`borderless_target`](AppKitShell::borderless_target) instead of being read
//!   back off the window afterwards.
//! * **The windowed placement has to be captured before all three**, and this is
//!   the trap the fix nearly fell into. The saved mask and frame used to be read
//!   inside the borderless arm, which was safe only because the options were
//!   applied later; moving the options up without moving the capture would have
//!   saved the *creation* frame as the way back and swapped one silent
//!   corruption for another. **Anything that reads the window's current geometry
//!   in this function belongs above the sequence**, not inside it — the mask
//!   changes the frame too, so "before the options" is not far enough.
//!
//! # Decision: `constrainFrameRect:toScreen:` is overridden, because a game says where its window goes
//!
//! `-[NSWindow setFrame:display:]` does not simply apply the rectangle it is
//! given: it passes it through `constrainFrameRect:toScreen:` first, whose
//! default implementation keeps a window's title bar clear of the menu bar and
//! the window itself on a screen. That is right for a document window a person
//! dragged and **wrong for every frame this backend sets**, all of which are
//! computed from an `NSScreen` rectangle that is by construction on that screen:
//! a borderless window is meant to cover the menu bar, and a restored windowed
//! one is meant to land exactly where it was.
//!
//! The default is not merely unhelpful, it is silent — it preserves the size it
//! was given and moves the origin. Overriding it to answer the proposed
//! rectangle unchanged is the documented way out, and the subclass to do it in
//! already exists for `canBecomeKeyWindow`. What that gives up is AppKit's safety
//! net for a frame this backend computed wrongly; what it buys is that a frame
//! this backend computed *correctly* is the frame the window gets.
//!
//! **It was not, however, the cause of the borderless-origin defect** — it was
//! the first of six mechanisms proposed for it and eliminated by measurement,
//! and it is kept because it is right on its own terms rather than because it
//! fixed anything. The others were a corrupted `NSRect` argument, the event
//! pump, a delegate callback, macOS state restoration, and an illegal
//! presentation-options combination. The answer was the ordering rule above, and
//! the `crcbl_core::log::debug!` trail through [`AppKitShell::apply_mode`] and [`set_frame`]
//! is what eliminated every one of them; `docs/backlog.md` carries the trail.
//!
//! Unlike the Win32 backend there is no `WINDOWPLACEMENT` to save: AppKit's
//! zoomed state is a property of the window rather than a rectangle to restore,
//! and `-[NSWindow setFrame:display:]` on a zoomed window un-zooms it in the way
//! the user's next click on the zoom button expects.
//!
//! # Decision: the shell owns the `CAMetalLayer`
//!
//! [`SurfaceTarget::AppKit`](crcbl_core::SurfaceTarget::AppKit) carries a
//! `CAMetalLayer *` and not an `NSView *`, and `crcbl_core::surface` says why in
//! as many words: Metal takes the layer directly and `VK_EXT_metal_surface`
//! takes it through MoltenVK, so **no HAL backend ever has to touch AppKit**.
//! That only works if somebody creates the layer, and that somebody is here.
//!
//! The view is made *layer-hosting* rather than layer-backed, and the two calls
//! go in the order `setLayer:` then `setWantsLayer:YES`. Backwards, AppKit
//! creates its own `CALayer` first and the `CAMetalLayer` set afterwards is
//! replaced — a window that renders nothing, with no error anywhere.

use core::ffi::CStr;
use core::ptr;
use std::sync::OnceLock;

use crate::{
    DisplayMode, MonitorId, PhysicalSize, ShellBackend, ShellError, SizeConstraints, WindowDesc,
};

use super::ffi::{self, Class, Id, Imp, NSRect, NSSize, ObjcBool, Sel, YES};
use super::geometry;
use super::monitors::Screen;
use super::panel::Panel;
use super::shell::{AppKitShell, AppWindow};
use super::view;
use crate::{PanelKeyboard, PopupInput};

/// The name of the `NSWindow` subclass this module builds at runtime.
const WINDOW_CLASS: &CStr = c"CrcblWindow";

/// The registration, once per process. `Err` carries what went wrong.
static WINDOW: OnceLock<Result<usize, String>> = OnceLock::new();
/// The same, for [`panel_class`]'s keyboard-taking panel.
static PANEL: OnceLock<Result<usize, String>> = OnceLock::new();
/// The same, for [`panel_class`]'s panel that never takes the keyboard.
static PASSIVE_PANEL: OnceLock<Result<usize, String>> = OnceLock::new();

/// `canBecomeKeyWindow` / `canBecomeMainWindow` — **always `YES`**.
///
/// One implementation for both, because the answer and the signature are the
/// same and two copies would be two things to keep in step. See the
/// [module docs](self) for what returning `NO` costs a borderless window.
///
/// # Safety
///
/// Called only by AppKit, on an instance of the class built below.
unsafe extern "C" fn can_become(_window: Id, _cmd: Sel) -> ObjcBool {
    YES
}

/// `canBecomeKeyWindow` / `canBecomeMainWindow` — **always `NO`**: a panel
/// that takes no keyboard, and the main-window question for every panel.
///
/// # Safety
///
/// Called only by AppKit, on an instance of a class built below.
unsafe extern "C" fn cannot_become(_window: Id, _cmd: Sel) -> ObjcBool {
    ffi::NO
}

/// `constrainFrameRect:toScreen:` — **the rectangle it was handed, unchanged**.
///
/// See the [module docs](self) for the argument. In short: every frame this
/// backend sets comes from an `NSScreen` rectangle, and AppKit's default
/// implementation silently moves such a frame to keep a title bar below the menu
/// bar — preserving the size and changing the origin, which is a borderless
/// window of exactly the right extent hanging off two edges of the display.
///
/// `screen` is ignored rather than consulted: the caller has already chosen the
/// screen, and this method exists to stop a second opinion being applied to that
/// choice.
///
/// # Safety
///
/// Called only by AppKit, on an instance of the class built below.
unsafe extern "C" fn constrain_frame(_window: Id, _cmd: Sel, frame: NSRect, _screen: Id) -> NSRect {
    frame
}

/// Builds — or returns — the `NSWindow` subclass.
///
/// Same shape and same reasoning as [`app`](super::app)'s delegate class,
/// including the already-registered fallback: a null from
/// `objc_allocateClassPair` means the same code is loaded twice in one process,
/// and the class that is already there is ours by name and by method table.
fn window_class() -> Result<Class, ShellError> {
    build_class(&ClassSpec {
        superclass: c"NSWindow",
        name: WINDOW_CLASS,
        key: can_become,
        main: can_become,
        cell: &WINDOW,
    })
}

/// The `NSPanel` subclass a [panel](crate::WindowRole::Panel) is made of:
/// one that can take the keyboard, and one that never does, which is what
/// keeps a click on a bar from taking the keyboard from the window the user
/// is typing in. Neither becomes the main window, which a panel never is.
pub(super) fn panel_class(takes_keyboard: bool) -> Result<Class, ShellError> {
    if takes_keyboard {
        build_class(&ClassSpec {
            superclass: c"NSPanel",
            name: c"CrcblPanel",
            key: can_become,
            main: cannot_become,
            cell: &PANEL,
        })
    } else {
        build_class(&ClassSpec {
            superclass: c"NSPanel",
            name: c"CrcblPassivePanel",
            key: cannot_become,
            main: cannot_become,
            cell: &PASSIVE_PANEL,
        })
    }
}

/// What [`build_class`] registers: the superclass, the name, the answers to
/// `canBecomeKeyWindow` and `canBecomeMainWindow`, and the once-per-process
/// cell the registration is kept in.
struct ClassSpec {
    superclass: &'static CStr,
    name: &'static CStr,
    key: unsafe extern "C" fn(Id, Sel) -> ObjcBool,
    main: unsafe extern "C" fn(Id, Sel) -> ObjcBool,
    cell: &'static OnceLock<Result<usize, String>>,
}

/// Builds — or returns — one of this module's runtime subclasses; see
/// [`window_class`].
fn build_class(spec: &ClassSpec) -> Result<Class, ShellError> {
    let outcome = spec.cell.get_or_init(|| {
        let Some(superclass) = ffi::class(spec.superclass) else {
            return Err(format!(
                "the Objective-C runtime has no {:?}, so this image has no AppKit in it",
                spec.superclass
            ));
        };
        // SAFETY: `NSWindow` is a live class, the name is a NUL-terminated
        // literal, and no extra instance storage is asked for.
        let class = unsafe { ffi::objc_allocateClassPair(superclass, spec.name.as_ptr(), 0) };
        if class.is_null() {
            let Some(existing) = ffi::class(spec.name) else {
                return Err(format!(
                    "objc_allocateClassPair refused {:?} and no class of that name exists",
                    spec.name
                ));
            };
            crcbl_core::log::debug!(
                "the {:?} class was already registered in this process",
                spec.name
            );
            return Ok(existing as usize);
        }

        let predicate = std::ffi::CString::new(format!("{}@:", ffi::ENC_BOOL))
            .expect("the encoding characters contain no NUL");
        for (name, answer) in [
            (c"canBecomeKeyWindow", spec.key),
            (c"canBecomeMainWindow", spec.main),
        ] {
            // SAFETY: casting a correctly-typed `extern "C"` function to the
            // runtime's opaque `IMP`; the encoding beside it describes the real
            // signature, and the class pair is not yet registered.
            let imp = unsafe {
                core::mem::transmute::<unsafe extern "C" fn(Id, Sel) -> ObjcBool, Imp>(answer)
            };
            // SAFETY: as above.
            if unsafe { ffi::class_addMethod(class, ffi::sel(name), imp, predicate.as_ptr()) }
                == ffi::NO
            {
                return Err(format!(
                    "class_addMethod refused {name:?} on {:?}",
                    spec.name
                ));
            }
        }

        // `NSRect constrainFrameRect:(NSRect)frame toScreen:(NSScreen *)screen`
        // — a rectangle returned, a rectangle and an object taken. See the
        // module docs for why this backend answers it unchanged.
        let constrain = std::ffi::CString::new(format!("{rect}@:{rect}@", rect = ffi::ENC_RECT))
            .expect("the encoding characters contain no NUL");
        // SAFETY: as above. The `NSRect` return is what makes this a separate
        // registration from the two predicates rather than another turn of that
        // loop: it needs its own signature and its own type encoding, and the
        // compiler emits whichever struct-return convention the target uses.
        let imp = unsafe {
            core::mem::transmute::<unsafe extern "C" fn(Id, Sel, NSRect, Id) -> NSRect, Imp>(
                constrain_frame,
            )
        };
        // SAFETY: as above.
        if unsafe {
            ffi::class_addMethod(
                class,
                ffi::sel(c"constrainFrameRect:toScreen:"),
                imp,
                constrain.as_ptr(),
            )
        } == ffi::NO
        {
            return Err(format!(
                "class_addMethod refused constrainFrameRect:toScreen: on {:?}",
                spec.name
            ));
        }
        // SAFETY: a complete class pair that has not been registered.
        unsafe { ffi::objc_registerClassPair(class) };
        Ok(class as usize)
    });
    match outcome {
        Ok(class) => Ok(*class as Class),
        Err(detail) => Err(ShellError::Connect {
            backend: ShellBackend::AppKit,
            detail: detail.clone(),
        }),
    }
}

/// Lets the desktop show through a window where its frame's alpha is below
/// one: no opaque fill behind the content, no shadow drawn from the opaque
/// outline, and a layer AppKit composites by its alpha.
///
/// # Safety
///
/// `window` must be a live `NSWindow` and `layer` its live `CAMetalLayer`, on
/// the main thread.
unsafe fn make_transparent(window: Id, layer: Id) {
    let Some(color) = ffi::class(c"NSColor") else {
        return;
    };
    // SAFETY: the caller guarantees both objects; `clearColor` is a class
    // accessor answering an autoreleased colour the window retains.
    unsafe {
        ffi::msg_set_bool(window, ffi::sel(c"setOpaque:"), false);
        let clear = ffi::msg(color.cast(), ffi::sel(c"clearColor"));
        ffi::msg1_void(window, ffi::sel(c"setBackgroundColor:"), clear);
        ffi::msg_set_bool(window, ffi::sel(c"setHasShadow:"), false);
        ffi::msg_set_bool(layer, ffi::sel(c"setOpaque:"), false);
    }
}

/// What [`create_native_window`](AppKitShell::create_native_window) builds:
/// an ordinary window, a panel, or a popup at a frame its parent decided.
#[derive(Clone, Copy, Debug)]
pub(super) enum Kind<'a> {
    Window,
    Panel(&'a Panel),
    Popup { frame: NSRect, input: PopupInput },
}

/// The objects one window is made of, and its geometry at creation.
#[derive(Clone, Copy, Debug)]
pub(super) struct Created {
    /// The `NSWindow`.
    pub window: Id,
    /// Its content view, which owns the layer.
    pub view: Id,
    /// The `CAMetalLayer`, which is the whole of
    /// [`SurfaceTarget::AppKit`](crcbl_core::SurfaceTarget::AppKit).
    pub layer: Id,
    /// `backingScaleFactor`, read back rather than assumed.
    pub scale: f64,
    /// The content area in backing pixels, which AppKit already knows.
    pub size: PhysicalSize,
}

/// A window's content area, in backing pixels.
///
/// `convertRectToBacking:` rather than the arithmetic in
/// [`geometry::physical_size`], deliberately: it is the API that is correct by
/// construction, and the arithmetic exists beside it so that the *relation* is
/// falsifiable on a machine with no Retina display. The macOS suite asserts the
/// two agree, which is a real cross-check at 2× and a tautology at 1× — see
/// [`AppKitShell`]'s tests, which say so.
///
/// # Safety
///
/// `view` must be a live `NSView`, on the main thread.
pub(super) unsafe fn backing_size(view: Id) -> PhysicalSize {
    // SAFETY: both are `NSView` accessors on a live view; `convertRectToBacking:`
    // takes and returns an `NSRect`.
    let backing = unsafe {
        let bounds = ffi::msg_rect(view, ffi::sel(c"bounds"));
        let convert: unsafe extern "C" fn(Id, Sel, NSRect) -> NSRect = ffi::msg_send_stret();
        convert(view, ffi::sel(c"convertRectToBacking:"), bounds)
    };
    geometry::physical_size(backing.size, 1.0)
}

/// A window's `backingScaleFactor`, or 1.0 if it is not on a screen yet.
///
/// # Safety
///
/// `window` must be a live `NSWindow`, on the main thread.
pub(super) unsafe fn backing_scale(window: Id) -> f64 {
    // SAFETY: an `NSWindow` accessor on a live window.
    geometry::usable_scale(unsafe { ffi::msg_f64(window, ffi::sel(c"backingScaleFactor")) })
}

/// `+[NSWindow frameRectForContentRect:styleMask:]` — the frame a window of
/// `mask` carries around `content`.
///
/// A **class** method, and a question about geometry rather than about any
/// window: that is what lets a caller ask it for the style a window is only
/// about to take. A window leaving borderless needs its frame before
/// `setStyleMask:` has run, and the window it would ask then still has no title
/// bar to measure.
fn frame_for_content(mask: usize, content: NSRect) -> NSRect {
    let Some(class) = ffi::class(c"NSWindow") else {
        // No AppKit in the process. Nothing can be asking for a frame.
        return content;
    };
    // SAFETY: `NSWindow` answers this class method on every macOS this backend
    // supports. It takes an `NSRect` and an `NSUInteger` and returns an
    // `NSRect`, which is the signature written here; it reads no window.
    let send: unsafe extern "C" fn(Id, Sel, NSRect, usize) -> NSRect =
        unsafe { ffi::msg_send_stret() };
    unsafe {
        send(
            class,
            ffi::sel(c"frameRectForContentRect:styleMask:"),
            content,
            mask,
        )
    }
}

/// Points the layer at the window's current backing store.
///
/// Called at creation and on every resize and scale change. `contentsScale` is
/// what makes Core Animation treat the drawable as high-resolution; without it a
/// Retina window presents a quarter-resolution image scaled up, which looks like
/// a renderer bug and is not one.
///
/// # Safety
///
/// `layer` must be a live `CAMetalLayer` and `view` its live host, on the main
/// thread.
pub(super) unsafe fn size_layer(layer: Id, view: Id, scale: f64) {
    // SAFETY: a live layer; both are `CAMetalLayer`/`CALayer` setters.
    unsafe {
        ffi::msg_send::<unsafe extern "C" fn(Id, Sel, f64)>()(
            layer,
            ffi::sel(c"setContentsScale:"),
            scale,
        );
        let size = backing_size(view);
        ffi::msg_set_size(
            layer,
            ffi::sel(c"setDrawableSize:"),
            NSSize::new(f64::from(size.width), f64::from(size.height)),
        );
    }
}

/// Applies the seam's size constraints to a window.
///
/// All three go straight across: AppKit's limits are on the **content**
/// rectangle and are in points, which is the seam's own request space. See
/// [`geometry::content_limits`].
///
/// # Safety
///
/// `window` must be a live `NSWindow`, on the main thread.
pub(super) unsafe fn apply_constraints(window: Id, constraints: SizeConstraints) {
    let (min, max) = geometry::content_limits(constraints);
    // SAFETY: three `NSWindow` setters, each taking an `NSSize`.
    unsafe {
        ffi::msg_set_size(window, ffi::sel(c"setContentMinSize:"), min);
        ffi::msg_set_size(window, ffi::sel(c"setContentMaxSize:"), max);
        ffi::msg_set_size(
            window,
            ffi::sel(c"setContentAspectRatio:"),
            geometry::aspect_size(constraints.aspect),
        );
    }
}

impl AppKitShell {
    /// Creates the window, its layer-hosting view and its `CAMetalLayer`.
    ///
    /// # Errors
    ///
    /// [`ShellError::Connect`] if a class is missing, or
    /// [`ShellError::WindowCreation`] if AppKit refused to make the window or
    /// the view.
    pub(super) fn create_native_window(
        &self,
        desc: &WindowDesc<'_>,
        target: Option<&Screen>,
        kind: Kind<'_>,
    ) -> Result<Created, ShellError> {
        let panel = match kind {
            Kind::Panel(panel) => Some(panel),
            Kind::Window | Kind::Popup { .. } => None,
        };
        let class = match kind {
            Kind::Panel(panel) => panel_class(panel.keyboard != PanelKeyboard::None)?,
            Kind::Popup { input, .. } => panel_class(input == PopupInput::Grab)?,
            Kind::Window => window_class()?,
        };
        // **`CrcblView`, not `NSView`.** A plain view answers `NO` to
        // `acceptsFirstResponder`, implements none of the responder methods
        // input arrives through, and conforms to no text-input protocol — so a
        // window built on one is a window that receives nothing. See
        // [`view`](super::view).
        let view_class = view::view_class()?;
        let Some(layer_class) = ffi::class(c"CAMetalLayer") else {
            return Err(ShellError::Connect {
                backend: ShellBackend::AppKit,
                detail: "the Objective-C runtime has no CAMetalLayer, so this image has no \
                         QuartzCore in it"
                    .to_string(),
            });
        };

        let mask = match kind {
            Kind::Panel(_) | Kind::Popup { .. } => geometry::PANEL_STYLE_MASK,
            Kind::Window => geometry::style_mask(desc.mode, desc.resizable),
        };
        // A borderless window is the screen's size, not the requested one:
        // `WindowDesc::size` is the *windowed* size and is remembered for the
        // trip back. In points, because that is the space `initWithContentRect:`
        // is in — and, on this platform, the seam's logical space too.
        let content = match (kind, desc.mode, target) {
            (Kind::Popup { frame, .. }, _, _) => frame,
            (Kind::Panel(panel), _, Some(screen)) => panel.frame_on(screen),
            (Kind::Window, DisplayMode::Borderless { .. }, Some(screen)) => screen.frame,
            (_, _, Some(screen)) => geometry::centred(screen.visible, geometry::points(desc.size)),
            // No screen to place against — a Mac with no display attached.
            // AppKit puts the window where it likes rather than this backend
            // inventing a desktop layout.
            (_, _, None) => NSRect::new(0.0, 0.0, desc.size.width, desc.size.height),
        };

        // SAFETY: `alloc` then the designated initialiser. `defer: NO` creates
        // the window device now rather than at first display, which is what
        // makes `backingScaleFactor` and the content view's bounds readable
        // before this function returns.
        let window = unsafe {
            let allocated = ffi::msg(class, ffi::sel(c"alloc"));
            let init: unsafe extern "C" fn(Id, Sel, NSRect, usize, usize, ObjcBool) -> Id =
                ffi::msg_send();
            init(
                allocated,
                ffi::sel(c"initWithContentRect:styleMask:backing:defer:"),
                content,
                mask,
                ffi::value::BACKING_BUFFERED,
                ffi::NO,
            )
        };
        if window.is_null() {
            return Err(ShellError::WindowCreation(
                "-[NSWindow initWithContentRect:styleMask:backing:defer:] returned nil".to_string(),
            ));
        }

        // **Before anything else that could close it.** `releasedWhenClosed`
        // defaults to `YES` on a programmatically created `NSWindow`, which
        // means `close` releases the object — and this backend holds the pointer
        // afterwards, in `destroy_window` and in `Drop`. Leaving it on is a
        // use-after-free that only fires on the close path.
        // SAFETY: a live window; the setter takes a `BOOL`.
        unsafe { ffi::msg_set_bool(window, ffi::sel(c"setReleasedWhenClosed:"), false) };

        // **A game window has no business in macOS state restoration**, and this
        // is a correctness decision rather than a workaround — it would be right
        // with no bug anywhere near it, on the same footing as overriding
        // `constrainFrameRect:toScreen:` above.
        //
        // `isRestorable` defaults to `YES`, which enrols the window in a feature
        // this backend cannot honour and does not want:
        //
        // * **It promises something the process cannot deliver.** Restoration
        //   works by AppKit re-creating windows at launch through a class named
        //   in `restorationClass`, or by an application delegate answering
        //   `restoreWindowWithIdentifier:state:completionHandler:`. This backend
        //   implements neither, and deliberately does not take the application's
        //   delegate slot at all — see [`app`](super::app). A restorable window
        //   with nothing able to restore it is a flag set to no purpose.
        // * **The placement is the game's, or the player's.** Where a window
        //   opens is a decision the seam hands to
        //   [`WindowDesc`](crate::WindowDesc) and, above that, to a settings
        //   screen. An operating-system feature that silently reinstates a
        //   position from a previous run is a second, invisible source of truth
        //   for the same value, and the two disagree the moment a game remembers
        //   its own.
        // * **It writes saved state to disk**, keyed by the application's
        //   identity, for a process that may be an unbundled binary with no
        //   stable one. Neither the writing nor the identity is something an
        //   engine should acquire as a side effect of opening a window.
        //
        // Whether it is also implicated in the borderless-origin defect is a
        // separate question the next run answers; it is turned off here because
        // it should be off, not because that answer is known.
        //
        // SAFETY: a live window; the setter takes a `BOOL`.
        unsafe { ffi::msg_set_bool(window, ffi::sel(c"setRestorable:"), false) };

        // **Evidence for the open borderless-origin defect**, and the creation
        // half of it. It has already earned its place: with the creation-time
        // `visibleFrame` of `[0, 63, 1024, 674]` in hand,
        // `centred(visible, 640×480)` is x = 192 and y = 63 + (674 − 480)/2 =
        // **160**, which is exactly where a borderless window keeps ending up. So
        // the origin a flip reverts to is precisely this rectangle's, there is no
        // second formula and no rounding, and the arithmetic here is right.
        // Reading the flip-time visible frame instead gives 158.5 and would have
        // sent the next reader looking for a bug in `centred` that is not there.
        //
        // The two properties beside it now **verify the setters above** rather
        // than discovering a default: `isRestorable` should read `false`, and a
        // `frameAutosaveName` of `Some("")` is autosave already off. A
        // `isRestorable true` here would mean `setRestorable:` did not take.
        //
        // SAFETY: a live window; `frame` returns an `NSRect`, `isRestorable`
        // answers `BOOL`, and `frameAutosaveName` an `NSString` the window owns.
        unsafe {
            crcbl_core::log::debug!(
                "create: mode {:?} asked {:?} -> content {content:?}, mask {mask:#x}; screen \
                 frame {:?} visible {:?}; window frame {:?}, isRestorable {}, \
                 frameAutosaveName {:?}",
                desc.mode,
                desc.size,
                target.map(|screen| screen.frame),
                target.map(|screen| screen.visible),
                ffi::msg_rect(window, ffi::sel(c"frame")),
                ffi::msg_bool(window, ffi::sel(c"isRestorable")),
                ffi::string_from_nsstring(ffi::msg(window, ffi::sel(c"frameAutosaveName"))),
            );
        }

        // SAFETY: `alloc` then `initWithFrame:` on `NSView`; the frame is in the
        // window's own coordinates, so the origin is zero whatever the window's
        // screen position is.
        let view = unsafe {
            let allocated = ffi::msg(view_class, ffi::sel(c"alloc"));
            let init: unsafe extern "C" fn(Id, Sel, NSRect) -> Id = ffi::msg_send();
            init(
                allocated,
                ffi::sel(c"initWithFrame:"),
                NSRect::new(0.0, 0.0, content.size.width, content.size.height),
            )
        };
        if view.is_null() {
            // SAFETY: releasing the window this function allocated and is about
            // to stop tracking.
            unsafe { ffi::msg_void(window, ffi::sel(c"release")) };
            return Err(ShellError::WindowCreation(
                "-[NSView initWithFrame:] returned nil".to_string(),
            ));
        }

        // SAFETY: `layer` is a class method returning an autoreleased
        // `CAMetalLayer`; it is retained below because this shell hands the
        // pointer out in `SurfaceTarget::AppKit` and must outlive the pool.
        let layer = unsafe { ffi::msg(layer_class, ffi::sel(c"layer")) };
        if layer.is_null() {
            // SAFETY: releasing what this function allocated.
            unsafe {
                ffi::msg_void(view, ffi::sel(c"release"));
                ffi::msg_void(window, ffi::sel(c"release"));
            }
            return Err(ShellError::WindowCreation(
                "+[CAMetalLayer layer] returned nil".to_string(),
            ));
        }
        // SAFETY: a live autoreleased object; the retain is balanced by the
        // release in `AppKitShell::release_window`.
        unsafe { ffi::msg_void(layer, ffi::sel(c"retain")) };

        // **`setLayer:` then `setWantsLayer:`**, in that order, which is what
        // makes the view layer-*hosting*. Backwards, AppKit creates its own
        // `CALayer` first and replaces ours; see the module docs.
        //
        // SAFETY: a live view and a live layer; both setters take what they are
        // given here.
        unsafe {
            ffi::msg1_void(view, ffi::sel(c"setLayer:"), layer);
            ffi::msg_set_bool(view, ffi::sel(c"setWantsLayer:"), true);
            ffi::msg1_void(window, ffi::sel(c"setContentView:"), view);
            // The window retains the content view, so this shell's own
            // reference from `alloc` is handed over.
            ffi::msg_void(view, ffi::sel(c"release"));
        }

        // **The switches without which input and drops do not exist**, and none
        // of them is visible to a test that calls a responder method itself —
        // see [`view`](super::view), which is where all five gaps are written
        // out.
        //
        // SAFETY: a live window and its live content view.
        unsafe {
            // Nothing generates `NSEventTypeMouseMoved` for a window that has
            // not asked. Without this, pointer motion with no button held simply
            // does not happen, and `mouseDragged:` covers up the absence for
            // exactly as long as somebody is holding a button.
            ffi::msg_set_bool(window, ffi::sel(c"setAcceptsMouseMovedEvents:"), true);
            // `sendEvent:` delivers key events to the first responder, which is
            // the *window* until something else claims it. Shared with
            // `apply_mode`, which has to do it again after every
            // `setStyleMask:` — see [`focus_content_view`].
            focus_content_view(window);
            // Enter, exit and cursor updates, none of which any other mechanism
            // produces.
            view::add_tracking_area(view);
            // And the drop gate, which is the same shape: **AppKit sends no
            // dragging message at all to a view that has not registered**, so
            // `accept_drops` being off is enforced by the system rather than by
            // this backend refusing an offer it was given. The shell checks the
            // flag again when it translates a drop, because registration is a
            // property of the view and anything in the process could set it.
            if desc.accept_drops {
                view::register_dragged_types(view);
            }
        }

        // SAFETY: an autoreleased `NSString`, valid for this pool.
        if let Some(title) = unsafe { ffi::nsstring(desc.title) } {
            // SAFETY: a live window and a live string, which the window copies.
            unsafe { ffi::msg1_void(window, ffi::sel(c"setTitle:"), title) };
        } else {
            return Err(ShellError::InvalidDescriptor(
                "title contains a NUL byte".to_string(),
            ));
        }

        // SAFETY: a live window.
        unsafe { apply_constraints(window, desc.constraints) };

        if let Some(panel) = panel {
            // SAFETY: a live window of a panel class, on the main thread.
            unsafe { Self::configure_panel(window, panel) };
        }
        if let Kind::Popup { input, .. } = kind {
            // SAFETY: a live window of a panel class, on the main thread.
            unsafe { Self::configure_popup(window, input) };
        }
        if desc.transparent {
            // SAFETY: a live window and its live layer; see `make_transparent`.
            unsafe { make_transparent(window, layer) };
        }

        // SAFETY: a live window; both read.
        let scale = unsafe { backing_scale(window) };
        // SAFETY: a live layer and its live host view.
        unsafe { size_layer(layer, view, scale) };

        // A popup is shown by the shell, which makes it its parent's child
        // window as it does.
        if desc.visible && !matches!(kind, Kind::Popup { .. }) {
            match panel {
                // SAFETY: a live panel of this shell, on the main thread.
                Some(panel) => unsafe { Self::show_panel(window, panel.keyboard) },
                // SAFETY: a live window; `nil` is the documented sender.
                None => unsafe {
                    ffi::msg1_void(window, ffi::sel(c"makeKeyAndOrderFront:"), ptr::null_mut());
                },
            }
        }

        Ok(Created {
            window,
            view,
            layer,
            scale,
            // SAFETY: a live view whose window device exists — `defer: NO`.
            size: unsafe { backing_size(view) },
        })
    }

    /// Switches a window between windowed and borderless.
    ///
    /// AppKit is the only party involved — there is no window manager to ask, as
    /// on Win32 and unlike X11 — so this either succeeds or the screen it named
    /// is gone.
    ///
    /// # Errors
    ///
    /// [`ShellError::NoSuchMonitor`] if the named screen was detached since
    /// [`monitors`](crate::Shell::monitors) was called, or
    /// [`ShellError::Backend`] if there is no screen at all to be borderless on.
    pub(super) fn apply_mode(
        &mut self,
        handle: crate::WindowId,
        mode: DisplayMode,
    ) -> Result<(), ShellError> {
        let state = self.window(handle)?;
        let window = state.window;
        let resizable = state.resizable;
        let saved = state.saved;

        // The screen a borderless window will cover, decided **before anything
        // moves**. `Some` here is exactly the borderless case.
        //
        // Reading it up front is what makes the ordering below possible, and it
        // is a better answer than the read-back this used to do afterwards: for
        // `Borderless { monitor: Some(id) }` the target is `id` by definition,
        // and for `None` it is the screen the window is on right now — which is
        // the very rectangle the frame is computed from. Neither needs the window
        // to have moved first.
        let target = match mode {
            DisplayMode::Borderless { monitor } => Some(self.borderless_target(window, monitor)?),
            DisplayMode::Windowed => None,
        };

        // **The window's placement is captured before anything touches it**, and
        // before the presentation options above all. Those move the window, so a
        // capture taken after them would remember the *creation* frame rather
        // than where the player had the window — which is the exact corruption
        // this ordering fix exists to stop, reintroduced through the back door
        // as the way back. It used to sit inside the borderless arm, where it
        // was safe only because `refresh_presentation` ran later; hoisting the
        // options without hoisting this would have swapped one silent defect for
        // another.
        //
        // `target.is_some()` is the borderless case, and `saved.is_none()` keeps
        // the first capture: two borderless flips in a row must not overwrite the
        // windowed placement with a borderless one.
        if target.is_some() && saved.is_none() {
            // SAFETY: a live window; both are accessors.
            let captured = unsafe {
                super::shell::Saved {
                    mask: ffi::msg_usize(window, ffi::sel(c"styleMask")),
                    frame: ffi::msg_rect(window, ffi::sel(c"frame")),
                }
            };
            crcbl_core::log::debug!("borderless: saved the windowed placement {captured:?}");
            self.window_mut(handle)?.saved = Some(captured);
        }

        // **The placement both legs will apply, decided before any of it is
        // applied.** Hoisting this out of the two arms is what lets the three
        // AppKit steps below read as the single ordered sequence they have to
        // be; buried in a `match`, the ordering rule was expressible only twice
        // and enforceable neither time.
        let (new_mask, new_frame) = match target {
            Some(screen) => (geometry::style_mask(mode, resizable), screen.frame),
            None => {
                let restored = match self.window_mut(handle)?.saved.take() {
                    Some(saved) => saved,
                    None => {
                        // **A window created borderless has nothing saved**, and
                        // there is nothing dishonest to do about it: it has never
                        // had a titled style or a windowed rectangle. So one is
                        // built — the windowed mask, and `WindowDesc::size`
                        // centred on the screen it is covering. Without this the
                        // mask would stay borderless while the effective mode
                        // said windowed, which is the seam reporting a mode the
                        // window is not in.
                        //
                        // **`WindowDesc::size` is a client area, and a titled
                        // frame is taller than one.** Creation hands the
                        // rectangle to `initWithContentRect:`; here it becomes a
                        // `setFrame:` rectangle, so AppKit is asked for the frame
                        // the windowed mask carries around that content. Using
                        // the content rectangle *as* the frame is what leaves the
                        // client area a title bar short of what was asked for.
                        let state = self.window(handle)?;
                        let mask = geometry::style_mask(DisplayMode::Windowed, resizable);
                        let size = geometry::points(state.requested_size);
                        let frame =
                            frame_for_content(mask, NSRect::new(0.0, 0.0, size.width, size.height));
                        let wanted = frame.size;
                        let area = self
                            .screen_of(window)
                            .and_then(|id| self.screens.iter().find(|screen| screen.id == id))
                            .map_or(
                                NSRect::new(0.0, 0.0, wanted.width, wanted.height),
                                |screen| geometry::centred(screen.visible, wanted),
                            );
                        super::shell::Saved { mask, frame: area }
                    }
                };
                (restored.mask, restored.frame)
            }
        };

        // The effective mode has to be settled before the options, because
        // `refresh_presentation` decides from it.
        let state = self.window_mut(handle)?;
        state.effective_mode = match target {
            Some(screen) => DisplayMode::Borderless {
                monitor: Some(screen.id),
            },
            None => DisplayMode::Windowed,
        };

        // ===================================================================
        // The ordered sequence. **Mask, then options, then frame.** Both of the
        // first two reposition the window and the third is the one that has to
        // survive, so the frame is last and nothing below it may move the
        // window. See the ordering rule in the [module docs](self); this is the
        // block it is about, and reordering these three lines is how the defect
        // it describes comes back.
        // ===================================================================

        // SAFETY: a live window; `setStyleMask:` takes an `NSUInteger`, and the
        // two accessors beside it take nothing.
        unsafe {
            ffi::msg_set_usize(window, ffi::sel(c"setStyleMask:"), new_mask);
            // The mask the window actually carries, the frame it moved to, and
            // whether the view still has the keyboard — this one call changes
            // all three, and each was a defect once.
            crcbl_core::log::debug!(
                "apply_mode: after setStyleMask: asked {new_mask:#x}, carries {:#x}, frame {:?}, \
                 view has the keyboard {}",
                ffi::msg_usize(window, ffi::sel(c"styleMask")),
                ffi::msg_rect(window, ffi::sel(c"frame")),
                view_has_focus(window)
            );
        }

        // **Second, not first and not last.** Last is the original defect, and
        // it is measured. First — before the mask — is what raised
        // `NSInvalidArgumentException` through Rust and aborted the process with
        // SIGTRAP, which is why there are three positions here rather than two.
        self.refresh_presentation();
        crcbl_core::log::debug!(
            "apply_mode: after refresh_presentation (options {:?}) the frame is {:?}",
            // SAFETY: the main thread, with `set_mode`'s pool in scope.
            unsafe { presentation_options() }.map(|options| format!("{options:#x}")),
            // SAFETY: a live `NSWindow` this shell owns; `frame` is an accessor.
            unsafe { ffi::msg_rect(window, ffi::sel(c"frame")) }
        );

        // **Last.** Everything that repositions a window has now happened.
        //
        // SAFETY: a live window; `nil` is the documented sender for
        // `makeKeyAndOrderFront:`, `isVisible` is an accessor taking nothing,
        // and `focus_content_view` documents what it needs.
        unsafe {
            set_frame(window, new_frame);
            if target.is_some() && ffi::msg_bool(window, ffi::sel(c"isVisible")) {
                // A window that was key stays key, but a borderless one has to
                // be told to come forward again on some systems — and asking
                // twice costs nothing. Ordering front was measured **not** to
                // move the window: a re-assert placed after it read
                // `from [0,0,1024,768]`, an exact no-op.
                //
                // **Guarded by `isVisible`**: a window hidden with
                // `set_visible(false)` (or created `visible: false`) must stay
                // hidden across a mode change. Win32 carries `WS_VISIBLE`
                // across the style change, and creation already guards its show
                // behind `desc.visible`; without the check,
                // `set_mode(Borderless)` pops the hidden window on screen and
                // takes key focus, and `window_state().visible` reports true
                // for a window nobody showed.
                ffi::msg1_void(window, ffi::sel(c"makeKeyAndOrderFront:"), ptr::null_mut());
            }

            // **The keyboard, last of all.** `setStyleMask:` above takes the
            // first responder off the content view — measured, on both legs —
            // and nothing between there and here gives it back, so a mode
            // change without this leaves the window swallowing every keystroke.
            //
            // *Last* rather than immediately after the mask, for the same
            // reason the frame is last: it is a state that has to survive, and
            // putting it after everything means nothing in this sequence can
            // take it away again. It is **not** covered by the rule that reads
            // of the window's geometry belong above the sequence — it reads the
            // content view and the responder chain, and neither the mask, the
            // options nor the frame invalidates those.
            focus_content_view(window);
        }

        // **The tail keeps its bracket readings**, which are what found the
        // ordering defect and are what make a reintroduction of it obvious:
        // anything added below that repositions the window shows up as a frame
        // that changed after the arm set it. Nothing here may reposition the
        // window. `size_layer` was measured not to — it sets the layer's scale
        // and drawable size and touches no geometry of the window's — and
        // `refresh_presentation`, which does, has moved above the arm where it
        // belongs.
        //
        // SAFETY: a live window.
        let scale = unsafe { backing_scale(window) };
        let state = self.window_mut(handle)?;
        state.scale = scale;
        // SAFETY: the layer and its host view are alive for as long as the
        // window is, and this is the same thread that created them.
        unsafe { size_layer(state.layer, state.view, scale) };

        // **The state this function promises, read back at its exit.** One
        // reading rather than the per-statement bracket the hunt needed: the
        // steps above each print what they changed, so a disagreement here and
        // agreement there localises a regression to the tail in one run.
        //
        // SAFETY: a live `NSWindow` this shell owns, on the main thread.
        crcbl_core::log::debug!(
            "apply_mode: {mode:?} done — frame {:?}, view has the keyboard {}",
            unsafe { ffi::msg_rect(window, ffi::sel(c"frame")) },
            unsafe { view_has_focus(window) }
        );
        Ok(())
    }

    /// The screen a borderless window should cover.
    ///
    /// A named screen is looked up in the enumeration — so a detached one is a
    /// clean [`ShellError::NoSuchMonitor`] rather than a window moved to
    /// nowhere — and `None` means the screen the window is already on, which is
    /// what [`DisplayMode::Borderless`]'s field documents.
    ///
    /// **The whole [`Screen`] rather than its rectangle**, because the caller
    /// needs both halves and needs them at the same moment: the frame to place
    /// the window with, and the [`MonitorId`] to record as the effective mode —
    /// which has to be settled *before* anything moves, since the presentation
    /// options are derived from it and must be applied first. Returning only the
    /// rectangle is what forced the id to be recovered afterwards by reading the
    /// screen back off the window, and that read is the reason the effective mode
    /// used to be assigned in a tail.
    fn borderless_target(
        &self,
        window: Id,
        monitor: Option<MonitorId>,
    ) -> Result<Screen, ShellError> {
        if let Some(monitor) = monitor {
            return self
                .screens
                .iter()
                .find(|screen| screen.id == monitor)
                .copied()
                .ok_or(ShellError::NoSuchMonitor(monitor.0));
        }
        self.screen_of(window)
            .and_then(|id| self.screens.iter().find(|screen| screen.id == id))
            // A hotplug between `[window screen]` and the enumeration this shell
            // holds: the primary is a better answer than a failure.
            .or_else(|| self.screens.first())
            .copied()
            .ok_or_else(|| {
                ShellError::Backend(
                    "there is no display to place a borderless window on".to_string(),
                )
            })
    }
}

/// `-[NSApp presentationOptions]`, asked of AppKit rather than read out of this
/// shell's record of what it last requested.
///
/// The distinction is the point: `refresh_presentation` early-returns when its
/// computed options match the cached copy, so the cache says what was *intended*
/// and only AppKit says what is in force. `None` means there is no
/// `NSApplication`, which cannot happen with a window open and is reported
/// rather than papered over.
///
/// # Safety
///
/// The main thread, with an autorelease pool in scope.
unsafe fn presentation_options() -> Option<usize> {
    let class = ffi::class(c"NSApplication")?;
    // SAFETY: a class method returning the singleton the runtime keeps for the
    // life of the process.
    let app = unsafe { ffi::msg(class, ffi::sel(c"sharedApplication")) };
    // SAFETY: a live `NSApplication`; the accessor takes no arguments and
    // answers an `NSUInteger`.
    (!app.is_null()).then(|| unsafe { ffi::msg_usize(app, ffi::sel(c"presentationOptions")) })
}

/// Gives the window's content view the first responder, and says so if AppKit
/// refuses.
///
/// # Why this is called more than once
///
/// `sendEvent:` delivers key events to the first responder, which is the
/// *window* until something claims it — so a window that is its own first
/// responder swallows every keystroke and hands the view none, with nothing
/// anywhere reporting it. `CrcblView` answers `YES` to `acceptsFirstResponder`
/// precisely so this can succeed.
///
/// Setting it once at creation is not enough: **`setStyleMask:` takes the
/// responder back.** It rebuilds the window's frame view and the content view
/// stops being first responder, which was measured on a runner — `true` before
/// the call and `false` one statement after, on both legs of a mode round trip.
/// A game that pressed F11 therefore went permanently deaf, and it is the exact
/// failure this call is on [`view`]'s list of five switches to prevent.
///
/// # Safety
///
/// `window` must be a live `NSWindow` whose content view is set, on the main
/// thread.
unsafe fn focus_content_view(window: Id) -> bool {
    // SAFETY: a live window; `contentView` answers the view the window owns.
    let view = unsafe { ffi::msg(window, ffi::sel(c"contentView")) };
    // SAFETY: `makeFirstResponder:` takes a responder and answers `BOOL`.
    let taken = unsafe {
        let make: unsafe extern "C" fn(Id, Sel, Id) -> ObjcBool = ffi::msg_send();
        make(window, ffi::sel(c"makeFirstResponder:"), view) != ffi::NO
    };
    if !taken {
        crcbl_core::log::warn!(
            "-[NSWindow makeFirstResponder:] refused the content view; this window will receive \
             no key events"
        );
    }
    taken
}

/// Whether the window's first responder is its own content view.
///
/// A pointer comparison rather than a class name, because it needs no runtime
/// entry point this module does not already have and answers the only question
/// that matters: `sendEvent:` delivers key events to the first responder, and a
/// window that is its own first responder receives every keystroke and hands the
/// view none.
///
/// # Safety
///
/// `window` must be a live `NSWindow`, on the main thread.
unsafe fn view_has_focus(window: Id) -> bool {
    // SAFETY: two `NSWindow` accessors on a live window, both answering objects
    // the window owns.
    unsafe {
        let responder = ffi::msg(window, ffi::sel(c"firstResponder"));
        let content = ffi::msg(window, ffi::sel(c"contentView"));
        !responder.is_null() && responder == content
    }
}

/// `setFrame:display:` with `display: YES`, and a warning if the window did not
/// go there.
///
/// A helper because the signature is long enough to be worth writing once, and
/// because `display: NO` — which is the tempting choice for something that is
/// about to be redrawn anyway — leaves the old frame on screen until the next
/// event, which reads as a laggy mode switch.
///
/// # The readback is here because the seam cannot see a position
///
/// `setFrame:display:` is not obliged to apply what it is given, and when it
/// declines it does so **silently and by moving the origin while keeping the
/// size** — see the [module docs](self) on `constrainFrameRect:toScreen:`, which
/// is the mechanism that shipped a screen-sized borderless window at the wrong
/// place. Nothing downstream can notice: [`WindowState`](crate::WindowState)
/// carries an extent and no origin, so every layer above this one would keep
/// reporting a perfectly correct size.
///
/// So this asks the window where it actually ended up. **All three rectangles
/// are reported** — where the window was, what was asked for, and where it
/// landed — because they distinguish causes that need opposite fixes and a
/// message carrying only the last two cannot:
///
/// * *before* and *applied* identical, *asked* different: the call did nothing.
/// * *applied* has the asked size at the *before* origin: the origin did not
///   arrive or was overruled — an `NSRect` is an HFA passed in `v0`–`v3` on this
///   ABI, so a wrong function type corrupts it exactly this quietly, and
///   `constrainFrameRect:toScreen:` overrules it exactly this quietly too.
/// * *applied* equals *asked*: this call is innocent and whatever moved the
///   window did it later.
///
/// # Safety
///
/// `window` must be a live `NSWindow`, on the main thread.
unsafe fn set_frame(window: Id, frame: NSRect) {
    // SAFETY: a live window; `frame` is an accessor returning an `NSRect`.
    let before = unsafe { ffi::msg_rect(window, ffi::sel(c"frame")) };
    // SAFETY: an `NSWindow` setter taking an `NSRect` and a `BOOL`.
    let send: unsafe extern "C" fn(Id, Sel, NSRect, ObjcBool) = unsafe { ffi::msg_send() };
    unsafe { send(window, ffi::sel(c"setFrame:display:"), frame, YES) };
    // SAFETY: as the read above.
    let applied = unsafe { ffi::msg_rect(window, ffi::sel(c"frame")) };

    crcbl_core::log::debug!("setFrame:display: from {before:?} asked {frame:?} landed {applied:?}");
    if !frame_was_honoured(before, frame, applied) {
        crcbl_core::log::warn!(
            "-[NSWindow setFrame:display:] did not take: the window was at {before:?}, was asked \
             for {frame:?}, and is at {applied:?}. Either the size did not arrive, or the origin \
             stayed exactly where it was — CrcblWindow overrides constrainFrameRect:toScreen: to \
             stop AppKit moving the origin, so if that override is installed the argument itself \
             is the suspect. A position a fraction of a point from the request is AppKit aligning \
             the frame to the display, and is not what this reports"
        );
    }
}

/// Whether `-[NSWindow setFrame:display:]` honoured `asked`.
///
/// **AppKit is entitled to align a frame to the display, so an origin can come
/// back a fraction of a point from one that was applied perfectly.**
/// `geometry::centred` produces origins like `y = 389.5` and `setFrame:` lands
/// them on the whole point the frame is aligned to. Comparing the two rectangles
/// for equality reports that as the failure the readback above exists to catch,
/// which is how a correct placement drew its warning on 2026-09-11:
///
/// ```text
/// was asked for NSRect { origin: { x: 708.0, y: 389.5 }, size: { 640.0, 512.0 } },
/// and is at      NSRect { origin: { x: 708.0, y: 389.0 }, size: { 640.0, 512.0 } }
/// ```
///
/// The two cases worth reporting are the ones nothing else can see: a **size**
/// that did not arrive, and an origin that **stayed exactly where it was** while
/// a different one was asked for — the silent drop `constrainFrameRect:toScreen:`
/// used to cause. A rounded origin is neither, and neither is a call that asked
/// for the rectangle the window already had.
fn frame_was_honoured(before: NSRect, asked: NSRect, applied: NSRect) -> bool {
    let size_arrived =
        applied.size.width == asked.size.width && applied.size.height == asked.size.height;
    let origin_dropped = applied.origin.x == before.origin.x
        && applied.origin.y == before.origin.y
        && (asked.origin.x != before.origin.x || asked.origin.y != before.origin.y);
    size_arrived && !origin_dropped
}

/// The `AppWindow` fields a configuration is read out of, in one place so the
/// two callers cannot drift.
///
/// # Safety
///
/// The window and view must be live, on the main thread.
pub(super) unsafe fn configuration_of(state: &AppWindow) -> crate::WindowConfiguration {
    crate::WindowConfiguration {
        // SAFETY: a live view of a live window.
        size: unsafe { backing_size(state.view) },
        scale_factor: state.scale,
        mode: state.effective_mode,
    }
}

/// That `CrcblWindow` really carries the three overrides it is built for.
///
/// The same shape and the same argument as [`view`](super::view)'s class suite:
/// `objc_allocateClassPair` and `class_addMethod` are Objective-C **runtime**
/// calls, thread-safe and needing no `NSApplication`, so a spawned `#[test]`
/// body may build this class and ask what it implements — see
/// [`session_support`](crate::appkit::session_support) for the rule and for what
/// it costs to get it wrong.
///
/// What it catches is a method the runtime **refused** and a selector spelled
/// wrong, both of which leave the superclass's implementation in place and fail
/// silently: a `canBecomeKeyWindow` that never runs is a borderless window that
/// stops taking keystrokes, and a `constrainFrameRect:toScreen:` that never runs
/// is a borderless window at the wrong origin — which is a defect that shipped
/// and that only `tests/appkit_session.rs`'s AppKit readback found.
#[cfg(all(test, target_os = "macos"))]
mod tests {
    use super::*;

    #[test]
    fn the_window_class_installs_every_override_a_borderless_window_needs() {
        let class = window_class().expect("CrcblWindow is built from runtime calls alone");

        for selector in [
            // Without these two a borderless window cannot take the keyboard or
            // the menu bar, because `NSWindowStyleMaskBorderless` is zero.
            c"canBecomeKeyWindow",
            c"canBecomeMainWindow",
            // And without this one AppKit is entitled to move any frame this
            // backend sets, keeping the size and changing the origin.
            c"constrainFrameRect:toScreen:",
        ] {
            // SAFETY: a class is an object, and every object answers
            // `instancesRespondToSelector:`.
            let installed = unsafe {
                let send: unsafe extern "C" fn(Id, Sel, Sel) -> ObjcBool = ffi::msg_send();
                send(
                    class,
                    ffi::sel(c"instancesRespondToSelector:"),
                    ffi::sel(selector),
                ) != ffi::NO
            };
            assert!(installed, "CrcblWindow does not implement {selector:?}");
        }
    }

    /// A placement AppKit aligned to the display is not a failure.
    ///
    /// The rectangle is the one from the born-borderless flip of 2026-09-11:
    /// `geometry::centred` asked for `y = 389.5` and `setFrame:` landed `389.0`
    /// with the size intact. Equality called that a dropped origin.
    #[test]
    fn a_frame_appkit_aligned_is_honoured() {
        let before = NSRect::new(0.0, 0.0, 2056.0, 1361.0);
        let asked = NSRect::new(708.0, 389.5, 640.0, 512.0);
        let applied = NSRect::new(708.0, 389.0, 640.0, 512.0);
        assert!(
            frame_was_honoured(before, asked, applied),
            "a fraction of a point is AppKit aligning the frame, not dropping the origin"
        );
    }

    /// And the silent drop this readback exists for still is one, at the size
    /// that was asked for — the shape `constrainFrameRect:toScreen:` produced.
    #[test]
    fn an_origin_left_where_it_was_is_not_honoured() {
        let before = NSRect::new(0.0, 0.0, 2056.0, 1361.0);
        let asked = NSRect::new(708.0, 389.5, 640.0, 512.0);
        let applied = NSRect::new(0.0, 0.0, 640.0, 512.0);
        assert!(!frame_was_honoured(before, asked, applied));
    }

    /// As is a size that never arrived, which is the other half of the message.
    #[test]
    fn a_size_that_did_not_arrive_is_not_honoured() {
        let before = NSRect::new(0.0, 0.0, 640.0, 480.0);
        let asked = NSRect::new(0.0, 0.0, 1024.0, 768.0);
        let applied = NSRect::new(0.0, 0.0, 640.0, 480.0);
        assert!(!frame_was_honoured(before, asked, applied));
    }

    /// A call that asked for the rectangle the window already has is innocent,
    /// even though its origin did not move.
    #[test]
    fn a_placement_that_changes_nothing_is_honoured() {
        let same = NSRect::new(100.0, 200.0, 640.0, 480.0);
        assert!(frame_was_honoured(same, same, same));
    }
}