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
//! [`Win32Shell`] — the shell itself, the message pump, and the [`Shell`]
//! implementation.

use core::ffi::c_void;
use core::ptr::{self, NonNull};
use core::time::Duration;
use std::collections::VecDeque;
use std::rc::Rc;

use crcbl_core::{EventTime, Pool, SurfaceTarget};

use crate::preedit::Composing;
use crate::{
    ClipboardContent, ClipboardOffer, ClipboardRequestId, CloseReply, CursorIcon, DisplayMode,
    LogicalSize, MimeType, MonitorId, MonitorInfo, PhysicalPoint, PhysicalRect, PhysicalSize,
    PointerMode, ReceivedMime, Shell, ShellBackend, ShellCaps, ShellError, ShellEvent,
    SizeConstraints, TextPurpose, WindowConfiguration, WindowDesc, WindowId, WindowState,
};

use super::TimeBase;
use super::clipboard::{self, Clipboard, Opened};
use super::devices::{Attribution, DeviceTable};
use super::events::RawEvent;
use super::ffi::{self, Handle, Msg, WindowPlacement, value};
use super::geometry;
use super::input;
use super::input::Contact;
use super::keys::{Consumed, Utf16};
use super::panel::Panel;
use super::pointer::{RawMotion, Visibility};
use super::proc::{self, Shared};
use super::window;

/// Why [`Win32Shell::wait`] came back.
///
/// `MsgWaitForMultipleObjectsEx` has four answers and three of them return
/// *immediately*, so the duration of a wait does not say which one happened —
/// a failed call and a wait that found a message already queued look identical
/// from a clock, and both look like a wait that simply did not work. This names
/// them.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Wake {
    /// The timeout elapsed with nothing to do: the wait slept, which is what
    /// [`ShellCaps::EVENT_WAIT`] promises is possible.
    TimedOut,
    /// A [`ShellWaker`](crate::ShellWaker) rang, from this thread or another.
    Woken,
    /// A message is available — either it arrived during the wait, or it was
    /// already queued when the wait started and `MWMO_INPUTAVAILABLE` reported
    /// it. Correct behaviour, and indistinguishable from a broken wait unless
    /// the queue is known to have been empty.
    Message,
    /// `WAIT_FAILED`. Returns at once and sleeps not at all.
    Failed {
        /// `GetLastError` immediately afterwards.
        error: u32,
    },
    /// Something outside the documented set. With a handle count of zero
    /// `WAIT_ABANDONED` and `WAIT_IO_COMPLETION` should both be unreachable,
    /// which is exactly why one arriving is worth naming rather than folding
    /// into another arm.
    Unexpected {
        /// The raw return value.
        outcome: u32,
    },
}

/// The windowed style and placement a borderless window will be restored to.
///
/// Captured on the way into [`DisplayMode::Borderless`] and applied verbatim on
/// the way out; see [`window`] for why a placement rather than a rectangle.
#[derive(Clone, Copy, Debug)]
pub(super) struct Saved {
    /// `GWL_STYLE` as it was.
    pub style: u32,
    /// `GWL_EXSTYLE` as it was.
    pub ex_style: u32,
    /// Show state, maximized position and restored rectangle.
    pub placement: WindowPlacement,
}

/// One window.
#[derive(Debug)]
pub(super) struct WinWindow {
    /// The `HWND`. Also what [`SurfaceTarget::Win32`] carries.
    hwnd: NonNull<c_void>,
    /// The same handle as the integer the raw queue matches on.
    pub key: isize,
    title: String,
    /// The **windowed** size that was asked for. Kept because a window created
    /// borderless has no saved placement to be restored to, and
    /// [`apply_mode`](Win32Shell::apply_mode) has to build one; see there.
    pub requested_size: LogicalSize,
    requested_mode: DisplayMode,
    pub requested_constraints: SizeConstraints,
    pub resizable: bool,
    /// The window's own DPI. Per window on this platform, unlike X11's global
    /// `Xft.dpi`.
    pub dpi: u32,
    pub scale_factor: f64,
    configuration: Option<WindowConfiguration>,
    /// The configuration the system has already told us about, waiting to be
    /// published on the next [`pump`](Shell::pump).
    ///
    /// The same shape as the X11 backend's, and for the same reason: the size
    /// is known synchronously, and the *only* thing making the caller wait is
    /// that [`WindowState::size`] is defined as `None` until a
    /// [`Resized`](ShellEvent::Resized) has been delivered. One pump, not a
    /// round trip, and not an invented delay either.
    pending: Option<WindowConfiguration>,
    /// The last size published, so the `WM_SIZE` that arrives during
    /// `CreateWindowExW` does not produce a second, identical
    /// [`Resized`](ShellEvent::Resized).
    last_size: Option<PhysicalSize>,
    /// The mode in effect. On Win32 this is whatever was last applied — there
    /// is no window manager to disagree — but the *monitor* in it is read back
    /// rather than assumed.
    pub effective_mode: DisplayMode,
    pub saved: Option<Saved>,
    /// The cursor last asked for: `None` for "never asked", `Some(None)` for
    /// hidden, `Some(Some(icon))` for a shape.
    cursor: Option<Option<CursorIcon>>,
    pub(super) pointer_mode: PointerMode,
    pub(super) focused: bool,
    visible: bool,
    close_pending: bool,
    /// Whether [`WindowDesc::accept_drops`] was set.
    ///
    /// The system already enforces this — no `WS_EX_ACCEPTFILES`, no
    /// `WM_DROPFILES` — so this is the second half of the gate rather than the
    /// gate, and [`dnd`](super::dnd) says why a second half is worth having.
    accept_drops: bool,
    /// What a panel was created with, or last given by
    /// [`set_panel`](Shell::set_panel), so it can be placed again; `None` for
    /// every other window. See [`panel`](super::panel).
    pub(super) panel: Option<Panel>,
    /// A panel's [`PanelDesc::namespace`](crate::PanelDesc::namespace), which
    /// places nothing here and is kept only so that `set_panel` refuses a
    /// change to it as every backend does. Empty for every other window.
    pub(super) panel_namespace: String,
    /// Whether [`WindowDesc::transparent`] was set: created with
    /// `WS_EX_NOREDIRECTIONBITMAP`, and so what
    /// [`SurfaceTarget::Win32`]'s `transparent` reports.
    pub(super) transparent: bool,
    /// What a popup was created with, or last repositioned to: its input,
    /// which decides how it is shown and whether losing the keyboard
    /// dismisses it, and its parent, which destroys it when it goes. `None`
    /// for every other window. See [`popup`](super::popup).
    pub(super) popup: Option<crate::PopupDesc>,
    /// What [`set_input_region`](Shell::set_input_region) last gave, kept to
    /// build the window region again against a new frame; `None` for the
    /// whole window.
    input_region: Option<Vec<PhysicalRect>>,
}

impl WinWindow {
    /// The raw handle, for the many calls that need it.
    pub(super) fn raw(&self) -> Handle {
        self.hwnd.as_ptr()
    }

    /// Whether this window asked for the cursor to be hidden.
    ///
    /// A window that has never asked is not asking for hidden — which is the
    /// difference between `None` and `Some(None)` and the reason the field is a
    /// nested `Option` rather than a flat one.
    pub(super) fn cursor_hidden(&self) -> bool {
        self.cursor == Some(None)
    }
}

/// A [`Shell`] backed by Win32.
///
/// Constructed through [`open`](crate::open) or
/// [`open_backend`](crate::open_backend); see the [module docs](super) for what
/// this backend implements, what waits for W2 and W3, and where Windows differs
/// from every other implementation of this seam.
pub struct Win32Shell {
    /// `HINSTANCE` of the module that registered the window class.
    instance: NonNull<c_void>,
    /// What the window procedure reaches through `GWLP_USERDATA`.
    ///
    /// An `Rc` because the procedure holds a raw pointer into it that must stay
    /// valid for as long as any window exists — which [`drop`](Self::drop)
    /// guarantees by destroying every window first.
    shared: Rc<Shared>,
    windows: Pool<WinWindow>,
    pub(super) monitors: Vec<MonitorInfo>,
    /// Device name → the [`MonitorId`] it was given, so an id is never reused
    /// within a session — the obligation [`monitor`](crate::monitor) states.
    pub(super) monitor_ids: Vec<(String, MonitorId)>,
    pub(super) next_monitor_id: u32,
    queue: VecDeque<ShellEvent>,
    time: TimeBase,
    /// The half-finished surrogate pair `WM_CHAR` may be in the middle of.
    ///
    /// Shell-wide rather than per window because the messages are a stream on
    /// one thread and only the focused window receives them: two windows cannot
    /// interleave halves of a pair.
    pub(super) text: Utf16,
    /// The previous absolute raw sample, for a device that reports positions.
    pub(super) raw_motion: RawMotion,
    /// Every touch contact currently down, with where it was last seen.
    pub(super) contacts: Vec<Contact>,
    /// Raw input handles and the stable [`DeviceId`](crcbl_core::input::DeviceId)s
    /// behind them.
    pub(super) devices: DeviceTable,
    /// Raw reports waiting for the key and button messages they produced.
    pub(super) attribution: Attribution,
    /// The high-resolution waitable timer a timed [`wait`](Self::wait) sleeps
    /// on, or `None` where the system refused one (before Windows 10 1803), in
    /// which case the wait falls back to its own millisecond timeout.
    wait_timer: Option<NonNull<c_void>>,
    /// The doorbell [`waker`](Shell::waker) hands out, made on the first ask
    /// and in the wait's handle set from then on — see `win32::wake`.
    wake: Option<std::sync::Arc<super::wake::WakeEvent>>,
    /// The windows showing a pre-edit, so an input method that ends a
    /// composition twice over reports it once.
    pub(super) composing: Composing,
    /// The keys an input method consumed, whose releases are not reported
    /// either.
    pub(super) consumed: Consumed,
    /// `ShowCursor`'s reference count, kept balanced.
    pub(super) visibility: Visibility,
    /// The next [`ClipboardRequestId`], which is unique for the session.
    ///
    /// Every read is answered before
    /// [`clipboard_request`](Shell::clipboard_request) returns, so nothing is
    /// keyed by this — but the seam promises ids are unique within a shell, and
    /// a consumer with two reads in flight tells them apart by nothing else.
    next_request: u32,
    /// [`Shell::request_frame`]s the next pump answers: nothing in a plain
    /// `HWND`'s messages paces presents, so
    /// [`ShellCaps::FRAME_CALLBACK`] is clear.
    frames: crate::frame::FrameRequests,
    caps: ShellCaps,
}

impl core::fmt::Debug for Win32Shell {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Win32Shell")
            .field("windows", &self.windows.len())
            .field("monitors", &self.monitors.len())
            .field("queued_events", &self.queue.len())
            .finish()
    }
}

/// Asks for per-monitor-v2 DPI awareness, once per process.
///
/// # Already set is not a failure
///
/// DPI awareness is a **process** property and something else may have set it
/// first — an application manifest, a host that embedded this engine, or an
/// earlier [`Win32Shell`] in the same process. All three answer
/// `ERROR_ACCESS_DENIED`, which means "it is already decided", not "it did not
/// work. Treating that as an error would make the second shell in a process
/// fail to open for a reason that is not a problem.
///
/// Anything else is logged and survived: a per-monitor-*v1* process still gets
/// correct scale factors, it just does not get `WM_DPICHANGED`'s suggested
/// rectangle, so a window dragged between monitors of different scales is left
/// for the user to resize. That is a degraded desktop, not a broken one, and
/// refusing to open a window over it would be worse.
fn set_dpi_awareness() {
    // SAFETY: the argument is a context pseudo-handle passed by value; the call
    // reads no memory of ours and cannot fail in a way that leaves state
    // half-changed.
    if unsafe { ffi::SetProcessDpiAwarenessContext(value::DPI_PER_MONITOR_AWARE_V2) } != 0 {
        crcbl_core::log::debug!("per-monitor-v2 DPI awareness is on");
        return;
    }
    // SAFETY: reads this thread's last error code, which the call above set.
    let error = unsafe { ffi::GetLastError() };
    if error == value::ERROR_ACCESS_DENIED {
        crcbl_core::log::debug!("the process DPI awareness was already set; leaving it alone");
        return;
    }
    crcbl_core::log::warn!(
        "SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2) failed with Win32 error {error}; \
         window scale factors will still be reported, but a window dragged between monitors of \
         different scales will not be resized for the new one"
    );
}

impl Win32Shell {
    /// Sets up the process's DPI awareness, registers the window class and
    /// enumerates the monitors.
    ///
    /// There is no connection to make — the window system is the kernel this
    /// process is already running on — so the only thing that can genuinely
    /// fail is the class registration, which is what a process with no usable
    /// window station cannot do. That makes it the honest failure point, and
    /// its `GetLastError` code the honest diagnostic.
    ///
    /// # Errors
    ///
    /// [`ShellError::Connect`] if the module handle or the window class could
    /// not be obtained.
    pub fn open() -> Result<Self, ShellError> {
        set_dpi_awareness();

        // SAFETY: a null module name asks for the handle of the executable that
        // started the process, which is documented never to fail for the
        // current module and is a pseudo-handle that needs no release.
        let instance = unsafe { ffi::GetModuleHandleW(ptr::null()) };
        let Some(instance) = NonNull::new(instance) else {
            return Err(ShellError::Connect {
                backend: ShellBackend::Win32,
                detail: "GetModuleHandleW(NULL) returned null".to_string(),
            });
        };
        window::register_class(instance.as_ptr())?;
        // Before any window exists, because the registration is per process and
        // the capability set is latched from whether it took.
        let raw_motion = input::register_raw_input();

        let mut shell = Self {
            instance,
            shared: Rc::new(Shared::default()),
            windows: Pool::new(),
            monitors: Vec::new(),
            monitor_ids: Vec::new(),
            next_monitor_id: 1,
            queue: VecDeque::new(),
            time: TimeBase::at(ffi::tick_nanos()),
            text: Utf16::default(),
            raw_motion: RawMotion::default(),
            contacts: Vec::new(),
            devices: DeviceTable::default(),
            attribution: Attribution::default(),
            // SAFETY: null attributes and a null name ask for an unnamed timer
            // with default security; the flags and access mask are constants
            // the call documents. A null return is a refusal, kept as `None`.
            wait_timer: NonNull::new(unsafe {
                ffi::CreateWaitableTimerExW(
                    ptr::null(),
                    ptr::null(),
                    value::CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
                    value::TIMER_SET_AND_WAIT,
                )
            }),
            wake: None,
            composing: Composing::default(),
            consumed: Consumed::default(),
            visibility: Visibility::default(),
            next_request: 1,
            frames: crate::frame::FrameRequests::default(),
            caps: Self::latch_caps(raw_motion),
        };
        shell.monitors = shell.enumerate_monitors();
        Ok(shell)
    }

    /// The capability set, which on this platform is a constant but one.
    ///
    /// See the [module docs](super) for why there is almost nothing to compute
    /// it from: every API this backend uses is present above its version floor
    /// or the process would not have started. The exception is
    /// [`RAW_POINTER_MOTION`](ShellCaps::RAW_POINTER_MOTION), which is latched
    /// from whether `RegisterRawInputDevices` actually took — the one call in
    /// this backend that can be refused for a reason outside the version floor.
    /// Each bit set here is exercised by a test in this module.
    const fn latch_caps(raw_motion: bool) -> ShellCaps {
        let caps = ShellCaps::MULTI_WINDOW
            .union(ShellCaps::EVENT_WAIT)
            .union(ShellCaps::WINDOW_POSITION)
            .union(ShellCaps::SERVER_DECORATIONS)
            .union(ShellCaps::FRACTIONAL_SCALE)
            .union(ShellCaps::ASPECT_HINT_HONORED)
            .union(ShellCaps::POINTER_LOCK)
            .union(ShellCaps::POINTER_CONFINE)
            .union(ShellCaps::POINTER_WARP)
            .union(ShellCaps::CLIPBOARD)
            .union(ShellCaps::DRAG_DROP)
            .union(ShellCaps::TEXT_IME)
            .union(ShellCaps::TEXT_COMPOSE)
            .union(ShellCaps::TOUCH)
            .union(ShellCaps::PANEL)
            .union(ShellCaps::PANEL_RESERVE)
            .union(ShellCaps::POPUP)
            .union(ShellCaps::POPUP_REPOSITION)
            .union(ShellCaps::TRANSPARENT)
            .union(ShellCaps::CURSOR_SHAPE)
            .union(ShellCaps::INPUT_REGION);
        #[cfg(feature = "accesskit")]
        let caps = caps.union(ShellCaps::ACCESSIBILITY);
        if raw_motion {
            caps.union(ShellCaps::RAW_POINTER_MOTION)
        } else {
            caps
        }
    }

    pub(super) fn instance(&self) -> Handle {
        self.instance.as_ptr()
    }

    pub(super) fn shared(&self) -> &Shared {
        &self.shared
    }

    /// The pointer the window procedure will find in `GWLP_USERDATA`.
    ///
    /// Borrowed from the `Rc`, not leaked out of it: the shell owns the
    /// allocation and outlives every window that holds this pointer.
    pub(super) fn shared_ptr(&self) -> *mut c_void {
        Rc::as_ptr(&self.shared) as *mut c_void
    }

    pub(super) fn window(&self, window: WindowId) -> Result<&WinWindow, ShellError> {
        self.windows
            .get(window.cast())
            .ok_or_else(|| ShellError::invalid_window(window))
    }

    pub(super) fn window_mut(&mut self, window: WindowId) -> Result<&mut WinWindow, ShellError> {
        self.windows
            .get_mut(window.cast())
            .ok_or_else(|| ShellError::invalid_window(window))
    }

    /// The id a device name was given, if this session has seen it.
    pub(super) fn monitor_id_of(&self, device: &str) -> Option<MonitorId> {
        self.monitor_ids
            .iter()
            .find(|(known, _)| known == device)
            .map(|(_, id)| *id)
    }

    /// Every live window, by the handle the seam names it with.
    pub(super) fn windows_iter(&self) -> impl Iterator<Item = (WindowId, &WinWindow)> {
        self.windows
            .iter()
            .map(|(handle, window)| (handle.cast(), window))
    }

    /// A message's `GetMessageTime` on the engine's clock.
    ///
    /// The clock is read *now* rather than being cached, because
    /// [`TimeBase::widen`] resolves the message's 32 bits against a full-width
    /// reading and a stale one would resolve the wrap the wrong way for exactly
    /// as long as it was stale.
    pub(super) fn event_time(&self, millis: u32) -> EventTime {
        self.time.event_time_at(ffi::tick_nanos(), millis)
    }

    /// Queues an event for the current [`pump`](Shell::pump) to deliver.
    pub(super) fn queue_event(&mut self, event: ShellEvent) {
        self.queue.push_back(event);
    }

    /// Discards any clipboard answer queued for a window that has just died.
    ///
    /// [Obligation 4](Shell) is about *accepted* requests and
    /// [obligation 1](Shell) forbids an event naming a stale handle; where they
    /// meet, the stale handle wins, exactly as the X11 backend decides it. A
    /// consumer can see a handle it destroyed; it cannot see an answer it will
    /// never be given for a window it no longer has.
    ///
    /// The window here is narrow — this backend answers a read before
    /// [`clipboard_request`](Shell::clipboard_request) returns, so the answer is
    /// only ever queued for the length of one frame — but a consumer that reads
    /// the clipboard and then closes the window in the same frame is an ordinary
    /// "paste and dismiss the dialog", not a contrivance.
    fn drop_clipboard_answers(&mut self, window: WindowId) {
        self.queue.retain(|event| {
            !matches!(event, ShellEvent::ClipboardData { window: asked, .. } if *asked == window)
        });
    }

    /// The clipboard's content in `mime`, read now.
    ///
    /// The whole of the read, and it is synchronous because Win32's clipboard
    /// is content rather than an owner to negotiate with — see [`clipboard`].
    /// The three outcomes are the three [`ClipboardContent`] means:
    ///
    /// * **The format is not there** — [`Empty`](ClipboardContent::Empty),
    ///   which the seam defines as merging "holds nothing" with "holds nothing
    ///   in that format". Both are true here and neither is a failure.
    /// * **The clipboard could not be opened or locked** —
    ///   [`Unavailable`](ClipboardContent::Unavailable). There may well be
    ///   something on it; we could not get at it.
    /// * **Anything else** — [`Bytes`](ClipboardContent::Bytes), possibly
    ///   empty, which is a successful transfer of nothing.
    fn read_clipboard(&self, hwnd: Handle, mime: MimeType) -> ClipboardContent {
        let Some(format) = clipboard::format_id(mime) else {
            // The window station would not name the format, so nothing can have
            // published anything under it. `format_id` has already logged.
            return ClipboardContent::Unavailable;
        };
        let board = match Clipboard::open(hwnd) {
            Ok(board) => board,
            Err(refused) => {
                crcbl_core::log::warn!(
                    "the clipboard was not readable within {:?}: {refused:?}",
                    clipboard::OPEN_BUDGET
                );
                return ClipboardContent::Unavailable;
            }
        };
        if let Opened::After { attempts } = board.opened() {
            crcbl_core::log::debug!(
                "the clipboard was held by another process for {attempts} attempts"
            );
        }
        let Some(bytes) = board.get(format) else {
            // A file list with no registered `text/uri-list` beside it is what
            // Explorer's "copy" leaves, and it is answered as one. The
            // registered format is asked first; see `clipboard`'s module docs.
            if mime == MimeType::UriList
                && let Some(list) = board.file_uri_list()
            {
                return ClipboardContent::Bytes(list);
            }
            // `GetClipboardData` answering null is "no such format on the
            // clipboard", which is exactly `Empty`. A lock failure logs and
            // lands here too — rare enough that distinguishing it would mean
            // threading an outcome through for a case nobody has seen.
            return ClipboardContent::Empty;
        };
        match clipboard::encoding_for(mime) {
            clipboard::Encoding::UnicodeText => {
                ClipboardContent::Bytes(clipboard::utf8_from_utf16_bytes(&bytes))
            }
            clipboard::Encoding::Registered(_) => {
                ClipboardContent::Bytes(clipboard::trim_trailing_nuls(&bytes).to_vec())
            }
        }
    }

    /// The [`WindowId`] owning an `HWND`, for routing a raw event.
    /// The system or the shell asked for a window to go: `WM_CLOSE`, or a
    /// panel's monitor being unplugged.
    ///
    /// One request until it is answered, as on Wayland.
    pub(super) fn close_requested(&mut self, window: WindowId) {
        let Ok(state) = self.window_mut(window) else {
            return;
        };
        if state.close_pending {
            return;
        }
        state.close_pending = true;
        self.queue.push_back(ShellEvent::CloseRequested { window });
    }

    /// How a panel or a popup is shown: whether it takes the keyboard when it
    /// appears. `None` for every other window, which `set_visible` shows the
    /// ordinary way. A grabbing popup takes the keyboard as an exclusive
    /// panel does; every other popup is shown as a panel that takes none.
    fn furniture_keyboard(
        panel: Option<&Panel>,
        popup: Option<crate::PopupInput>,
    ) -> Option<crate::PanelKeyboard> {
        match (panel, popup) {
            (Some(panel), _) => Some(panel.keyboard),
            (None, Some(crate::PopupInput::Grab)) => Some(crate::PanelKeyboard::Exclusive),
            (None, Some(_)) => Some(crate::PanelKeyboard::None),
            (None, None) => None,
        }
    }

    /// Shows a panel through [`show_panel`](Self::show_panel), records
    /// whether it is now showing, and queues
    /// [`FocusRefused`](ShellEvent::FocusRefused) if the foreground it asked
    /// for was refused.
    fn show_panel_window(
        &mut self,
        window: WindowId,
        keyboard: crate::PanelKeyboard,
    ) -> Result<(), ShellError> {
        let hwnd = self.window(window)?.raw();
        let granted = Self::show_panel(hwnd, keyboard);
        self.window_mut(window)?.visible = Self::is_showing(hwnd);
        if !granted {
            self.queue.push_back(ShellEvent::FocusRefused { window });
        }
        Ok(())
    }

    fn window_by_key(&self, key: isize) -> Option<WindowId> {
        self.windows
            .iter()
            .find(|(_, window)| window.key == key)
            .map(|(handle, _)| handle.cast())
    }

    fn unsupported(what: &'static str) -> ShellError {
        ShellError::Unsupported {
            backend: ShellBackend::Win32,
            what,
        }
    }

    /// Whether a window is on screen and not iconified.
    ///
    /// Asked rather than assumed, because `ShowWindow` does not always do what
    /// it was told: showing an already-visible window is a no-op that still
    /// returns, and a minimized window is `IsWindowVisible` **and** `IsIconic`.
    /// [`WindowState::visible`] is defined as "mapped and not minimized", which
    /// is exactly this expression.
    fn is_showing(hwnd: Handle) -> bool {
        // SAFETY: `hwnd` is a live window of this shell; both calls only read.
        unsafe { ffi::IsWindowVisible(hwnd) != 0 && ffi::IsIconic(hwnd) == 0 }
    }

    /// Runs every message queued for this thread through the window procedure.
    ///
    /// `PeekMessageW` with a null window takes messages for **every** window on
    /// this thread, which is where all of this shell's windows deliver — the
    /// thread affinity the module docs describe, used rather than worked
    /// around. It returns zero when the queue is empty, so this terminates:
    /// `WM_PAINT` is the one message that would repeat forever if it were left
    /// unhandled, and `DefWindowProc` validates it.
    ///
    /// # `TranslateMessage` is not optional, and leaving it out was silent
    ///
    /// A `WM_KEYDOWN` names a key; the **character** it produces is a separate
    /// message that only exists because `TranslateMessage` ran, and it is where
    /// dead keys, AltGr and an input method's commit all arrive. This loop did
    /// not call it, so `WM_CHAR` was never generated for a real keystroke: the
    /// `Char` branch of the window procedure, the surrogate reassembly in
    /// [`keys`](super::keys) and every
    /// [`TextCommit`](ShellEvent::TextCommit) were unreachable from a keyboard,
    /// and typing into a Crucible window on Windows produced no text at all.
    ///
    /// **Nothing in the in-crate suite could see it.** Those tests send
    /// `WM_CHAR` with `SendMessageW` — the real procedure, the real
    /// reassembly — and a message sent directly does not pass through the queue
    /// this call is part of. It took a keystroke injected from another process
    /// to reach the gap, which is what `tests/win32_e2e.rs` is for.
    ///
    /// It is called on every message rather than on key messages only, which is
    /// what `winuser.h` documents: it inspects the message itself and does
    /// nothing for the ones it does not translate.
    /// [`wait_events`](Shell::wait_events), with the reason it came back.
    ///
    /// # Why the return value exists at all
    ///
    /// `Shell::wait_events` returns `()`, so the first version of this dropped
    /// `MsgWaitForMultipleObjectsEx`'s answer on the floor. Three of its four
    /// outcomes are *immediate* — a message was already queued, one arrived, or
    /// the call failed outright — and discarding the value makes them
    /// indistinguishable from each other and from a wait that slept properly.
    /// The first CI run on Windows produced exactly that: three 50 ms waits in
    /// 47 ms, with nothing to say which of the three had happened.
    ///
    /// [`Wake`] is what a test asserts on, because
    /// [`EVENT_WAIT`](ShellCaps::EVENT_WAIT) is a claim about a *mechanism* —
    /// "this sleeps" — and a wall clock cannot tell a wait that slept from a
    /// wait that failed on a loaded runner. Nothing in the backend branches on
    /// it; the trait method still ignores it, because a caller has nothing to do
    /// differently either way.
    ///
    /// # Decision: drain first, and no `MWMO_INPUTAVAILABLE`
    ///
    /// **That diagnosis then named the cause, and this is the fix it pointed
    /// at.** The second CI run reported `Wake::Message` after 14 ms with the
    /// queue holding `0x0040` in both halves of `GetQueueStatus` — which is
    /// `QS_SENDMESSAGE`, a message *sent* to a window of this thread rather than
    /// posted to its queue.
    ///
    /// `PeekMessageW` **dispatches** a sent message but does not retrieve it, so
    /// the `QS_SENDMESSAGE` bit is still set when the wait starts. Asking for
    /// `MWMO_INPUTAVAILABLE` is asking to be woken by exactly that bit, so the
    /// wait returned immediately, every time, forever — a shell that never
    /// sleeps and says nothing about it.
    ///
    /// So this drains the queue itself and then sleeps with **no flags**.
    /// Draining is what clears the state, because `PeekMessageW` returning
    /// `FALSE` is what marks the queue as seen; sleeping with no flags waits for
    /// something genuinely new, which `QS_ALLINPUT` still covers — sent messages
    /// included. `MWMO_INPUTAVAILABLE` exists for a caller that does *not* drain
    /// before sleeping, and draining ourselves is strictly better: it removes
    /// the reason for the flag and the spurious wake in one move.
    ///
    /// # What the third run said, and what the instrumentation answered
    ///
    /// **That change did what it was expected to do, and was not the whole
    /// answer.** The third CI run reported `0x80008` — `QS_POSTMESSAGE` in both
    /// halves of `GetQueueStatus` — on a queue drained microseconds earlier. So
    /// the failure was instrumented rather than guessed at a third time:
    /// `peek_pending` was added to print the `MSG` itself
    /// beside the `QS_` word, on the reasoning that a message id is a number
    /// that can be looked up.
    ///
    /// The fourth run printed the observation that ended it:
    ///
    /// ```text
    /// it took 1.3311ms, the queue holds 0x400040,
    /// and the message still in it is None
    /// ```
    ///
    /// **A `QS_` bit is set and `PeekMessageW` has nothing to return.** That is
    /// documented behaviour rather than a defect in this code: `0x40` is
    /// `QS_SENDMESSAGE`, a message *sent* to a window of this thread, and
    /// `PeekMessageW` dispatches such a message without ever retrieving it — so
    /// it cannot report it and cannot clear its bit, while a wait that includes
    /// `QS_SENDMESSAGE` in its mask wakes on that bit at once. MSDN's own caveat
    /// covers it: a `QS_` flag being set does not guarantee that a subsequent
    /// `PeekMessage` will return a message. The third run's `0x80008` was the
    /// same phenomenon showing a different bit.
    ///
    /// # The fix: `QS_ALLEVENTS`, which is `QS_ALLINPUT` minus that bit
    ///
    /// The wait now asks for [`QS_ALL_EVENTS`](value::QS_ALL_EVENTS), which is
    /// exactly `QS_ALLINPUT` without `QS_SENDMESSAGE` — the one bit that cannot
    /// be cleared and therefore must not be waited on. Sent messages are still
    /// dispatched, by the `PeekMessageW` in
    /// [`drain_messages`](Self::drain_messages) on the next pump; what changes
    /// is that one arriving *during* a sleep waits out the timeout instead of
    /// cutting it short. [`QS_ALL_EVENTS`](value::QS_ALL_EVENTS) states that
    /// trade in full, and `queue_status` keeps asking for `QS_ALLINPUT`,
    /// because a diagnosis wants every bit including the one the wait ignores.
    ///
    /// **Nobody has run this on Windows yet.** The three previous rounds each
    /// looked like a fix too; what is different is that this one is a
    /// description of an observation rather than of a hypothesis. The next run
    /// says whether it is enough.
    fn wait(&mut self, timeout: Option<Duration>) -> Wake {
        // Before the wait, not after: an undrained queue is what
        // `MWMO_INPUTAVAILABLE` was there for, and this is the same guarantee
        // without the bit that never clears. Whatever the window procedure
        // records here is delivered by the next `pump`, exactly as if the
        // messages had been drained there.
        self.drain_messages();
        // **A timed wait sleeps on the high-resolution timer, not on its own
        // timeout.** `MsgWaitForMultipleObjectsEx`'s millisecond timeout expires
        // on the system clock tick, 15.6 ms by default, and nothing in this
        // process raises the resolution — so a 4 ms wait slept a whole tick
        // whenever no message arrived, capping a windowed game near 64 frames a
        // second (found by EW: 15.6 ms a frame windowed, 2.2 ms offscreen). The
        // timer is armed to the timeout in 100 ns units and waited on with no
        // timeout of its own; a zero timeout, or a system that refused the
        // timer, keeps the plain millisecond wait.
        let armed = match (timeout, self.wait_timer) {
            (Some(timeout), Some(timer)) if !timeout.is_zero() => {
                // Negative is relative. Saturating at `i64::MIN` is a wait of
                // some 29 000 years, which is "forever" for any caller.
                let due =
                    i64::try_from(timeout.as_nanos() / 100).map_or(i64::MIN, |ticks| -ticks.max(1));
                // SAFETY: `timer` is the live handle `open` created; `due` is a
                // live `i64` the call reads; no completion routine.
                let set = unsafe {
                    ffi::SetWaitableTimer(
                        timer.as_ptr(),
                        &raw const due,
                        0,
                        ptr::null(),
                        ptr::null(),
                        0,
                    )
                };
                (set != 0).then_some(timer.as_ptr())
            }
            _ => None,
        };
        let milliseconds = match (armed, timeout) {
            (Some(_), _) | (None, None) => value::INFINITE,
            // `INFINITE` is `u32::MAX`, so a timeout that saturates to it would
            // silently become "forever" — 49 days of sleep is close enough to
            // the caller's intent, but never waking is not.
            (None, Some(timeout)) => u32::try_from(timeout.as_millis())
                .unwrap_or(value::INFINITE - 1)
                .min(value::INFINITE - 1),
        };
        // The handle set: the armed timer, then the wake event, either of
        // which may be absent. A message is the index one past the last.
        let mut handle_array: [ffi::Handle; 2] = [ptr::null_mut(); 2];
        let mut handles = 0;
        let mut push = |handle: ffi::Handle| {
            handle_array[handles as usize] = handle;
            handles += 1;
            handles - 1
        };
        let timer_index = armed.map(&mut push);
        let wake_index = self.wake.as_ref().map(|wake| push(wake.handle()));
        // SAFETY: `handle_array` holds `handles` live handles — the timer this
        // shell owns and the wake event its `Arc` keeps alive — for the length
        // of the call. Zero flags is the documented "wait for a new message",
        // which is what the drain above makes correct.
        //
        // `QS_ALLEVENTS`, not `QS_ALLINPUT`: the difference is `QS_SENDMESSAGE`,
        // which `PeekMessageW` can neither return nor clear, so waiting on it is
        // waiting on a bit that is already set. See the doc comment.
        let outcome = unsafe {
            ffi::MsgWaitForMultipleObjectsEx(
                handles,
                handle_array.as_ptr(),
                milliseconds,
                value::QS_ALL_EVENTS,
                0,
            )
        };
        // `WAIT_OBJECT_0 + i` is handle `i` signalled, and `WAIT_OBJECT_0 +
        // handles` a message.
        let signalled = outcome.checked_sub(value::WAIT_OBJECT_0);
        match outcome {
            value::WAIT_TIMEOUT => Wake::TimedOut,
            _ if signalled.is_some() && signalled == timer_index => Wake::TimedOut,
            _ if signalled.is_some() && signalled == wake_index => Wake::Woken,
            message if message == value::WAIT_OBJECT_0 + handles => Wake::Message,
            // SAFETY: reading the calling thread's last error, immediately
            // after the call that set it.
            value::WAIT_FAILED => Wake::Failed {
                error: unsafe { ffi::GetLastError() },
            },
            other => Wake::Unexpected { outcome: other },
        }
    }

    /// Which kinds of message this thread's queue holds, as `QS_*` bits.
    ///
    /// Only ever called to *explain* a [`Wake::Message`] that should have been
    /// a [`Wake::TimedOut`] — so it is not on the wait path and costs a wait
    /// nothing. Kept beside [`wait`](Self::wait) because that is the only
    /// question it answers, and test-only because only a test knows what the
    /// queue was supposed to hold.
    #[cfg(test)]
    fn queue_status() -> u32 {
        // SAFETY: reading the calling thread's own queue state. The call takes
        // no pointers and has no failure mode.
        unsafe { ffi::GetQueueStatus(value::QS_ALL_INPUT) }
    }

    /// The message at the head of this thread's queue, without taking it.
    ///
    /// The other half of the same question [`queue_status`](Self::queue_status)
    /// answers, and the half that ends an argument: a `QS_` word says a *posted
    /// message* is there, and this says **which** one — an id to look up rather
    /// than a bit to theorise about. `PM_NOREMOVE`, so asking changes nothing
    /// the next wait will see.
    ///
    /// Test-only for the same reason: only a test knows what the queue was
    /// supposed to hold, and nothing in the backend branches on the answer.
    #[cfg(test)]
    fn peek_pending() -> Option<Msg> {
        let mut message = Msg::default();
        // SAFETY: `message` is a live, initialised `MSG` the call writes into;
        // a null window and a zero filter range ask for every window and thread
        // message, and `PM_NOREMOVE` leaves the queue exactly as it was found.
        let pending = unsafe {
            ffi::PeekMessageW(&raw mut message, ptr::null_mut(), 0, 0, value::PM_NOREMOVE)
        };
        (pending != 0).then_some(message)
    }

    fn drain_messages(&mut self) {
        let mut message = Msg::default();
        loop {
            // SAFETY: `message` is a live, initialised `MSG` the call writes
            // into; a null window and a zero filter range ask for everything.
            let pending = unsafe {
                ffi::PeekMessageW(&raw mut message, ptr::null_mut(), 0, 0, value::PM_REMOVE)
            };
            if pending == 0 {
                break;
            }
            // SAFETY: `message` is the message this thread just took off its own
            // queue. `TranslateMessage` reads it and posts any `WM_CHAR` it
            // produces to the front of the same queue, where the next turn of
            // this loop picks it up; `DispatchMessageW` then calls the window
            // procedure re-entrantly, which is why nothing here holds a borrow
            // of `shared`.
            unsafe {
                ffi::TranslateMessage(&raw const message);
                ffi::DispatchMessageW(&raw const message);
            }
        }
    }

    /// Turns what the window procedure recorded into shell state and events.
    ///
    /// Takes the whole queue first, so no borrow is held while this calls back
    /// into the system — several of these branches do, and the procedure they
    /// re-enter would panic on a `RefCell` that was still borrowed.
    fn translate(&mut self) {
        // The keyboard can move with no message to this thread saying so
        // (the foreground taken by another process while this one is busy
        // arrives as `WM_ACTIVATEAPP`, but nothing guarantees every case
        // does), so it is checked once per pump as well.
        let _ = self.shared.sync_focus(proc::foreground());
        for event in self.shared.take_events() {
            let window = event.hwnd().and_then(|key| self.window_by_key(key));
            match event {
                RawEvent::Resized { hwnd, size } => {
                    let Some(window) = window else { continue };
                    let showing = Self::is_showing(hwnd as Handle);
                    let Ok(state) = self.window_mut(window) else {
                        continue;
                    };
                    state.visible = showing;
                    // The `WM_SIZE` that `CreateWindowExW` and `SetWindowPos`
                    // send is a restatement of a size this shell already
                    // published; only a change is an event.
                    if state.last_size == Some(size) {
                        continue;
                    }
                    state.last_size = Some(size);
                    // The window region is in window coordinates, so a new size
                    // moves the frame it keeps. After the restatement check, so
                    // nothing the rebuild itself sends can start it again; a
                    // mode change, which keeps the size, rebuilds in `set_mode`.
                    if let Some(region) = state.input_region.as_deref()
                        && let Err(error) = Self::apply_input_region(hwnd as Handle, Some(region))
                    {
                        crcbl_core::log::warn!(
                            "the input region was not rebuilt for the new frame: {error}"
                        );
                    }
                    state.pending = Some(WindowConfiguration {
                        size,
                        scale_factor: state.scale_factor,
                        mode: state.effective_mode,
                    });
                }
                RawEvent::Minimized { .. } => {
                    let Some(window) = window else { continue };
                    if let Ok(state) = self.window_mut(window) {
                        // No size is published: a minimized window's client
                        // area is 0×0, and that is not an extent.
                        state.visible = false;
                    }
                }
                RawEvent::DpiChanged { hwnd, dpi } => {
                    let Some(window) = window else { continue };
                    let hwnd = hwnd as Handle;
                    let size = Self::client_size_of(hwnd);
                    let scale_factor = geometry::scale_from_dpi(dpi);
                    let Ok(state) = self.window_mut(window) else {
                        continue;
                    };
                    state.dpi = dpi;
                    state.scale_factor = scale_factor;
                    // Set unconditionally, and after the `Resized` that the
                    // procedure's own `SetWindowPos` produced: the two share
                    // one `pending` slot, so the configuration published names
                    // the new size *and* the new scale, which is the pairing
                    // `WindowConfiguration` exists to keep together.
                    state.last_size = Some(size);
                    state.pending = Some(WindowConfiguration {
                        size,
                        scale_factor,
                        mode: state.effective_mode,
                    });
                    // The frame scales with the DPI, so the constraints the
                    // window procedure enforces are now computed from a stale
                    // one.
                    if let Ok(state) = self.window(window) {
                        self.refresh_limits(state);
                    }
                    // A panel's size and margins are logical, so its place on
                    // its monitor moved with the scale.
                    self.place_panel(window);
                }
                RawEvent::Focus { focused, .. } => {
                    let Some(window) = window else { continue };
                    if let Ok(state) = self.window_mut(window) {
                        state.focused = focused;
                    }
                    if !focused {
                        self.consumed.clear();
                    }
                    // The window procedure has already released or re-applied
                    // the *clip*, because one frame of a hostage desktop is one
                    // frame too many. Both are refreshed again here, and neither
                    // is redundant: `refresh_clip` is what establishes a
                    // confinement that was asked for **before** the window had
                    // the keyboard, and the visibility is what makes alt-tabbing
                    // out of mouselook give the pointer back. See
                    // [`input`](super::input).
                    self.refresh_clip();
                    self.refresh_cursor_visibility();
                    self.queue.push_back(ShellEvent::Focus { window, focused });
                    // A grabbing popup is dismissed by losing the keyboard:
                    // a click outside it activated what was clicked.
                    let grabbing = self.window(window).is_ok_and(|state| {
                        state
                            .popup
                            .is_some_and(|popup| popup.input == crate::PopupInput::Grab)
                    });
                    if !focused && grabbing {
                        self.close_requested(window);
                    }
                }
                RawEvent::CloseRequested { .. } => {
                    let Some(window) = window else { continue };
                    self.close_requested(window);
                }
                RawEvent::Destroyed { .. } => {
                    // Only reachable when the window went away without this
                    // shell asking — `destroy_window` removes the pool entry
                    // *before* calling `DestroyWindow`, so its own
                    // `WM_DESTROY` finds nothing here and does not report a
                    // second `WindowDestroyed`.
                    let Some(window) = window else { continue };
                    if let Some(removed) = self.windows.remove(window.cast()) {
                        self.shared.forget(removed.key);
                        self.drop_clipboard_answers(window);
                        self.forget_input(window);
                        self.queue.push_back(ShellEvent::WindowDestroyed { window });
                    }
                }

                RawEvent::FilesDropped { hwnd, millis } => {
                    // **Taken before anything can `continue`.** The payload and
                    // its marker are one drop; leaving the payload behind when
                    // the marker is discarded would hand it to the next marker
                    // for that window, which is a scene importing the assets
                    // dropped on the previous one.
                    let Some(dropped) = self.shared.take_drop(hwnd) else {
                        continue;
                    };
                    let Some(window) = window else { continue };
                    if !self.window(window).is_ok_and(|state| state.accept_drops) {
                        // The system does not send `WM_DROPFILES` to a window
                        // without `WS_EX_ACCEPTFILES`, so this is the case
                        // where something outside this backend set the bit. The
                        // descriptor said no; that is the answer.
                        crcbl_core::log::debug!(
                            "discarding a drop of {} file(s) on a window created without \
                             accept_drops",
                            dropped.paths.len()
                        );
                        continue;
                    }
                    let time = self.event_time(millis);
                    let position = Some(PhysicalPoint::new(
                        f64::from(dropped.x),
                        f64::from(dropped.y),
                    ));
                    // One event per file, which is what `DroppedFile`
                    // documents: a multi-file drop is several.
                    for path in dropped.paths {
                        self.queue.push_back(ShellEvent::DroppedFile {
                            window,
                            time,
                            path,
                            position,
                        });
                    }
                }
                RawEvent::Preedit { hwnd, millis } => {
                    // Taken before anything can `continue`, for the reason the
                    // drop payload is.
                    let Some(preedit) = self.shared.take_preedit(hwnd) else {
                        continue;
                    };
                    let Some(window) = window else { continue };
                    let (text, cursor) = crate::preedit::preedit(&preedit.units, preedit.cursor);
                    if !self.composing.report(window, &text) {
                        continue;
                    }
                    let time = self.event_time(millis);
                    self.queue.push_back(ShellEvent::TextPreedit {
                        window,
                        time,
                        text,
                        cursor,
                    });
                }
                RawEvent::MonitorsChanged => {
                    let monitors = self.enumerate_monitors();
                    let removed =
                        crate::monitor::queue_removed(&self.monitors, &monitors, &mut self.queue);
                    self.monitors = monitors;
                    self.place_panels(&removed);
                    self.queue.push_back(ShellEvent::MonitorsChanged);
                }
                RawEvent::DeviceRemoved { device } => self.devices.remove(device),
                #[cfg(feature = "accesskit")]
                RawEvent::AccessibilityActivated { .. } => {
                    let Some(window) = window else { continue };
                    self.queue
                        .push_back(ShellEvent::AccessibilityActivated { window });
                }
                RawEvent::AppBar {
                    notification,
                    param,
                    ..
                } => match (window, super::panel::appbar_notice(notification, param)) {
                    (Some(window), Some(super::panel::AppBarNotice::Moved)) => {
                        self.place_panel(window);
                    }
                    (Some(window), Some(super::panel::AppBarNotice::FullScreenApp(active))) => {
                        self.queue
                            .push_back(ShellEvent::FullScreenApp { window, active });
                    }
                    _ => {}
                },

                // The input half, which needs the layout, the modifier snapshot
                // and this shell's own surrogate and raw-motion state — see
                // [`input`](super::input).
                input_event @ (RawEvent::Key { .. }
                | RawEvent::Char { .. }
                | RawEvent::PointerMotion { .. }
                | RawEvent::PointerFocus { .. }
                | RawEvent::Button { .. }
                | RawEvent::Wheel { .. }
                | RawEvent::Touch { .. }
                | RawEvent::RawMotion { .. }
                | RawEvent::RawKey { .. }) => self.translate_input(input_event, window),
            }
        }
    }

    /// Turns the requests readers made into
    /// [`AccessibilityAction`](ShellEvent::AccessibilityAction)s.
    ///
    /// Read off the adapters' own queue rather than the window procedure's,
    /// because a request may be made on another thread; see
    /// [`access`](super::access). One for a window that has gone since is
    /// dropped, as every event naming a stale handle is.
    #[cfg(feature = "accesskit")]
    fn translate_actions(&mut self) {
        for (hwnd, request) in self.shared.access().take_actions() {
            let Some(window) = self.window_by_key(hwnd) else {
                continue;
            };
            self.queue
                .push_back(ShellEvent::AccessibilityAction { window, request });
        }
    }

    /// Publishes configurations the system has already told us about.
    ///
    /// The one place Win32's synchronous geometry meets P0.4's asynchronous
    /// contract, and a copy of the X11 backend's for the same reason. A scale
    /// change is emitted **before** the resize it came with, because a consumer
    /// that recreates a swapchain on `Resized` must already know the scale it
    /// is recreating at.
    fn publish_configurations(&mut self) {
        let ready: Vec<(WindowId, WindowConfiguration)> = self
            .windows
            .iter_mut()
            .filter_map(|(handle, window)| {
                window.pending.take().map(|config| (handle.cast(), config))
            })
            .collect();
        for (window, config) in ready {
            let previous = self
                .windows
                .get(window.cast())
                .and_then(|state| state.configuration);
            if let Some(state) = self.windows.get_mut(window.cast()) {
                state.configuration = Some(config);
            }
            if previous.is_some_and(|previous| {
                (previous.scale_factor - config.scale_factor).abs() > f64::EPSILON
            }) {
                self.queue.push_back(ShellEvent::ScaleFactorChanged {
                    window,
                    scale_factor: config.scale_factor,
                    size: config.size,
                });
            }
            self.queue.push_back(ShellEvent::Resized {
                window,
                size: config.size,
                scale_factor: config.scale_factor,
            });
        }
    }
}

impl Shell for Win32Shell {
    fn backend(&self) -> ShellBackend {
        ShellBackend::Win32
    }

    /// What this backend can do, fixed for the shell's lifetime.
    ///
    /// Each bit is a claim about code that exists in this backend:
    ///
    /// * [`MULTI_WINDOW`](ShellCaps::MULTI_WINDOW) — nothing about a window
    ///   class or a message queue is single-window.
    /// * [`EVENT_WAIT`](ShellCaps::EVENT_WAIT) —
    ///   [`wait_events`](Shell::wait_events) is
    ///   `MsgWaitForMultipleObjectsEx`, which genuinely blocks.
    /// * [`WINDOW_POSITION`](ShellCaps::WINDOW_POSITION) — one signed virtual
    ///   screen, `SetWindowPos` moves a window in it, and monitor bounds tile.
    ///   What that buys today is borderless on a *named* monitor, which the
    ///   Wayland backend can only hint at.
    /// * [`SERVER_DECORATIONS`](ShellCaps::SERVER_DECORATIONS) — a windowed
    ///   window is `WS_OVERLAPPEDWINDOW`, so the system draws the caption and
    ///   the borders and the UI layer must not.
    /// * [`FRACTIONAL_SCALE`](ShellCaps::FRACTIONAL_SCALE) — 125% is 120 DPI is
    ///   1.25, which is what the display settings offer and what
    ///   `GetDpiForWindow` reports.
    /// * [`ASPECT_HINT_HONORED`](ShellCaps::ASPECT_HINT_HONORED) — `WM_SIZING`,
    ///   named by the windowing rules as this platform's form of it,
    ///   and implemented in [`geometry`].
    /// * [`POINTER_CONFINE`](ShellCaps::POINTER_CONFINE) and
    ///   [`POINTER_LOCK`](ShellCaps::POINTER_LOCK) — `ClipCursor` over the
    ///   client rectangle, plus a hidden cursor and a recentre for the second.
    ///   Re-established when the window moves, resizes or regains focus, and
    ///   released the instant it loses it.
    /// * [`POINTER_WARP`](ShellCaps::POINTER_WARP) — `SetCursorPos`, which
    ///   Windows has and Wayland refuses to.
    /// * [`RAW_POINTER_MOTION`](ShellCaps::RAW_POINTER_MOTION) — `WM_INPUT`, and
    ///   the **only bit here that is genuinely latched**: it is set if and only
    ///   if `RegisterRawInputDevices` succeeded at [`open`](Self::open). An
    ///   absolute-reporting device (remote desktop, a tablet) is differenced
    ///   rather than misread, so the bit means the same thing on `mstsc` as it
    ///   does on a desk.
    /// * [`CLIPBOARD`](ShellCaps::CLIPBOARD) — `OpenClipboard` in both
    ///   directions, with `CF_UNICODETEXT` for text and a
    ///   `RegisterClipboardFormatW` format named after the mime for everything
    ///   else, so an engine-to-engine copy is lossless while Notepad still gets
    ///   the text. A constant rather than a latch: the calls are `user32`'s and
    ///   are present or the process did not start. See
    ///   [`clipboard`].
    /// * [`DRAG_DROP`](ShellCaps::DRAG_DROP) — `DragAcceptFiles` plus
    ///   `WM_DROPFILES`, honouring
    ///   [`WindowDesc::accept_drops`](crate::WindowDesc::accept_drops). The bit
    ///   is what [`DroppedFile`](ShellEvent::DroppedFile) documents it to be —
    ///   *files, in* — and no more: there is no drop cursor and no hover
    ///   feedback while a drag is in the air, because that is `IDropTarget` and
    ///   `IDropTarget` is COM. [`dnd`](super::dnd) gives the argument, and it is
    ///   a gap in *feedback* rather than in the capability the seam names.
    /// * [`TEXT_COMPOSE`](ShellCaps::TEXT_COMPOSE) — the system composes:
    ///   `TranslateMessage` turns a dead key and the next key into one
    ///   `WM_CHAR`, which arrives as [`TextCommit`](ShellEvent::TextCommit),
    ///   surrogate pairs joined. `win32_e2e`'s dead-key test proves the
    ///   composition on a real desktop.
    /// * [`TEXT_IME`](ShellCaps::TEXT_IME) — `imm32`. The `WM_IME_*` family
    ///   falls through to `DefWindowProc`, which delivers an IME's committed
    ///   string as `WM_CHAR` and so as a commit. The composition in progress is
    ///   read from `WM_IME_COMPOSITION` and reported as
    ///   [`TextPreedit`](ShellEvent::TextPreedit), and
    ///   [`set_text_input_area`](Shell::set_text_input_area) places the input
    ///   method's composition window and candidate list at the caret. **The
    ///   pre-edit and placement are unverified against a real input method**:
    ///   their desktop test, behind the `win32-ime-e2e` feature, has never
    ///   passed, and only the decoding and bookkeeping around the `imm32` calls
    ///   are unit-tested. The bit is set because the path is written, not
    ///   because it is proven.
    /// * [`TOUCH`](ShellCaps::TOUCH) — `WM_POINTERDOWN`/`UPDATE`/`UP` for
    ///   `PT_TOUCH` pointers, with a capture change or a cancelled release as
    ///   [`Cancelled`](crcbl_core::input::TouchPhase::Cancelled). The primary
    ///   contact reaches `DefWindowProc`, which synthesizes the mouse messages
    ///   the seam obliges a touch backend to deliver; a secondary one does not,
    ///   because given two fingers the default handler turns a pinch into a
    ///   synthesized Ctrl press. A constant, like the rest: a touchscreen
    ///   plugged in mid-session delivers pointer messages to a window that
    ///   never asked for them, so there is nothing to latch on.
    /// * [`PANEL`](ShellCaps::PANEL) — a frameless tool window placed by its
    ///   anchors; see [`panel`](super::panel).
    /// * [`PANEL_RESERVE`](ShellCaps::PANEL_RESERVE) — a reserving panel is an
    ///   appbar, which is how the taskbar reserves its edge too.
    /// * [`POPUP`](ShellCaps::POPUP) — an owned `WS_POPUP` placed against its
    ///   parent and kept on its monitor; see [`popup`](super::popup).
    /// * [`POPUP_REPOSITION`](ShellCaps::POPUP_REPOSITION) — the same placement
    ///   worked out again, and the window moved with `SetWindowPos`.
    /// * [`CURSOR_SHAPE`](ShellCaps::CURSOR_SHAPE) — the stock `user32`
    ///   cursors, answered from `WM_SETCURSOR`; see
    ///   [`set_cursor`](Shell::set_cursor).
    /// * [`TRANSPARENT`](ShellCaps::TRANSPARENT) — the window system's half: a
    ///   transparent window of any role is created with
    ///   `WS_EX_NOREDIRECTIONBITMAP`, a frameless one is given square corners
    ///   and no border, and its [`SurfaceTarget::Win32`] says `transparent`.
    ///   The frame's half is the GPU backend's, and only D3D12 has it: it
    ///   presents through a DirectComposition visual with premultiplied alpha.
    ///   Vulkan refuses such a surface by name in `create_surface`, which is
    ///   how a caller on that backend finds out; the shell cannot know which
    ///   backend will draw. A pixel of alpha zero is still hit by the pointer
    ///   (this is not a layered window); click-through to another program is
    ///   [`INPUT_REGION`](ShellCaps::INPUT_REGION)'s.
    /// * [`INPUT_REGION`](ShellCaps::INPUT_REGION) — a window region, which
    ///   Windows hit-tests before any window procedure is asked, so the pointer
    ///   outside it reaches the window beneath in any process.
    ///   `WM_NCHITTEST`'s `HTTRANSPARENT`, which
    ///   [`PopupInput::Passthrough`](crate::PopupInput::Passthrough) answers,
    ///   only reaches windows of the same thread. The region also clips what
    ///   the window draws, and keeps the frame of a captioned window; see
    ///   [`set_input_region`](Shell::set_input_region).
    /// * [`ACCESSIBILITY`](ShellCaps::ACCESSIBILITY), only when built with the
    ///   `accesskit` feature — each window has a UI Automation adapter that
    ///   answers `WM_GETOBJECT` with the tree `update_accessibility` sent, and
    ///   turns a reader's requests into `AccessibilityAction`s. There is no
    ///   deactivation to report; see the `access` module. (Plain names, not
    ///   links: none of the three exists without the feature.)
    ///
    /// [`HW_UPSCALE`](ShellCaps::HW_UPSCALE) is clear for a reason rather than
    /// for want of work — a plain `HWND` presents at its own size, and the
    /// renderer does the upscale blit.
    /// [`PANEL_EXCLUSIVE_KEYBOARD`](ShellCaps::PANEL_EXCLUSIVE_KEYBOARD) is
    /// clear by decision: an exclusive panel takes the foreground when shown,
    /// and taking it back whenever the user clicks away would fight both the
    /// user and the system's foreground lock.
    fn caps(&self) -> ShellCaps {
        self.caps
    }

    /// Creates a window. **The window has no size yet.**
    ///
    /// # Windows knows the size, and this still returns without one
    ///
    /// `CreateWindowExW` takes a size and `GetClientRect` answers immediately,
    /// so — as on X11 and unlike Wayland — the size is known before this
    /// function returns. It is not *reported* before this function returns,
    /// because [`WindowState::size`] is defined as `None` until a
    /// [`Resized`](ShellEvent::Resized) has been delivered and delivering
    /// events is [`pump`](Shell::pump)'s job. The create-then-wait loop is real
    /// here as well; it just completes on the **first pump**.
    ///
    /// # `app_id` is validated and not yet applied
    ///
    /// Win32 has no `WM_CLASS`. The equivalent is the *Application User Model
    /// ID*, a process-wide string set with
    /// `SetCurrentProcessExplicitAppUserModelID` from `shell32`, and it is what
    /// decides taskbar grouping and which shortcut a window is matched to.
    /// This slice does not set it — that is a third system library for a
    /// property that is not per window — so
    /// [`WindowDesc::app_id`](crate::WindowDesc::app_id) is checked for
    /// validity and otherwise unused here. `docs/backlog.md` carries it.
    ///
    /// # Errors
    ///
    /// [`ShellError::NoSuchMonitor`] for a borderless request naming a monitor
    /// that is gone, [`ShellError::InvalidDescriptor`] for a title or app id
    /// containing a NUL byte or a size that rounds to nothing, and
    /// [`ShellError::WindowCreation`] carrying the `GetLastError` code if the
    /// system refused.
    fn create_window(&mut self, desc: &WindowDesc<'_>) -> Result<WindowId, ShellError> {
        crate::role::check(desc, self.caps(), self.backend(), |monitor| {
            self.monitor(monitor).is_some()
        })?;
        if let DisplayMode::Borderless {
            monitor: Some(monitor),
        } = desc.mode
            && self.monitor(monitor).is_none()
        {
            return Err(ShellError::NoSuchMonitor(monitor.0));
        }
        // Validated here as well as in `create_native_window`, because the app
        // id never reaches a system call and would otherwise be accepted with
        // a NUL in it on this backend and rejected on the other two.
        if ffi::wide(desc.app_id).is_none() {
            return Err(ShellError::InvalidDescriptor(
                "app_id contains a NUL byte".to_string(),
            ));
        }
        let target = match desc.mode {
            DisplayMode::Borderless {
                monitor: Some(monitor),
            } => self.monitor(monitor).cloned(),
            _ => self
                .monitors
                .iter()
                .find(|monitor| monitor.is_primary)
                .or_else(|| self.monitors.first())
                .cloned(),
        };
        let mut panel = match desc.role {
            crate::WindowRole::Panel(panel) => Some(Panel::new(&panel, desc.size)),
            _ => None,
        };
        let popup = match desc.role {
            crate::WindowRole::Popup(popup) => Some(popup),
            _ => None,
        };
        let created = match (&mut panel, &popup) {
            (Some(panel), _) => self.create_panel(desc, panel)?,
            (None, Some(popup)) => self.create_popup(desc, popup)?,
            (None, None) => self.create_native_window(desc, target.as_ref())?,
        };
        let Some(hwnd) = NonNull::new(created.hwnd) else {
            return Err(ShellError::WindowCreation(
                "CreateWindowExW returned null".to_string(),
            ));
        };
        let scale_factor = geometry::scale_from_dpi(created.dpi);
        let effective_mode = match desc.mode {
            // Neither a panel nor a popup has a full-screen state: see
            // `set_mode`.
            _ if panel.is_some() || popup.is_some() => DisplayMode::Windowed,
            DisplayMode::Windowed => DisplayMode::Windowed,
            DisplayMode::Borderless { .. } => DisplayMode::Borderless {
                monitor: self.monitor_of(created.hwnd),
            },
        };
        let window = WinWindow {
            hwnd,
            key: proc::key(created.hwnd),
            title: desc.title.to_string(),
            requested_size: desc.size,
            requested_mode: desc.mode,
            requested_constraints: desc.constraints,
            resizable: desc.resizable,
            dpi: created.dpi,
            scale_factor,
            configuration: None,
            // Known now, delivered on the first pump; see the doc comment.
            pending: Some(WindowConfiguration {
                size: created.size,
                scale_factor,
                mode: effective_mode,
            }),
            last_size: Some(created.size),
            effective_mode,
            saved: None,
            cursor: None,
            pointer_mode: PointerMode::Free,
            focused: false,
            visible: Self::is_showing(created.hwnd),
            close_pending: false,
            accept_drops: desc.accept_drops,
            transparent: desc.transparent,
            panel,
            panel_namespace: match desc.role {
                crate::WindowRole::Panel(panel) => panel.namespace.to_string(),
                _ => String::new(),
            },
            popup,
            input_region: None,
        };
        if desc.transparent {
            Self::apply_transparent_chrome(hwnd.as_ptr());
        }
        let window = self.windows.insert(window).cast();
        // Here rather than in the window procedure: the adapter must exist, and
        // UI Automation be initialised, before the first `WM_GETOBJECT` is
        // answered, and must not be created while one is. The waker is what
        // gets a reader's request delivered while this thread is asleep. A
        // window shown as it was created took the focus before its adapter
        // existed, so the adapter starts from where the focus is now.
        #[cfg(feature = "accesskit")]
        {
            let waker = self.waker();
            // SAFETY: takes no arguments and only reads which window of this
            // thread has the keyboard focus. The foreground too, by the rule
            // `proc::focus_change` states: thread focus alone is not the
            // keyboard.
            let focused = unsafe { ffi::GetFocus() } == hwnd.as_ptr()
                && proc::foreground() == proc::key(hwnd.as_ptr());
            self.shared.access().open(hwnd.as_ptr(), focused, waker);
        }
        let state = self.window(window)?;
        self.refresh_limits(state);
        if desc.visible
            && let Some(keyboard) = Self::furniture_keyboard(panel.as_ref(), popup.map(|p| p.input))
        {
            self.show_panel_window(window, keyboard)?;
        }
        Ok(window)
    }

    fn destroy_window(&mut self, window: WindowId) -> Result<(), ShellError> {
        // Its popups first, each with its own `WindowDestroyed`, as every
        // backend destroys a popup with its parent. The system would take
        // owned windows down with their owner anyway, but report nothing.
        let popups: Vec<WindowId> = self
            .windows_iter()
            .filter(|(_, state)| state.popup.is_some_and(|popup| popup.parent == window))
            .map(|(id, _)| id)
            .collect();
        for popup in popups {
            self.destroy_window(popup)?;
        }
        let removed = self
            .windows
            .remove(window.cast())
            .ok_or_else(|| ShellError::invalid_window(window))?;
        self.shared.forget(removed.key);
        self.drop_clipboard_answers(window);
        self.forget_input(window);
        if removed.panel.is_some_and(|panel| panel.appbar) {
            Self::remove_appbar(removed.raw());
        }
        // The pool entry is gone **before** the system call, deliberately:
        // `DestroyWindow` dispatches `WM_DESTROY` into the window procedure
        // synchronously, and the `RawEvent::Destroyed` that produces must not
        // find a live window and report a second `WindowDestroyed` for it.
        //
        // SAFETY: destroying this shell's own window, from the thread that
        // created it — which is this one, because a `Shell` is not `Send`.
        unsafe { ffi::DestroyWindow(removed.raw()) };
        self.queue.push_back(ShellEvent::WindowDestroyed { window });
        Ok(())
    }

    fn window_state(&self, window: WindowId) -> Result<WindowState, ShellError> {
        let state = self.window(window)?;
        Ok(WindowState {
            configuration: state.configuration,
            requested_mode: state.requested_mode,
            requested_constraints: state.requested_constraints,
            focused: state.focused,
            visible: state.visible,
            pointer_mode: state.pointer_mode,
            close_pending: state.close_pending,
        })
    }

    /// Sets the title bar text.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] if the handle is stale, or
    /// [`ShellError::InvalidDescriptor`] if the title contains a NUL byte —
    /// which would silently truncate it, since every `*W` entry point takes a
    /// NUL-terminated string.
    fn set_title(&mut self, window: WindowId, title: &str) -> Result<(), ShellError> {
        let hwnd = self.window(window)?.raw();
        let wide = ffi::wide(title).ok_or_else(|| {
            ShellError::InvalidDescriptor("title contains a NUL byte".to_string())
        })?;
        // SAFETY: `wide` is NUL-terminated UTF-16 that outlives the call, which
        // copies it. This dispatches `WM_SETTEXT` into the window procedure
        // synchronously; nothing is borrowed here.
        unsafe { ffi::SetWindowTextW(hwnd, wide.as_ptr()) };
        self.window_mut(window)?.title = title.to_string();
        Ok(())
    }

    /// Shows or hides the window — **really**, as on X11 and unlike Wayland.
    ///
    /// `ShowWindow` is reversible with no state lost, so this is one of the
    /// places the two desktop backends can do something the Wayland one
    /// documents that it cannot: there, a surface is mapped exactly while it
    /// has a buffer, and buffers belong to the renderer.
    ///
    /// [`WindowState::visible`] is read back from the system rather than set
    /// from the request, so a `SW_SHOWNORMAL` that was overruled — by a policy,
    /// or by the window being minimized — reports what happened.
    fn set_visible(&mut self, window: WindowId, visible: bool) -> Result<(), ShellError> {
        let state = self.window(window)?;
        let hwnd = state.raw();
        if visible
            && let Some(keyboard) =
                Self::furniture_keyboard(state.panel.as_ref(), state.popup.map(|popup| popup.input))
        {
            return self.show_panel_window(window, keyboard);
        }
        let command = if visible {
            // `SW_SHOWNORMAL` rather than `SW_SHOW`: it also restores a window
            // that the user minimized, which is what "make it visible" means.
            value::SW_SHOW_NORMAL
        } else {
            value::SW_HIDE
        };
        // SAFETY: this shell's own window; dispatches `WM_SHOWWINDOW` and
        // possibly `WM_SIZE` into the window procedure synchronously.
        unsafe { ffi::ShowWindow(hwnd, command) };
        let showing = Self::is_showing(hwnd);
        self.window_mut(window)?.visible = showing;
        Ok(())
    }

    /// Asks for windowed or borderless.
    ///
    /// A request in the seam's sense, and on this platform one that is always
    /// granted: there is no window manager to refuse it, because borderless is
    /// this process changing its own window's style. What can still disagree
    /// with the request is the **monitor** — `Borderless { monitor: None }`
    /// means "wherever the window already is", and the answer names it — which
    /// is why [`mode_request_honoured`](WindowState::mode_request_honoured)
    /// compares with [`DisplayMode::satisfied_by`] rather than with `==`.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] for a stale handle,
    /// [`ShellError::NoSuchMonitor`] if the named monitor is gone, or
    /// [`ShellError::Backend`] if there is no monitor at all to be borderless
    /// on.
    fn set_mode(&mut self, window: WindowId, mode: DisplayMode) -> Result<(), ShellError> {
        if let DisplayMode::Borderless {
            monitor: Some(monitor),
        } = mode
            && self.monitor(monitor).is_none()
        {
            return Err(ShellError::NoSuchMonitor(monitor.0));
        }
        let state = self.window_mut(window)?;
        state.requested_mode = mode;
        // Neither a panel nor a popup has a full-screen state, so the request
        // stays one: its effective mode is always windowed, as on Wayland.
        if state.panel.is_some() || state.popup.is_some() {
            return Ok(());
        }
        self.apply_mode(window, mode)?;

        // The `WM_SIZE` the style change produced is already in the raw queue,
        // and `translate` will discard it as a restatement — so the
        // configuration is published from here instead, which also covers the
        // case where the two modes happen to have the same client size.
        let config = Self::configuration_of(self.window(window)?);
        let state = self.window_mut(window)?;
        state.pending = Some(config);
        state.last_size = Some(config.size);
        // The frame came or went, and the window region keeps it.
        if let Some(region) = state.input_region.as_deref() {
            Self::apply_input_region(state.raw(), Some(region))?;
        }
        Ok(())
    }

    /// Asks for resize limits and an aspect lock.
    ///
    /// Applied through the window procedure, which answers `WM_GETMINMAXINFO`
    /// and `WM_SIZING` from numbers this call precomputes — see [`geometry`].
    /// Unlike X11's `WM_NORMAL_HINTS`, there is nobody who might not read them:
    /// the enforcement is in this process.
    ///
    /// Nothing is resized here. A constraint bounds what the *user* may drag
    /// to; a window that is already outside it stays there until it is next
    /// resized, which is what every Windows application does and what
    /// [`SizeConstraints`] documents as "hints, whose only observable effect is
    /// whatever size the window system reports next".
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] if the handle is stale.
    fn set_constraints(
        &mut self,
        window: WindowId,
        constraints: SizeConstraints,
    ) -> Result<(), ShellError> {
        self.window_mut(window)?.requested_constraints = constraints;
        let state = self.window(window)?;
        self.refresh_limits(state);
        Ok(())
    }

    fn reposition_popup(
        &mut self,
        popup: WindowId,
        anchor: crate::LogicalRect,
        size: crate::LogicalSize,
    ) -> Result<(), ShellError> {
        self.move_popup(popup, anchor, size)
    }

    fn set_panel(
        &mut self,
        window: WindowId,
        size: crate::LogicalSize,
        panel: &crate::PanelDesc<'_>,
    ) -> Result<(), ShellError> {
        self.reconfigure_panel(window, size, panel)
    }

    fn monitors(&self) -> &[MonitorInfo] {
        &self.monitors
    }

    /// Runs the message queue and delivers what it produced.
    ///
    /// The order is the same as the two Linux backends': the system first, so
    /// that everything the window procedure recorded during this drain is
    /// visible; then the translation into shell state; then the configurations,
    /// so a resize and its scale change land together; then delivery.
    ///
    /// **This does not return during a user drag-resize.** That is a Win32
    /// fact, not a bug in this loop; the [module docs](super) state what it
    /// costs and why the alternative is a decision above this crate.
    fn pump(&mut self, sink: &mut dyn FnMut(ShellEvent)) {
        self.keep_alive();
        let windows = &self.windows;
        self.queue.extend(
            self.frames
                .take_due(|window| windows.contains(window.cast())),
        );
        // Drain by count, not `while let`: a sink that creates a window must
        // not be able to spin this loop, and whatever it queued belongs to the
        // next frame — which is what the message queue would have done anyway.
        for _ in 0..self.queue.len() {
            let Some(event) = self.queue.pop_front() else {
                break;
            };
            sink(event);
        }
    }

    /// Runs the message queue and keeps what it produced.
    ///
    /// The `PeekMessageW` inside [`drain_messages`](Self::drain_messages) is
    /// what the system times: a thread that makes one at least every five
    /// seconds is not hung, whatever it does in between.
    fn keep_alive(&mut self) {
        self.drain_messages();
        self.translate();
        #[cfg(feature = "accesskit")]
        self.translate_actions();
        self.publish_configurations();
    }

    /// Blocks until a message arrives or `timeout` elapses.
    ///
    /// The hazard is a wait that sleeps out its whole timeout with work already
    /// queued — an editor that called [`pump`](Shell::pump), was handed
    /// something that queued another message, and then went to sleep. What
    /// closes it is that [`wait`](Self::wait) **drains the queue before it
    /// sleeps**, so there is never work waiting when the sleep starts. See
    /// there for why that is the drain rather than `MWMO_INPUTAVAILABLE`, and
    /// for the CI run that decided it.
    ///
    /// A frame that is due is work of the same kind with no message behind
    /// it, so the wait returns at once while one is.
    fn wait_events(&mut self, timeout: Option<Duration>) {
        if self.frames.is_pending() {
            return;
        }
        // A wait that cannot wait turns an editor idling at zero frames per
        // second into one spinning a core, and says nothing while it does it.
        // Nothing can be done about it here — the caller has no alternative to
        // offer — so it is reported and the loop continues.
        match self.wait(timeout) {
            Wake::TimedOut | Wake::Woken | Wake::Message => {}
            Wake::Failed { error } => {
                crcbl_core::log::warn!(
                    "MsgWaitForMultipleObjectsEx failed with error {error}; not sleeping"
                );
            }
            Wake::Unexpected { outcome } => {
                crcbl_core::log::warn!(
                    "MsgWaitForMultipleObjectsEx returned {outcome}, which is not one of its documented answers"
                );
            }
        }
    }

    /// An auto-reset event in the wait's handle set — see `win32::wake`.
    /// `None` only if the system refused an event, which is logged.
    fn waker(&mut self) -> Option<crate::ShellWaker> {
        crate::waker::lazy_waker(&mut self.wake, "Win32", super::wake::WakeEvent::new)
    }

    fn align_event_clock(&mut self, elapsed: Duration) {
        self.time.align_at(
            ffi::tick_nanos(),
            u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX),
        );
    }

    /// The handles `crcbl-vk` needs for `vkCreateWin32SurfaceKHR`.
    ///
    /// Available the instant the window exists — Win32 creates a real, sized
    /// window synchronously — so a HAL surface can be created immediately and
    /// only the swapchain waits for the first
    /// [`Resized`](ShellEvent::Resized).
    ///
    /// The `HINSTANCE` is the module that registered the window class, which is
    /// what the WSI extension wants: it identifies the class the `HWND` belongs
    /// to, not the thread.
    ///
    /// Lifetime: the window lives as long as the [`WindowId`], so a HAL surface
    /// built from these must be destroyed before
    /// [`destroy_window`](Shell::destroy_window) and before the shell is
    /// dropped.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] if the handle is stale.
    fn surface_target(&self, window: WindowId) -> Result<SurfaceTarget, ShellError> {
        let state = self.window(window)?;
        Ok(SurfaceTarget::Win32 {
            hinstance: self.instance,
            hwnd: state.hwnd,
            transparent: state.transparent,
        })
    }

    /// Frees, confines or locks the pointer.
    ///
    /// # Both captured modes are one `ClipCursor`
    ///
    /// [`Confined`](PointerMode::Confined) and [`Locked`](PointerMode::Locked)
    /// bound the pointer identically — the client rectangle, in screen
    /// coordinates — and differ in what is *reported*:
    ///
    /// * Confined keeps the cursor drawn and
    ///   [`abs`](ShellEvent::PointerMotion) flowing. The clip is the whole
    ///   implementation.
    /// * Locked additionally hides the cursor, suppresses `abs` — which
    ///   [`ShellEvent::PointerMotion`] documents as the invariant of the mode —
    ///   and recentres the pointer when it drifts, so leaving the mode does not
    ///   leave the cursor in a corner. The camera reads `WM_INPUT` instead, which
    ///   is unaffected by either the clip or the recentre.
    ///
    /// Unlike X11 there is no grab to be refused: `ClipCursor` is not a request
    /// to another client, so this cannot fail for a reason outside this
    /// process. It *can* stop being in effect, and does — see [`input`] and the
    /// window procedure, which release the clip the instant the window loses
    /// focus and re-establish it when focus returns, when the window moves and
    /// when it is resized.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] for a stale handle, or
    /// [`ShellError::Unsupported`] naming the mode — which is what a caller
    /// that checked [`PointerMode::required_cap`] against
    /// [`caps`](Shell::caps) never sees.
    fn set_pointer_mode(&mut self, window: WindowId, mode: PointerMode) -> Result<(), ShellError> {
        self.window(window)?;
        if !self.caps.contains(mode.required_cap()) {
            return Err(Self::unsupported(mode.as_str()));
        }
        // The clip is per shell rather than per window — there is one cursor —
        // so a second window taking it releases the first's, exactly as the X11
        // backend's grab does.
        let previous: Vec<WindowId> = self
            .windows_iter()
            .filter(|(handle, state)| *handle != window && state.pointer_mode.is_captured())
            .map(|(handle, _)| handle)
            .collect();
        for held in previous {
            if let Ok(state) = self.window_mut(held) {
                state.pointer_mode = PointerMode::Free;
            }
        }
        self.window_mut(window)?.pointer_mode = mode;
        self.refresh_clip();
        self.refresh_cursor_visibility();
        if mode == PointerMode::Locked
            && let Some(size) = self.window(window)?.configuration.map(|config| config.size)
        {
            // Start from the middle, so the first recentre is not immediate.
            //
            // Logged rather than propagated: the lock itself is established by
            // this point — the clip is applied and the cursor hidden — and the
            // centring is a convenience. Refusing the whole mode change because
            // the pointer could not be moved would report a lock that is in
            // fact in force.
            if let Err(why) = self.warp_to_client(
                window,
                i32::try_from(size.width / 2).unwrap_or(0),
                i32::try_from(size.height / 2).unwrap_or(0),
            ) {
                crcbl_core::log::warn!("the pointer lock could not start from the middle: {why}");
            }
        }
        Ok(())
    }

    /// Sets the cursor shape, or hides the cursor with `None`.
    ///
    /// # Two mechanisms, because Windows has two questions
    ///
    /// A *shape* is answered from `WM_SETCURSOR`, and only from there: the
    /// system asks on every pointer movement and applies the window class's
    /// cursor if nothing answers, so a `SetCursor` called from here would be
    /// overwritten before the next frame. The loaded `HCURSOR` is therefore
    /// recorded for the window procedure — see [`proc`] — and takes effect on
    /// the next movement, which is the first moment anything could have been
    /// drawn anyway.
    ///
    /// *Hiding* is `ShowCursor`, whose per-thread reference count is the classic
    /// bug in this API and is kept balanced by
    /// [`pointer::Visibility`](super::pointer::Visibility) rather than by
    /// counting calls by hand. It applies while the **focused** window wants it
    /// hidden; [`input`] states that rule and its one visible
    /// consequence.
    ///
    /// Unlike the X11 backend, a named shape really is applied here: Windows
    /// ships the stock cursor set in `user32`, so there is no theme to load and
    /// no dependency to take on. Three of the seam's shapes have no exact stock
    /// equivalent and are approximated;
    /// [`pointer::cursor_id`](super::pointer::cursor_id) names which and to what.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] if the handle is stale.
    fn set_cursor(
        &mut self,
        window: WindowId,
        cursor: Option<CursorIcon>,
    ) -> Result<(), ShellError> {
        self.window_mut(window)?.cursor = Some(cursor);
        if let Some(icon) = cursor {
            self.apply_cursor_shape(window, icon)?;
        }
        self.refresh_cursor_visibility();
        Ok(())
    }

    /// Moves the pointer to a position in the window.
    ///
    /// `ClientToScreen` then `SetCursorPos`, which is the capability
    /// [`POINTER_WARP`](ShellCaps::POINTER_WARP) names and which Wayland
    /// deliberately does not have. The warning on the trait method applies here
    /// as it does on X11 and is worth repeating: a camera must not be built on
    /// this. A warp produces a `WM_MOUSEMOVE` indistinguishable from the user's
    /// own, so a warp-based camera fights every real movement — which is why
    /// [`PointerMode::Locked`] plus `WM_INPUT` exists.
    ///
    /// The position is **clamped by the system** to the current clip rectangle
    /// and to the virtual screen, so a warp outside a confined window lands on
    /// its edge rather than being refused.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] if the handle is stale.
    fn warp_pointer(
        &mut self,
        window: WindowId,
        position: PhysicalPoint,
    ) -> Result<(), ShellError> {
        self.window(window)?;
        self.warp_to_client(window, position.x as i32, position.y as i32)
    }

    fn reply_close_request(
        &mut self,
        window: WindowId,
        reply: CloseReply,
    ) -> Result<(), ShellError> {
        let state = self.window_mut(window)?;
        if !state.close_pending {
            return Err(ShellError::NoPendingCloseRequest { window });
        }
        state.close_pending = false;
        match reply {
            CloseReply::Keep => Ok(()),
            CloseReply::Close => self.destroy_window(window),
        }
    }

    /// Publishes content, or empties the clipboard with an empty slice.
    ///
    /// Every offer is written under its own format — `CF_UNICODETEXT` for text
    /// and a registered format named after the mime for everything else — so a
    /// `[text, ron]` pair reaches Notepad *and* round-trips through another
    /// Crucible losslessly. The reader picks; see [`clipboard`]. A
    /// `text/uri-list` offer is published a second time as the `CF_HDROP` file
    /// list Explorer pastes, from the URIs in it that name Windows files.
    ///
    /// # "Release" means "empty", because that is what Win32 has
    ///
    /// The seam says an empty slice "releases the clipboard, where the platform
    /// can express that". X11 and Wayland can: both have an *owner*, and giving
    /// it up leaves whatever the previous owner published in place. Windows has
    /// no owner to give up — the clipboard is content, and the only thing a
    /// process can do to content it put there is discard it. So an empty slice
    /// is `EmptyClipboard`, and afterwards the clipboard holds nothing rather
    /// than holding what it held before this shell wrote to it.
    ///
    /// # Win32 never returns `NeedsUserInteraction`, and that is complete
    ///
    /// [Obligation 6](Shell) says a backend on a platform with no
    /// user-interaction rule never returns
    /// [`ShellError::NeedsUserInteraction`]. Windows has no such rule: any
    /// window of any process may open the clipboard at any time, with no serial,
    /// no timestamp and no focus involved. A background process can take the
    /// clipboard here, which Wayland forbids by design — a real difference in
    /// what the two platforms permit, not a gap in this backend. The same
    /// sentence the X11 backend writes, for the same reason.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] for a stale handle, or
    /// [`ShellError::Backend`] if the clipboard could not be opened within
    /// [`clipboard::OPEN_BUDGET`] — another process is holding it — or if the
    /// system refused every format.
    fn clipboard_offer(
        &mut self,
        window: WindowId,
        offers: &[ClipboardOffer<'_>],
    ) -> Result<(), ShellError> {
        let hwnd = self.window(window)?.raw();
        let board = Clipboard::open(hwnd).map_err(|refused| {
            ShellError::Backend(format!(
                "the clipboard could not be opened within {:?} ({refused:?}); another process is \
                 holding it",
                clipboard::OPEN_BUDGET
            ))
        })?;
        if let Opened::After { attempts } = board.opened() {
            crcbl_core::log::debug!(
                "the clipboard was held by another process for {attempts} attempts"
            );
        }
        // Required before a write, and the whole of a release. It also makes
        // this process the clipboard's owner, which is what lets the system
        // synthesize `CF_TEXT` from the `CF_UNICODETEXT` written below.
        if !board.empty() {
            return Err(ShellError::Backend(
                "EmptyClipboard was refused, so nothing could be published".to_string(),
            ));
        }
        if offers.is_empty() {
            return Ok(());
        }

        let mut published = 0usize;
        for offer in offers {
            let Some(format) = clipboard::format_id(offer.mime) else {
                continue;
            };
            let encoding = clipboard::encoding_for(offer.mime);
            if encoding == clipboard::Encoding::UnicodeText
                && core::str::from_utf8(offer.bytes).is_err()
            {
                // `ClipboardOffer` is explicit that it does not validate, and
                // `CF_UNICODETEXT` cannot carry a byte that is not a character.
                // Said out loud, because the replacement characters are
                // otherwise found by whoever pastes.
                crcbl_core::log::warn!(
                    "a text/plain clipboard offer is not valid UTF-8; the invalid bytes will \
                     paste as replacement characters"
                );
            }
            if board.put(format, &clipboard::payload_bytes(encoding, offer.bytes)) {
                published += 1;
            }
            // Explorer pastes files from `CF_HDROP` alone; see `clipboard`.
            if offer.mime == MimeType::UriList && board.put_file_list(offer.bytes) {
                published += 1;
            }
        }
        if published == 0 {
            // The clipboard has already been emptied, which is the honest state
            // to leave it in: this process claimed it and then published
            // nothing, rather than leaving somebody else's content under our
            // ownership.
            return Err(ShellError::Backend(
                "no offered format could be published to the clipboard".to_string(),
            ));
        }
        Ok(())
    }

    /// Asks for the clipboard's content in `mime`.
    ///
    /// # Which obligations this satisfies, and how
    ///
    /// * **Exactly one answer** ([obligation 4](Shell)): the read happens inside
    ///   this call and its [`ClipboardData`](ShellEvent::ClipboardData) is
    ///   queued before it returns, so it is delivered by the next
    ///   [`pump`](Shell::pump). There is no list of outstanding reads to lose
    ///   one from. The single exception is the one the X11 backend names too —
    ///   a window destroyed before that pump, where obligation 1 wins; see
    ///   [`drop_clipboard_answers`](Self::drop_clipboard_answers).
    /// * **Within a bounded time** (also 4): the only wait is
    ///   `OpenClipboard` being refused by a process that has it open, and that
    ///   is retried for [`clipboard::OPEN_BUDGET`]
    ///   and then answered [`Unavailable`](ClipboardContent::Unavailable). There
    ///   is no transfer to stall: `GetClipboardData` hands over memory rather
    ///   than starting a conversation with another process, so this backend has
    ///   no equivalent of X11's `INCR` deadline or Wayland's pipe timeout.
    /// * **Held rather than answered empty** ([obligation 5](Shell)): there is
    ///   nothing to hold. Any window may open the clipboard at any time —
    ///   **Win32 has neither Wayland's focus gate nor its serial requirement** —
    ///   so "hold until answerable" collapses to "answer", and the obligation is
    ///   discharged by answering. [`clipboard_readable`](Shell::clipboard_readable)
    ///   is therefore left at the trait's provided default, which that method's
    ///   own documentation names this platform as being right for.
    ///
    /// # The answer names the format that was asked for
    ///
    /// [`ClipboardData::mime`](ShellEvent::ClipboardData) is "the format that
    /// was delivered, spelled the way the *other* application spelled it". A
    /// Win32 clipboard format is a **number**, not a string: there is no peer
    /// spelling in existence to report, so the answer echoes the request. That
    /// is not a shortcut — it is what `ReceivedMime` documents for the case
    /// where there is nothing else to say.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] for a stale handle. An empty clipboard is
    /// an answer, not an error.
    fn clipboard_request(
        &mut self,
        window: WindowId,
        mime: MimeType,
    ) -> Result<ClipboardRequestId, ShellError> {
        let hwnd = self.window(window)?.raw();
        let request = ClipboardRequestId(self.next_request);
        self.next_request = self.next_request.wrapping_add(1);
        let content = self.read_clipboard(hwnd, mime);
        // Queued rather than returned, even though it is known now: a consumer
        // written against the asynchronous shape is one that also works on X11,
        // where the answer is several round trips away.
        self.queue_event(ShellEvent::ClipboardData {
            window,
            request,
            mime: ReceivedMime::from(mime),
            content,
        });
        Ok(request)
    }

    /// Places the input method's composition window at `area` and its
    /// candidate list beside it, now and at the start of every composition.
    ///
    /// Restated at `WM_IME_STARTCOMPOSITION` from the recorded area because an
    /// input method may reset a placement between compositions.
    ///
    /// # Errors
    ///
    /// [`ShellError::InvalidWindow`] for a stale handle.
    /// Hands the tree to the window's UI Automation adapter, which keeps it
    /// only once a reader has asked for the window — the
    /// [`AccessibilityActivated`](ShellEvent::AccessibilityActivated) — and
    /// raises what it changed for the reader.
    ///
    /// **Windows never says a reader stopped**, so
    /// [`AccessibilityDeactivated`](ShellEvent::AccessibilityDeactivated) is
    /// never raised here and trees keep being kept once a reader has asked.
    /// See [`access`](super::access).
    #[cfg(feature = "accesskit")]
    fn update_accessibility(
        &mut self,
        window: WindowId,
        update: crate::accesskit::TreeUpdate,
    ) -> Result<(), ShellError> {
        let key = self.window(window)?.key;
        self.shared.access().update(key, update);
        Ok(())
    }

    fn set_text_input_area(
        &mut self,
        window: WindowId,
        area: Option<PhysicalRect>,
    ) -> Result<(), ShellError> {
        let state = self.window(window)?;
        let hwnd = state.raw();
        let key = state.key;
        let area = area.map(geometry::rect_from);
        self.shared.set_input_area(key, area);
        input::place_ime(hwnd, area);
        Ok(())
    }

    /// A window region built from the rectangles and the window's frame, and
    /// built again on every resize and mode change, since it is in window
    /// coordinates and keeps the frame. A passthrough popup keeps answering `HTTRANSPARENT`
    /// everywhere and gets no region, which would only clip its drawing.
    fn set_input_region(
        &mut self,
        window: WindowId,
        region: Option<&[PhysicalRect]>,
    ) -> Result<(), ShellError> {
        let state = self.window_mut(window)?;
        if state
            .popup
            .is_some_and(|popup| popup.input == crate::PopupInput::Passthrough)
        {
            return Ok(());
        }
        Self::apply_input_region(state.raw(), region)?;
        state.input_region = region.map(<[PhysicalRect]>::to_vec);
        Ok(())
    }

    /// Answered at the next pump: nothing in a plain `HWND`'s messages paces
    /// presents, so [`ShellCaps::FRAME_CALLBACK`] is clear.
    fn request_frame(&mut self, window: WindowId) -> Result<(), ShellError> {
        self.window(window)?;
        self.frames.request(window);
        Ok(())
    }

    /// The window's default input context, given back for an ordinary field
    /// and taken away otherwise: a password or a PIN keeps the input method
    /// out, as a Windows password box does, so nothing typed into it reaches
    /// an input method's dictionary. A composition's start restates the caret
    /// area on whichever context it runs in, so nothing is placed here.
    fn set_text_input(
        &mut self,
        window: WindowId,
        field: Option<TextPurpose>,
    ) -> Result<(), ShellError> {
        let composes = field.is_some_and(|purpose| !purpose.is_sensitive());
        input::enable_ime(self.window(window)?.raw(), composes)
    }
}

impl Drop for Win32Shell {
    /// Destroys every window this shell still owns.
    ///
    /// The order is the whole soundness argument for the raw pointer in
    /// `GWLP_USERDATA`: `DestroyWindow` dispatches `WM_DESTROY` and
    /// `WM_NCDESTROY` into the window procedure **synchronously**, and the
    /// procedure reads [`Shared`] through that pointer. Both messages therefore
    /// have to run while this `Rc` is still alive, which is exactly what doing
    /// it here — before the field is dropped — guarantees.
    ///
    /// The window class is deliberately not unregistered; see [`window`].
    /// Neither is the raw-input registration; see [`input`].
    ///
    /// # Two pieces of desktop state have to be handed back
    ///
    /// Both are process- or thread-wide rather than per window, so destroying
    /// the windows does not undo them, and both are user-visible if they are
    /// left behind:
    ///
    /// * The **cursor clip**. A process that exits with the cursor clipped
    ///   leaves it clipped to a rectangle no window occupies. Windows does clean
    ///   this up when the *process* exits, which is exactly why it must be done
    ///   here — a shell dropped by a host application that keeps running would
    ///   otherwise hold the desktop hostage with no window to point at.
    /// * The **`ShowCursor` count**, for the same reason and with the same
    ///   asymmetry: one hide outstanding is an invisible cursor over every
    ///   window this thread owns afterwards.
    fn drop(&mut self) {
        input::release_clip();
        Self::show_cursor(self.visibility.want(false));
        // Popups before the windows they belong to, deepest first, so each is
        // destroyed by this shell rather than taken down with its owner.
        fn depth<'a>(windows: &'a Pool<WinWindow>, mut window: &'a WinWindow) -> usize {
            let mut depth = 0;
            while let Some(parent) = window
                .popup
                .and_then(|popup| windows.get(popup.parent.cast()))
            {
                depth += 1;
                window = parent;
            }
            depth
        }
        let mut windows: Vec<(usize, Handle)> = self
            .windows
            .iter()
            .map(|(_, window)| (depth(&self.windows, window), window.raw()))
            .collect();
        windows.sort_by_key(|&(depth, _)| core::cmp::Reverse(depth));
        let windows: Vec<Handle> = windows.into_iter().map(|(_, hwnd)| hwnd).collect();
        // The work area an appbar reserved outlives its window otherwise.
        for (_, window) in self.windows.iter() {
            if window.panel.is_some_and(|panel| panel.appbar) {
                Self::remove_appbar(window.raw());
            }
        }
        for hwnd in windows {
            // SAFETY: destroying this shell's own windows from the thread that
            // created them — a `Shell` is not `Send`, so this is that thread.
            unsafe { ffi::DestroyWindow(hwnd) };
        }
        if let Some(timer) = self.wait_timer.take() {
            // SAFETY: the timer handle `open` created, closed once; no wait on
            // it can be in progress, because only `wait` waits on it and
            // `drop` has the shell exclusively.
            unsafe { ffi::CloseHandle(timer.as_ptr()) };
        }
    }
}

/// Tests that need a real desktop, and are therefore the point of the
/// `build + test (windows-latest)` CI job.
///
/// **Almost nothing here is `#[ignore]`d, and the exceptions are named where
/// they sit.** `docs/notes/process.md` calls a silently-skipped suite a known
/// trap, and this slice is deliberately how the project finds out whether a
/// GitHub runner gives a process a usable window station: if it does not,
/// [`Win32Shell::open`] fails and every one of these says so, which is an
/// answer. A skipped test is not.
///
/// The exceptions are the three pointer tests whose precondition is a
/// foreground window on an uncontended desktop, which a shared runner does not
/// provide. They are not skipped — `run-win32-e2e.ps1` passes `--run-ignored
/// all` and runs on a real interactive desktop, which is the only place that
/// precondition holds. Each carries its own comment saying so.
#[cfg(all(test, target_os = "windows"))]
mod tests;