crcbl-shell 0.4.0

Crucible windowing shell: own Wayland, X11, Web/canvas and headless backends
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
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
//! The window procedure, and the state it reaches through `GWLP_USERDATA`.
//!
//! # How the procedure finds this shell
//!
//! A `WNDPROC` is a bare `extern "system"` function: the system calls it with
//! an `HWND` and nothing else, so a backend with per-window state has to attach
//! that state to the window. The standard mechanism, and the one used here, is
//! `GWLP_USERDATA` — a pointer-sized slot on every window — bootstrapped in
//! `WM_NCCREATE`, which carries the `lpParam` handed to `CreateWindowExW` and
//! is the **first** message a window ever receives that can.
//!
//! What the slot holds is one [`Shared`] for the whole shell, not one record
//! per window. Two reasons:
//!
//! * The procedure needs the event queue, which is shell-wide, far more often
//!   than it needs anything per-window.
//! * A per-window `Box` would have to be freed exactly once, from
//!   `WM_NCDESTROY`, on a path that also runs when `CreateWindowExW` fails
//!   halfway. One allocation with the shell's lifetime has no such path.
//!
//! The pointer is cleared in `WM_NCDESTROY`, which is the last message a window
//! receives. After that the `HWND` value may be handed to a *new* window by the
//! system, and a stale pointer left in the slot would be read by whatever ran
//! next.
//!
//! # What the procedure may and may not do
//!
//! It runs **inside** whatever the shell was doing — see [`events`](super::events)
//! — so it must not touch the shell. It may:
//!
//! * push a [`RawEvent`], which borrows a `RefCell` for the length of one
//!   `push`;
//! * read a window's cached [`Limits`], for the two messages the system
//!   demands an answer to *now*;
//! * answer those two messages by writing into the structure the system lent
//!   us.
//!
//! The `RefCell` borrows are the part that has to stay short. A shell method
//! holding one across a `SetWindowPos` would panic when the procedure it
//! synchronously invokes tried to take it — which is a real hazard rather than
//! a theoretical one, because every mode change does exactly that call. It is
//! why [`Shared`]'s methods each take and release a borrow rather than handing
//! one out.
//!
//! # The two messages that cannot wait for the pump
//!
//! Everything else this backend handles is *recorded* and answered later.
//! `WM_GETMINMAXINFO` and `WM_SIZING` cannot be: the system is asking a
//! question mid-drag and will use whatever is in the structure when the
//! procedure returns. Both are answered from numbers the shell precomputed —
//! see [`geometry`] — so the procedure does arithmetic and nothing else. That
//! is what makes the aspect lock
//! ([`ShellCaps::ASPECT_HINT_HONORED`](crate::ShellCaps::ASPECT_HINT_HONORED))
//! and the size constraints work *during* a drag rather than after it.
//!
//! # The four things input adds to that list
//!
//! Input mostly fits the record-and-answer-later shape, and four pieces of it
//! do not. Each is here because deferring it to the next
//! [`pump`](crate::Shell::pump) would be observably wrong, not merely late:
//!
//! 1. **`WM_SETCURSOR`.** The system asks which cursor to draw and uses
//!    `DefWindowProc`'s answer — the *class* cursor — if we do not give one.
//!    Answering next frame means answering after the arrow has already been
//!    drawn, on every mouse movement.
//! 2. **`SetCapture` on a button press.** A button released outside the window
//!    is delivered to whatever window is under the pointer unless the capture
//!    was taken *when the button went down*. Taking it a frame later is taking
//!    it after the drag has already escaped.
//! 3. **`TrackMouseEvent` on the derived enter.** Windows has no
//!    `WM_MOUSEENTER`; the leave notification has to be armed at the moment the
//!    pointer arrives, and the request is one-shot.
//! 4. **`ClipCursor(NULL)` on focus loss.** This is the one that is not a
//!    latency question at all. A process that keeps the cursor clipped after it
//!    stops being the foreground window has **taken the desktop hostage**: the
//!    user cannot reach the taskbar, another window, or the button that would
//!    close ours. Deferring it to the next pump makes the hostage-taking last
//!    exactly as long as the application's next frame takes — which, for the
//!    application that is about to hang, is forever. So the clip is released
//!    here, synchronously, before the procedure returns.
//!
//! The cursor's *visibility* deliberately does **not** join that list. It is
//! `ShowCursor`'s per-thread reference count, which only takes effect while the
//! pointer is over a window of this thread — so an un-hide that arrives a frame
//! late is an invisible cursor over our own window for one frame, and cannot
//! escape onto the desktop the way a clip can. It stays with the shell, where
//! the count has one owner. See [`pointer::Visibility`].

use core::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::path::PathBuf;

use crate::AspectRatio;

use super::events::{RawEvent, enqueue};
use super::ffi::{Handle, Rect};
use super::geometry::{Frame, Track};

// The procedure itself is the only part of this module that calls into the
// system, so everything above it — the shared queue, the cached limits and the
// word arithmetic — compiles and is tested on every host. See
// [`geometry`](super::geometry) for why that matters.
#[cfg(target_os = "windows")]
use super::dnd;
#[cfg(target_os = "windows")]
use super::ffi::{self, CreateStructW, Lparam, Lresult, MinMaxInfo, Point, Wparam, msg, value};
#[cfg(target_os = "windows")]
use super::geometry;
#[cfg(target_os = "windows")]
use super::input::{self, RawReport};
#[cfg(target_os = "windows")]
use super::{keys, pointer};
#[cfg(target_os = "windows")]
use crate::PhysicalSize;
#[cfg(target_os = "windows")]
use core::ptr;
#[cfg(target_os = "windows")]
use crcbl_core::input::ButtonState;

/// An `HWND` as the integer the queue and the window pool match on.
///
/// Never converted back: the shell keeps the real handle, and a pointer
/// reconstructed from a queue entry could name a window that has since been
/// destroyed.
pub(super) fn key(hwnd: Handle) -> isize {
    hwnd as isize
}

/// The numbers the window procedure answers a synchronous message from.
///
/// Precomputed by the shell whenever the constraints, the style or the DPI
/// change, because working them out needs `AdjustWindowRectExForDpi` and the
/// procedure must not call into the system beyond answering.
#[derive(Clone, Copy, Debug)]
pub(super) struct Limits {
    /// Which window.
    pub hwnd: isize,
    /// Interactive resize limits, in window pixels.
    pub track: Track,
    /// The aspect lock, if one was asked for.
    pub aspect: Option<AspectRatio>,
    /// The window's non-client padding at its current style and DPI.
    pub frame: Frame,
}

/// A window's cursor shape, for the one message allowed to set it.
///
/// Recorded by [`set_cursor`](crate::Shell::set_cursor) and read by
/// `WM_SETCURSOR`. Hiding is **not** here: it is `ShowCursor`'s reference count,
/// which is per thread rather than per window and belongs to the shell.
#[derive(Clone, Copy, Debug)]
pub(super) struct Shape {
    /// Which window.
    pub hwnd: isize,
    /// The `HCURSOR`, owned by the system — a stock cursor from `LoadCursorW`,
    /// which must not be destroyed.
    pub cursor: Handle,
}

/// The paths one `WM_DROPFILES` carried.
///
/// Kept beside the event queue rather than in it, because a [`RawEvent`] is
/// `Copy` and this is not — see [`dnd`] for why the reading cannot be deferred
/// to the pump either. [`RawEvent::FilesDropped`] is the marker that keeps this
/// in order relative to everything else the procedure saw.
#[derive(Clone, Debug)]
pub(super) struct FileDrop {
    /// Which window, so a marker claims its own payload.
    pub hwnd: isize,
    /// The dropped paths, one [`DroppedFile`](crate::ShellEvent::DroppedFile)
    /// each. May be empty for a drop of nothing this backend could decode.
    pub paths: Vec<PathBuf>,
    /// Client x of the drop point.
    pub x: i32,
    /// Client y.
    pub y: i32,
}

/// The composition string one `WM_IME_COMPOSITION` carried.
///
/// Beside the queue rather than in it for the reason [`FileDrop`] is: a
/// [`RawEvent`] is `Copy` and a string is not, and the string belongs to the
/// input context *now* — by the time the pump runs, the next keystroke may have
/// changed it. [`RawEvent::Preedit`] is the marker that claims it.
#[derive(Clone, Debug)]
pub(super) struct Preedit {
    /// Which window, so a marker claims its own payload.
    pub hwnd: isize,
    /// The composition, as UTF-16 code units; empty when it ended.
    pub units: Vec<u16>,
    /// The input method's cursor, as a code unit index, if it said.
    pub cursor: Option<usize>,
}

/// What the shell and its window procedure share.
///
/// Owned by the shell as an `Rc`, and reached by the procedure as a raw pointer
/// out of `GWLP_USERDATA`. The soundness argument is one sentence and the shell
/// is what has to keep it true: **every window is destroyed before this is
/// dropped**, so no procedure can run after the allocation goes away. See
/// [`Win32Shell::drop`](super::Win32Shell).
///
/// The four `Cell`s are the state the *procedure* owns rather than the shell,
/// because each answers a question the procedure has to act on before it
/// returns — see the [module docs](self). They are `Cell` rather than `RefCell`
/// because each is one `Copy` value: there is no borrow to be held across a call
/// back into the system, which is the hazard the `RefCell`s carry.
#[derive(Debug, Default)]
pub(super) struct Shared {
    events: RefCell<Vec<RawEvent>>,
    limits: RefCell<Vec<Limits>>,
    shapes: RefCell<Vec<Shape>>,
    /// Drop payloads, oldest first, each claimed by the
    /// [`FilesDropped`](RawEvent::FilesDropped) marker that was pushed with it.
    drops: RefCell<VecDeque<FileDrop>>,
    /// Composition payloads, oldest first, each claimed by the
    /// [`Preedit`](RawEvent::Preedit) marker that was pushed with it.
    preedits: RefCell<VecDeque<Preedit>>,
    /// Each window's text input area, in client pixels, as
    /// [`set_text_input_area`](crate::Shell::set_text_input_area) last said.
    /// Re-applied when a composition starts, because an input method is free
    /// to forget a placement between compositions.
    input_areas: RefCell<Vec<(isize, Rect)>>,
    /// The popups the pointer passes through, answered `HTTRANSPARENT` in
    /// `WM_NCHITTEST`.
    passthrough: RefCell<Vec<isize>>,
    /// The window `TrackMouseEvent` is currently armed for, or zero.
    ///
    /// Also the answer to "is the pointer inside?", which is the only way to
    /// derive the entry Windows does not send.
    tracked: Cell<isize>,
    /// The window holding the mouse capture, and which buttons are down on it.
    ///
    /// A mask rather than a count; [`pointer::button_bit`] says why.
    capture: Cell<(isize, u32)>,
    /// The window the cursor is clipped to, or zero.
    ///
    /// Kept across a focus loss — the clip is *released* there and re-applied
    /// when focus comes back — so this records what the shell asked for rather
    /// than what the system currently has.
    clipped: Cell<isize>,
    /// The window holding this thread's keyboard focus, from `WM_SETFOCUS` and
    /// `WM_KILLFOCUS`, or zero.
    ///
    /// Not the same as having the keyboard: a window can hold its thread's
    /// focus while another process holds the foreground, which is where a
    /// panel refused the foreground is left. See [`focus_change`].
    keyboard: Cell<isize>,
    /// The window last reported focused through
    /// [`Focus`](RawEvent::Focus), or zero.
    focused: Cell<isize>,
    /// Each window's UI Automation adapter; see [`access`](super::access).
    #[cfg(all(target_os = "windows", feature = "accesskit"))]
    access: super::access::Adapters,
}

impl Shared {
    /// Records something the window procedure saw.
    fn push(&self, event: RawEvent) {
        enqueue(&mut self.events.borrow_mut(), event);
    }

    /// Takes everything recorded since the last call.
    ///
    /// Takes rather than drains, so the borrow ends before the caller starts
    /// resolving handles — which may call back into the system and therefore
    /// into the procedure.
    pub(super) fn take_events(&self) -> Vec<RawEvent> {
        core::mem::take(&mut self.events.borrow_mut())
    }

    /// Records the paths a `WM_DROPFILES` carried, for the marker pushed with
    /// it to claim.
    fn push_drop(&self, drop: FileDrop) {
        self.drops.borrow_mut().push_back(drop);
    }

    /// Takes the oldest drop payload recorded for `hwnd`.
    ///
    /// Matched by window rather than taken from the front, so that a payload
    /// discarded by [`forget`](Self::forget) — the window died before the pump
    /// reached its marker — cannot hand the *next* window's paths to a marker
    /// that is not theirs.
    pub(super) fn take_drop(&self, hwnd: isize) -> Option<FileDrop> {
        let mut drops = self.drops.borrow_mut();
        let index = drops.iter().position(|drop| drop.hwnd == hwnd)?;
        drops.remove(index)
    }

    /// Publishes the numbers the procedure answers `WM_GETMINMAXINFO` and
    /// `WM_SIZING` from, replacing any earlier ones for the same window.
    pub(super) fn set_limits(&self, limits: Limits) {
        let mut table = self.limits.borrow_mut();
        match table.iter_mut().find(|known| known.hwnd == limits.hwnd) {
            Some(known) => *known = limits,
            None => table.push(limits),
        }
    }

    /// Drops everything recorded for a window, on the way out.
    ///
    /// Every table **and** every `Cell` that could still name it: an `HWND`
    /// value is handed to a new window after the old one dies, so a leftover
    /// clip or capture target would be applied to whichever window inherits the
    /// number.
    pub(super) fn forget(&self, hwnd: isize) {
        self.limits.borrow_mut().retain(|known| known.hwnd != hwnd);
        self.shapes.borrow_mut().retain(|known| known.hwnd != hwnd);
        self.drops.borrow_mut().retain(|drop| drop.hwnd != hwnd);
        self.preedits
            .borrow_mut()
            .retain(|preedit| preedit.hwnd != hwnd);
        self.input_areas
            .borrow_mut()
            .retain(|&(known, _)| known != hwnd);
        self.passthrough.borrow_mut().retain(|&known| known != hwnd);
        if self.tracked.get() == hwnd {
            self.tracked.set(0);
        }
        if self.capture.get().0 == hwnd {
            self.capture.set((0, 0));
        }
        if self.clipped.get() == hwnd {
            self.clipped.set(0);
        }
        if self.keyboard.get() == hwnd {
            self.keyboard.set(0);
        }
        if self.focused.get() == hwnd {
            self.focused.set(0);
        }
        #[cfg(all(target_os = "windows", feature = "accesskit"))]
        self.access.close(hwnd);
    }

    /// The windows' accessibility adapters.
    #[cfg(all(target_os = "windows", feature = "accesskit"))]
    pub(super) fn access(&self) -> &super::access::Adapters {
        &self.access
    }

    /// Makes the pointer pass through `hwnd`; see [`passthrough`](Self).
    pub(super) fn set_passthrough(&self, hwnd: isize) {
        self.passthrough.borrow_mut().push(hwnd);
    }

    /// A window's limits, by value so no borrow outlives the lookup.
    fn limits_for(&self, hwnd: isize) -> Option<Limits> {
        self.limits
            .borrow()
            .iter()
            .find(|known| known.hwnd == hwnd)
            .copied()
    }

    /// Publishes the cursor `WM_SETCURSOR` will set for a window.
    pub(super) fn set_shape(&self, shape: Shape) {
        let mut table = self.shapes.borrow_mut();
        match table.iter_mut().find(|known| known.hwnd == shape.hwnd) {
            Some(known) => *known = shape,
            None => table.push(shape),
        }
    }

    /// Records a composition string, for the marker pushed with it to claim.
    fn push_preedit(&self, preedit: Preedit) {
        self.preedits.borrow_mut().push_back(preedit);
    }

    /// Takes the oldest composition recorded for `hwnd`, matched by window for
    /// the reason [`take_drop`](Self::take_drop) is.
    pub(super) fn take_preedit(&self, hwnd: isize) -> Option<Preedit> {
        let mut preedits = self.preedits.borrow_mut();
        let index = preedits.iter().position(|preedit| preedit.hwnd == hwnd)?;
        preedits.remove(index)
    }

    /// Publishes a window's text input area, or clears it with `None`.
    pub(super) fn set_input_area(&self, hwnd: isize, area: Option<Rect>) {
        let mut table = self.input_areas.borrow_mut();
        table.retain(|&(known, _)| known != hwnd);
        if let Some(area) = area {
            table.push((hwnd, area));
        }
    }

    /// A window's text input area, by value.
    fn input_area_for(&self, hwnd: isize) -> Option<Rect> {
        self.input_areas
            .borrow()
            .iter()
            .find(|&&(known, _)| known == hwnd)
            .map(|&(_, area)| area)
    }

    /// A window's cursor, or `None` to leave the class cursor alone.
    fn shape_for(&self, hwnd: isize) -> Option<Handle> {
        self.shapes
            .borrow()
            .iter()
            .find(|known| known.hwnd == hwnd)
            .map(|known| known.cursor)
    }

    /// Whether the pointer has just arrived in `hwnd`.
    ///
    /// `true` exactly once per entry, which is what makes it both the
    /// [`PointerFocus`](RawEvent::PointerFocus) edge and the moment to arm
    /// `TrackMouseEvent`. Two windows cannot both be tracked, because the
    /// pointer is in one place.
    fn enter(&self, hwnd: isize) -> bool {
        if self.tracked.get() == hwnd {
            return false;
        }
        self.tracked.set(hwnd);
        true
    }

    /// Whether a `WM_MOUSELEAVE` belongs to the window currently tracked.
    ///
    /// A leave for a window we are not tracking is a stale one-shot from before
    /// the last entry, and reporting it would tell a consumer the pointer left a
    /// window it is currently inside.
    fn leave(&self, hwnd: isize) -> bool {
        if self.tracked.get() != hwnd {
            return false;
        }
        self.tracked.set(0);
        true
    }

    /// Records a button going down; `true` when the capture must be taken.
    ///
    /// A press in a *different* window replaces the mask rather than merging
    /// into it: the previous window's buttons cannot still be down if the
    /// pointer is here, and merging would leave a bit set that no release ever
    /// clears — a capture that is never given back.
    fn press(&self, hwnd: isize, bit: u32) -> bool {
        let (holder, held) = self.capture.get();
        if holder != hwnd {
            self.capture.set((hwnd, bit));
            return true;
        }
        self.capture.set((hwnd, held | bit));
        held == 0
    }

    /// Records a button coming up; `true` when the capture must be released.
    fn release(&self, hwnd: isize, bit: u32) -> bool {
        let (holder, held) = self.capture.get();
        if holder != hwnd {
            // A release with no press — a button held down before this window
            // existed, or one whose press went to another application.
            return false;
        }
        let held = held & !bit;
        if held == 0 {
            self.capture.set((0, 0));
            return true;
        }
        self.capture.set((hwnd, held));
        false
    }

    /// Forgets the capture because somebody else took it.
    ///
    /// `WM_CAPTURECHANGED` arrives when another window grabs the mouse — a
    /// system menu, a drag from another application. Our mask would otherwise
    /// still say a button is down and the next release would try to give back a
    /// capture we no longer hold.
    fn capture_lost(&self) {
        self.capture.set((0, 0));
    }

    /// Records which window the cursor is clipped to; zero for none.
    pub(super) fn set_clipped(&self, hwnd: isize) {
        self.clipped.set(hwnd);
    }

    /// The window the cursor is clipped to, or zero.
    pub(super) fn clipped(&self) -> isize {
        self.clipped.get()
    }

    /// Reports the keyboard moving, if it has, given the window `foreground`
    /// (as a [`key`]) that holds the foreground now; see [`focus_change`].
    /// Returns the windows that lost and gained it, for a caller with a clip
    /// to move.
    ///
    /// Asked on every message that can move either half and once per pump,
    /// so a change the messages did not show is reported a frame late rather
    /// than never.
    pub(super) fn sync_focus(&self, foreground: isize) -> (Option<isize>, Option<isize>) {
        let (now, lost, gained) = focus_change(self.keyboard.get(), foreground, self.focused.get());
        self.focused.set(now);
        for (hwnd, focused) in [(lost, false), (gained, true)] {
            let Some(hwnd) = hwnd else { continue };
            self.push(RawEvent::Focus { hwnd, focused });
            // UI Automation's focus follows the window's. The adapter raises
            // what that changed after it has let go of its table, since the
            // raising may itself ask for the provider.
            #[cfg(all(target_os = "windows", feature = "accesskit"))]
            self.access.focus(hwnd, focused);
        }
        (lost, gained)
    }
}

/// Which window has the keyboard, and which lost and gained it since
/// `reported`: `(now, lost, gained)`, each a [`key`] and zero or `None` for no
/// window.
///
/// A window has the keyboard when it holds its thread's keyboard focus
/// (`keyboard`) **and** is the foreground window (`foreground`). The first half
/// alone is what `WM_SETFOCUS` says, and it is not enough: `ShowWindow` with
/// `SW_SHOWNORMAL` activates a window within its own thread, focus included,
/// even when the system refuses that thread the foreground — and then the
/// keys go to the foreground window while the shell would have said `Focus`.
/// Every window this backend makes is top-level, so the foreground window is
/// the focused one itself whenever it has the keyboard.
const fn focus_change(
    keyboard: isize,
    foreground: isize,
    reported: isize,
) -> (isize, Option<isize>, Option<isize>) {
    let now = if keyboard != 0 && keyboard == foreground {
        keyboard
    } else {
        0
    };
    if now == reported {
        return (now, None, None);
    }
    let lost = if reported == 0 { None } else { Some(reported) };
    let gained = if now == 0 { None } else { Some(now) };
    (now, lost, gained)
}

/// The window holding the foreground, as a [`key`]; zero for none.
#[cfg(target_os = "windows")]
pub(super) fn foreground() -> isize {
    // SAFETY: no arguments; the handle is only turned into a key.
    key(unsafe { ffi::GetForegroundWindow() })
}

/// `LOWORD`.
const fn low_word(value: usize) -> u32 {
    (value & 0xFFFF) as u32
}

/// `HIWORD`.
const fn high_word(value: usize) -> u32 {
    ((value >> 16) & 0xFFFF) as u32
}

/// The window procedure for every window this backend creates.
///
/// # Safety
///
/// Called only by the system, for a window created by
/// [`window`](super::window)'s class. The `w_param`/`l_param` interpretation of
/// each message is the one `winuser.h` documents, and each is asserted in the
/// `SAFETY` comment where it is relied on.
#[cfg(target_os = "windows")]
pub(super) unsafe extern "system" fn window_proc(
    hwnd: Handle,
    message: u32,
    w_param: Wparam,
    l_param: Lparam,
) -> Lresult {
    // SAFETY: the default handler accepts every message for a live window, and
    // `hwnd` is live for the whole of this call — the system owns it and is
    // what called us.
    let default = || unsafe { ffi::DefWindowProcW(hwnd, message, w_param, l_param) };

    if message == msg::NC_CREATE {
        // SAFETY: for `WM_NCCREATE` the system documents `l_param` as a
        // pointer to the `CREATESTRUCTW` it built from the `CreateWindowExW`
        // arguments; it is valid for the duration of this message, and only
        // one field is read out of it.
        let shared = unsafe { (*(l_param as *const CreateStructW)).lp_create_params };
        // SAFETY: `hwnd` is this window and `GWLP_USERDATA` is a pointer-sized
        // slot the system reserves for the application on every window. The
        // value is a pointer to the shell's `Shared`, which outlives every
        // window it created.
        unsafe { ffi::SetWindowLongPtrW(hwnd, value::GWLP_USERDATA, shared as isize) };
        // `WM_NCCREATE` must reach the default handler: refusing it aborts the
        // creation, and swallowing it leaves the non-client area uninitialised.
        return default();
    }

    // SAFETY: reading back the slot written above. Zero until `WM_NCCREATE`
    // (`WM_GETMINMAXINFO` genuinely arrives before it) and zero again after
    // `WM_NCDESTROY`, both of which the null check below handles.
    let shared = unsafe { ffi::GetWindowLongPtrW(hwnd, value::GWLP_USERDATA) } as *const Shared;
    // SAFETY: non-null here means the pointer was stored by `create_window`
    // from an `Rc<Shared>` the shell owns and has not dropped — the shell
    // destroys every window before releasing it, and a destroyed window's slot
    // is cleared in `WM_NCDESTROY`. The reference does not escape this call.
    let Some(shared) = (unsafe { shared.as_ref() }) else {
        return default();
    };
    let window = key(hwnd);
    // The message's own time, not the time the queue is drained. A window
    // procedure is never handed its `MSG`, so this is the only way to reach it;
    // `TimeBase` widens the 32 bits and rebases them onto the engine's clock.
    //
    // SAFETY: `GetMessageTime` takes no arguments, reads no memory of ours and
    // answers for the message this thread is currently dispatching — which is
    // the one that called us.
    let millis = unsafe { ffi::GetMessageTime() } as u32;

    match message {
        msg::SIZE => {
            if w_param == value::SIZE_MINIMIZED {
                // A minimized window reports 0×0, which is a visibility change
                // wearing a resize's clothes. See the module docs.
                shared.push(RawEvent::Minimized { hwnd: window });
                // **No reclip here.** `client_screen_rect` of an iconic window
                // maps both corners of the 0×0 client area to the same point,
                // and re-applying that pins the cursor for the whole minimized
                // period — the desktop-hostage invariant this module exists to
                // keep. The recorded target survives, so the first `WM_SIZE`
                // after restore re-clips from the real rectangle.
                if shared.clipped() == window {
                    input::release_clip();
                }
            } else {
                shared.push(RawEvent::Resized {
                    hwnd: window,
                    // `l_param` carries the **client** area, which is exactly
                    // what the seam means by a window's size, so there is no
                    // frame to subtract here.
                    size: PhysicalSize::new(
                        low_word(l_param as usize),
                        high_word(l_param as usize),
                    ),
                });
                // A clip is a screen rectangle built from a client rectangle
                // that has just changed shape.
                input::reclip(shared, hwnd, window);
            }
            0
        }

        // Nothing is recorded — the seam has no "the window moved" event — and
        // the clip still has to follow, because it was computed in screen
        // coordinates from a window that is no longer there.
        msg::MOVE => {
            input::reclip(shared, hwnd, window);
            0
        }

        // The modal drag loop ran our procedure for every intermediate position
        // and returned here. Re-establishing once at the end is not redundant
        // with the `WM_MOVE`/`WM_SIZE` arms above: a drag that the user cancels
        // with Escape ends with neither.
        msg::EXIT_SIZE_MOVE => {
            input::reclip(shared, hwnd, window);
            default()
        }

        // Intercepted, never forwarded: `DefWindowProc`'s answer to `WM_CLOSE`
        // is `DestroyWindow`, and the whole point of
        // [`CloseReply`](crate::CloseReply) is that the application gets to say
        // no. This one missing `default()` call is the difference between a
        // save prompt and a lost document.
        msg::CLOSE => {
            shared.push(RawEvent::CloseRequested { hwnd: window });
            0
        }

        msg::SET_FOCUS | msg::KILL_FOCUS | msg::ACTIVATE | msg::ACTIVATE_APP => {
            match message {
                msg::SET_FOCUS => shared.keyboard.set(window),
                msg::KILL_FOCUS if shared.keyboard.get() == window => shared.keyboard.set(0),
                _ => {}
            }
            let (lost, gained) = shared.sync_focus(foreground());
            // **Not deferred to the pump.** A window that keeps the cursor
            // clipped after it stops being the foreground window has taken the
            // desktop hostage; see the module docs for why one frame of that is
            // one frame too many. The recorded target survives, so focus coming
            // back restores the clip the caller asked for. A clip regained by
            // another window than this one is the pump's `refresh_clip`.
            let clipped = shared.clipped();
            if clipped != 0 && lost == Some(clipped) {
                input::release_clip();
            }
            if clipped == window && gained == Some(window) {
                input::apply_clip(hwnd);
            }
            // Activation is the default handler's to finish: it is what moves
            // the focus into an activated window.
            if matches!(message, msg::ACTIVATE | msg::ACTIVATE_APP) {
                return default();
            }
            0
        }

        // A UI Automation client asking for the window's provider. Answered
        // here, because the answer is the return value; the first time it is
        // asked is the reader arriving, which the program hears from the pump
        // and answers by sending a tree. See [`access`](super::access).
        #[cfg(feature = "accesskit")]
        msg::GET_OBJECT => {
            let answer = shared.access().get_object(window, w_param, l_param);
            if answer.activated {
                shared.push(RawEvent::AccessibilityActivated { hwnd: window });
            }
            answer.lresult.unwrap_or_else(default)
        }

        msg::KEY_DOWN | msg::KEY_UP | msg::SYS_KEY_DOWN | msg::SYS_KEY_UP => {
            let pressed = message == msg::KEY_DOWN || message == msg::SYS_KEY_DOWN;
            shared.push(RawEvent::Key {
                hwnd: window,
                scancode: keys::scancode(l_param),
                virtual_key: low_word(w_param) as u16,
                state: ButtonState::from_pressed(pressed),
                // Bit 30 is meaningful only on the way down; on a release it is
                // always set, and reading it there reports every release as a
                // repeat.
                repeat: pressed && keys::was_already_down(l_param),
                millis,
            });
            if message == msg::SYS_KEY_DOWN || message == msg::SYS_KEY_UP {
                // The `WM_SYS*` pair **must** reach the default handler. It is
                // what turns Alt+F4 into the `WM_CLOSE` this backend intercepts
                // into a close *request*, and what opens the window menu on
                // Alt+Space. Swallowing them silently removes the platform's own
                // keyboard contract from every Crucible window.
                default()
            } else {
                0
            }
        }

        // **A bare Alt or F10 must not open the window menu.** Forwarding the
        // `WM_SYS*` keys above is what makes `DefWindowProc` answer the release
        // of an Alt pressed on its own with `SC_KEYMENU` and an `lParam` of
        // zero, and letting that through enters the system's modal menu loop:
        // every key after it goes to the menu until Alt is tapped again, so a
        // player who brushes Alt loses the keyboard. Only that one form is
        // answered here — Alt+Space carries its character in `lParam` and still
        // opens the menu, and Alt+F4 arrives as `SC_CLOSE` — which is what SDL
        // and GLFW do for the same reason.
        msg::SYS_COMMAND => {
            if w_param & value::SC_COMMAND_MASK == value::SC_KEY_MENU && l_param == 0 {
                return 0;
            }
            default()
        }

        // Text, which is not the same thing as a keystroke — see
        // [`TextCommit`](crate::ShellEvent::TextCommit). `WM_SYSCHAR` is
        // deliberately absent: Alt+E is a menu accelerator, not the letter E.
        msg::CHAR => {
            shared.push(RawEvent::Char {
                hwnd: window,
                unit: low_word(w_param) as u16,
                millis,
            });
            0
        }

        // The input method's half of text. Every one of these still reaches
        // the default handler: it draws the input method's own composition
        // window, and it turns a committed result into the `WM_IME_CHAR` and
        // then `WM_CHAR` that the arm above records as the commit. What is
        // taken here is the composition in progress, which nothing else reports.
        msg::IME_START_COMPOSITION => {
            // An input method is free to forget a placement between
            // compositions, so the caret area is restated at every start.
            if let Some(area) = shared.input_area_for(window) {
                input::place_ime(hwnd, Some(area));
            }
            default()
        }
        msg::IME_COMPOSITION => {
            // `lParam` names what changed; only a changed composition string
            // is a pre-edit. A result alone is the commit, which arrives as
            // `WM_CHAR`.
            if keys::composition_changed(l_param) {
                let (units, cursor) = input::composition(hwnd);
                shared.push_preedit(Preedit {
                    hwnd: window,
                    units,
                    cursor,
                });
                shared.push(RawEvent::Preedit {
                    hwnd: window,
                    millis,
                });
            }
            default()
        }
        msg::IME_END_COMPOSITION => {
            shared.push_preedit(Preedit {
                hwnd: window,
                units: Vec::new(),
                cursor: None,
            });
            shared.push(RawEvent::Preedit {
                hwnd: window,
                millis,
            });
            default()
        }

        msg::MOUSE_MOVE => {
            let (x, y) = pointer::point(l_param);
            if shared.enter(window) {
                // There is no `WM_MOUSEENTER`. The arrival is derived from the
                // first movement after a leave, and the leave has to be asked
                // for — `TrackMouseEvent` is one-shot, so this is also where it
                // is re-armed.
                input::track_leave(hwnd);
                shared.push(RawEvent::PointerFocus {
                    hwnd: window,
                    entered: true,
                    x,
                    y,
                    millis,
                });
            }
            shared.push(RawEvent::PointerMotion {
                hwnd: window,
                x,
                y,
                millis,
            });
            0
        }

        msg::MOUSE_LEAVE => {
            if shared.leave(window) {
                shared.push(RawEvent::PointerFocus {
                    hwnd: window,
                    entered: false,
                    x: 0,
                    y: 0,
                    millis,
                });
            }
            0
        }

        msg::L_BUTTON_DOWN
        | msg::L_BUTTON_UP
        | msg::R_BUTTON_DOWN
        | msg::R_BUTTON_UP
        | msg::M_BUTTON_DOWN
        | msg::M_BUTTON_UP
        | msg::X_BUTTON_DOWN
        | msg::X_BUTTON_UP => {
            let Some((button, state)) = pointer::button(message, w_param) else {
                return default();
            };
            let (x, y) = pointer::point(l_param);
            let bit = pointer::button_bit(button);
            // The capture, taken on the first button down and given back on the
            // last button up. Without it a drag that leaves the window is
            // delivered to whatever is under the pointer, so the release never
            // arrives here and the button stays down forever from the
            // consumer's point of view.
            if state.is_pressed() {
                if shared.press(window, bit) {
                    // SAFETY: this shell's own window, on the thread that owns
                    // it — a window procedure runs on that thread by
                    // construction. `SetCapture` sends `WM_CAPTURECHANGED` to
                    // whoever held it, which this procedure handles below.
                    unsafe { ffi::SetCapture(hwnd) };
                }
            } else if shared.release(window, bit) {
                // SAFETY: releasing a capture this thread holds; a no-op when
                // it does not.
                unsafe { ffi::ReleaseCapture() };
            }
            shared.push(RawEvent::Button {
                hwnd: window,
                button,
                state,
                x,
                y,
                millis,
            });
            if message == msg::X_BUTTON_DOWN || message == msg::X_BUTTON_UP {
                // The two `WM_XBUTTON*` messages are the only mouse messages
                // documented to want `TRUE` rather than zero.
                1
            } else {
                0
            }
        }

        // Somebody else took the mouse. Our mask has to be cleared or the next
        // release tries to give back a capture we no longer hold.
        //
        // `l_param` is the window *gaining* it, and the check is not
        // decoration: `SetCapture` sends this to the window losing the capture,
        // and our own call above is what sends it. Clearing unconditionally
        // would wipe the mask one line after the press that set it, and the
        // matching release would then never give the capture back.
        msg::CAPTURE_CHANGED => {
            if l_param != hwnd as Lparam {
                shared.capture_lost();
            }
            default()
        }

        msg::MOUSE_WHEEL | msg::MOUSE_H_WHEEL => {
            // **The one pair that carries screen coordinates.** Every other
            // mouse message is in client space; reading these the same way puts
            // a scroll at a position off the bottom-right of the window on any
            // window that is not at the desktop origin.
            let (x, y) = pointer::point(l_param);
            let mut position = Point { x, y };
            // SAFETY: `position` is a live, initialised `POINT` the call
            // converts in place, and `hwnd` is a live window of this shell.
            unsafe { ffi::ScreenToClient(hwnd, &raw mut position) };
            shared.push(RawEvent::Wheel {
                hwnd: window,
                horizontal: message == msg::MOUSE_H_WHEEL,
                ticks: high_word(w_param) as u16 as i16,
                x: position.x,
                y: position.y,
                millis,
            });
            0
        }

        msg::POINTER_DOWN
        | msg::POINTER_UPDATE
        | msg::POINTER_UP
        | msg::POINTER_CAPTURE_CHANGED => {
            // The default handler is what turns the primary contact into the
            // mouse messages every pointer binding reads, which
            // `ShellEvent::Touch` makes an obligation on a backend that reports
            // touch — so everything but a secondary finger goes to it, recorded
            // or not.
            let Some((pointer_id, phase)) = pointer::touch(message, w_param) else {
                return default();
            };
            let position = if message == msg::POINTER_CAPTURE_CHANGED {
                // Carries no position, and the pointer's kind may no longer be
                // answerable. Recorded for any pointer: the shell keeps only the
                // ids it is tracking as contacts.
                None
            } else {
                let mut kind = 0;
                // SAFETY: `pointer_id` is the id this message is about, live for
                // the duration of the message, and `kind` is a live `u32` the
                // call writes into.
                let known = unsafe { ffi::GetPointerType(pointer_id, &raw mut kind) } != 0;
                if !known || kind != value::PT_TOUCH {
                    return default();
                }
                // Screen coordinates, as on the wheel pair.
                let (x, y) = pointer::point(l_param);
                let mut position = Point { x, y };
                // SAFETY: `position` is a live, initialised `POINT` converted in
                // place, and `hwnd` is a live window of this shell.
                unsafe { ffi::ScreenToClient(hwnd, &raw mut position) };
                Some((position.x, position.y))
            };
            shared.push(RawEvent::Touch {
                hwnd: window,
                pointer_id,
                phase,
                position,
                millis,
            });
            if position.is_some() && !pointer::is_primary(w_param) {
                // **A secondary finger is handled here and never reaches the
                // default handler.** Given two fingers, `DefWindowProc`'s legacy
                // promotion recognizes a pinch and synthesizes a Ctrl press for
                // the Ctrl+wheel that means "zoom" to an old application — a key
                // nobody pressed, arriving on whatever the game bound to Ctrl —
                // and withholds the first finger's click. The seam says a
                // secondary contact has no pointer events at all; answering it
                // here is how that stays true. Measured, not assumed: see
                // `win32_e2e`'s two-finger test.
                return 0;
            }
            default()
        }

        msg::INPUT => {
            // SAFETY: for `WM_INPUT` the system documents `l_param` as an
            // `HRAWINPUT` handle valid for the duration of this message, which
            // is exactly what `GetRawInputData` takes.
            match unsafe { input::read_raw(l_param) } {
                Some(RawReport::Mouse { device, mouse }) => shared.push(RawEvent::RawMotion {
                    hwnd: window,
                    device: device as isize,
                    flags: mouse.us_flags,
                    // The low half of the button word is `usButtonFlags`.
                    buttons: low_word(mouse.ul_buttons as usize) as u16,
                    x: mouse.l_last_x,
                    y: mouse.l_last_y,
                    millis,
                }),
                Some(RawReport::Keyboard { device, keyboard }) => {
                    shared.push(RawEvent::RawKey {
                        hwnd: window,
                        device: device as isize,
                        make_code: keyboard.make_code,
                        flags: keyboard.flags,
                        millis,
                    });
                }
                None => {}
            }
            // `WM_INPUT` must reach the default handler even when it was read:
            // that is what releases the report's buffer.
            default()
        }

        // Only the removal matters: an arriving device is named by its first
        // report. Forwarded because the default handler owns the rest.
        msg::INPUT_DEVICE_CHANGE => {
            if w_param == value::GIDC_REMOVAL {
                shared.push(RawEvent::DeviceRemoved { device: l_param });
            }
            default()
        }

        msg::SET_CURSOR => {
            // Only over the drawable area. Every other hit-test code names a
            // piece of the frame the system owns a cursor for, and answering
            // for those replaces the resize arrows with ours.
            if low_word(l_param as usize) != value::HT_CLIENT {
                return default();
            }
            let Some(cursor) = shared.shape_for(window) else {
                // Nothing was asked for, so the class cursor is right and
                // `DefWindowProc` is what sets it.
                return default();
            };
            // SAFETY: `cursor` is a stock cursor handle from `LoadCursorW`,
            // owned by the system and valid for the process's lifetime.
            unsafe { ffi::SetCursor(cursor) };
            // `TRUE`: the cursor was set, so `DefWindowProc` must not overwrite
            // it with the class one on the next mouse movement.
            1
        }

        msg::DPI_CHANGED => {
            // SAFETY: for `WM_DPICHANGED` the system documents `l_param` as a
            // pointer to the `RECT` it suggests the window move to; it is
            // valid for the duration of this message and is only read.
            let suggested = unsafe { *(l_param as *const Rect) };
            // Applying the suggestion is not optional in the per-monitor-v2
            // contract: a window that ignores it stays at its old pixel size on
            // a monitor at a different scale, which is the "everything is tiny
            // on the second screen" bug. The `WM_SIZE` this produces is what
            // reports the new size.
            //
            // SAFETY: moving this shell's own window, with a null insert-after
            // that `SWP_NO_Z_ORDER` makes unused.
            unsafe {
                ffi::SetWindowPos(
                    hwnd,
                    ptr::null_mut(),
                    suggested.left,
                    suggested.top,
                    suggested.width(),
                    suggested.height(),
                    value::SWP_NO_Z_ORDER | value::SWP_NO_ACTIVATE,
                );
            }
            shared.push(RawEvent::DpiChanged {
                hwnd: window,
                // Both halves of `w_param` carry a DPI and Windows has never
                // produced an anisotropic one; the X half is what
                // `GetDpiForWindow` would answer.
                dpi: low_word(w_param),
            });
            0
        }

        msg::GET_MIN_MAX_INFO => {
            let Some(limits) = shared.limits_for(window) else {
                return default();
            };
            // SAFETY: for `WM_GETMINMAXINFO` the system documents `l_param` as
            // a pointer to a `MINMAXINFO` it has already filled with defaults
            // and expects to be written through; it is valid for the duration
            // of this message.
            let info = unsafe { &mut *(l_param as *mut MinMaxInfo) };
            if let Some(min) = limits.track.min {
                info.pt_min_track_size = min;
            }
            if let Some(max) = limits.track.max {
                info.pt_max_track_size = max;
            }
            // `pt_max_size` is deliberately untouched — a constraint bounds a
            // *drag*, and a maximize button that produced a window smaller than
            // the monitor would be a different feature.
            0
        }

        msg::SIZING => {
            let Some(limits) = shared.limits_for(window) else {
                return default();
            };
            let Some(aspect) = limits.aspect else {
                return default();
            };
            // SAFETY: for `WM_SIZING` the system documents `l_param` as a
            // pointer to the `RECT` it is about to apply, and rewriting it is
            // the documented way to constrain an interactive resize. Valid for
            // the duration of this message.
            let rect = unsafe { &mut *(l_param as *mut Rect) };
            *rect = geometry::adjust_sizing_rect(*rect, w_param, aspect, limits.frame);
            // `TRUE`: the rectangle was changed.
            1
        }

        // The one message whose payload cannot wait for the pump: the `HDROP`
        // is live for the length of this call and is owed a `DragFinish`. The
        // paths therefore come out here and go on the shared drop queue, and
        // the event pushed after them is the marker that claims them. See
        // [`dnd`](super::dnd).
        msg::DROP_FILES => {
            // SAFETY: for `WM_DROPFILES` the system documents `w_param` as the
            // `HDROP` for this drop, valid until it is finished — which
            // `take_drop` does, exactly once, before returning.
            let (paths, point) = unsafe { dnd::take_drop(w_param as Handle) };
            shared.push_drop(FileDrop {
                hwnd: window,
                paths,
                x: point.x,
                y: point.y,
            });
            shared.push(RawEvent::FilesDropped {
                hwnd: window,
                millis,
            });
            0
        }

        msg::DESTROY => {
            shared.push(RawEvent::Destroyed { hwnd: window });
            0
        }

        msg::NC_DESTROY => {
            // The clip outlives the window that asked for it unless somebody
            // takes it back, and a window being destroyed is the one moment
            // nothing else will.
            if shared.clipped() == window {
                input::release_clip();
            }
            shared.forget(window);
            // The last message this window will ever receive, and the last
            // moment the slot is ours: the `HWND` value can be reused
            // afterwards, and a stale `Shared` pointer left behind would be
            // read by whatever window inherits it.
            //
            // SAFETY: as the write in `WM_NCCREATE`; the window is still live
            // for the duration of this message.
            unsafe { ffi::SetWindowLongPtrW(hwnd, value::GWLP_USERDATA, 0) };
            default()
        }

        // Sent to every top-level window, so a two-window shell sees it twice
        // for one cable; `enqueue` collapses that. Forwarded as well, because
        // the default handler does bookkeeping of its own.
        msg::DISPLAY_CHANGE => {
            shared.push(RawEvent::MonitorsChanged);
            default()
        }

        // A tooltip the pointer passes through to the bar beneath it, which
        // `HTTRANSPARENT` does for a window on the same thread — and a popup's
        // parent always is.
        msg::NC_HIT_TEST if shared.passthrough.borrow().contains(&window) => value::HT_TRANSPARENT,

        // A work area is part of a monitor's description, and a panel placed
        // in one has to move with it: the taskbar moved, or an appbar came or
        // went. Every other parameter is somebody else's.
        msg::SETTING_CHANGE => {
            if w_param == value::SPI_SET_WORK_AREA {
                shared.push(RawEvent::MonitorsChanged);
            }
            default()
        }

        // The shell's notification to an appbar, registered by
        // `register_appbar`. Recorded, because answering it moves the window,
        // which re-enters this procedure.
        msg::APP_BAR => {
            shared.push(RawEvent::AppBar {
                hwnd: window,
                notification: w_param as u32,
                param: l_param,
            });
            0
        }

        _ => default(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const PANEL: isize = 0x10;
    const OTHER: isize = 0x20;
    /// Another process's window holding the foreground.
    const ELSEWHERE: isize = 0x99;

    /// pikr's report on 0.3.0: an `Exclusive` panel shown while another
    /// process held the foreground took its thread's focus (`WM_SETFOCUS`)
    /// and was reported focused right after `FocusRefused`. Red when thread
    /// focus alone counts as the keyboard again.
    #[test]
    fn thread_focus_without_the_foreground_is_not_the_keyboard() {
        assert_eq!(focus_change(PANEL, ELSEWHERE, 0), (0, None, None));
        // The user clicks it: the foreground arrives, the focus is unchanged.
        assert_eq!(focus_change(PANEL, PANEL, 0), (PANEL, None, Some(PANEL)));
        // Unchanged since the last report: nothing to say.
        assert_eq!(focus_change(PANEL, PANEL, PANEL), (PANEL, None, None));
        // The foreground goes elsewhere before the focus does.
        assert_eq!(
            focus_change(PANEL, ELSEWHERE, PANEL),
            (0, Some(PANEL), None)
        );
        // The focus moves between this thread's windows.
        assert_eq!(
            focus_change(OTHER, OTHER, PANEL),
            (OTHER, Some(PANEL), Some(OTHER))
        );
        // No window of this thread holds the focus, whoever is foreground.
        assert_eq!(focus_change(0, 0, 0), (0, None, None));
    }

    /// The events `sync_focus` queues follow the same rule, once each, in
    /// loss-then-gain order, and `forget` drops a dead window's half of it.
    #[test]
    fn sync_focus_reports_each_change_once() {
        let shared = Shared::default();
        let focus = |hwnd, focused| RawEvent::Focus { hwnd, focused };

        shared.keyboard.set(PANEL);
        assert_eq!(shared.sync_focus(ELSEWHERE), (None, None));
        assert!(shared.take_events().is_empty(), "refused: nothing to say");

        assert_eq!(shared.sync_focus(PANEL), (None, Some(PANEL)));
        assert_eq!(shared.sync_focus(PANEL), (None, None));
        assert_eq!(shared.take_events(), [focus(PANEL, true)]);

        shared.keyboard.set(OTHER);
        assert_eq!(shared.sync_focus(OTHER), (Some(PANEL), Some(OTHER)));
        assert_eq!(
            shared.take_events(),
            [focus(PANEL, false), focus(OTHER, true)]
        );

        shared.forget(OTHER);
        assert_eq!(shared.sync_focus(OTHER), (None, None));
        assert!(
            shared.take_events().is_empty(),
            "a dead window loses nothing"
        );
    }

    #[test]
    fn a_passthrough_popup_is_forgotten_with_its_window() {
        let shared = Shared::default();
        shared.set_passthrough(0x10);
        shared.set_passthrough(0x20);
        assert!(shared.passthrough.borrow().contains(&0x10));
        shared.forget(0x10);
        assert_eq!(*shared.passthrough.borrow(), [0x20]);
    }
    use crate::{LogicalSize, SizeConstraints};

    const FRAME: Frame = Frame {
        width: 16,
        height: 39,
    };

    fn limits(hwnd: isize) -> Limits {
        Limits {
            hwnd,
            track: super::super::geometry::track_sizes(
                SizeConstraints::min(LogicalSize::new(320.0, 240.0)),
                1.0,
                FRAME,
            ),
            aspect: None,
            frame: FRAME,
        }
    }

    #[test]
    fn a_window_is_keyed_by_its_handle_and_a_null_one_is_zero() {
        // The queue matches on this, so it has to be the handle and nothing
        // derived from it. Null is what an `HWND` field holds before a window
        // exists, and it must not collide with a real window — no window has
        // handle zero.
        assert_eq!(key(core::ptr::null_mut()), 0);
        assert_eq!(key(0x1234 as Handle), 0x1234);
    }

    #[test]
    fn a_words_halves_are_the_client_width_and_height() {
        // `WM_SIZE` packs both into one `LPARAM`, and reading them the wrong
        // way round produces a window that is exactly as tall as it is wide.
        let packed = (720usize << 16) | 1280usize;
        assert_eq!(low_word(packed), 1280);
        assert_eq!(high_word(packed), 720);
        // A 4K width is still inside 16 bits; the ceiling is 65535, which no
        // client area reaches.
        assert_eq!(low_word(3840), 3840);
        assert_eq!(high_word(0xFFFF_FFFF), 0xFFFF);
    }

    #[test]
    fn limits_are_replaced_per_window_and_forgotten_on_the_way_out() {
        let shared = Shared::default();
        shared.set_limits(limits(1));
        shared.set_limits(limits(2));
        assert_eq!(shared.limits_for(1).map(|found| found.hwnd), Some(1));

        // Replaced in place, not appended: a second `set_constraints` must not
        // leave the first answer for `WM_GETMINMAXINFO` to find.
        let mut updated = limits(1);
        updated.aspect = Some(AspectRatio::WIDESCREEN);
        shared.set_limits(updated);
        assert_eq!(shared.limits.borrow().len(), 2);
        assert_eq!(
            shared.limits_for(1).and_then(|found| found.aspect),
            Some(AspectRatio::WIDESCREEN)
        );

        // The two numbers `WM_GETMINMAXINFO` and `WM_SIZING` are answered
        // from, carried whole rather than recomputed in the procedure.
        let found = shared.limits_for(2).expect("still there");
        assert_eq!(found.frame, FRAME);
        assert_eq!(
            found.track.min,
            Some(super::super::ffi::Point {
                x: 320 + FRAME.width,
                y: 240 + FRAME.height
            })
        );

        shared.forget(1);
        assert!(shared.limits_for(1).is_none());
        assert!(shared.limits_for(2).is_some(), "one window, not the table");
    }

    #[test]
    fn the_pointer_enters_once_per_arrival_and_a_stale_leave_is_ignored() {
        // Windows sends no `WM_MOUSEENTER`, so the arrival is derived from the
        // first movement after a leave — which means the latch has to be right
        // or every mouse movement reports an entry.
        let shared = Shared::default();
        assert!(shared.enter(1), "the first movement is an arrival");
        assert!(!shared.enter(1), "and the next two hundred are not");
        assert!(!shared.enter(1));

        // The pointer is in one place, so moving to another window is a leave
        // of the first even before its one-shot fires.
        assert!(shared.enter(2));
        assert!(
            !shared.leave(1),
            "a leave for a window we stopped tracking is stale"
        );
        assert!(shared.leave(2));
        assert!(!shared.leave(2), "and it only fires once");
        // After a leave the next movement is an arrival again.
        assert!(shared.enter(2));
    }

    #[test]
    fn the_capture_is_taken_on_the_first_button_and_given_back_on_the_last() {
        // The whole point of the mask: a two-button drag that releases one
        // button outside the window must not release the capture, or the second
        // release goes to whatever is underneath and the button stays down
        // forever from the consumer's point of view.
        let shared = Shared::default();
        const LEFT: u32 = 1;
        const RIGHT: u32 = 2;
        assert!(shared.press(1, LEFT), "the first press takes it");
        assert!(!shared.press(1, RIGHT), "the second does not take it twice");
        assert!(!shared.release(1, LEFT), "one button is still down");
        assert!(shared.release(1, RIGHT), "the last one gives it back");
        assert!(!shared.release(1, RIGHT), "and not a second time");

        // A release with no press — a button held before the window existed.
        // A counter would go negative here and never release again.
        assert!(!shared.release(1, LEFT));
        assert!(shared.press(1, LEFT), "which must still work afterwards");

        // A press in another window replaces rather than merging: the first
        // window's bit would otherwise never be cleared.
        assert!(shared.press(2, RIGHT));
        assert!(shared.release(2, RIGHT));

        // And somebody else taking the mouse clears the mask outright.
        assert!(shared.press(1, LEFT));
        shared.capture_lost();
        assert!(
            shared.press(1, RIGHT),
            "the next press takes the capture again"
        );
    }

    #[test]
    fn a_cursor_shape_is_replaced_per_window_and_read_back_for_wm_setcursor() {
        let shared = Shared::default();
        let arrow = 0x1234 as Handle;
        let beam = 0x5678 as Handle;
        assert_eq!(shared.shape_for(1), None, "nothing asked for yet");

        shared.set_shape(Shape {
            hwnd: 1,
            cursor: arrow,
        });
        shared.set_shape(Shape {
            hwnd: 2,
            cursor: beam,
        });
        assert_eq!(shared.shape_for(1), Some(arrow));
        assert_eq!(shared.shape_for(2), Some(beam));

        // Replaced in place: a second `set_cursor` must not leave the first
        // shape for `WM_SETCURSOR` to find.
        shared.set_shape(Shape {
            hwnd: 1,
            cursor: beam,
        });
        assert_eq!(shared.shape_for(1), Some(beam));
        assert_eq!(shared.shapes.borrow().len(), 2);
    }

    #[test]
    fn a_destroyed_window_leaves_nothing_behind_that_names_it() {
        // An `HWND` value is handed to a *new* window after the old one dies,
        // so a leftover clip or capture target would be applied to whichever
        // window inherits the number — and a leftover tracking latch would
        // suppress the new window's first pointer entry.
        let shared = Shared::default();
        shared.set_limits(limits(1));
        shared.set_shape(Shape {
            hwnd: 1,
            cursor: 0x1234 as Handle,
        });
        assert!(shared.enter(1));
        assert!(shared.press(1, 1));
        shared.set_clipped(1);

        shared.forget(1);
        assert!(shared.limits_for(1).is_none());
        assert_eq!(shared.shape_for(1), None);
        assert_eq!(shared.clipped(), 0);
        assert!(shared.enter(1), "the reused handle is a fresh arrival");
        assert!(shared.press(1, 1), "and a fresh capture");

        // Another window's state is untouched.
        shared.set_clipped(2);
        shared.set_shape(Shape {
            hwnd: 2,
            cursor: 0x5678 as Handle,
        });
        shared.forget(1);
        assert_eq!(shared.clipped(), 2);
        assert!(shared.shape_for(2).is_some());
    }

    #[test]
    fn each_drop_marker_claims_its_own_payload_in_arrival_order() {
        // The pairing the `FilesDropped` marker depends on. Getting it wrong
        // hands one window's dropped files to another window's event, which is
        // an asset imported into the wrong scene rather than an error.
        let shared = Shared::default();
        let drop = |hwnd: isize, name: &str| FileDrop {
            hwnd,
            paths: vec![PathBuf::from(name)],
            x: 4,
            y: 5,
        };
        shared.push_drop(drop(1, "first"));
        shared.push_drop(drop(2, "other window"));
        shared.push_drop(drop(1, "second"));

        let first = shared.take_drop(1).expect("queued");
        assert_eq!(
            first.paths,
            [PathBuf::from("first")],
            "oldest first, per window"
        );
        assert_eq!(
            (first.x, first.y),
            (4, 5),
            "the drop point travels with its paths — `DroppedFile` carries both"
        );
        assert_eq!(
            shared.take_drop(1).expect("queued").paths,
            [PathBuf::from("second")]
        );
        assert!(shared.take_drop(1).is_none(), "and no third");
        assert_eq!(
            shared.take_drop(2).expect("untouched").paths,
            [PathBuf::from("other window")],
            "another window's drop was never in the running"
        );
    }

    #[test]
    fn a_drop_whose_window_died_before_the_pump_is_discarded_with_it() {
        // The reason `take_drop` matches on the window rather than popping the
        // front: `forget` removes the payload, and a marker still in the raw
        // queue must then find nothing — not the next window's files.
        let shared = Shared::default();
        shared.push_drop(FileDrop {
            hwnd: 1,
            paths: vec![PathBuf::from("doomed")],
            x: 0,
            y: 0,
        });
        shared.push_drop(FileDrop {
            hwnd: 2,
            paths: vec![PathBuf::from("survivor")],
            x: 0,
            y: 0,
        });
        shared.forget(1);
        assert!(shared.take_drop(1).is_none());
        assert_eq!(
            shared.take_drop(2).expect("still there").paths,
            [PathBuf::from("survivor")]
        );
    }

    #[test]
    fn a_composition_is_claimed_by_its_own_window_and_dies_with_it() {
        // Same shape as the drop queue: matched by window, oldest first, and
        // gone with a window that was destroyed before the pump reached it.
        let shared = Shared::default();
        let preedit = |hwnd, text: &str| Preedit {
            hwnd,
            units: text.encode_utf16().collect(),
            cursor: Some(1),
        };
        shared.push_preedit(preedit(1, "か"));
        shared.push_preedit(preedit(2, "な"));
        shared.push_preedit(preedit(1, "かな"));
        let first = shared.take_preedit(1).expect("queued");
        assert_eq!(String::from_utf16_lossy(&first.units), "か");
        assert_eq!(first.cursor, Some(1));
        shared.forget(1);
        assert!(shared.take_preedit(1).is_none(), "went with its window");
        assert_eq!(
            String::from_utf16_lossy(&shared.take_preedit(2).expect("untouched").units),
            "な"
        );
    }

    #[test]
    fn a_text_input_area_is_replaced_cleared_and_forgotten_per_window() {
        let shared = Shared::default();
        let area = |left| Rect {
            left,
            top: 10,
            right: left + 5,
            bottom: 30,
        };
        shared.set_input_area(1, Some(area(0)));
        shared.set_input_area(2, Some(area(100)));
        shared.set_input_area(1, Some(area(50)));
        assert_eq!(shared.input_area_for(1).map(|area| area.left), Some(50));
        shared.set_input_area(1, None);
        assert!(
            shared.input_area_for(1).is_none(),
            "None hands placement back"
        );
        shared.forget(2);
        assert!(shared.input_area_for(2).is_none(), "went with its window");
    }

    #[test]
    fn taking_the_queue_empties_it_and_holds_no_borrow_afterwards() {
        // The re-entrancy rule in one test: after `take_events` the `RefCell`
        // is free, so the window procedure a subsequent `SetWindowPos` invokes
        // can push into it. Holding a borrow here would panic instead.
        let shared = Shared::default();
        shared.push(RawEvent::MonitorsChanged);
        shared.push(RawEvent::CloseRequested { hwnd: 7 });
        let taken = shared.take_events();
        assert_eq!(taken.len(), 2);
        assert!(shared.take_events().is_empty());
        shared.push(RawEvent::Destroyed { hwnd: 7 });
        assert_eq!(shared.take_events(), [RawEvent::Destroyed { hwnd: 7 }]);
    }
}