crcbl-shell 0.8.1

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
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
//! A real AppKit window session, driven from the **process's main thread**.
//!
//! ```text
//! cargo test -p crcbl-shell --test appkit_session
//! ```
//!
//! # Why this is a `harness = false` target and not a `#[test]`
//!
//! **Rust's `libtest` always runs a test body on a thread it spawns**, and
//! AppKit is main-thread-only: `NSApplication` raises rather than returning an
//! error, and an Objective-C exception unwinding into Rust is undefined
//! behaviour. `crcbl-shell`'s AppKit backend therefore refuses to open off the
//! main thread on purpose (`appkit::app::require_main_thread`), which means a
//! `#[test]` can never drive an AppKit window — not with `--test-threads=1`, not
//! by invoking the binary directly. That was measured on this workspace's
//! toolchain rather than recalled: a probe asserting `gettid() == getpid()`
//! inside a `#[test]` fails both ways.
//!
//! So this target owns its `main`, which cargo and `nextest` run *as* the
//! process. It needs no feature gate and no workflow change: the existing
//! `build + test (macos-latest)` job picks it up, which is the whole point —
//! **the window lifecycle M1 wrote had no executable coverage at all without
//! it.**
//!
//! # It answers one question the rest of the suite cannot
//!
//! Whether a GitHub macOS runner gives a process a usable WindowServer session.
//! The Windows half of P5C asked the same question of its runner and the answer
//! was yes; nothing about that transfers, because a Mac denies windows to a
//! process in an SSH or launchd context in a way Windows does not. If this
//! fails on the first run, *that* is the finding, and it fails naming the step
//! it reached rather than dying quietly.
//!
//! # On every other platform it is a no-op that says so
//!
//! `harness = false` targets are built and run on **every** host, so this must
//! succeed on Linux and Windows. It prints why it did nothing rather than
//! reporting a pass it did not earn — a helper that reports success on a
//! platform where it cannot have done anything is the failure this whole suite
//! exists to avoid.
//!
//! # M4 extends this target rather than adding a second one
//!
//! The end-to-end pass could have been a `appkit_e2e` target of its own, on the
//! model of `tests/win32_e2e.rs`. It is here instead, and the reason is not
//! tidiness:
//!
//! * **`nextest` runs test binaries in parallel.** Two harness-less targets that
//!   each bootstrap an `NSApplication`, activate it and take the key window
//!   would be two processes fighting over which one is frontmost — and
//!   `CGEventPost` delivers to *whoever is frontmost*, so the loser would report
//!   that injected input never arrived. Serialising them means a test group in
//!   `.config/nextest.toml`, which is a configuration change for a problem one
//!   process does not have.
//! * **There is one `NSApplication` per process and it is not restartable.** A
//!   second target would repeat the bootstrap, the activation and the window,
//!   and would then be asserting the same M1 lifecycle a second time to get to
//!   its own subject.
//!
//! The cost is that this target is one `nextest` test, so a failure anywhere in
//! it reports the whole session as failed. That is paid for by every step
//! printing what it reached before the next one starts, which is what a
//! `harness = false` target has instead of a test name.
//!
//! # What M4 adds, and the one thing it cannot do
//!
//! * **Input the window system generated**, through `CGEventPost` — the macOS
//!   counterpart of `SendInput`. That is what reaches `interpretKeyEvents:`,
//!   which no test had ever reached: `appkit::view`'s module docs list five
//!   switches that decide
//!   whether an event is *generated or routed* rather than what a responder does
//!   with it, and every one of them is invisible to a test that calls the
//!   responder method itself. The Win32 half of P5C shipped a backend whose
//!   `TextCommit` was unreachable from a real keyboard for exactly that reason.
//! * **A pasteboard round trip against `pbcopy` and `pbpaste`**, which are
//!   Apple's own processes and have no `crcbl-shell` in them. A shell answering
//!   its own reads out of a cache passes an in-process round trip and fails this.
//! * **AppKit as the judge** of the first responder, the mouse-moved flag, the
//!   dragged types, the style mask, the frame and the screen —
//!   [`crcbl_shell::session_support::window_facts`] reads them off `NSWindow`
//!   rather than out of the backend's own record of what it asked for.
//!
//! # M5: only the injection needs the keyboard, and the runner may refuse it
//!
//! The first macOS run of M4 got as far as the key window and stopped there.
//! `-[NSApp keyWindow]` was nil, and the diagnostic beside it was decisive:
//! `can_become_key: true`, `visible: true`, one window with the right title and
//! the right class, `app_active: false`. The `canBecomeKeyWindow` override is
//! installed and answering; the **application** never becomes active, which is
//! what a GitHub runner does to an unbundled binary and which no backend can
//! fix. Three things follow, and they are this slice:
//!
//! * **The readback stops going through the key window.** Every fact M4 asserts
//!   — the three view switches, the geometry, the style mask, the backing scale
//!   — is an ordinary accessor that any window answers, active or not. They come
//!   from this process's own window, found by title among `-[NSApp windows]`, so
//!   they run on a runner that never activates instead of being discarded.
//! * **The harness asks for activation itself**, through
//!   [`macos::frontmost`] — exactly parallel to `tests/win32_e2e.rs`'s
//!   `desktop::take_foreground`, and in a harness for the same reason: a game
//!   does not get to steal the focus, so the lever must not live in
//!   `src/appkit/`.
//! * **If it is still refused, the injection is skipped out loud.**
//!   `CGEventPost` puts an event in the session's stream and the session gives
//!   it to whoever is frontmost, so it is the one part that cannot proceed.
//!   `docs/notes/process.md` calls a silently-skipped end-to-end check a known
//!   trap, so the skip prints what did not run, why, and the evidence — and
//!   `docs/backlog.md` carries `interpretKeyEvents:` as unverified on CI.
//!
//! **The runner granted the activation**, so the skip branch is written and
//! unrun and the whole M4 judge passes. It also found the one thing M4 had
//! genuinely wrong: **a warp is not an event.**
//! `CGWarpMouseCursorPosition` moves the cursor and posts nothing, so a wait for
//! "the pointer to be reported after a warp" is a wait for something that does
//! not exist. It looked correct because [`macos::input`]'s warp crossed the
//! window's edge and AppKit re-evaluates its tracking areas against where the
//! cursor actually is — a `mouseEntered:`, not a report of the warp — while
//! [`macos::injected_input`]'s warp stayed inside the window, crossed nothing
//! and waited ten seconds. [`macos::wait_for_pointer`] now posts a real
//! `kCGEventMouseMoved` to ask the question, and the warp is left to do only
//! what it promises.
//!
//! **And a posted mouse event carries no delta unless the poster puts one on
//! it.** `CGEventCreateMouseEvent` places an event at an absolute location and
//! leaves `kCGMouseEventDeltaX`/`Y` at zero; `appkit::view` reads exactly those
//! fields into `raw_delta`, so the next run reported `(0.0, 0.0)` — faithfully.
//! The assertion had been checking a value this harness never supplied, and
//! could only ever have passed by accident. [`macos::quartz::move_mouse_by`]
//! now sets the field, so the seam is held to reporting *the* delta rather than
//! *a* delta, and the Y-up position against Y-down delta asymmetry that
//! `appkit::pointer` exists for is finally observable.
//!
//! What is **not** here is the pass at a *running game*: launching a sample,
//! pressing F11 at it and reading the mode it reports. `apps/sandbox` runs
//! windowed here on Metal now that `crcbl-mtl` exists, so such a pass is
//! reachable — what stops it is a measurement rather than a missing renderer.
//! **F11 does not reach this seam.** Posted through `CGEventPost` it is
//! swallowed whole, `PointerFocus` and no `Key` on every attempt, while `A`,
//! `ArrowUp` and `F1` arrive through the same path: macOS binds F11 to a system
//! shortcut (Show Desktop) and the session takes the key before any application
//! is offered it. A player's press goes the same way, so every sample's mode key
//! is out of reach on this platform unless that shortcut is turned off or the
//! key is pressed with `Fn`. `docs/backlog.md` carries it, and what to do about
//! the binding is its decision rather than this harness's.
//!
//! # Owning `main` means owning libtest's command line, not only its body
//!
//! `cargo nextest` — which is what CI runs — enumerates a target by executing it
//! with `--list --format terse` and parsing `<name>: test` lines out of its
//! stdout. A harness that ignores argv answers that with prose, so the
//! *listing* step runs the whole session and then fails to parse it; that is how
//! this target broke three CI jobs at once on the run that introduced it. The
//! listing is therefore answered in [`main`] before anything else happens.
//!
//! # Why it is not called `appkit_e2e`
//!
//! By subject it belongs with `tests/wayland_e2e.rs`, `tests/x11_e2e.rs` and
//! `tests/win32_e2e.rs`: it is the macOS member of that family, driving a real
//! WindowServer and injecting real input. The name stops short of `_e2e` on
//! purpose, because in this crate that suffix has come to carry a second
//! meaning the reader relies on — every other `*_e2e.rs` here opens with a
//! crate-level `#![cfg(all(target_os = …, feature = "…-e2e"))]`, carries
//! `#[ignore]`, and is driven by a harness script of its own. None of that is
//! true of this file. It is behind no feature, it is not ignored, and the
//! ordinary `build + test (macos-latest)` sweep runs it.
//!
//! That is not an oversight to be tidied up later; it is the reason the target
//! exists. `.github/workflows/ci.yml` says so at its AppKit step: this is the
//! *only* executable coverage the AppKit backend has, so a gate would mean the
//! backend had none by default. A name promising a switch nobody has to throw
//! would be the more expensive kind of wrong — the reader would go looking for
//! the feature that turns it on and conclude, wrongly, that it is off.

/// The one test this target contains, under the name `nextest` reports it by.
const TEST_NAME: &str = "appkit_session";

fn main() {
    // libtest's list protocol, answered before any work: `--list` prints one
    // `<name>: test` line per test and exits. `--ignored` asks for the ignored
    // subset, and nothing here is `#[ignore]`d — on the same terms as the rest
    // of this crate's suites — so that listing is empty rather than the same
    // line again, which would enumerate one test twice.
    let args: Vec<String> = std::env::args().collect();
    if args.iter().any(|arg| arg == "--list") {
        if !args.iter().any(|arg| arg == "--ignored") {
            println!("{TEST_NAME}: test");
        }
        return;
    }

    #[cfg(not(target_os = "macos"))]
    {
        println!(
            "crcbl appkit session: skipped — AppKit is a macOS window system and \
             this is not macOS"
        );
    }
    #[cfg(target_os = "macos")]
    {
        macos::run();
    }
}

#[cfg(target_os = "macos")]
mod macos {
    use std::time::{Duration, Instant};

    use crcbl_core::log::Filter;
    use crcbl_shell::{
        ButtonState, ClipboardContent, ClipboardOffer, CursorIcon, DisplayMode, KeyCode, Keysym,
        LogicalPoint, LogicalRect, LogicalSize, MimeType, MonitorId, PanelAnchor, PanelDesc,
        PanelKeyboard, PanelLayer, PhysicalPoint, PhysicalRect, PhysicalSize, PointerButton,
        PointerMode, PopupDesc, PopupInput, PopupPlacement, ReceivedMime, ScrollDelta, Shell,
        ShellBackend, ShellCaps, ShellError, ShellEvent, SizeConstraints, TextPurpose, WindowDesc,
        WindowId, WindowRole, open_backend, session_support,
    };

    /// The window's title, which is **how this session finds its own window**.
    ///
    /// [`session_support::window_facts`] looks it up by this among
    /// `-[NSApp windows]`, so it is the identity every readback rests on rather
    /// than a decoration — and the same string is what tells
    /// [`session_support::key_window`]'s answer apart from another
    /// application's.
    const TITLE: &str = "crcbl appkit session";

    /// `kVK_ANSI_A`, which `appkit::keys::key_code` maps to
    /// [`KeyCode::KeyA`] — and which, unlike an arrow key, is a character.
    const VK_A: u16 = 0x00;

    /// `kVK_UpArrow`. A key that moves a cursor and commits no text, which is
    /// the other half of the text assertion.
    const VK_UP_ARROW: u16 = 0x7E;

    /// The relative delta the injected pointer move carries, in Quartz's global
    /// display coordinates — **right and up**, since Quartz's Y is down.
    ///
    /// It is both the distance the cursor travels and the value written into
    /// the event's `kCGMouseEventDeltaX`/`Y`, so the two cannot drift apart.
    ///
    /// Every part of the pair is doing a job:
    ///
    /// * **The magnitudes differ**, so a backend reading `deltaX` and `deltaY`
    ///   out of each other's field reports a pair that is wrong rather than one
    ///   that happens to be symmetric.
    /// * **The signs differ**, so the reflection this whole check exists for
    ///   fails *distinctly* from a swap: reflecting Y makes the second
    ///   component positive, while swapping makes the first one negative.
    ///   Having a negative component at all is also the only way to see that one
    ///   survives the trip rather than being clamped or `abs`ed somewhere.
    /// * **It is large enough to be identifiable.** `wait_for_pointer` posts
    ///   moves at the parked point, so this move's report has to be
    ///   recognisable as this one and not as one of those; half of each
    ///   component is the margin that decides it.
    /// * **It is small enough to stay on the window**, since a mouse-moved
    ///   event goes to whatever is under the cursor, and the parked point is a
    ///   quarter of the way in from the window's top-left corner.
    const NUDGE: (i64, i64) = (40, -30);

    /// How long any one step may take before the session is called stuck.
    ///
    /// A window system that answers at all answers in milliseconds; this is
    /// long enough that a loaded runner is not mistaken for a broken one, and
    /// short enough that a genuine hang is a failure rather than a timeout of
    /// the job.
    const DEADLINE: Duration = Duration::from_secs(10);

    /// The size asked for. Deliberately small: the Windows runner's desktop
    /// turned out to be 1024×768, and a window that does not fit is one the
    /// system is entitled to move or resize before anyone looks at it.
    const REQUESTED: LogicalSize = LogicalSize::new(640.0, 480.0);

    /// How far a reported pointer position may sit from the one that was asked
    /// for and still be a report *of* it.
    ///
    /// One backing pixel per space crossed: a warp lands on a whole display
    /// pixel and the report comes back through the view's points, so the two
    /// need not agree exactly. It is also what makes a position *identifying* —
    /// [`wait_for_pointer`] uses it to tell its own report from somebody
    /// else's, so it has to stay far smaller than the distance between the
    /// points this session parks the cursor at.
    const POINTER_SLACK: f64 = 3.0;

    pub fn run() {
        install_logger();
        println!("crcbl appkit session: opening the AppKit backend on the main thread");

        let mut shell = match open_backend(ShellBackend::AppKit) {
            Ok(shell) => shell,
            Err(error) => panic!(
                "crcbl appkit session: the AppKit backend would not open: {error}\n\
                 If this is the first run, the finding is that a GitHub macOS \
                 runner does not give a process a WindowServer session."
            ),
        };
        assert_eq!(shell.backend(), ShellBackend::AppKit);
        println!(
            "crcbl appkit session: opened, caps {:?}, {} monitor(s)",
            shell.caps(),
            shell.monitors().len()
        );
        assert!(
            !shell.monitors().is_empty(),
            "a session with a window server has at least one screen"
        );

        // The checks that need AppKit *objects* rather than only the
        // Objective-C runtime, run now that `NSApplication` is up and this is
        // the main thread. Each of them was, or would have been, a `#[test]`
        // that passes on a developer's host and fails on the runner — see
        // `crcbl_shell::session_support`, which states the rule.
        for (name, outcome) in [
            (
                "every cursor shape",
                session_support::every_cursor_shape_exists(),
            ),
            (
                "the dragged-type registration",
                session_support::dragged_types_register_on_a_view(),
            ),
            (
                "the general pasteboard",
                session_support::pasteboard_round_trip(),
            ),
        ] {
            match outcome {
                Ok(()) => println!("crcbl appkit session: {name} answered"),
                Err(detail) => panic!("crcbl appkit session: {name} — {detail}"),
            }
        }

        // **`accept_drops: true`**, so that the drop path is registered on a
        // real window rather than only on the throwaway view above. There is no
        // way to *deliver* a drop from inside this process — a drag comes from
        // another application's mouse — so what this earns is that nothing
        // about the registration refuses a window that also has a tracking area
        // and a first responder.
        let window = shell
            .create_window(&WindowDesc {
                title: TITLE,
                app_id: "sh.kryptic.crcbl.appkit-session",
                size: REQUESTED,
                constraints: SizeConstraints::min(LogicalSize::new(320.0, 180.0)),
                mode: DisplayMode::Windowed,
                resizable: true,
                visible: true,
                accept_drops: true,
                role: WindowRole::Toplevel,
                transparent: false,
            })
            .expect("create_window");

        // The seam's hard ordering constraint: no size until the window system
        // has configured the window, whatever the platform knew at create time.
        assert_eq!(
            shell.window_state(window).expect("state").size(),
            None,
            "a window has no size until the window system says so"
        );

        let size = wait_for(&mut shell, "the first Resized", |shell| {
            shell
                .window_state(window)
                .ok()
                .and_then(|state| state.size())
        });
        println!("crcbl appkit session: configured at {size:?}");
        assert!(
            size.width > 0 && size.height > 0,
            "a configured window has an extent a swapchain could be built at: {size:?}"
        );

        // The surface handle exists as soon as the window does, and a mode
        // change is the only thing that invalidates it.
        shell.surface_target(window).expect("surface_target");

        // **The one precondition this harness arranges for itself**, before
        // anything reads the window: see [`frontmost`] for why it is here and
        // not in the backend. Its answer decides only whether input may be
        // injected — nothing below it is gated on the keyboard.
        let activation = take_activation(&mut shell);

        let facts = appkit_agrees(size);
        input(&mut shell, window, size);
        text_input(&mut shell, window);
        match &activation {
            Ok(active) => {
                println!(
                    "crcbl appkit session: this process is frontmost and its window has the \
                     keyboard, so input can be injected: {active:?}"
                );
                warp_round_trip(&mut shell, window, size);
                injected_input(&mut shell, window, size);
            }
            Err(refused) => injection_skipped(refused),
        }
        clipboard(&mut shell, window);
        clipboard_peer(&mut shell, window);
        waker(&mut shell);
        resize(&mut shell, window, facts.backing_scale);

        // **The whole placement before the flip**, so the way back can be judged
        // on more than its style mask. Position is invisible to the seam —
        // `WindowState` carries an extent and no origin — so AppKit is the only
        // party that can say whether a restore put the window back where it was.
        // `win32/window.rs` restores a whole `WINDOWPLACEMENT` for exactly this
        // reason and the Win32 suite asserts it for exactly this reason.
        let before_borderless = appkit_says("before going borderless").frame;

        // **Both flips run before either is judged**, and that is not a
        // loosening — every assertion below is made about the same snapshot it
        // was made about before, `WindowFacts` being owned data. It is a round
        // trip: the borderless origin is a known open defect, so asserting it
        // here would panic before the *restore* ever ran and throw away half the
        // evidence of a run that costs ten minutes. Both halves are gathered,
        // then both are checked.
        let borderless = flip(
            &mut shell,
            window,
            DisplayMode::Borderless { monitor: None },
        );
        println!("crcbl appkit session: borderless at {borderless:?}");
        // Judged against `NSScreen` rather than against the request: borderless
        // here is "a frameless window at screen size", and both halves of that
        // are things AppKit will say out loud.
        let covering = appkit_says("borderless");

        let windowed = flip(&mut shell, window, DisplayMode::Windowed);
        println!("crcbl appkit session: windowed again at {windowed:?}");
        let restored = appkit_says("windowed again");
        println!(
            "crcbl appkit session: the mode round trip — before {before_borderless:?}, \
             borderless {:?}, restored {:?}; first responder {} -> {} -> {}",
            covering.frame,
            restored.frame,
            facts.first_responder,
            covering.first_responder,
            restored.first_responder
        );

        assert!(
            !covering.titled,
            "a borderless window has no title bar; the style mask is {:#x}",
            covering.style_mask
        );
        // **The keyboard survives the flip**, asserted here and not only after
        // the round trip. `setStyleMask:` takes the first responder off the
        // content view, so a mode change used to leave the window swallowing
        // every keystroke — and a game goes borderless and *stays* there, so a
        // responder that came back only on the way out would be a game that is
        // deaf for exactly as long as anybody is playing it. An end-to-end check
        // would have passed that.
        assert_eq!(
            covering.first_responder, "CrcblView",
            "a borderless window still routes key events to the view: `sendEvent:` delivers to \
             the first responder, so the window being its own is every keystroke going nowhere. \
             {covering:?}"
        );
        let screen = covering
            .screen_frame
            .expect("a visible window is on a screen");
        // **The origin is the half of this the seam cannot see, and the half that
        // was wrong.** The first run to reach here reported the size exactly
        // right and the origin at (192, 160) — the window's *creation* origin,
        // `geometry::centred` of the requested size on the visible frame — which
        // is a screen-sized window hanging off two edges of the display. Every
        // earlier run printed `PhysicalSize { 1024, 768 }` and was happy, because
        // that is all `WindowState` carries. Only asking AppKit for the frame
        // shows it, which is the entire argument for this readback layer: **this
        // is the first defect it caught, and it is the kind only it can catch.**
        //
        // **Two candidate mechanisms are ruled out by evidence and neither
        // should be tried again.**
        //
        // * `constrainFrameRect:toScreen:`. `CrcblWindow` overrides it, the host
        //   test asserting the override is installed passes on the runner, and
        //   the window still comes out at (192, 160). Its default moves a window
        //   *down* to clear the menu bar, which was never this failure's shape.
        // * A corrupted `NSRect` argument. The backend's own trail reported
        //   `setFrame:display: from [192,256,512,416] asked [0,0,1024,768]
        //   landed [0,0,1024,768]` — the rectangle arrived intact and was
        //   applied, so the HFA-in-`v0`–`v3` theory is dead too.
        //
        // * **The pump**, a delegate callback, and macOS state restoration. The
        //   frame is wrong before the first pump, and `setRestorable:NO` took
        //   without changing the outcome.
        //
        // **It was `setPresentationOptions:`**, which returns every window of the
        // application to its *creation* frame — which is why the origin the
        // window reverted to was exactly `centred([0, 63, 1024, 674], 640x480)`,
        // the creation rectangle, on both axes. `apply_mode` now applies the
        // presentation options before the mask and the frame; `appkit::window`'s
        // module docs carry the measurement and the rule.
        assert_eq!(
            covering.frame, screen,
            "borderless covers the screen AppKit says it is on, exactly.\n\
             The window was at {before_borderless:?} before the flip, and everything AppKit says \
             about it while borderless is {covering:?}.\n\
             The mechanism was `setPresentationOptions:`, which returns every window of the \
             application to its **creation** frame — so `apply_mode` now applies the \
             presentation options before the style mask and the frame, and the frame is the \
             last geometry it sets. A failure here means that ordering has been undone or \
             something new below the arm repositions the window; the `apply_mode:` readings in \
             this test's stderr say which statement, because each of them prints the frame \
             after one step."
        );
        assert_eq!(
            borderless,
            PhysicalSize::new(
                (screen[2] * covering.backing_scale).round() as u32,
                (screen[3] * covering.backing_scale).round() as u32,
            ),
            "and the extent the seam reports is that screen in backing pixels, at a scale of \
             {}",
            covering.backing_scale
        );

        assert!(
            restored.titled,
            "a windowed window has its title bar back; the style mask is {:#x}",
            restored.style_mask
        );
        // And the whole rectangle, not just the mask. The backend saves the frame
        // before it goes borderless and puts it back, so a window that was
        // somewhere before a mode round trip is in the same place after one —
        // which is what a player alt-tabbing out of fullscreen expects and what
        // nothing below AppKit could report.
        //
        // **This half was corrupted too and nobody could see it**, because the
        // borderless assertion above fired first for eight rounds. The restore
        // leg came back at `[192,160,640,512]` — the creation frame, moved *and*
        // resized — from the same `setPresentationOptions:` mechanism as the way
        // in. One bug, both directions, and this is the assertion that pins the
        // direction that had none.
        assert_eq!(
            restored.frame, before_borderless,
            "the way back restores the whole placement, origin included, and not merely the size"
        );
        // And the keyboard again, because the way back changes the style mask
        // too and therefore takes the responder a second time.
        assert_eq!(
            restored.first_responder, "CrcblView",
            "and the view still has the keyboard after a full mode round trip: {restored:?}"
        );

        // A second window, created hidden, is flipped borderless after the main
        // window's own round trip is fully asserted — so the regression this
        // guards cannot disturb a single one of the assertions above.
        hidden_window_stays_hidden(&mut shell);

        // And a third, born borderless rather than flipped into it: the creation
        // arm and the "nothing saved" way back were the two paths no window here
        // had ever taken.
        a_window_born_borderless_round_trips(&mut shell);

        // And a fourth, which names the monitor it wants: the `Some(id)` arm of
        // the creation lookup, and the refusal that stands beside it.
        a_borderless_window_names_its_monitor(&mut shell);

        // And the two panels lyr and pikr are: a bar and a launcher.
        panels(&mut shell);

        // And the popups a bar opens: placed against their parent, and gone
        // before it.
        popups(&mut shell);

        shell.destroy_window(window).expect("destroy_window");
        assert!(
            shell.window_state(window).is_err(),
            "a destroyed window is a stale handle"
        );
        println!("crcbl appkit session: a window opened, changed mode twice and closed");
    }

    /// The M2 half: the capability set, the pointer modes and the cursor.
    ///
    /// **Nothing here posts an event**, which is what keeps it on this side of
    /// the activation gate. Every assertion is either a property of the
    /// capability set or a call whose contract is that it fails only for a stale
    /// handle, so all of it runs on a runner that never activates. The half that
    /// needs the window server to answer is [`warp_round_trip`].
    fn input(shell: &mut Box<dyn Shell>, window: WindowId, size: PhysicalSize) {
        let caps = shell.caps();
        assert!(
            caps.has_mouselook(),
            "a locked pointer and a delta beside it are both implemented: {caps:?}"
        );
        assert!(caps.contains(ShellCaps::POINTER_WARP), "{caps:?}");
        assert!(caps.contains(ShellCaps::TEXT_COMPOSE), "{caps:?}");
        assert!(
            caps.contains(ShellCaps::CURSOR_SHAPE),
            "NSCursor applies every shape: {caps:?}"
        );
        assert!(
            caps.contains(ShellCaps::TEXT_IME),
            "fields switch the input method, the pre-edit is sent and the candidate window \
             follows the caret: {caps:?}"
        );
        assert!(
            !caps.contains(ShellCaps::POINTER_CONFINE),
            "macOS has no confine API and this backend must not claim one: {caps:?}"
        );

        // The capability and the method agree, which is what makes the bit
        // checkable rather than decorative.
        let refused = shell
            .set_pointer_mode(window, PointerMode::Confined)
            .expect_err("confinement is not implementable on this platform");
        assert!(
            refused.to_string().contains("confine"),
            "the refusal has to name the mode, or nobody can act on it: {refused}"
        );

        // A warp to somewhere outside the window is **clamped, not refused** —
        // a seam claim that needs nobody to be listening, so it stays here.
        // Past the bottom-right corner, so the system's clamp to the display
        // arrangement lands it in the screen's corner.
        shell
            .warp_pointer(
                window,
                PhysicalPoint::new(
                    f64::from(size.width) + 200.0,
                    f64::from(size.height) + 200.0,
                ),
            )
            .expect("a warp outside the window is clamped, not refused");
        shell.pump(&mut |_event| {});

        // Locking is the mode macOS does have, and it is observable through the
        // seam rather than only through the calls it made.
        shell
            .set_pointer_mode(window, PointerMode::Locked)
            .expect("pointer lock");
        assert_eq!(
            shell.window_state(window).expect("state").pointer_mode,
            PointerMode::Locked
        );
        shell
            .set_pointer_mode(window, PointerMode::Free)
            .expect("back to free");
        assert_eq!(
            shell.window_state(window).expect("state").pointer_mode,
            PointerMode::Free,
            "the freeze is desktop-wide, so leaving it set would follow the runner out \
             of this process"
        );

        // Cursor shapes and hiding, which have no seam readback — the contract
        // is that they fail only for a stale handle.
        for cursor in [
            Some(CursorIcon::Crosshair),
            Some(CursorIcon::Text),
            None,
            Some(CursorIcon::Default),
        ] {
            shell.set_cursor(window, cursor).expect("set_cursor");
        }
        println!("crcbl appkit session: pointer modes, cursors and the warp all answered");
    }

    /// The input method's half of the seam: [`Shell::set_text_input`],
    /// [`Shell::set_text_input_area`] and the pre-edit, judged by AppKit.
    ///
    /// # The harness plays the input method
    ///
    /// No input method can be driven on a runner, so this calls the view's
    /// own `NSTextInputClient` methods — `setMarkedText:selectedRange:replacementRange:`,
    /// `unmarkText`, `insertText:replacementRange:` — the way one does from
    /// inside `interpretKeyEvents:`, through
    /// [`session_support::mark_text`] and its neighbours. What that proves is
    /// everything on this side of the protocol: which windows report a
    /// pre-edit, with what cursor, how each one ends, what the input context
    /// is restricted to and where the view says the caret is. What it cannot
    /// prove is that a real input method gives way to the restriction, which
    /// `docs/backlog.md` carries.
    ///
    /// # Nothing here needs the keyboard but the last check
    ///
    /// Each call is synchronous and recorded before it returns, so one pump
    /// delivers what it caused. Secure event input is held only while the
    /// window is **key**, so that check asserts whichever state the window is
    /// in rather than being skipped on a runner that never activates.
    fn text_input(shell: &mut Box<dyn Shell>, window: WindowId) {
        let facts = |step: &str| {
            session_support::text_input_facts(TITLE)
                .unwrap_or_else(|detail| panic!("crcbl appkit session: {step}: {detail}"))
        };
        let mark = |text: &str, cursor: usize| {
            session_support::mark_text(TITLE, text, cursor).unwrap_or_else(|detail| {
                panic!("crcbl appkit session: marking {text:?}: {detail}")
            });
        };
        let unmark = || {
            session_support::unmark_text(TITLE)
                .unwrap_or_else(|detail| panic!("crcbl appkit session: unmarking: {detail}"));
        };
        let pumped = |shell: &mut Box<dyn Shell>| {
            let mut seen = Vec::new();
            shell.pump(&mut |event| seen.push(event));
            seen
        };

        // --- a window with no field keeps the input method out ---
        let fresh = facts("a new window");
        assert!(
            fresh.roman_only,
            "a window starts with no field, so its input context is held to the keyboard \
             layouts and no input method composes in it: {fresh:?}"
        );
        // A dead key's accent is marked text too, so it is tracked — the input
        // method needs the answers to compose it — and not reported.
        mark("\u{b4}", 1);
        assert!(facts("an accent marked").has_marked_text);
        let seen = pumped(shell);
        assert!(
            preedits(&seen).is_empty(),
            "with no field, marked text is the dead key's and no pre-edit is reported, as on \
             Win32 and Wayland: {:?}",
            preedits(&seen)
        );
        unmark();
        assert!(!facts("the accent unmarked").has_marked_text);
        assert!(
            preedits(&pumped(shell)).is_empty(),
            "nothing was shown, so nothing ends"
        );

        // --- an ordinary field lets every input method in ---
        shell
            .set_text_input(window, Some(TextPurpose::Normal))
            .expect("set_text_input");
        let open = facts("an ordinary field");
        assert!(
            open.allowed_input_sources.is_empty() && !open.roman_only,
            "an ordinary field allows every input source, which is nil: {open:?}"
        );

        // The pre-edit, with its cursor walked from UTF-16 units to bytes:
        // three units in is past か (one unit, three bytes) and the surrogate
        // pair 𠀋 (two units, four bytes), so seven bytes.
        mark("か𠀋な", 3);
        assert_eq!(
            preedits(&pumped(shell)),
            [("か𠀋な".to_owned(), Some(7))],
            "the marked text is the pre-edit, and its cursor is a byte offset on a char boundary"
        );
        unmark();
        assert_eq!(
            preedits(&pumped(shell)),
            [(String::new(), None)],
            "unmarkText ends the pre-edit with an empty one"
        );

        // A commit while text is marked replaces it: the pre-edit ends and
        // the conversion arrives as a commit.
        mark("かな", 2);
        pumped(shell);
        session_support::commit_text(TITLE, "仮名")
            .unwrap_or_else(|detail| panic!("crcbl appkit session: committing: {detail}"));
        let committed = pumped(shell);
        assert_eq!(
            preedits(&committed),
            [(String::new(), None)],
            "{:?}",
            names(&committed)
        );
        assert!(
            committed.iter().any(
                |event| matches!(event, ShellEvent::TextCommit { text, .. } if text == "仮名")
            ),
            "the conversion is committed: {:?}",
            names(&committed)
        );
        assert!(!facts("after the commit").has_marked_text);

        // Clearing the field mid-composition ends the pre-edit the consumer
        // was shown, drops the marked text, and keeps the input method out
        // again.
        mark("かな", 2);
        pumped(shell);
        shell.set_text_input(window, None).expect("set_text_input");
        assert_eq!(
            preedits(&pumped(shell)),
            [(String::new(), None)],
            "a field that goes away ends its composition"
        );
        let closed = facts("the field cleared");
        assert!(closed.roman_only && !closed.has_marked_text, "{closed:?}");

        // --- the caret area places the candidate window ---
        let window_facts = appkit_says("before placing the caret");
        let [x, y, ..] = window_facts.frame;
        let height = window_facts.content_points[1];
        let scale = window_facts.backing_scale;
        let unplaced = facts("no caret area").first_rect;
        assert!(
            near(unplaced, [x, y, 0.0, 0.0]),
            "with no area the candidate window goes to the window's own origin, {:?}, and the \
             view said {unplaced:?}",
            [x, y]
        );
        // A caret line 24 pixels tall whose top is 60 pixels down: the view's
        // rectangle has its origin at the line's *bottom* edge, Y up, in points.
        let area = PhysicalRect::new(40, 60, 200, 24);
        shell
            .set_text_input_area(window, Some(area))
            .expect("set_text_input_area");
        let placed = facts("a caret area").first_rect;
        let want = [
            x + 40.0 / scale,
            y + height - (60.0 + 24.0) / scale,
            200.0 / scale,
            24.0 / scale,
        ];
        assert!(
            near(placed, want),
            "firstRectForCharacterRange: answers the caret area in screen points: asked for \
             {area:?} in a {height}-point view at {scale}x on a window at {:?}, wanted {want:?} \
             and the view said {placed:?}",
            [x, y]
        );
        shell
            .set_text_input_area(window, None)
            .expect("set_text_input_area");
        assert!(
            near(facts("the caret area handed back").first_rect, unplaced),
            "None hands placement back"
        );

        // --- a password field holds secure input while the window is key ---
        if fresh.secure_input {
            println!(
                "crcbl appkit session: secure input was already held when this ran, by \
                 something that is not this shell, so whether this shell takes and gives it \
                 back cannot be read off the session and is not asserted"
            );
        } else {
            shell
                .set_text_input(window, Some(TextPurpose::Password))
                .expect("set_text_input");
            let focused = shell.window_state(window).expect("state").focused;
            let guarded = facts("a password field");
            assert!(
                guarded.roman_only,
                "a password keeps input methods out: {guarded:?}"
            );
            assert_eq!(
                guarded.secure_input, focused,
                "secure input is held exactly while the window with the password field has the \
                 keyboard (focused: {focused}): {guarded:?}"
            );
            shell.set_text_input(window, None).expect("set_text_input");
            assert!(
                !facts("the password field cleared").secure_input,
                "clearing the field gives secure input back, or no other process on the machine \
                 can read the keyboard"
            );
            println!(
                "crcbl appkit session: a password field {} secure input with the window {}",
                if focused { "held" } else { "did not hold" },
                if focused { "key" } else { "not key" }
            );
        }
        println!("crcbl appkit session: fields, the pre-edit and the caret area all answered");
    }

    /// Every pre-edit in `events`, as its text and cursor.
    fn preedits(events: &[ShellEvent]) -> Vec<(String, Option<usize>)> {
        events
            .iter()
            .filter_map(|event| match event {
                ShellEvent::TextPreedit { text, cursor, .. } => Some((text.clone(), *cursor)),
                _ => None,
            })
            .collect()
    }

    /// Whether two `[x, y, width, height]` rectangles agree to well within a
    /// backing pixel.
    fn near(a: [f64; 4], b: [f64; 4]) -> bool {
        a.iter().zip(b).all(|(a, b)| (a - b).abs() < 1e-6)
    }

    /// The warp, judged by where the **window system** says the pointer ended
    /// up.
    ///
    /// # The one piece of arithmetic in this slice that only a real window can check
    ///
    /// A position crosses **three** spaces on its way from the seam to
    /// CoreGraphics — the view's own Y-up points, AppKit's Y-up screen points,
    /// and Quartz's Y-down global pixels — and getting either reflection wrong
    /// puts the cursor the same distance on the *wrong side of the middle*. That
    /// is indistinguishable from a working warp for as long as the target
    /// happens to be centred, which is exactly what a hand-written fixture would
    /// pick.
    ///
    /// # The warp is only half the round trip, and the other half must be posted
    ///
    /// `CGWarpMouseCursorPosition` moves the cursor and generates no event, so
    /// there is nothing to read back from the warp itself. The seam converts
    /// `target` into Quartz's global space and puts the cursor there;
    /// `quartz::cursor` reads back the global point it chose;
    /// [`wait_for_pointer`] posts a real `kCGEventMouseMoved` *at that point*,
    /// and the backend converts what the window server delivers back into the
    /// seam's space. **Both conversions are therefore checked against each
    /// other** — a warp that put the cursor in the wrong place posts the move in
    /// the wrong place and comes back disagreeing with `target`.
    ///
    /// # Why this is behind the activation gate and [`input`] is not
    ///
    /// It posts. `CGEventPost` hands the event to the session and the session
    /// gives it to whatever is frontmost, so running this without the keyboard
    /// would move the cursor inside somebody else's application and then fail
    /// waiting for a report that was never coming to us — which is the whole
    /// failure M5 exists to stop. [`input`] posts nothing and stays unconditional.
    fn warp_round_trip(shell: &mut Box<dyn Shell>, window: WindowId, size: PhysicalSize) {
        let target =
            PhysicalPoint::new(f64::from(size.width) * 0.75, f64::from(size.height) * 0.50);
        shell.warp_pointer(window, target).expect("warp_pointer");
        let landed = wait_for_pointer(shell, quartz::cursor(), target);
        println!("crcbl appkit session: warped to {target:?} and landed at {landed:?}");
    }

    /// The M3 half: a real round trip through the **system** pasteboard, in
    /// both formats, through the seam rather than through the FFI.
    ///
    /// # What this earns over `session_support::pasteboard_round_trip`
    ///
    /// That one proves the pasteboard calls work and that the change count
    /// advances. This one proves the *seam* does: that
    /// [`clipboard_offer`](Shell::clipboard_offer) publishes both formats under
    /// types the reader half finds, that
    /// [`clipboard_request`](Shell::clipboard_request) answers **exactly once**
    /// per accepted read with the id it returned, and that the three
    /// [`ClipboardContent`] outcomes are told apart — a backend that answered
    /// `Empty` for "I did not look" would pass a bytes-only check and violate
    /// obligation 5.
    ///
    /// It ends by clearing the pasteboard, which is what an empty offer means
    /// on this platform and is also the state to leave a shared runner in.
    /// How long the waking thread lets the wait settle before it rings.
    const RING_AFTER: Duration = Duration::from_millis(50);

    /// How long a wait sleeps to show the queue is quiet: one that sleeps it
    /// out found nothing pending, so a quicker return after it is the ring's.
    const QUIET: Duration = Duration::from_millis(100);

    /// Pumps until a short wait sleeps out its timeout, so the queue holds
    /// nothing that would end the next wait for reasons of its own.
    fn settle(shell: &mut Box<dyn Shell>) {
        let deadline = Instant::now() + DEADLINE;
        while Instant::now() < deadline {
            shell.pump(&mut |_| {});
            let start = Instant::now();
            shell.wait_events(Some(QUIET));
            if start.elapsed() >= QUIET {
                return;
            }
        }
        println!("crcbl appkit session: the queue never went quiet; the waker checks run anyway");
    }

    /// **A [`ShellWaker`](crcbl_shell::ShellWaker) ends a wait from another
    /// thread, and a ring a pump took still ends the next wait.** The second
    /// is the case the AppKit wake has to handle by hand: the pump dequeues the
    /// ring's event before the wait can see it.
    fn waker(shell: &mut Box<dyn Shell>) {
        let waker = shell.waker().expect("the AppKit backend has a waker");

        settle(shell);
        let ringer = waker.clone();
        let thread = std::thread::spawn(move || {
            std::thread::sleep(RING_AFTER);
            ringer.wake();
        });
        let start = Instant::now();
        shell.wait_events(Some(DEADLINE));
        let slept = start.elapsed();
        thread.join().expect("the ringing thread");
        assert!(
            slept < DEADLINE / 2,
            "a ring from another thread did not end the wait: it slept {slept:?}"
        );

        settle(shell);
        waker.wake();
        shell.pump(&mut |_| {});
        let start = Instant::now();
        shell.wait_events(Some(DEADLINE));
        let slept = start.elapsed();
        assert!(
            slept < DEADLINE / 2,
            "a ring the pump took did not end the next wait: it slept {slept:?}"
        );
        println!(
            "crcbl appkit session: the waker ended a wait from another thread and through a pump"
        );
    }

    fn clipboard(shell: &mut Box<dyn Shell>, window: WindowId) {
        const TEXT: &str = "crcbl M3 — clipboard 🎮";
        const RON: &str = "(kind:\"node\",id:7)";

        let caps = shell.caps();
        assert!(caps.contains(ShellCaps::CLIPBOARD), "{caps:?}");
        assert!(caps.contains(ShellCaps::DRAG_DROP), "{caps:?}");
        assert!(
            shell.clipboard_readable(window),
            "any process may read the general pasteboard at any time, so this backend has \
             nothing to gate on and must say so"
        );

        // Both formats at once, which is what the windowing rules specify: the
        // text is what TextEdit pastes and the RON is what another Crucible
        // pastes, from one write.
        shell
            .clipboard_offer(
                window,
                &[ClipboardOffer::text(TEXT), ClipboardOffer::ron(RON)],
            )
            .expect("clipboard_offer");

        let (mime, content) = paste(shell, window, MimeType::TextUtf8);
        assert_eq!(
            content.text(),
            Some(TEXT),
            "the general pasteboard answered {content:?} for the text that was just written"
        );
        assert!(
            mime.matches(MimeType::TextUtf8),
            "a pasteboard type is a UTI and not a mime, so the answer must name the format \
             that was asked for: {mime}"
        );

        let (_, content) = paste(shell, window, MimeType::CrcblRon);
        assert_eq!(
            content.bytes(),
            Some(RON.as_bytes()),
            "the engine's own format has to be byte-exact or a pasted scene node will not \
             parse: {content:?}"
        );

        // A format nobody published. `Empty` and not `Unavailable`: the
        // pasteboard was read and holds nothing in that type, which
        // `ClipboardContent` is explicit is not a failure.
        let (_, content) = paste(shell, window, MimeType::UriList);
        assert_eq!(
            content,
            ClipboardContent::Empty,
            "a type nobody wrote is an empty answer, not a failed read"
        );

        // An empty offer, which on this platform can only mean "clear" — there
        // is no owner to give up.
        shell
            .clipboard_offer(window, &[])
            .expect("an empty offer clears the pasteboard");
        let (_, content) = paste(shell, window, MimeType::TextUtf8);
        assert_eq!(
            content,
            ClipboardContent::Empty,
            "clearContents left {content:?} behind"
        );

        println!("crcbl appkit session: both formats round-tripped through the system pasteboard");
    }

    // -----------------------------------------------------------------------
    // M4: AppKit as the judge
    // -----------------------------------------------------------------------

    /// Puts a logger behind the backend's own `log::` calls.
    ///
    /// # Instrumentation that cannot report is the same defect as a check that cannot fail
    ///
    /// `appkit::window::set_frame` reads the frame back after every placement
    /// and warns when the window did not go where it was put — and that warning
    /// was added, shipped, and **produced nothing**, because nothing in this
    /// process had ever installed a logger. `crcbl_core::log::warn!` with no logger behind it
    /// is a discarded string. A whole CI round trip was spent on a diagnostic
    /// that was structurally incapable of speaking, which is exactly the shape
    /// this suite keeps finding in assertions and had not thought to look for in
    /// logging.
    ///
    /// # The filter is fixed here rather than read from the environment
    ///
    /// `Filter::from_env` would leave the session's own diagnosis depending on
    /// whether a CI job happened to set `CRCBL_LOG`, and the job does not. The
    /// backend's window and shell modules are turned up to `debug` and
    /// everything else left at `info`, so the placement trail is complete and
    /// nothing else buries it — `shell` is where `set_mode` brackets what
    /// `window` reports, and it contains no other `debug!` at all, so widening
    /// to it costs exactly one line of output. `try_init_logging` rather than
    /// `init_logging` because its `Result` says whether a logger was already
    /// installed, and ignoring that silently is how this went wrong the first
    /// time.
    fn install_logger() {
        let filter = Filter::parse(
            "info,crcbl_shell::appkit::window=debug,crcbl_shell::appkit::shell=debug",
        );
        match crcbl_core::log::try_init_logging(filter) {
            Ok(()) => println!(
                "crcbl appkit session: logging installed — backend warnings and the window \
                 module's placement trail follow on stderr"
            ),
            Err(error) => println!(
                "crcbl appkit session: a logger was already installed, so the backend's own \
                 account of where it put the window may be filtered out: {error}"
            ),
        }
    }

    /// Everything AppKit will say about **this session's own window**, or a
    /// failure naming the step that asked.
    ///
    /// The window is found by [`TITLE`] among `-[NSApp windows]`, so this is
    /// also the check that the window is still ours and still named: a title
    /// that changed is a lookup that fails, carrying every title that was there
    /// instead. That is what replaced an `assert_eq!` on the title, which could
    /// only have been made about a window already found by some other means.
    fn appkit_says(step: &str) -> session_support::WindowFacts {
        session_support::window_facts(TITLE).unwrap_or_else(|detail| {
            panic!(
                "crcbl appkit session: {step}: {detail}\n\
                 What the application says: {:?}",
                session_support::activation(TITLE)
            )
        })
    }

    /// Tries to make this process frontmost and its window key, and answers what
    /// AppKit said once it stopped trying.
    ///
    /// # Judged by the mechanism, not by a return value
    ///
    /// [`frontmost::ask`] answering `true` means a method returned `YES`, which
    /// is not the same as the session having activated anything. What is read
    /// instead is [`session_support::activation`], which is `-[NSApp isActive]`
    /// and `-[NSWindow isKeyWindow]` — the two things that decide where an
    /// injected event actually lands. That is the same standard
    /// `tests/win32_e2e.rs` holds `take_foreground` to, which judges by
    /// `GetForegroundWindow` and ignores what `SetForegroundWindow` returned.
    ///
    /// The lever is pulled once per turn rather than once, for the reason that
    /// suite gives too: a single refusal must not decide a test.
    ///
    /// `Ok` when both are true, `Err` carrying the last state otherwise —
    /// **which is not a failure**, only the fact that injection cannot proceed.
    /// See [`injection_skipped`].
    fn take_activation(
        shell: &mut Box<dyn Shell>,
    ) -> Result<session_support::Activation, session_support::Activation> {
        let started = Instant::now();
        let mut announced = false;
        loop {
            let asked = frontmost::ask();
            if !announced {
                announced = true;
                // Printed once, and it is the line that separates "the lever
                // does not exist on this system" from "the lever said yes and
                // the session did nothing" — two findings a bare timeout could
                // not tell apart.
                println!(
                    "crcbl appkit session: asked the session for activation; \
                     -[NSRunningApplication activateWithOptions:] answered {asked}"
                );
            }
            shell.pump(&mut |_event: ShellEvent| {});
            let state = session_support::activation(TITLE).unwrap_or_else(|detail| {
                panic!(
                    "crcbl appkit session: the shell is open and there is no NSApplication \
                     behind it: {detail}"
                )
            });
            // Both halves: an active application whose key window belongs to
            // somebody else sends this session's keystrokes to that window, and
            // a key window in an inactive application receives nothing at all.
            if state.app_active && state.is_key {
                return Ok(state);
            }
            if started.elapsed() >= DEADLINE {
                return Err(state);
            }
            shell.wait_events(Some(Duration::from_millis(20)));
        }
    }

    /// Says — at length, and where nobody can miss it — that the injected-input
    /// half of this session did not run, and why.
    ///
    /// # A skip that is quiet is worse than a failure
    ///
    /// `docs/notes/process.md` names a silently-skipped end-to-end check as a
    /// known trap: it reports the same green as one that ran, so a path stops
    /// being covered and nothing says so. This is the opposite of that. It names
    /// what was skipped, names what is therefore unverified, and prints the
    /// [`session_support::Activation`] evidence inline so the next reader can
    /// tell the session's refusal from a defect of ours without another CI round
    /// trip.
    ///
    /// It does **not** fail the session, and that is deliberate: whether a CI
    /// runner activates an unbundled binary is not a property of this engine,
    /// and a red run here would say the backend is broken when it is not.
    fn injection_skipped(refused: &session_support::Activation) {
        // Which of the two refused, read off the evidence rather than assumed:
        // the observed case is an inactive application, and an active one whose
        // keyboard is elsewhere is a different sentence with the same
        // consequence.
        let mechanism = if refused.app_active {
            "the application is active but the keyboard is on another window"
        } else {
            "the application never became active"
        };
        // The half that says whose defect this is, and it is a genuine branch:
        // `can_become_key: false` on a `CrcblWindow` would be **ours** — the
        // `canBecomeKeyWindow` override missing from the class the window turned
        // out to be, which is a borderless window that silently stops taking
        // keystrokes. A CI run must not have to know which one it got.
        let whose = if refused.can_become_key {
            "can_become_key is true, so the CrcblWindow canBecomeKeyWindow override is installed \
             and answering: this is the session refusing to activate an unbundled binary, and no \
             backend can fix it"
        } else {
            "can_become_key is FALSE on our own window, which is a defect of this backend and not \
             the runner's refusal: the canBecomeKeyWindow override is not on the class the window \
             turned out to be, and a borderless window would silently stop receiving keystrokes"
        };
        println!(
            "crcbl appkit session: ============================================================\n\
             crcbl appkit session: INJECTED INPUT WAS NOT EXERCISED — {mechanism}, so no window \
             of this process holds it.\n\
             crcbl appkit session: CGEventPost puts an event in the *session's* stream and the \
             session gives it to whatever is frontmost, so posting now would type this test's \
             keystrokes into some other process on the runner rather than into our window.\n\
             crcbl appkit session: What AppKit says: {refused:?}\n\
             crcbl appkit session: {whose}. The harness asked for activation itself, once per \
             turn for {DEADLINE:?}, through -[NSRunningApplication activateWithOptions:] and \
             -[NSApp activateIgnoringOtherApps:], and was refused.\n\
             crcbl appkit session: SKIPPED: the injected key and its release, the TextCommit \
             behind interpretKeyEvents:, the arrow key, the window-server pointer motion, the \
             click, the wheel notch, and the warp round trip — which is skipped because \
             reading a warp back needs a posted mouse-moved, a warp generating no event of \
             its own. Nothing else: the capability set, the confine refusal, the pointer \
             modes, the cursors, the clipboard, pbcopy/pbpaste, the resize, both mode flips \
             and every AppKit readback all ran and asserted.\n\
             crcbl appkit session: STILL UNVERIFIED ON CI: interpretKeyEvents: and the whole \
             injected-input path. docs/backlog.md carries it as a gap, on the same terms as \
             the F11 pass that needs a renderer.\n\
             crcbl appkit session: ============================================================"
        );
    }

    /// What `NSWindow` says about the window the shell just built.
    ///
    /// # Three structural claims stop being structural here
    ///
    /// `appkit::view`'s module docs list five switches that decide whether an
    /// event is *generated or routed*, and `docs/backlog.md` has carried all five
    /// as "structural rather than verified" since M2 — because each is invisible
    /// to a test that calls the responder method itself. Three of them have a
    /// readback on a live window, and this is where they are read:
    ///
    /// * **`setAcceptsMouseMovedEvents:`** — the system generates no
    ///   `NSEventTypeMouseMoved` at all for a window that answers `false`, so a
    ///   game would have a pointer that only moves while a button is held.
    /// * **`makeFirstResponder:`** — `sendEvent:` delivers key events to the
    ///   first responder, which is the *window* until something claims it. A
    ///   window that is its own first responder swallows every keystroke.
    /// * **`registerForDraggedTypes:`** — on the real window this time, rather
    ///   than on the throwaway view
    ///   [`session_support::dragged_types_register_on_a_view`] builds, so it also
    ///   says that a window carrying a tracking area and a first responder still
    ///   ends up registered.
    ///
    /// # None of it needs the window to be key, and M4 learned that the hard way
    ///
    /// The first version reached the window through `-[NSApp keyWindow]` and
    /// waited for one, so on a runner that never activates this whole function
    /// — the three switches included — never ran at all. Every field it reads is
    /// an accessor any `NSWindow` answers whether or not it has the keyboard, so
    /// it now reads them off this session's own window, found by title. Whether
    /// the window *is* key is reported by [`session_support::WindowFacts`] and
    /// gates only the injection.
    fn appkit_agrees(size: PhysicalSize) -> session_support::WindowFacts {
        let facts = appkit_says("the window AppKit knows about");
        assert!(
            facts.accepts_mouse_moved,
            "-[NSWindow acceptsMouseMovedEvents] is what makes pointer motion exist at all; \
             without it a game's camera only moves while a button is held: {facts:?}"
        );
        assert_eq!(
            facts.first_responder, "CrcblView",
            "the content view has the first responder, so `sendEvent:` routes key events to it \
             rather than to the window: {facts:?}"
        );
        assert_eq!(
            facts.content_view, "CrcblView",
            "and the content view is this backend's own class: {facts:?}"
        );
        assert_eq!(
            facts.dragged_types,
            vec!["public.file-url".to_owned()],
            "a window created with accept_drops: true is registered for exactly the type this \
             backend reads; AppKit sends no dragging message to a view registered for nothing: \
             {facts:?}"
        );
        assert!(facts.titled, "a windowed window is titled: {facts:?}");

        // The extent, judged the long way round: the content view's frame in
        // points times the window's backing scale. The backend computes the same
        // number through `convertRectToBacking:` on the view's *bounds*, so this
        // is two different AppKit reads agreeing rather than the backend agreeing
        // with itself.
        assert_eq!(
            (
                (facts.content_points[0] * facts.backing_scale).round() as u32,
                (facts.content_points[1] * facts.backing_scale).round() as u32,
            ),
            (size.width, size.height),
            "the seam reported {size:?}; AppKit says the content view is {:?} points at a \
             backing scale of {}",
            facts.content_points,
            facts.backing_scale
        );
        println!(
            "crcbl appkit session: AppKit agrees — first responder {}, mouse-moved {}, dragged \
             types {:?}, scale {}, application active {}, window key {}",
            facts.first_responder,
            facts.accepts_mouse_moved,
            facts.dragged_types,
            facts.backing_scale,
            facts.app_active,
            facts.is_key
        );
        facts
    }

    /// A resize AppKit performed, delivered as one [`ShellEvent::Resized`] at the
    /// size the window system ended at.
    ///
    /// `-[NSWindow setContentSize:]` is what a user dragging the window's edge
    /// produces, minus the modal drag loop that `pump` cannot return from — the
    /// same substitution the Win32 suite makes with `SetWindowPos`, and for the
    /// same reason: CI has nobody to hold a mouse button.
    fn resize(shell: &mut Box<dyn Shell>, window: WindowId, scale: f64) {
        /// The content size to ask for, in points. Small enough to fit on the
        /// 1024×768 display both CI runners turned out to have.
        const POINTS: (f64, f64) = (512.0, 384.0);
        session_support::resize_window(TITLE, POINTS.0, POINTS.1).expect("resize_window");

        let expected = PhysicalSize::new(
            (POINTS.0 * scale).round() as u32,
            (POINTS.1 * scale).round() as u32,
        );
        let got = wait_for(shell, "the resize AppKit was asked for", |shell| {
            shell
                .window_state(window)
                .ok()
                .and_then(|state| state.size())
                .filter(|size| *size == expected)
        });
        // And AppKit's own account of the same window, which is what makes this
        // a resize rather than the seam echoing a number back.
        let after = appkit_says("after the resize");
        assert_eq!(
            after.content_points,
            [POINTS.0, POINTS.1],
            "-[NSWindow setContentSize:] took: {after:?}"
        );
        println!("crcbl appkit session: resized to {got:?} backing pixels");
    }

    // -----------------------------------------------------------------------
    // M4: input the window system generated
    // -----------------------------------------------------------------------

    /// What a failure to see injected input most likely means, printed with
    /// every one of them.
    ///
    /// **Nothing here has run on a Mac.** The one thing that could stop all of
    /// it is TCC: macOS 10.14 and later require a process to hold the
    /// Accessibility right before it may synthesize keyboard events, and a
    /// GitHub runner has nobody to grant one. What is *not* known — and what the
    /// first run of this answers — is whether that gate applies when the events
    /// are delivered back to the posting process itself, which is the whole of
    /// what happens here. If it does, every assertion below fails with this text
    /// beside it and the finding is the runner's, not the backend's.
    const INJECTION_HINT: &str = "\nNo injected event arriving at all, with the window key and \
         this process frontmost, is the signature of TCC refusing CGEventPost: macOS 10.14+ \
         gates synthetic keyboard events behind the Accessibility right, and a CI runner has \
         nobody to grant one. Whether that gate applies to events posted back to the posting \
         process is what this run answers. If it does, the fallback is \
         `-[NSApplication postEvent:atStart:]`, which needs no permission and still goes through \
         nextEventMatchingMask:, sendEvent:, the first responder and interpretKeyEvents: — \
         everything but the window server's own leg.";

    /// Keyboard, text and pointer, all of it generated by the window system
    /// rather than by a call into a responder.
    ///
    /// # This is the test M4 exists for
    ///
    /// `TextCommit` on macOS comes out of `insertText:replacementRange:`, which
    /// is called **by the input method**, from inside `interpretKeyEvents:`,
    /// which `keyDown:` calls on a real key event that `sendEvent:` routed to a
    /// first responder whose `inputContext` is non-nil — and that last part is
    /// only true because `CrcblView` conforms to `NSTextInputClient`. Every link
    /// in that chain was written on a Linux machine and none of them had ever
    /// run. The Win32 half of P5C was in exactly this state and its e2e suite
    /// found `TranslateMessage` missing from the pump, which is the same defect
    /// one platform over.
    ///
    /// # Nothing is posted until this process is the one input goes to
    ///
    /// `CGEventPost` puts the event in the **session's** stream and the session
    /// hands it to whatever is frontmost. Posting while another application holds
    /// the key window types into that application — on a CI runner, into the
    /// job's own shell. So the key window is checked first, by title, and this
    /// fails rather than posting if it is not ours.
    fn injected_input(shell: &mut Box<dyn Shell>, window: WindowId, size: PhysicalSize) {
        // **The one place `-[NSApp keyWindow]` is still the question asked**,
        // because it is the one place the answer changes what may happen: the
        // session hands an injected event to whoever is frontmost. Reached only
        // after `take_activation` said both halves are true, so a failure here
        // is the keyboard moving between that check and this one.
        let before = session_support::key_window()
            .unwrap_or_else(|detail| panic!("crcbl appkit session: before injecting: {detail}"));
        assert_eq!(
            before.title, TITLE,
            "input follows the key window, so nothing may be posted while somebody else has it: \
             {before:?}"
        );

        // --- a printable key, and the text it commits ---
        quartz::tap_key(VK_A);
        let typed = collect_until(shell, "the injected 'a' and its release", |seen| {
            seen.iter()
                .filter(|event| matches!(event, ShellEvent::Key { .. }))
                .count()
                >= 2
        });
        let typed_keys = keys(&typed);
        assert_eq!(
            typed_keys[0].0,
            u32::from(VK_A),
            "the scan code is the `kVK_*` the event carried: {:?}",
            names(&typed)
        );
        assert_eq!(
            typed_keys[0].1,
            Some(KeyCode::KeyA),
            "the physical position"
        );
        assert_eq!(typed_keys[0].3, ButtonState::Pressed);
        assert!(!typed_keys[0].4, "the first press is not a repeat");
        assert_eq!(
            typed_keys[0].2,
            Keysym::from_char('a'),
            "the layout's symbol, lowercased so a rebind menu reads the same as it does on Linux"
        );
        assert_eq!(typed_keys[1].3, ButtonState::Released);

        let committed: String = typed
            .iter()
            .filter_map(|event| match event {
                ShellEvent::TextCommit { text, .. } => Some(text.as_str()),
                _ => None,
            })
            .collect();
        assert_eq!(
            committed,
            "a",
            "**the first text this backend has ever committed from a real keystroke.** It needs \
             `sendEvent:` to route the event to the first responder, `keyDown:` to hand it to \
             `interpretKeyEvents:`, and the view's `inputContext` to be non-nil — which it is \
             only because CrcblView conforms to NSTextInputClient. The events that did arrive \
             were {:?}",
            names(&typed)
        );

        // --- and a key that is not a character ---
        quartz::tap_key(VK_UP_ARROW);
        let arrow = collect_until(shell, "the injected arrow key", |seen| {
            seen.iter()
                .any(|event| matches!(event, ShellEvent::Key { .. }))
        });
        let arrow_keys = keys(&arrow);
        assert_eq!(
            arrow_keys[0].1,
            Some(KeyCode::ArrowUp),
            "{:?}",
            names(&arrow)
        );
        assert_eq!(arrow_keys[0].0, u32::from(VK_UP_ARROW));
        assert!(
            !names(&arrow).contains(&"TextCommit"),
            "an arrow key moves a cursor; it is not a character in a text field, and a backend \
             reading `-[NSEvent characters]` instead of asking the input method would commit one \
             here: {:?}",
            names(&arrow)
        );

        // --- the pointer, moved by the window server ---
        //
        // Parked inside the window with the seam's own warp first, because a
        // mouse-moved event goes to the window under the cursor: posting motion
        // while the cursor sits over some other application's window is a test of
        // that application.
        //
        // **This is the call that failed on the first macOS run**, and the
        // reason is worth keeping next to it: the cursor is already inside the
        // window by now, so this warp crosses no boundary — no `mouseEntered:`,
        // and a warp generates no `mouseMoved:` either, so the old version waited
        // ten seconds for an event nothing had asked for.
        // [`wait_for_pointer`] posts the question now instead of assuming the
        // warp was one.
        let target =
            PhysicalPoint::new(f64::from(size.width) * 0.25, f64::from(size.height) * 0.25);
        shell.warp_pointer(window, target).expect("warp_pointer");
        let parked = wait_for_pointer(shell, quartz::cursor(), target);

        // **Identified by how far it went, not by being the last one.**
        // `wait_for_pointer` posts a move at the parked point on every turn
        // until one is reported, and the last of those can still be in flight
        // when this starts collecting — so "the most recent `PointerMotion`" can
        // be a stale report of `parked`, which would then be compared against
        // itself. `NUDGE` is far enough outside that ambiguity that a report of
        // it cannot be one of them, which is the same rule the Win32 suite
        // arrived at: on a live desktop, find your own event by its payload.
        //
        // The filter takes the **distance** and the assertions below take the
        // **direction**, deliberately: a backend that reflected the Y would
        // report a point `NUDGE.1` on the wrong side of `parked`, which this
        // admits and the assertion catches. Filtering on direction would have
        // hidden exactly the bug this is here for.
        let from = quartz::cursor();
        quartz::move_mouse_by(from, NUDGE);
        let reach = (NUDGE.0.abs() as f64 / 2.0, NUDGE.1.abs() as f64 / 2.0);
        let injected = |event: &ShellEvent| match event {
            ShellEvent::PointerMotion {
                abs: Some(at),
                raw_delta: Some(delta),
                ..
            } if (at.x - parked.x).abs() >= reach.0 && (at.y - parked.y).abs() >= reach.1 => {
                Some((*at, *delta))
            }
            _ => None,
        };
        let moved = collect_until(shell, "the injected motion", |seen| {
            seen.iter().any(|event| injected(event).is_some())
        });
        let (at, delta) = moved
            .iter()
            .rev()
            .find_map(injected)
            .expect("just waited for it");
        // **The asymmetry `appkit::pointer` exists to make visible, observed at
        // last.** `locationInWindow` is Y-up and is flipped into this seam's
        // Y-down space; the delta beside it is Quartz's own, which is Y-down
        // already and must *not* be flipped. So this move — right and up the
        // screen — has to come back as a larger window X, a **smaller** window
        // Y, and a delta whose second component is still negative. A backend
        // that reflected the delta as well as the position passes the first half
        // and fails the second, and the symptom in a game is an inverted
        // first-person camera.
        assert!(
            at.x > parked.x && at.y < parked.y,
            "the cursor was moved right and up the screen by {NUDGE:?} from {parked:?} and the \
             window reported {at:?}; the desktop is {:?} and the events were {:?}",
            quartz::cursor(),
            names(&moved)
        );
        assert!(
            delta.0 > 0.0 && delta.1 < 0.0,
            "the raw delta beside it is Quartz's own, unflipped: this move set \
             kCGMouseEventDeltaX/Y to {NUDGE:?} and the seam reported {delta:?}. (0, 0) means \
             the posted event carried no delta at all — CGEventCreateMouseEvent computes none, \
             which is what the run before this one found. A *positive* second component with \
             the position above correct is the delta being reflected into the position's Y-up \
             convention, which is an inverted camera. A negative *first* component is deltaX \
             and deltaY being read from each other's field."
        );
        // Proportion, on top of the signs, and it is scale-independent on
        // purpose: `-[NSEvent deltaX]` is documented in device-independent
        // points while the field set above is in the event stream's own units,
        // and nothing here can say whether a Retina host scales between the two.
        // A uniform factor is a fact about the display rather than a defect and
        // passes; one axis scaled differently from the other is a defect and
        // does not. The signs above have already ruled out reflection and swap,
        // so this is the remaining way the pair can be wrong.
        let asked = NUDGE.0 as f64 / NUDGE.1 as f64;
        let got = delta.0 / delta.1;
        assert!(
            (got - asked).abs() <= asked.abs() * 0.05,
            "the delta's two axes are out of proportion with each other: {NUDGE:?} was posted \
             and {delta:?} came back, a ratio of {got} where {asked} was asked for. The signs \
             are right, so this is one axis being scaled and the other not"
        );

        // --- a click, and a scroll ---
        let at = quartz::cursor();
        quartz::click(at.x, at.y);
        let clicked = collect_until(shell, "the injected click", |seen| {
            seen.iter()
                .filter(|event| matches!(event, ShellEvent::Button { .. }))
                .count()
                >= 2
        });
        let buttons: Vec<_> = clicked
            .iter()
            .filter_map(|event| match event {
                ShellEvent::Button { button, state, .. } => Some((*button, *state)),
                _ => None,
            })
            .collect();
        assert_eq!(
            buttons,
            vec![
                (PointerButton::Left, ButtonState::Pressed),
                (PointerButton::Left, ButtonState::Released),
            ],
            "one click, two events: {:?}",
            names(&clicked)
        );

        quartz::scroll_lines(1);
        let scrolled = collect_until(shell, "the injected scroll", |seen| {
            seen.iter()
                .any(|event| matches!(event, ShellEvent::Wheel { .. }))
        });
        let wheel = scrolled
            .iter()
            .find_map(|event| match event {
                ShellEvent::Wheel { delta, .. } => Some(*delta),
                _ => None,
            })
            .expect("just waited for it");
        // **The unit, not the sign.** `kCGScrollEventUnitLine` produces an
        // `NSEvent` whose `hasPreciseScrollingDeltas` is `NO`, which is the whole
        // of what decides `Lines` against `Pixels` — and that is the branch worth
        // pinning. The *sign* is not: "natural" scrolling is a per-user system
        // preference that inverts it, so an assertion on it would be an assertion
        // about the runner's settings. `docs/backlog.md` carries the horizontal
        // sign as unverified for the same reason.
        match wheel {
            ScrollDelta::Lines { x, y } => {
                assert_eq!(x, 0.0, "a vertical notch has no horizontal component");
                assert!(
                    y != 0.0,
                    "a notch of one line is not zero lines, and a zero here is far more likely \
                     to be this harness than the backend: it means the amount never reached \
                     CGEventCreateScrollWheelEvent. `wheel1` is that function's last **named** \
                     parameter and only `wheel2`/`wheel3` are variadic, so a declaration that \
                     starts its `...` one parameter early passes the amount on the stack while \
                     the callee reads register w3 — which is exactly how this assertion first \
                     went red. Check the declaration before suspecting `appkit::events`."
                );
            }
            other => panic!(
                "a line-unit scroll wheel event is Lines and not {other:?}; a backend reading \
                 hasPreciseScrollingDeltas backwards would report a trackpad's pixels here: {:?}",
                names(&scrolled)
            ),
        }
        println!(
            "crcbl appkit session: the window server's own keyboard, text, pointer, click and \
             scroll all arrived"
        );
    }

    /// Every key event in `events`, flattened for assertion.
    ///
    /// Fails rather than answering an empty list: every caller indexes it, and
    /// "the slice was empty" is a worse message than the one the wait already
    /// printed.
    fn keys(events: &[ShellEvent]) -> Vec<(u32, Option<KeyCode>, Keysym, ButtonState, bool)> {
        let keys: Vec<_> = events
            .iter()
            .filter_map(|event| match event {
                ShellEvent::Key {
                    scancode,
                    key_code,
                    keysym,
                    state,
                    repeat,
                    ..
                } => Some((scancode.0, *key_code, *keysym, *state, *repeat)),
                _ => None,
            })
            .collect();
        assert!(!keys.is_empty(), "no key events in {:?}", names(events));
        keys
    }

    /// The names of everything in `events`, for a failure message.
    fn names(events: &[ShellEvent]) -> Vec<&'static str> {
        events.iter().map(ShellEvent::name).collect()
    }

    /// Pumps until everything collected so far satisfies `enough`, and answers
    /// all of it.
    ///
    /// A deadline and a poll rather than a sleep, which
    /// `docs/notes/process.md` makes the rule for anything asynchronous — and
    /// here the asynchronous thing is the **window server**, which is the case
    /// the rule was written for. Everything is kept rather than only the
    /// matching event, because the assertions that follow need to see what
    /// *else* arrived: a CI runner is a real desktop and an event this process
    /// did not cause can turn up at any moment.
    fn collect_until(
        shell: &mut Box<dyn Shell>,
        what: &str,
        mut enough: impl FnMut(&[ShellEvent]) -> bool,
    ) -> Vec<ShellEvent> {
        let started = Instant::now();
        let mut seen = Vec::new();
        loop {
            shell.pump(&mut |event| seen.push(event));
            if enough(&seen) {
                return seen;
            }
            assert!(
                started.elapsed() < DEADLINE,
                "crcbl appkit session: waited {DEADLINE:?} for {what} and it never came; the \
                 events that did arrive were {:?}.{INJECTION_HINT}",
                names(&seen)
            );
            shell.wait_events(Some(Duration::from_millis(20)));
        }
    }

    // -----------------------------------------------------------------------
    // M4: the pasteboard, against processes that are not this one
    // -----------------------------------------------------------------------

    /// A round trip through the system pasteboard with **another process** on
    /// the far side, in both directions.
    ///
    /// # What this earns over [`clipboard`]
    ///
    /// That one writes with `NSPasteboard` and reads with `NSPasteboard`, from
    /// one process. It would pass unchanged against a shell that never touched
    /// the pasteboard server at all and answered its own reads out of a cache —
    /// which is precisely the failure the Win32 suite added a second binary to
    /// rule out. Only a reader with no `crcbl-shell` in it can tell those apart.
    ///
    /// # Decision: `pbcopy` and `pbpaste`, not a helper binary of ours
    ///
    /// The Win32 suite ships `crcbl-e2e-win32-clip` because Windows has no stock
    /// command-line clipboard client. macOS has two, in `/usr/bin`, installed on
    /// every Mac and written by Apple — so a peer of our own would be a second
    /// hand-written Objective-C FFI whose only advantage over Apple's is that we
    /// maintain it. It would also have to be a `[[bin]]` with no
    /// `required-features`, since this target has none, and would therefore be
    /// built on Linux, Windows and `wasm32` by every `--all-features` job to do
    /// nothing there.
    ///
    /// What the substitution costs is the **engine's own format**: `pbpaste`
    /// reads text and cannot be asked for `application/x-crcbl+ron`. So the
    /// cross-process claim is made about the format another application would
    /// actually read, and the RON half stays covered in-process by [`clipboard`].
    /// `docs/backlog.md` records the difference rather than implying parity.
    fn clipboard_peer(shell: &mut Box<dyn Shell>, window: WindowId) {
        const OURS: &str = "crcbl M4 — written by the shell 🎮";
        const THEIRS: &str = "crcbl M4 — written by pbcopy 🎮";

        // Out: the shell writes, and a process with none of this code in it
        // reads what the shell wrote.
        shell
            .clipboard_offer(window, &[ClipboardOffer::text(OURS)])
            .expect("clipboard_offer");
        assert_eq!(
            pbpaste().trim_end_matches('\n'),
            OURS,
            "another process read the general pasteboard and did not find what this shell put \
             there, so the bytes never reached the pasteboard server"
        );

        // In: the same, the other way round. `pbcopy` is a whole process
        // starting, claiming the pasteboard and exiting, none of which this one
        // pumped for — which on X11 or Wayland would be a conversation this shell
        // had to take part in, and here is nobody's business but the server's.
        pbcopy(THEIRS);
        let (_, content) = paste(shell, window, MimeType::TextUtf8);
        assert_eq!(
            content.text(),
            Some(THEIRS),
            "the shell read {content:?} rather than what pbcopy had just written, so it is \
             answering out of its own cache rather than from the pasteboard server"
        );

        // Left empty, which is the state to leave a shared runner in.
        shell
            .clipboard_offer(window, &[])
            .expect("an empty offer clears the pasteboard");
        println!("crcbl appkit session: pbcopy and pbpaste both agree with the seam");
    }

    /// Everything `/usr/bin/pbpaste` prints, or a failure naming why it could
    /// not be asked.
    fn pbpaste() -> String {
        let output = crcbl_spawn::command("/usr/bin/pbpaste")
            .output()
            .expect("pbpaste is part of a stock macOS");
        assert!(
            output.status.success(),
            "pbpaste failed with {:?}: {:?}",
            output.status,
            String::from_utf8_lossy(&output.stderr)
        );
        String::from_utf8_lossy(&output.stdout).into_owned()
    }

    /// Hands `text` to `/usr/bin/pbcopy` and waits for it to exit.
    ///
    /// Waiting matters: the pasteboard is claimed while `pbcopy` runs, so a read
    /// issued before it exits is a race rather than a test.
    fn pbcopy(text: &str) {
        use std::io::Write as _;
        let mut child = crcbl_spawn::command("/usr/bin/pbcopy")
            .stdin(std::process::Stdio::piped())
            .spawn()
            .expect("pbcopy is part of a stock macOS");
        child
            .stdin
            .take()
            .expect("stdin was piped")
            .write_all(text.as_bytes())
            .expect("pbcopy reads its stdin");
        let status = child.wait().expect("pbcopy exits");
        assert!(status.success(), "pbcopy failed with {status:?}");
    }

    /// Issues one read and pumps until its answer arrives, asserting there is
    /// **exactly one**.
    ///
    /// The pump after the answer is the half that has teeth: obligation 4 is
    /// "exactly once", and a backend that queued its answer twice would satisfy
    /// a loop that stopped at the first one.
    fn paste(
        shell: &mut Box<dyn Shell>,
        window: WindowId,
        mime: MimeType,
    ) -> (ReceivedMime, ClipboardContent) {
        let request = shell
            .clipboard_request(window, mime)
            .expect("clipboard_request");
        let started = Instant::now();
        let mut answers = Vec::new();
        let mut seen = Vec::new();
        let collect = |event: ShellEvent, answers: &mut Vec<_>, seen: &mut Vec<_>| {
            seen.push(event.name());
            if let ShellEvent::ClipboardData {
                window: asked,
                request: id,
                mime,
                content,
            } = event
            {
                assert_eq!(asked, window, "an answer named a window nobody asked about");
                assert_eq!(id, request, "an answer named a request nobody issued");
                answers.push((mime, content));
            }
        };
        loop {
            shell.pump(&mut |event| collect(event, &mut answers, &mut seen));
            if !answers.is_empty() {
                break;
            }
            assert!(
                started.elapsed() < DEADLINE,
                "crcbl appkit session: waited {DEADLINE:?} for the answer to {request} \
                 ({mime}) and nothing arrived; the events that did were {seen:?}"
            );
            shell.wait_events(Some(Duration::from_millis(20)));
        }
        // One more turn, collecting the same way, so a **second** answer is
        // caught here rather than left in the queue for whatever pumps next.
        shell.pump(&mut |event| collect(event, &mut answers, &mut seen));
        assert_eq!(
            answers.len(),
            1,
            "{request} was answered {} times, and obligation 4 says exactly once: {answers:?}",
            answers.len()
        );
        answers.remove(0)
    }

    /// Posts a mouse-moved at `post_at` until the window system reports where
    /// the pointer is, and answers the position it reported.
    ///
    /// A loop of its own rather than [`wait_for`], because this one needs the
    /// event's **payload** and that function's own pump has already discarded
    /// it by the time its predicate runs.
    ///
    /// # A warp is not an event, and the first macOS run proved it twice over
    ///
    /// This used to warp and then wait, which is a category error:
    /// **`CGWarpMouseCursorPosition` moves the cursor and posts nothing.** Apple
    /// documents it that way, and the run showed both halves of what that means
    /// in one log. [`input`] warps *outside* the window and then to a point
    /// inside it, and passed — because AppKit re-evaluates its tracking areas
    /// against where the cursor actually is, so crossing the boundary produces a
    /// `mouseEntered:` and therefore a `PointerFocus` with a position, warp or no
    /// warp. [`injected_input`] then warped from one point inside the window to
    /// another, crossed nothing, and waited ten seconds for an event that was
    /// never going to exist:
    ///
    /// ```text
    /// waited 10s for the pointer to be reported somewhere after a warp and
    /// nothing arrived; the events that did were ["MonitorsChanged"]
    /// ```
    ///
    /// So the warp is left to do its own job — moving the cursor, which is the
    /// thing [`Shell::warp_pointer`] promises — and the *question* is asked with
    /// a real `kCGEventMouseMoved` through the window server, which is the same
    /// machinery the click and the wheel already use. Posted every turn rather
    /// than once: `CGWarpMouseCursorPosition` suppresses local events for a
    /// short interval afterwards, and a single post swallowed by that window
    /// would be a flake rather than a finding.
    ///
    /// Either event answers. `PointerFocus` comes from the tracking area,
    /// registered `NSTrackingActiveAlways`, so it fires whether or not this
    /// process is frontmost; `PointerMotion` additionally needs the window to be
    /// **key**, because macOS sends mouse-moved events only to a key window that
    /// has asked for them.
    ///
    /// # `want` is what makes a report *this* report, and taking the freshest
    /// one instead was a flake
    ///
    /// This used to accept the first position of any value, on the reasoning
    /// that the freshest report describes where the cursor was just put. It does
    /// not, and CI showed it: `warp_round_trip` asked for x = 480 in a 640×480
    /// window and was told 320, which is the window's exact **centre** —
    /// `PointerMode::Locked` warps there before it freezes the cursor
    /// (`appkit::input::centre_pointer`), [`input`] leaves that lock behind
    /// without pumping, and AppKit re-evaluates its tracking areas against a
    /// warp. So a truthful report of where the cursor was one step ago was still
    /// in flight, arrived first, and was read as the answer to a question asked
    /// after it.
    ///
    /// Draining before the warp would not fix it — the window server delivers
    /// asynchronously, so the stale report need not have *arrived* yet by the
    /// time anything drains. The position it carries is the only thing that
    /// distinguishes it, which is the rule the Win32 suite reached first and the
    /// nudge below already follows: **on a live desktop, identify your own event
    /// by its payload.**
    ///
    /// It keeps its teeth in the direction that matters. A backend that converts
    /// the position wrongly reports the same wrong point every turn, never
    /// matches, and fails on the deadline below with every position it saw
    /// printed — the reflection this exists to catch cannot slip through, since
    /// [`POINTER_SLACK`] is a few pixels and a reflection is off by hundreds.
    fn wait_for_pointer(
        shell: &mut Box<dyn Shell>,
        post_at: quartz::Point,
        want: PhysicalPoint,
    ) -> PhysicalPoint {
        let started = Instant::now();
        let mut seen = Vec::new();
        let mut reported: Vec<PhysicalPoint> = Vec::new();
        loop {
            quartz::move_mouse(post_at.x, post_at.y);
            let mut landed = None;
            shell.pump(&mut |event: ShellEvent| {
                seen.push(event.name());
                match event {
                    ShellEvent::PointerFocus {
                        entered: true,
                        position: Some(at),
                        ..
                    }
                    | ShellEvent::PointerMotion { abs: Some(at), .. } => {
                        reported.push(at);
                        if (at.x - want.x).abs() <= POINTER_SLACK
                            && (at.y - want.y).abs() <= POINTER_SLACK
                        {
                            landed = Some(at);
                        }
                    }
                    _ => {}
                }
            });
            if let Some(at) = landed {
                return at;
            }
            assert!(
                started.elapsed() < DEADLINE,
                "crcbl appkit session: waited {DEADLINE:?} for the pointer to be reported at \
                 {want:?}. A kCGEventMouseMoved was posted at {post_at:?} on every turn of \
                 that wait; the positions that came back were {reported:?} and the events \
                 that arrived were {seen:?}. What AppKit says about this window: {:?}.\n\
                 Read the reported positions first, because they separate the mechanisms:\n\
                 * **Positions arrived, none of them {want:?}.** The conversion is wrong \
                 rather than absent. A Y that is the window's height minus the one asked \
                 for is a missing flip; an X that matches while the Y does not is the \
                 desktop reflection rather than the window one; both wrong by the window's \
                 origin is the screen's space reaching the seam unconverted.\n\
                 * **No positions at all, and nothing was posted.** \
                 CGWarpMouseCursorPosition generates no event, so a wait that relies on the \
                 warp alone sees nothing — that is what this function was rewritten to stop \
                 doing. If the posted move is also arriving nowhere, CGEventPost is being \
                 refused, which is TCC and which the injected keyboard above would have \
                 failed on first.\n\
                 * **No PointerFocus and no PointerMotion, with the post landing.** The \
                 NSTrackingArea is not delivering; it is registered NSTrackingActiveAlways \
                 and NSTrackingInVisibleRect, so it should fire even for an inactive \
                 application.\n\
                 * **A PointerFocus with no PointerMotion.** acceptsMouseMovedEvents is off, \
                 or the window is not key — read is_key in the facts above rather than \
                 guessing, because both are printed there.",
                session_support::activation(TITLE)
            );
            shell.wait_events(Some(Duration::from_millis(20)));
        }
    }

    /// Asks for a mode and waits until the window system has answered it.
    fn flip(shell: &mut Box<dyn Shell>, window: WindowId, mode: DisplayMode) -> PhysicalSize {
        shell.set_mode(window, mode).expect("set_mode");
        let what = if matches!(mode, DisplayMode::Windowed) {
            "windowed"
        } else {
            "borderless"
        };
        wait_for(shell, what, |shell| {
            let state = shell.window_state(window).ok()?;
            // Both halves: the request has to be *honoured*, and there has to be
            // a size to render at afterwards. A mode that reported success with
            // no extent behind it would pass on the first check alone.
            state
                .mode_request_honoured()
                .then(|| state.size())
                .flatten()
        })
    }

    /// A window hidden at creation must stay hidden across a mode change.
    ///
    /// `apply_mode` used to order the window front unconditionally in the
    /// borderless arm — `makeKeyAndOrderFront:` behind no `isVisible` check —
    /// so `set_mode(Borderless)` popped a hidden window on screen and took key
    /// focus with it, and `window_state().visible` reported true for a window
    /// nobody showed. Creation guards its own show behind `desc.visible` and
    /// the Win32 sibling carries `WS_VISIBLE` across its style change; this
    /// step pins the same guarantee on AppKit, which reads `isVisible` for the
    /// truth.
    ///
    /// A second window, deliberately: the main window is visible and key for
    /// the whole session, so only a separate one can carry hidden state. Its
    /// title is distinct, because [`session_support::window_facts`] finds
    /// windows by title.
    ///
    /// Visibility is judged through the seam (`window_state().visible`), and
    /// the keyboard through AppKit's own `isKeyWindow` readback — the half
    /// [`session_support::window_facts`] answers without any activation.
    fn hidden_window_stays_hidden(shell: &mut Box<dyn Shell>) {
        const HIDDEN_TITLE: &str = "crcbl appkit session — hidden mode flip";
        let hidden = shell
            .create_window(&WindowDesc {
                title: HIDDEN_TITLE,
                app_id: "sh.kryptic.crcbl.appkit-session",
                size: REQUESTED,
                constraints: SizeConstraints::min(LogicalSize::new(320.0, 180.0)),
                mode: DisplayMode::Windowed,
                resizable: true,
                visible: false,
                accept_drops: false,
                role: WindowRole::Toplevel,
                transparent: false,
            })
            .expect("create_window");

        // Creation must not show it.
        assert!(
            !shell.window_state(hidden).expect("state").visible,
            "a window created visible: false is not on screen"
        );

        // The flip, and a pump until the window system has honoured it.
        let _ = flip(shell, hidden, DisplayMode::Borderless { monitor: None });
        assert!(
            !shell.window_state(hidden).expect("state").visible,
            "set_mode(Borderless) must not show a hidden window: apply_mode used to order it \
             front unconditionally, so window_state().visible reported true for a window nobody \
             showed"
        );
        let facts = session_support::window_facts(HIDDEN_TITLE)
            .expect("the hidden window is still this process's to read");
        assert!(
            !facts.is_key,
            "a hidden window takes no key focus across a mode change: {facts:?}"
        );

        shell.destroy_window(hidden).expect("destroy_window");
        println!(
            "crcbl appkit session: a window hidden at creation stayed hidden across a mode flip"
        );
    }

    /// A window **born** borderless has no saved placement to restore, so the
    /// way back has to build one rather than inherit it.
    ///
    /// Every other window here is created windowed and flipped, which left two
    /// paths unrun. `create_native_window`'s borderless arm places a new window
    /// at the target screen's own frame rather than at a centred windowed
    /// rectangle, and `apply_mode`'s `saved.is_none()` branch builds the titled
    /// mask and the requested size that a window which was never windowed needs
    /// in order to leave borderless at all. Without that branch the mask would
    /// stay borderless while the effective mode read `Windowed`, which is the
    /// seam reporting a mode the window is not in.
    ///
    /// Hidden, like [`hidden_window_stays_hidden`]'s window: what this asserts is
    /// placement and style, not presentation, so there is nothing to show — and
    /// no test in this tree gets to open a window on the user's display.
    fn a_window_born_borderless_round_trips(shell: &mut Box<dyn Shell>) {
        const BORN_TITLE: &str = "crcbl appkit session — born borderless";

        let born = shell
            .create_window(&WindowDesc {
                title: BORN_TITLE,
                app_id: "sh.kryptic.crcbl.appkit-session",
                size: REQUESTED,
                constraints: SizeConstraints::min(LogicalSize::new(320.0, 180.0)),
                mode: DisplayMode::Borderless { monitor: None },
                resizable: true,
                visible: false,
                accept_drops: false,
                role: WindowRole::Toplevel,
                transparent: false,
            })
            .expect("create_window");

        assert!(
            !shell.window_state(born).expect("state").visible,
            "a window created visible: false is not on screen, whichever style it is born in"
        );

        // The seam's rule holds for this arm too: there is no extent until the
        // window system has configured the window.
        let covering = wait_for(
            shell,
            "the born-borderless window's first Resized",
            |shell| shell.window_state(born).ok().and_then(|state| state.size()),
        );
        assert!(
            covering.width > 0 && covering.height > 0,
            "a configured window has an extent a swapchain could be built at: {covering:?}"
        );

        // **The creation arm, judged against `NSScreen`.** Borderless here means
        // a frameless window at the target screen's size, and a window with no
        // title bar has its frame and its content rectangle be the same
        // rectangle — so a window created with `initWithContentRect:` at the
        // screen's frame covers that screen exactly, and not the windowed
        // request.
        let born_facts = session_support::window_facts(BORN_TITLE)
            .expect("the born-borderless window is this process's to read");
        let screen = born_facts
            .screen_frame
            .expect("a window created against a screen is on one");
        assert!(
            !born_facts.titled,
            "a window created borderless has no title bar; the style mask is {:#x}",
            born_facts.style_mask
        );
        assert_eq!(
            born_facts.frame, screen,
            "a window created borderless covers the screen it was created against, exactly — \
             not the centred windowed rectangle. frame {:?}, screen {screen:?}",
            born_facts.frame
        );

        // **The way back with nothing saved.** This is `apply_mode`'s
        // `saved.is_none()` branch: it must build a titled mask and the size the
        // window asked for, because this window has never had either.
        let windowed = flip(shell, born, DisplayMode::Windowed);
        let restored = session_support::window_facts(BORN_TITLE)
            .expect("the born-borderless window is this process's to read");
        assert!(
            restored.titled,
            "leaving borderless builds a titled mask; the style mask is {:#x}",
            restored.style_mask
        );
        assert_eq!(
            restored.content_points,
            [REQUESTED.width, REQUESTED.height],
            "the way back uses the size the window asked for, which it has never had: frame {:?}",
            restored.frame
        );
        assert!(
            windowed.width < covering.width && windowed.height < covering.height,
            "the restored window is smaller than the screen it was covering: {windowed:?} \
             against {covering:?}"
        );

        // And borderless again, so the pair is a round trip rather than a
        // one-way change that leaves nothing reading the window's second state.
        let _ = flip(shell, born, DisplayMode::Borderless { monitor: None });
        assert!(
            !shell.window_state(born).expect("state").visible,
            "neither flip may show a window created hidden"
        );
        let again = session_support::window_facts(BORN_TITLE)
            .expect("the born-borderless window is this process's to read");
        assert!(
            !again.titled,
            "the second borderless flip drops the title bar again; the style mask is {:#x}",
            again.style_mask
        );
        assert_eq!(
            again.frame, screen,
            "and covers the screen again: frame {:?}, screen {screen:?}",
            again.frame
        );

        shell.destroy_window(born).expect("destroy_window");
        println!(
            "crcbl appkit session: a window born borderless covered its screen, restored the \
             requested placement and went back"
        );
    }

    /// **The monitor a borderless window names is resolved, and one that is not
    /// attached is refused rather than falling back to the primary.**
    ///
    /// Every borderless window this session made before now named no monitor, so
    /// `create_window`'s `Some(id)` arm — the lookup, and the refusal beside it —
    /// had never run.
    ///
    /// **The refusal is what makes the lookup observable on a machine with one
    /// display.** With a single screen attached, "the window covers monitor 0"
    /// and "the window covers the primary" are the same rectangle, so a backend
    /// that ignored the id entirely would satisfy the placement assertion below;
    /// it could not, however, refuse an id no screen has — which is the first
    /// half, and the half a second display would not be needed to catch. The
    /// placement assertion is the shape a two-display machine discriminates on.
    ///
    /// Hidden, like its siblings: placement and style are the subject, so there
    /// is nothing to show.
    fn a_borderless_window_names_its_monitor(shell: &mut Box<dyn Shell>) {
        const NAMED_TITLE: &str = "crcbl appkit session — borderless on a monitor";

        assert!(
            !shell.monitors().is_empty(),
            "a Mac with a window server has at least one screen to be borderless on"
        );
        let target = shell.monitors()[0].id;

        // **An id no screen has.** `u32::MAX` rather than a plausible next id, so
        // this cannot start passing because a display was plugged in.
        let absent = MonitorId(u32::MAX);
        let refused = shell.create_window(&WindowDesc {
            title: NAMED_TITLE,
            app_id: "sh.kryptic.crcbl.appkit-session",
            size: REQUESTED,
            constraints: SizeConstraints::min(LogicalSize::new(320.0, 180.0)),
            mode: DisplayMode::Borderless {
                monitor: Some(absent),
            },
            resizable: true,
            visible: false,
            accept_drops: false,
            role: WindowRole::Toplevel,
            transparent: false,
        });
        assert!(
            matches!(refused, Err(ShellError::NoSuchMonitor(id)) if id == absent.0),
            "a borderless request naming a display that is gone must fail rather than quietly \
             land on the primary: {refused:?}"
        );

        let named = shell
            .create_window(&WindowDesc {
                title: NAMED_TITLE,
                app_id: "sh.kryptic.crcbl.appkit-session",
                size: REQUESTED,
                constraints: SizeConstraints::min(LogicalSize::new(320.0, 180.0)),
                mode: DisplayMode::Borderless {
                    monitor: Some(target),
                },
                resizable: true,
                visible: false,
                accept_drops: false,
                role: WindowRole::Toplevel,
                transparent: false,
            })
            .expect("create_window on an attached monitor");
        assert!(
            !shell.window_state(named).expect("state").visible,
            "a window created visible: false is not on screen, whichever monitor it names"
        );
        let _covering = wait_for(shell, "the named-monitor window's first Resized", |shell| {
            shell
                .window_state(named)
                .ok()
                .and_then(|state| state.size())
        });

        let facts = session_support::window_facts(NAMED_TITLE)
            .expect("the named-monitor window is this process's to read");
        assert!(
            !facts.titled,
            "a window created borderless has no title bar; the style mask is {:#x}",
            facts.style_mask
        );
        let screen = facts
            .screen_frame
            .expect("a window created against a screen is on one");
        assert_eq!(
            facts.frame, screen,
            "a borderless window covers the monitor it named, exactly: frame {:?}, screen \
             {screen:?}",
            facts.frame
        );
        assert_eq!(
            shell.window_state(named).expect("state").requested_mode,
            DisplayMode::Borderless {
                monitor: Some(target),
            },
            "the seam echoes the monitor the caller named"
        );

        shell.destroy_window(named).expect("destroy_window");
        println!(
            "crcbl appkit session: a borderless window named its monitor, and a monitor that is \
             not attached was refused"
        );
    }

    /// Pumps until `ready` answers, or fails naming the step that did not.
    /// A bar across the top of the primary screen and an overlay launcher,
    /// both hidden: what is asserted is the class, the level, the spaces they
    /// join, the frame and the transparency, which AppKit reports for a hidden
    /// window as readily as for a shown one.
    fn panels(shell: &mut Box<dyn Shell>) {
        const BAR: &str = "crcbl appkit session — bar";
        const LAUNCHER: &str = "crcbl appkit session — launcher";
        /// `NSWindowStyleMaskNonactivatingPanel`.
        const NONACTIVATING: usize = 1 << 7;
        /// `NSStatusWindowLevel` and `NSPopUpMenuWindowLevel`, as `NSWindow.h`
        /// documents them.
        const STATUS_LEVEL: isize = 25;
        const POP_UP_MENU_LEVEL: isize = 101;
        /// Every space, stationary in Mission Control, out of window cycling.
        const EVERY_SPACE: usize = (1 << 0) | (1 << 4) | (1 << 6);
        /// `NSWindowCollectionBehaviorFullScreenAuxiliary`.
        const FULL_SCREEN_AUXILIARY: usize = 1 << 8;
        const HEIGHT: f64 = 24.0;

        let caps = shell.caps();
        assert!(caps.contains(ShellCaps::PANEL | ShellCaps::TRANSPARENT));
        assert!(
            !caps.contains(ShellCaps::PANEL_RESERVE),
            "macOS reserves space for nothing but the menu bar and the Dock"
        );
        let primary = shell
            .monitors()
            .iter()
            .find(|monitor| monitor.is_primary)
            .map(|monitor| monitor.id)
            .expect("a primary monitor");
        let bar = shell
            .create_window(&WindowDesc {
                title: BAR,
                size: LogicalSize::new(0.0, HEIGHT),
                visible: false,
                transparent: true,
                role: WindowRole::Panel(PanelDesc {
                    monitor: Some(primary),
                    anchor: PanelAnchor::TOP | PanelAnchor::LEFT | PanelAnchor::RIGHT,
                    ..PanelDesc::default()
                }),
                ..WindowDesc::default()
            })
            .expect("a panel");
        wait_for(shell, "the bar's first configure", |shell| {
            shell.window_state(bar).ok()?.size()
        });
        let facts = session_support::window_facts(BAR).expect("the bar is readable");
        let visible = session_support::menu_bar_screen_visible_frame().expect("a screen");
        assert_eq!(facts.style_mask & NONACTIVATING, NONACTIVATING, "{facts:?}");
        assert!(!facts.titled, "a panel has no title bar: {facts:?}");
        assert_eq!(facts.level, STATUS_LEVEL, "above the menu bar: {facts:?}");
        assert_eq!(
            facts.collection_behavior & EVERY_SPACE,
            EVERY_SPACE,
            "{facts:?}"
        );
        assert_eq!(
            facts.collection_behavior & FULL_SCREEN_AUXILIARY,
            0,
            "{facts:?}"
        );
        assert!(
            !facts.hides_on_deactivate,
            "a bar that hid whenever its application was not active: {facts:?}"
        );
        assert!(!facts.opaque, "a transparent window: {facts:?}");
        assert!(
            shell.surface_target(bar).expect("a live bar").transparent(),
            "the surface target says the bar is transparent"
        );
        assert!(!facts.can_become_key, "a bar takes no keyboard: {facts:?}");
        assert_eq!(
            [
                facts.frame[0],
                facts.frame[1] + facts.frame[3],
                facts.frame[2],
                facts.frame[3]
            ],
            [visible[0], visible[1] + visible[3], visible[2], HEIGHT],
            "across the top of the visible frame, at its own height: {facts:?}"
        );

        // lyr's bar on reload: taller, an overlay, and clickable into, in place.
        const TALLER: f64 = 40.0;
        let reloaded = PanelDesc {
            monitor: Some(primary),
            layer: PanelLayer::Overlay,
            anchor: PanelAnchor::TOP | PanelAnchor::LEFT | PanelAnchor::RIGHT,
            keyboard: PanelKeyboard::OnDemand,
            ..PanelDesc::default()
        };
        shell
            .set_panel(bar, LogicalSize::new(0.0, TALLER), &reloaded)
            .expect("set_panel");
        wait_for(shell, "the bar's resize to its new height", |shell| {
            let state = shell.window_state(bar).ok()?;
            (state.size()?.height
                == LogicalSize::new(0.0, TALLER)
                    .to_physical(state.scale_factor()?)
                    .height)
                .then_some(())
        });
        let facts = session_support::window_facts(BAR).expect("the bar is readable");
        assert_eq!(
            [facts.frame[1] + facts.frame[3], facts.frame[3]],
            [visible[1] + visible[3], TALLER],
            "still against the top, at its new height: {facts:?}"
        );
        assert_eq!(facts.level, POP_UP_MENU_LEVEL, "an overlay now: {facts:?}");
        assert!(
            facts.can_become_key,
            "an on-demand bar can take the keyboard: {facts:?}"
        );
        let renamed = PanelDesc {
            namespace: "renamed",
            ..reloaded
        };
        assert!(
            matches!(
                shell.set_panel(bar, LogicalSize::new(0.0, TALLER), &renamed),
                Err(ShellError::InvalidDescriptor(_))
            ),
            "a panel's namespace is fixed"
        );
        shell.destroy_window(bar).expect("destroy the bar");

        let launcher = shell
            .create_window(&WindowDesc {
                title: LAUNCHER,
                size: LogicalSize::new(400.0, 300.0),
                visible: false,
                role: WindowRole::Panel(PanelDesc {
                    layer: PanelLayer::Overlay,
                    keyboard: PanelKeyboard::Exclusive,
                    ..PanelDesc::default()
                }),
                ..WindowDesc::default()
            })
            .expect("a panel");
        wait_for(shell, "the launcher's first configure", |shell| {
            shell.window_state(launcher).ok()?.size()
        });
        let facts = session_support::window_facts(LAUNCHER).expect("the launcher is readable");
        assert_eq!(facts.level, POP_UP_MENU_LEVEL, "{facts:?}");
        assert_eq!(
            facts.collection_behavior & FULL_SCREEN_AUXILIARY,
            FULL_SCREEN_AUXILIARY,
            "an overlay may join a full-screen window's space: {facts:?}"
        );
        assert!(
            facts.can_become_key,
            "a launcher takes the keyboard: {facts:?}"
        );
        assert!(facts.opaque, "not asked to be transparent: {facts:?}");
        assert!(
            !shell
                .surface_target(launcher)
                .expect("a live launcher")
                .transparent(),
            "nor does its surface target say so"
        );
        assert_eq!(facts.content_points, [400.0, 300.0], "{facts:?}");
        shell
            .destroy_window(launcher)
            .expect("destroy the launcher");
        println!("crcbl appkit session: a bar and a launcher are panels at their levels");
    }

    /// A hidden window and three hidden popups below a 100×20 button at (10,
    /// 10) of it, one per input: AppKit reports their frames, classes and
    /// mouse handling for hidden windows as for shown ones.
    fn popups(shell: &mut Box<dyn Shell>) {
        const OWNER: &str = "crcbl appkit session — popup owner";
        let owner = shell
            .create_window(&WindowDesc {
                title: OWNER,
                size: LogicalSize::new(500.0, 400.0),
                visible: false,
                ..WindowDesc::default()
            })
            .expect("a window");
        wait_for(shell, "the owner's first configure", |shell| {
            shell.window_state(owner).ok()?.size()
        });
        let owner_facts = session_support::window_facts(OWNER).expect("the owner is readable");
        let made = [
            ("crcbl appkit session — tooltip", PopupInput::Passthrough),
            ("crcbl appkit session — hover card", PopupInput::Pointer),
            ("crcbl appkit session — menu", PopupInput::Grab),
        ]
        .map(|(title, input)| {
            let popup = shell
                .create_window(&WindowDesc {
                    title,
                    size: LogicalSize::new(200.0, 150.0),
                    visible: false,
                    role: WindowRole::Popup(PopupDesc {
                        parent: owner,
                        anchor: LogicalRect::new(
                            LogicalPoint::new(10.0, 10.0),
                            LogicalSize::new(100.0, 20.0),
                        ),
                        placement: PopupPlacement::Below,
                        input,
                    }),
                    ..WindowDesc::default()
                })
                .expect("a popup");
            (popup, title, input)
        });
        // The content sits at the bottom of a titled frame, under the title
        // bar, so its top is the frame's origin plus its own height.
        let content_top = owner_facts.frame[1] + owner_facts.content_points[1];
        for (_, title, input) in made {
            let facts = session_support::window_facts(title).expect("the popup is readable");
            assert_eq!(
                facts.frame,
                [
                    owner_facts.frame[0] + 10.0,
                    content_top - 30.0 - 150.0,
                    200.0,
                    150.0
                ],
                "below the button, left edges aligned: {facts:?}"
            );
            assert!(!facts.titled, "{facts:?}");
            assert_eq!(
                facts.ignores_mouse_events,
                input == PopupInput::Passthrough,
                "{input:?}: {facts:?}"
            );
            assert_eq!(
                facts.can_become_key,
                input == PopupInput::Grab,
                "{input:?}: {facts:?}"
            );
        }

        // The hover card follows a button that moved right, at a new size.
        let (card, card_title, _) = made[1];
        shell
            .reposition_popup(
                card,
                LogicalRect::new(LogicalPoint::new(60.0, 10.0), LogicalSize::new(100.0, 20.0)),
                LogicalSize::new(240.0, 90.0),
            )
            .expect("reposition the hover card");
        wait_for(shell, "the hover card's resize", |shell| {
            (shell.window_state(card).ok()?.size()?
                == LogicalSize::new(240.0, 90.0)
                    .to_physical(shell.window_state(card).ok()?.scale_factor()?))
            .then_some(())
        });
        let facts = session_support::window_facts(card_title).expect("the card is readable");
        assert_eq!(
            facts.frame,
            [
                owner_facts.frame[0] + 60.0,
                content_top - 30.0 - 90.0,
                240.0,
                90.0
            ],
            "below the moved button, at its new size: {facts:?}"
        );

        let mut destroyed = Vec::new();
        shell.pump(&mut |_| {});
        shell.destroy_window(owner).expect("destroy the owner");
        shell.pump(&mut |event| {
            if let ShellEvent::WindowDestroyed { window } = event {
                destroyed.push(window);
            }
        });
        assert_eq!(
            destroyed.last(),
            Some(&owner),
            "the owner last: {destroyed:?}"
        );
        assert_eq!(destroyed.len(), made.len() + 1, "{destroyed:?}");
        for (popup, _, _) in made {
            assert!(destroyed.contains(&popup), "{destroyed:?}");
        }
        println!("crcbl appkit session: three popups opened below their button and went first");
    }

    fn wait_for<T>(
        shell: &mut Box<dyn Shell>,
        what: &str,
        mut ready: impl FnMut(&mut Box<dyn Shell>) -> Option<T>,
    ) -> T {
        let started = Instant::now();
        let mut seen = Vec::new();
        loop {
            shell.pump(&mut |event: ShellEvent| seen.push(event.name()));
            if let Some(value) = ready(shell) {
                return value;
            }
            assert!(
                started.elapsed() < DEADLINE,
                "crcbl appkit session: waited {DEADLINE:?} for {what} and it never came; \
                 the events that did arrive were {seen:?}"
            );
            // Sleeping is what `wait_events` is for, and exercising it here is
            // free: it is advisory, so a backend that returns immediately just
            // spins this loop as it would have anyway.
            shell.wait_events(Some(Duration::from_millis(20)));
        }
    }

    // -----------------------------------------------------------------------
    // M5: the activation a harness may arrange and a backend may not
    // -----------------------------------------------------------------------

    /// Asking the session to make **this** process frontmost.
    ///
    /// # Decision: this is a harness, so it is not in `src/appkit/`
    ///
    /// `tests/win32_e2e.rs`'s `desktop::take_foreground` states the rule for the
    /// other platform and it is the same rule here: **a game does not get to
    /// steal the focus.** A backend that knew how to force itself frontmost is a
    /// backend that could do it to a user mid-sentence, so
    /// `appkit::app::activate` does the polite thing — `-[NSApp activate]` where
    /// it exists, `activateIgnoringOtherApps:` before that — and stops. What is
    /// here is what an automated harness does to arrange a precondition a human
    /// would have arranged by clicking on the window, and it belongs on this side
    /// of the seam for exactly that reason.
    ///
    /// # Why `NSRunningApplication` and not more of what the backend already did
    ///
    /// The backend's own activation runs on every `open()` and the runner still
    /// reported `app_active: false`. On macOS 14 `-[NSApplication activate]` is
    /// *cooperative*: it asks, and an application that is not entitled to
    /// interrupt whatever is frontmost does not get to. `respondsToSelector:`
    /// finds that selector on any modern system, so the backend takes that branch
    /// and never reaches the forceful one.
    ///
    /// `-[NSRunningApplication activateWithOptions:]` is the lever from the other
    /// side — the process object rather than the application object — and
    /// `NSApplicationActivateIgnoringOtherApps` is the bit that says "do it
    /// anyway". Both spellings are deprecated as of macOS 14 and neither is
    /// removed; deprecated is a compiler diagnostic in Objective-C and nothing at
    /// all across `objc_msgSend`. The legacy `activateIgnoringOtherApps:` is sent
    /// as well, because it is the branch the backend skips and it costs one
    /// message.
    ///
    /// **Every selector is checked with `respondsToSelector:` first.** Sending
    /// one a system does not have is a crash, not a graceful nothing, and this
    /// crate has no deployment target to reason from — the same discipline
    /// `appkit::ffi::responds_to` exists for.
    ///
    /// # Hand-written FFI rather than the backend's
    ///
    /// `crcbl_shell::appkit::ffi` is `pub(crate)`, rightly, and [`quartz`] gives
    /// the argument in full: a harness that drove the backend through the
    /// backend's own ABI declarations would be grading its own homework. So this
    /// declares the three runtime entry points it needs and transmutes
    /// `objc_msgSend` per call site, which is the only form of the call that is
    /// correct on `aarch64-apple-darwin` — see [`msg_send`].
    mod frontmost {
        use core::ffi::{CStr, c_char, c_void};

        /// Any Objective-C object pointer: `id`, and also `Class`.
        type Id = *mut c_void;
        /// `SEL`.
        type Sel = *const c_void;
        /// `BOOL`, as `i8`: it is `signed char` on x86_64 and `_Bool` on arm64,
        /// and only the second has exactly two valid bit patterns — so reading
        /// one straight into a Rust `bool` would be undefined behaviour the day
        /// a method answered `2`.
        type ObjcBool = i8;

        /// `NSApplicationActivateAllWindows` — bring this process's other
        /// windows up with it, rather than only the frontmost one.
        const ACTIVATE_ALL_WINDOWS: usize = 1 << 0;
        /// `NSApplicationActivateIgnoringOtherApps` — the bit that makes this a
        /// harness's call and not a well-behaved application's.
        const ACTIVATE_IGNORING_OTHER_APPS: usize = 1 << 1;

        #[link(name = "objc")]
        unsafe extern "C" {
            fn objc_getClass(name: *const c_char) -> Id;
            fn sel_registerName(name: *const c_char) -> Sel;
            /// The dispatch trampoline. **Never called through this
            /// declaration** — see [`msg_send`].
            fn objc_msgSend();
            fn objc_autoreleasePoolPush() -> *mut c_void;
            fn objc_autoreleasePoolPop(pool: *mut c_void);
        }

        /// A scope in which autoreleased objects are valid.
        ///
        /// **`+[NSRunningApplication currentApplication]` returns an
        /// autoreleased object**, and [`ask`] is called once per turn of a poll
        /// that can run for the whole deadline. With no pool the runtime logs
        /// `autoreleased with no pool in place — just leaking` and does exactly
        /// that, several hundred times, which buries the session's own narrative
        /// in the one log a reader has. The backend's `appkit::ffi::Pool` is
        /// `pub(crate)` and this is a harness, so this is the same two runtime
        /// calls declared here.
        struct Pool(*mut c_void);

        impl Pool {
            /// Pushes a pool, popped by [`Drop`] in the reverse order.
            fn push() -> Self {
                // SAFETY: the runtime's own pool stack; the token is opaque and
                // is handed back unmodified in `drop`.
                Self(unsafe { objc_autoreleasePoolPush() })
            }
        }

        impl Drop for Pool {
            fn drop(&mut self) {
                // SAFETY: `self.0` is the token this pool pushed and has not
                // been popped before. Rust's drop order gives the nesting the
                // runtime requires.
                unsafe { objc_autoreleasePoolPop(self.0) };
            }
        }

        /// `objc_msgSend`, transmuted to the exact signature of the method being
        /// sent.
        ///
        /// **There is no variadic ABI to declare it with on
        /// `aarch64-apple-darwin`.** Apple's arm64 convention passes variadic
        /// arguments on the stack and ordinary ones in registers, and
        /// `objc_msgSend` is a trampoline that must be called with the signature
        /// of the method it dispatches to, so that every argument is already in
        /// the register the implementation reads it from. Declaring it
        /// `fn(Id, Sel, ...)` compiles, links, runs, and hands the method
        /// whatever happened to be lying in that register. The same hazard
        /// [`quartz`](super::quartz) documents for
        /// `CGEventCreateScrollWheelEvent`, arriving through the runtime instead.
        ///
        /// # Safety
        ///
        /// `F` must be a function pointer type whose signature is exactly the
        /// receiver, the selector, the method's arguments and its return type.
        unsafe fn msg_send<F: Copy>() -> F {
            assert!(
                size_of::<F>() == size_of::<*const c_void>(),
                "msg_send's type parameter must be a function pointer"
            );
            // SAFETY: `F` is a function pointer of pointer size, asserted above,
            // and the value copied into it is the address of a real function.
            // The caller carries the obligation that the signature matches.
            unsafe { core::mem::transmute_copy(&(objc_msgSend as *const c_void)) }
        }

        /// The selector for `name`, interned by the runtime.
        fn sel(name: &CStr) -> Sel {
            // SAFETY: a NUL-terminated C string that outlives the call; the
            // result is a pointer into the runtime's own table.
            unsafe { sel_registerName(name.as_ptr()) }
        }

        /// Whether `receiver` implements `selector`.
        ///
        /// # Safety
        ///
        /// `receiver` must be a live object; every object answers this.
        unsafe fn responds_to(receiver: Id, selector: Sel) -> bool {
            // SAFETY: `respondsToSelector:` is on `NSObject`, so every object
            // implements it with this signature.
            let send: unsafe extern "C" fn(Id, Sel, Sel) -> ObjcBool = unsafe { msg_send() };
            unsafe { send(receiver, sel(c"respondsToSelector:"), selector) != 0 }
        }

        /// Pulls both levers once, and answers whether
        /// `activateWithOptions:` said yes.
        ///
        /// **That answer is not the judgment** and no caller should treat it as
        /// one: a method returning `YES` is not the session having activated
        /// anything. `take_activation` reads `-[NSApp isActive]` afterwards
        /// instead, exactly as the Win32 suite judges `take_foreground` by
        /// `GetForegroundWindow`. What the return value is good for is telling
        /// "this system has no such lever" apart from "the lever was pulled and
        /// nothing moved", which are different findings and would otherwise both
        /// arrive as a timeout.
        #[must_use]
        pub fn ask() -> bool {
            let _pool = Pool::push();
            // SAFETY: every call below is a class lookup, a `respondsToSelector:`
            // or a message whose signature is written at its own call site.
            // `currentApplication` and `sharedApplication` are class methods
            // returning objects the runtime owns for the life of the process,
            // and each is null-checked before anything is sent to it.
            unsafe {
                let running = objc_getClass(c"NSRunningApplication".as_ptr());
                let application = objc_getClass(c"NSApplication".as_ptr());
                if running.is_null() || application.is_null() {
                    return false;
                }
                let class_method: unsafe extern "C" fn(Id, Sel) -> Id = msg_send();

                // The legacy, forceful spelling on `NSApp`. The backend skips
                // this branch on any system new enough to have `activate`, so
                // sending it here is not a repeat of what `open()` already did.
                let app = class_method(application, sel(c"sharedApplication"));
                let ignoring = sel(c"activateIgnoringOtherApps:");
                if !app.is_null() && responds_to(app, ignoring) {
                    let send: unsafe extern "C" fn(Id, Sel, ObjcBool) = msg_send();
                    send(app, ignoring, 1);
                }

                // And the process object's own, which is the lever this module
                // exists for.
                let current = class_method(running, sel(c"currentApplication"));
                let with_options = sel(c"activateWithOptions:");
                if current.is_null() || !responds_to(current, with_options) {
                    return false;
                }
                let activate: unsafe extern "C" fn(Id, Sel, usize) -> ObjcBool = msg_send();
                activate(
                    current,
                    with_options,
                    ACTIVATE_ALL_WINDOWS | ACTIVATE_IGNORING_OTHER_APPS,
                ) != 0
            }
        }
    }

    // -----------------------------------------------------------------------
    // The Quartz event stream, which is the only way in from outside
    // -----------------------------------------------------------------------

    /// The CoreGraphics surface a *harness* needs, as distinct from the
    /// backend's.
    ///
    /// Hand-written here rather than borrowed from `crcbl_shell::appkit::ffi`,
    /// which is `pub(crate)` — and rightly so, on the same grounds
    /// `tests/win32_e2e.rs` gives for its own `user32` table: a test that drove
    /// the backend through the backend's own ABI declarations would be grading
    /// its own homework. These are the calls somebody else's input device makes.
    ///
    /// # Decision: posted from this process, and that is still "from outside"
    ///
    /// The Win32 suite injects from a **second process** because `SendInput`
    /// from the thread that owns the window is the one arrangement that proves
    /// nothing about the message queue. That argument does not transfer.
    /// `CGEventPost` does not put an event in this process's queue: it hands it
    /// to the **window server**, which decides who is frontmost, builds the
    /// `NSEvent`, and delivers it to the application's run loop — so the event
    /// re-enters through `nextEventMatchingMask:` exactly as a keyboard's would,
    /// whoever posted it. A second process would add a TCC surface (synthetic
    /// events *to another application* are unambiguously gated) and one more
    /// binary built on three platforms that cannot use it, and would buy nothing
    /// this does not already have.
    mod quartz {
        use core::ffi::c_void;

        /// `CGEventRef`.
        type EventRef = *mut c_void;

        /// `CGPoint`, in Quartz's global space: **Y down** from the top-left of
        /// the primary display, which is the space this seam already uses and
        /// the opposite of AppKit's.
        #[repr(C)]
        #[derive(Clone, Copy, Debug, Default, PartialEq)]
        pub struct Point {
            /// Rightwards from the primary display's left edge.
            pub x: f64,
            /// **Downwards** from the primary display's top edge.
            pub y: f64,
        }

        /// `kCGHIDEventTap` — inject where a device would, ahead of everything
        /// that filters the session's input.
        const HID_TAP: u32 = 0;
        /// `kCGEventLeftMouseDown`.
        const LEFT_DOWN: u32 = 1;
        /// `kCGEventLeftMouseUp`.
        const LEFT_UP: u32 = 2;
        /// `kCGEventMouseMoved`.
        const MOUSE_MOVED: u32 = 5;
        /// `kCGMouseButtonLeft`.
        const BUTTON_LEFT: u32 = 0;

        // The `CGEventField` numbers this harness sets. **Every one of them is
        // zero on an event `CGEventCreateMouseEvent` just built**, which is the
        // whole reason they are here: that function places an event at an
        // absolute location and computes nothing else about it.

        /// `kCGMouseEventClickState` — which click of a multiple-click this is.
        ///
        /// **Not read by this seam**, which takes the button from
        /// `buttonNumber` alone, so nothing asserted here depends on it. Set
        /// anyway because a press whose click state is zero is not what a real
        /// click looks like, and AppKit is entitled to act on that rather than
        /// on nothing — which would be a defect of the harness reported as one
        /// of the backend.
        const MOUSE_CLICK_STATE: u32 = 1;
        /// `kCGMouseEventDeltaX`.
        const MOUSE_DELTA_X: u32 = 4;
        /// `kCGMouseEventDeltaY`.
        const MOUSE_DELTA_Y: u32 = 5;
        /// `kCGScrollEventUnitLine` — a wheel notch rather than a trackpad's
        /// pixels, which is what decides `ScrollDelta::Lines` at the far end.
        const UNIT_LINE: u32 = 1;

        #[link(name = "CoreGraphics", kind = "framework")]
        unsafe extern "C" {
            fn CGEventCreate(source: *mut c_void) -> EventRef;
            fn CGEventGetLocation(event: EventRef) -> Point;
            fn CGEventCreateKeyboardEvent(
                source: *mut c_void,
                keycode: u16,
                down: bool,
            ) -> EventRef;
            fn CGEventCreateMouseEvent(
                source: *mut c_void,
                kind: u32,
                at: Point,
                button: u32,
            ) -> EventRef;
            /// **Partly variadic, and the boundary is the whole point.** The C
            /// signature is:
            ///
            /// ```c
            /// CGEventRef CGEventCreateScrollWheelEvent(CGEventSourceRef source,
            ///                                          CGScrollEventUnit units,
            ///                                          CGWheelCount wheelCount,
            ///                                          int32_t wheel1, ...);
            /// ```
            ///
            /// **`wheel1` is a named parameter.** Only `wheel2` and `wheel3` are
            /// variadic, and getting that boundary wrong is not a diagnostic —
            /// it is a wrong-register read at run time. On
            /// `aarch64-apple-darwin` — the runner's architecture and every
            /// Mac's since 2020 — variadic arguments go on the **stack** while
            /// named ones go in registers, so declaring `wheel1` inside the
            /// variadic list puts the amount on the stack while the callee reads
            /// it from `w3`.
            ///
            /// That is not hypothetical: this was declared with `...` starting
            /// one parameter too early, and the macOS run scrolled by whatever
            /// happened to be in that register, which was **zero** —
            /// `a notch of one line is not zero lines`. The previous comment here
            /// described this exact hazard and then committed it, which is the
            /// part worth remembering: `appkit::ffi` documents it at length for
            /// `objc_msgSend` and everybody was watching the Objective-C
            /// dispatch, while the defect arrived through a plain C function
            /// nobody had counted the parameters of.
            ///
            /// Posting one axis means the variadic list is **empty**, which the
            /// declaration still has to say correctly — an empty list is not the
            /// same as no list.
            fn CGEventCreateScrollWheelEvent(
                source: *mut c_void,
                units: u32,
                wheels: u32,
                wheel1: i32,
                ...
            ) -> EventRef;
            fn CGEventPost(tap: u32, event: EventRef);
            /// Sets one integer-valued field on an event before it is posted.
            ///
            /// The only way to put a **relative delta** on a synthesized mouse
            /// move: `CGEventCreateMouseEvent` takes an absolute location and
            /// leaves `kCGMouseEventDeltaX`/`Y` at zero, and `-[NSEvent deltaX]`
            /// reads exactly those fields.
            fn CGEventSetIntegerValueField(event: EventRef, field: u32, value: i64);
        }

        #[link(name = "CoreFoundation", kind = "framework")]
        unsafe extern "C" {
            fn CFRelease(object: *mut c_void);
        }

        /// Where the cursor is, in Quartz's global space.
        ///
        /// Read through an event rather than through `NSEvent`'s
        /// `mouseLocation`, so that no part of this file is in AppKit's Y-up
        /// space: mixing the two is the mistake `appkit::geometry` exists to
        /// prevent, and a harness that made it would be reporting the backend's
        /// coordinate handling as broken while its own arithmetic was.
        #[must_use]
        pub fn cursor() -> Point {
            // SAFETY: a null source asks for an event stamped with the current
            // state; the result is a live `CGEventRef` this function owns and
            // releases, and `CGEventGetLocation` only reads it.
            unsafe {
                let event = CGEventCreate(core::ptr::null_mut());
                if event.is_null() {
                    return Point::default();
                }
                let at = CGEventGetLocation(event);
                CFRelease(event);
                at
            }
        }

        /// Presses and releases one key by its `kVK_*` code.
        ///
        /// Two events rather than one, and in that order: an injected key
        /// describes a **transition**, which the Windows half of P5C paid a CI
        /// round trip to learn — a second down for a key already held produces
        /// no event at all, so a press that is never released leaves the session
        /// with a stuck modifier for the next test.
        pub fn tap_key(keycode: u16) {
            key(keycode, true);
            key(keycode, false);
        }

        /// One key edge.
        ///
        /// **The one field this leaves at its default is the right one.**
        /// `kCGKeyboardEventAutorepeat` is zero on a freshly built event and
        /// `-[NSEvent isARepeat]` reads it, which is what
        /// [`injected_input`](super::injected_input) asserts about the first
        /// press — so the default is the value under test rather than an
        /// oversight. Checked when the mouse delta turned out not to be.
        fn key(keycode: u16, down: bool) {
            // SAFETY: a null source is the documented "no particular source";
            // the event is live, posted, and released exactly once here.
            unsafe {
                let event = CGEventCreateKeyboardEvent(core::ptr::null_mut(), keycode, down);
                if event.is_null() {
                    return;
                }
                CGEventPost(HID_TAP, event);
                CFRelease(event);
            }
        }

        /// Moves the cursor to a point in Quartz's global space, carrying **no**
        /// relative delta.
        ///
        /// Which is honest rather than convenient: the event genuinely says the
        /// pointer did not travel, and this is used to ask where the pointer is
        /// rather than to claim it moved. [`move_mouse_by`] is the one that
        /// makes a claim about travel.
        pub fn move_mouse(x: f64, y: f64) {
            mouse(MOUSE_MOVED, Point { x, y }, &[]);
        }

        /// Moves the cursor `delta` from `from`, and **says so on the event**.
        ///
        /// # `CGEventCreateMouseEvent` does not compute a delta, and that cost a round trip
        ///
        /// It places an event at an absolute location and leaves
        /// `kCGMouseEventDeltaX`/`Y` at zero. `-[NSEvent deltaX]` reads exactly
        /// those fields, so a backend passing them through faithfully — which
        /// `appkit::view` does, straight into `raw_delta` — reports the zero it
        /// was handed. The first run to reach this asserted the delta was
        /// positive in both axes and got `(0.0, 0.0)`.
        ///
        /// **The assertion was checking a value this harness had never
        /// supplied**, so it could only ever have passed by accident, from some
        /// unrelated movement on the runner's desk. Setting the field turns it
        /// into a real check: the window server is told a known delta, and the
        /// seam can be held to reporting *that* rather than merely something
        /// non-zero.
        pub fn move_mouse_by(from: Point, delta: (i64, i64)) {
            let at = Point {
                x: from.x + delta.0 as f64,
                y: from.y + delta.1 as f64,
            };
            mouse(
                MOUSE_MOVED,
                at,
                &[(MOUSE_DELTA_X, delta.0), (MOUSE_DELTA_Y, delta.1)],
            );
        }

        /// A press and its release, both at the same point, each carrying a
        /// click state of one.
        pub fn click(x: f64, y: f64) {
            let at = Point { x, y };
            let single = &[(MOUSE_CLICK_STATE, 1)];
            mouse(LEFT_DOWN, at, single);
            mouse(LEFT_UP, at, single);
        }

        /// One mouse event, with `fields` set on it before it is posted.
        fn mouse(kind: u32, at: Point, fields: &[(u32, i64)]) {
            // SAFETY: as in `key`; `at` is a `CGPoint` passed by value, and each
            // entry of `fields` is a documented `CGEventField` of a mouse event
            // set on the live event before it is posted.
            unsafe {
                let event = CGEventCreateMouseEvent(core::ptr::null_mut(), kind, at, BUTTON_LEFT);
                if event.is_null() {
                    return;
                }
                for (field, value) in fields {
                    CGEventSetIntegerValueField(event, *field, *value);
                }
                CGEventPost(HID_TAP, event);
                CFRelease(event);
            }
        }

        /// Scrolls `lines` notches on the vertical axis.
        ///
        /// **Nothing here rests on a default.** The unit is an argument of the
        /// constructor rather than a field left unset, and it is the whole of
        /// what decides `hasPreciseScrollingDeltas` at the far end and therefore
        /// `ScrollDelta::Lines` against `Pixels`; the per-axis amount is the
        /// variadic argument below. Checked alongside the mouse delta, which did
        /// rest on one.
        pub fn scroll_lines(lines: i32) {
            // SAFETY: as in `key`. `wheels: 1` promises exactly one axis, so
            // `lines` is `wheel1` — the last **named** parameter — and the
            // variadic list is empty, which is what one axis means. Passing it
            // as `i32` is the declaration's `int32_t` rather than a widened
            // integer literal.
            unsafe {
                let event =
                    CGEventCreateScrollWheelEvent(core::ptr::null_mut(), UNIT_LINE, 1, lines);
                if event.is_null() {
                    return;
                }
                CGEventPost(HID_TAP, event);
                CFRelease(event);
            }
        }
    }
}