detached ce78abc

feat(xwm): add map flow, property decoding, serial pairing and reparent handling

MapRequest batches ten GetProperty, GetGeometry and MapWindow; Ready fires once
every reply (and the XRes pid fallback, propClientIDs) is in. Titles, class,
transient-for and fullscreen changes reach the host only when they differ.
WL_SURFACE_SERIAL pairs with set_serial in either order. ReparentNotify drops
or adopts root children. Adds recorded-stream allocation tests.
commit
ce78abc602422d152d0ca8f64f1a568cd8e7c991
author
brice · · 2026-10-22 12:43 UTC
committer
brice · · 2026-10-22 17:54 UTC
parent
50a2e95
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
11 files · +4896 -33
M Makefile +1 -1
@@ -69,7 +69,7 @@ adapter-imports:
6969 [ -z "$$bad" ] || { echo "$$bad"; echo 'adapter imports another adapter: use ports or a shared library (Makefile ADAPTER_LIBS)' >&2; exit 1; }
7070 # Fast, mandatory guards for tiled content publications and SHM copies.
7171 perf-check:
72- CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations)$$' -count=1
72+ CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations|TestPropertyUnchangedAllocations|TestDispatchAllocations)$$' -count=1
7373 CGO_ENABLED=0 go test -tags xturbo_poison ./internal/adapters/wayland/xwm -count=1
7474 check: vet test arch fakes-check log-check perf-check
7575
@@ -69,7 +69,7 @@ adapter-imports:
69 [ -z "$$bad" ] || { echo "$$bad"; echo 'adapter imports another adapter: use ports or a shared library (Makefile ADAPTER_LIBS)' >&2; exit 1; }69 [ -z "$$bad" ] || { echo "$$bad"; echo 'adapter imports another adapter: use ports or a shared library (Makefile ADAPTER_LIBS)' >&2; exit 1; }
70 # Fast, mandatory guards for tiled content publications and SHM copies.70 # Fast, mandatory guards for tiled content publications and SHM copies.
71 perf-check:71 perf-check:
72- CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations)$$' -count=172+ CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations|TestPropertyUnchangedAllocations|TestDispatchAllocations)$$' -count=1
73 CGO_ENABLED=0 go test -tags xturbo_poison ./internal/adapters/wayland/xwm -count=173 CGO_ENABLED=0 go test -tags xturbo_poison ./internal/adapters/wayland/xwm -count=1
74 check: vet test arch fakes-check log-check perf-check74 check: vet test arch fakes-check log-check perf-check
75 75
M internal/adapters/wayland/xwm/events.go +14 -6
@@ -34,6 +34,8 @@ func (x *XWM) onEvent(ev *xturbo.Event) {
3434 x.onCreateNotify(ev)
3535 case xproto.KindDestroyNotify:
3636 x.onDestroyNotify(ev)
37+ case xproto.KindReparentNotify:
38+ x.onReparentNotify(ev)
3739 }
3840 if x.host == nil {
3941 if ev.Kind == xproto.KindMapRequest {
@@ -108,12 +110,11 @@ func (x *XWM) onError(e *xturbo.XError) {
108110 if e.Code == xproto.BadAccess {
109111 x.bootErr = ErrWMExists
110112 }
111- case kindProp, kindGeom, kindConfigure, kindFocus:
112- if e.Code != xproto.BadWindow {
113- return
114- }
115- // The window died between request and reply; DestroyNotify follows.
116- _, rec := x.tab.live(e.Args[0])
113+ case kindProp, kindGeom, kindClientIds, kindConfigure, kindFocus:
114+ // Usually BadWindow: the window died between request and reply and
115+ // DestroyNotify follows. Whatever the code, the awaited reply will
116+ // never come, so the map must not wait for it.
117+ s, rec := x.tab.live(e.Args[0])
117118 if rec == nil {
118119 x.stats.Stale++
119120 return
@@ -123,6 +124,13 @@ func (x *XWM) onError(e *xturbo.XError) {
123124 rec.pending &^= 1 << e.Args[1]
124125 case kindGeom:
125126 rec.pending &^= propGeom
127+ case kindClientIds:
128+ rec.pending &^= propClientIDs
129+ default:
130+ return
131+ }
132+ if e.Code != xproto.BadWindow { // a dead window is never announced
133+ x.checkReady(s)
126134 }
127135 }
128136 }
@@ -34,6 +34,8 @@ func (x *XWM) onEvent(ev *xturbo.Event) {
34 x.onCreateNotify(ev)34 x.onCreateNotify(ev)
35 case xproto.KindDestroyNotify:35 case xproto.KindDestroyNotify:
36 x.onDestroyNotify(ev)36 x.onDestroyNotify(ev)
37+ case xproto.KindReparentNotify:
38+ x.onReparentNotify(ev)
37 }39 }
38 if x.host == nil {40 if x.host == nil {
39 if ev.Kind == xproto.KindMapRequest {41 if ev.Kind == xproto.KindMapRequest {
@@ -108,12 +110,11 @@ func (x *XWM) onError(e *xturbo.XError) {
108 if e.Code == xproto.BadAccess {110 if e.Code == xproto.BadAccess {
109 x.bootErr = ErrWMExists111 x.bootErr = ErrWMExists
110 }112 }
111- case kindProp, kindGeom, kindConfigure, kindFocus:113+ case kindProp, kindGeom, kindClientIds, kindConfigure, kindFocus:
112- if e.Code != xproto.BadWindow {114+ // Usually BadWindow: the window died between request and reply and
113- return115+ // DestroyNotify follows. Whatever the code, the awaited reply will
114- }116+ // never come, so the map must not wait for it.
115- // The window died between request and reply; DestroyNotify follows.117+ s, rec := x.tab.live(e.Args[0])
116- _, rec := x.tab.live(e.Args[0])
117 if rec == nil {118 if rec == nil {
118 x.stats.Stale++119 x.stats.Stale++
119 return120 return
@@ -123,6 +124,13 @@ func (x *XWM) onError(e *xturbo.XError) {
123 rec.pending &^= 1 << e.Args[1]124 rec.pending &^= 1 << e.Args[1]
124 case kindGeom:125 case kindGeom:
125 rec.pending &^= propGeom126 rec.pending &^= propGeom
127+ case kindClientIds:
128+ rec.pending &^= propClientIDs
129+ default:
130+ return
131+ }
132+ if e.Code != xproto.BadWindow { // a dead window is never announced
133+ x.checkReady(s)
126 }134 }
127 }135 }
128 }136 }
M internal/adapters/wayland/xwm/mapflow.go +368 -13
@@ -1,21 +1,376 @@
11 package xwm
22
3-import "bnema.dev/xturbo"
3+import (
4+ "bnema.dev/xturbo"
5+ "bnema.dev/xturbo/xproto"
6+)
47
5-// beginMap starts the map flow of a managed window: properties, geometry,
6-// MapWindow. Also called by Attach for windows mapped before it.
7-func (x *XWM) beginMap(s Slot) { _ = s }
8+// ICCCM WM_STATE values.
9+const (
10+ wmStateWithdrawn uint32 = 0
11+ wmStateNormal uint32 = 1
12+)
813
9-func (x *XWM) onMapRequest(ev *xturbo.Event) { _ = ev }
10-func (x *XWM) onMapNotify(ev *xturbo.Event) { _ = ev }
11-func (x *XWM) onUnmapNotify(ev *xturbo.Event) { _ = ev }
12-func (x *XWM) onClientMessage(ev *xturbo.Event) { _ = ev }
14+// _NET_WM_STATE client message actions.
15+const (
16+ netStateRemove = 0
17+ netStateAdd = 1
18+ netStateToggle = 2
19+)
1320
14-func (x *XWM) onGeomReply(s Slot, rec *record, r *xturbo.Reply) { _, _, _ = s, rec, r }
15-func (x *XWM) onClientIdsReply(s Slot, rec *record, r *xturbo.Reply) { _, _, _ = s, rec, r }
21+// beginMap starts the map flow of a managed window: every property, the
22+// geometry and MapWindow go out in one batch (wlroots order). Also called by
23+// Attach for windows mapped before it.
24+func (x *XWM) beginMap(s Slot) {
25+ rec := x.tab.rec(s)
26+ if rec.kind != kindManaged || rec.mapping {
27+ return
28+ }
29+ rec.mapping, rec.ready = true, false
30+ rec.hasNetName = false
31+ rec.pending = propGeom
32+ xid := rec.xid
33+ for idx := 0; idx < propCount; idx++ {
34+ x.requestProp(s, idx)
35+ }
36+ b := x.conn.Buf()
37+ x.expect(xproto.GetGeometry(b, xproto.Drawable(xid)), kindGeom, x.tab.tag(s), 0)
38+ xproto.MapWindow(b, xproto.Window(xid))
39+}
1640
17-// SetSerial records the serial of a wl_surface (xwayland_surface_v1.set_serial).
18-func (x *XWM) SetSerial(surfaceID uint64, serial uint64) { _, _ = surfaceID, serial }
41+func (x *XWM) onMapRequest(ev *xturbo.Event) {
42+ if s := x.tab.lookup(uint32(xproto.MapRequestOf(ev).Window())); s != 0 {
43+ x.beginMap(s)
44+ }
45+}
46+
47+func (x *XWM) onMapNotify(ev *xturbo.Event) {
48+ if s := x.tab.lookup(uint32(xproto.MapNotifyOf(ev).Window())); s != 0 {
49+ x.tab.rec(s).mapped = true
50+ }
51+}
52+
53+// onUnmapNotify ends the mapped life of a managed window; a later MapRequest
54+// starts over and re-reads the properties. Synthetic events (ICCCM unmap
55+// requests) are not an unmap.
56+func (x *XWM) onUnmapNotify(ev *xturbo.Event) {
57+ if ev.SendEvent {
58+ return
59+ }
60+ s := x.tab.lookup(uint32(xproto.UnmapNotifyOf(ev).Window()))
61+ if s == 0 {
62+ return
63+ }
64+ rec := x.tab.rec(s)
65+ rec.mapped = false
66+ if rec.kind != kindManaged {
67+ return
68+ }
69+ wasMapping := rec.mapping
70+ rec.ready, rec.mapping, rec.pending = false, false, 0
71+ rec.serial, rec.surface = 0, 0
72+ if wasMapping {
73+ xid := rec.xid
74+ x.setProp32Typed(xid, atomWMState, x.atoms[atomWMState], []uint32{wmStateWithdrawn, 0})
75+ xproto.DeleteProperty(x.conn.Buf(), xproto.Window(xid), xproto.Atom(x.atoms[atomNetWMState]))
76+ x.unlist(xid)
77+ }
78+ if rec.id != 0 {
79+ x.host.Dissociate(s, false)
80+ }
81+}
82+
83+// setProp32Typed is setProp32 with an explicit property type atom.
84+func (x *XWM) setProp32Typed(win uint32, prop int, typ uint32, vals []uint32) {
85+ x.setProp32(win, prop, xproto.Atom(typ), vals)
86+}
87+
88+// checkReady fires Ready once every awaited reply is in.
89+func (x *XWM) checkReady(s Slot) {
90+ rec := x.tab.rec(s)
91+ if rec == nil || rec.pending != 0 || rec.ready || !rec.mapping || rec.kind != kindManaged || x.host == nil {
92+ return
93+ }
94+ rec.ready = true
95+ info := x.info(s)
96+ xid := rec.xid
97+
98+ var state [2]uint32
99+ n := 0
100+ if rec.modal {
101+ state[n] = x.atoms[atomNetWMStateModal]
102+ n++
103+ }
104+ if rec.fullscreen {
105+ state[n] = x.atoms[atomNetWMStateFullscreen]
106+ n++
107+ }
108+ x.setProp32(xid, atomNetWMState, xproto.AtomAtom, state[:n])
109+ x.setProp32Typed(xid, atomWMState, x.atoms[atomWMState], []uint32{wmStateNormal, 0})
110+ x.clients = append(x.clients, xid)
111+ x.stacking = append(x.stacking, xid)
112+ x.publishLists()
113+
114+ x.host.Ready(s, info)
115+ x.tryPair(s)
116+}
117+
118+// info is what the host learns when a window is ready.
119+func (x *XWM) info(s Slot) WindowInfo {
120+ rec := x.tab.rec(s)
121+ parent := x.managedSlot(rec.transient)
122+ fixed := rec.minW > 0 && rec.minH > 0 && rec.minW == rec.maxW && rec.minH == rec.maxH
123+ info := WindowInfo{
124+ PID: int(rec.pid),
125+ Floating: floatingType(rec.wtype) || parent != 0 || fixed,
126+ Parent: parent,
127+ Modal: rec.modal,
128+ Fullscreen: rec.fullscreen,
129+ Title: rec.title,
130+ AppID: rec.class,
131+ }
132+ if info.Floating {
133+ info.Width, info.Height = int(rec.w), int(rec.h)
134+ }
135+ return info
136+}
137+
138+func (x *XWM) onGeomReply(s Slot, rec *record, r *xturbo.Reply) {
139+ if g, ok := xproto.ParseGetGeometryReply(r.Raw); ok {
140+ rec.x, rec.y, rec.w, rec.h = int32(g.X()), int32(g.Y()), uint32(g.Width()), uint32(g.Height())
141+ }
142+ rec.pending &^= propGeom
143+ x.checkReady(s)
144+}
145+
146+// publishLists rewrites _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING.
147+func (x *XWM) publishLists() {
148+ x.setProp32(x.root, atomNetClientList, xproto.AtomWindow, x.clients)
149+ x.setProp32(x.root, atomNetClientListStacking, xproto.AtomWindow, x.stacking)
150+}
151+
152+// unlist drops xid from both client lists and republishes them if it was in.
153+func (x *XWM) unlist(xid uint32) {
154+ was := len(x.clients)
155+ x.clients = removeID(x.clients, xid)
156+ x.stacking = removeID(x.stacking, xid)
157+ if len(x.clients) != was {
158+ x.publishLists()
159+ }
160+}
161+
162+func removeID(l []uint32, xid uint32) []uint32 {
163+ for i, v := range l {
164+ if v == xid {
165+ return append(l[:i], l[i+1:]...)
166+ }
167+ }
168+ return l
169+}
170+
171+// onClientMessage handles WL_SURFACE_SERIAL (pairing), the legacy
172+// WL_SURFACE_ID (counted, ignored) and _NET_WM_STATE requests.
173+func (x *XWM) onClientMessage(ev *xturbo.Event) {
174+ e := xproto.ClientMessageOf(ev)
175+ if e.Format() != 32 {
176+ return
177+ }
178+ typ := uint32(e.Type())
179+ switch typ {
180+ case x.atoms[atomWLSurfaceSerial]:
181+ s := x.tab.lookup(uint32(e.Window()))
182+ if s == 0 {
183+ return
184+ }
185+ rec := x.tab.rec(s)
186+ if rec.serial != 0 {
187+ x.log.Debug().Uint32("xid", rec.xid).Msg("duplicate WL_SURFACE_SERIAL ignored")
188+ return
189+ }
190+ d := e.Data().Data32()
191+ rec.serial = uint64(d[1])<<32 | uint64(d[0])
192+ x.tryPair(s)
193+ case x.atoms[atomWLSurfaceID]:
194+ x.stats.LegacyID++
195+ if x.stats.LegacyID == 1 {
196+ x.log.Warn().Msg("WL_SURFACE_ID received: Xwayland did not bind xwayland_shell_v1, window ignored")
197+ }
198+ case x.atoms[atomNetWMState]:
199+ x.onNetWMState(e)
200+ }
201+}
202+
203+// onNetWMState forwards fullscreen requests to the host and records them; the
204+// property itself is written when core answers with a configure.
205+func (x *XWM) onNetWMState(e xproto.ClientMessageEvent) {
206+ s := x.tab.lookup(uint32(e.Window()))
207+ if s == 0 {
208+ return
209+ }
210+ rec := x.tab.rec(s)
211+ if rec.kind != kindManaged {
212+ return
213+ }
214+ d := e.Data().Data32()
215+ apply := func(cur bool) bool {
216+ switch d[0] {
217+ case netStateRemove:
218+ return false
219+ case netStateAdd:
220+ return true
221+ case netStateToggle:
222+ return !cur
223+ }
224+ return cur
225+ }
226+ for _, a := range d[1:3] {
227+ switch {
228+ case a == 0:
229+ case a == x.atoms[atomNetWMStateModal]:
230+ rec.modal = apply(rec.modal)
231+ case a == x.atoms[atomNetWMStateFullscreen]:
232+ on := apply(rec.fullscreen)
233+ if on == rec.fullscreen {
234+ continue
235+ }
236+ rec.fullscreen = on
237+ if rec.ready {
238+ x.host.Fullscreen(s, on)
239+ rec = x.tab.rec(s)
240+ }
241+ }
242+ }
243+}
244+
245+// onReparentNotify keeps the table to root's direct children: a window moved
246+// away is forgotten, one moved into root is treated as newly created.
247+func (x *XWM) onReparentNotify(ev *xturbo.Event) {
248+ e := xproto.ReparentNotifyOf(ev)
249+ xid := uint32(e.Window())
250+ if xid == x.wmWin {
251+ return
252+ }
253+ s := x.tab.lookup(xid)
254+ if uint32(e.Parent()) != x.root {
255+ if s != 0 {
256+ x.dropWindow(s)
257+ }
258+ return
259+ }
260+ if s != 0 {
261+ return
262+ }
263+ s = x.insert(xid)
264+ if s == 0 {
265+ return
266+ }
267+ rec := x.tab.rec(s)
268+ rec.override = e.OverrideRedirect()
269+ rec.x, rec.y = int32(e.X()), int32(e.Y())
270+ if rec.override {
271+ rec.kind = kindUnmanaged
272+ x.stats.UnmanagedSeen++
273+ return
274+ }
275+ if x.host != nil {
276+ x.adopt(s)
277+ }
278+}
279+
280+// dropWindow forgets s: the host is told if it knew the window.
281+func (x *XWM) dropWindow(s Slot) {
282+ rec := x.tab.rec(s)
283+ xid := rec.xid
284+ if rec.kind == kindManaged {
285+ x.unlist(xid)
286+ if x.active == s {
287+ x.active = 0
288+ }
289+ if rec.id != 0 && x.host != nil {
290+ rec.id = 0
291+ x.host.Dissociate(s, true)
292+ }
293+ }
294+ x.tab.remove(s)
295+}
296+
297+// tryPair links the window to the wl_surface carrying its serial, once both
298+// are known and the window is ready (the host maps on the surface's first
299+// buffer, so it must know the window's info by then). Unmanaged windows keep
300+// the surface but are never associated.
301+func (x *XWM) tryPair(s Slot) {
302+ rec := x.tab.rec(s)
303+ if rec.serial == 0 || rec.surface != 0 {
304+ return
305+ }
306+ if rec.kind == kindManaged && !rec.ready {
307+ return
308+ }
309+ for i := 0; i < x.unpairedN; i++ {
310+ if x.unpaired[i].serial != rec.serial {
311+ continue
312+ }
313+ rec.surface = x.unpaired[i].surface
314+ x.unpairedN--
315+ x.unpaired[i] = x.unpaired[x.unpairedN]
316+ if rec.kind == kindManaged {
317+ x.log.Debug().Uint32("xid", rec.xid).Uint64("surface", rec.surface).Msg("x11 window paired")
318+ x.host.Associate(s, rec.surface)
319+ }
320+ return
321+ }
322+}
323+
324+// SetSerial records the serial of a wl_surface (xwayland_surface_v1.set_serial)
325+// and pairs it with the window announcing the same serial.
326+func (x *XWM) SetSerial(surfaceID uint64, serial uint64) {
327+ if serial == 0 || x.closed {
328+ return
329+ }
330+ for i := 0; i < x.unpairedN; i++ {
331+ if x.unpaired[i].serial == serial {
332+ x.unpaired[i].surface = surfaceID
333+ x.pairSerial(serial)
334+ return
335+ }
336+ }
337+ if x.unpairedN == unpairedCap {
338+ copy(x.unpaired[:], x.unpaired[1:])
339+ x.unpairedN--
340+ if !x.warnedUnpaired {
341+ x.warnedUnpaired = true
342+ x.log.Warn().Msg("too many unpaired surfaces, dropping the oldest")
343+ }
344+ }
345+ x.unpaired[x.unpairedN] = struct{ serial, surface uint64 }{serial, surfaceID}
346+ x.unpairedN++
347+ x.pairSerial(serial)
348+}
349+
350+// pairSerial offers a newly known surface serial to the windows.
351+func (x *XWM) pairSerial(serial uint64) {
352+ for i := range x.tab.recs {
353+ if r := &x.tab.recs[i]; r.kind != kindFree && r.serial == serial && r.surface == 0 {
354+ x.tryPair(Slot(i + 1))
355+ break
356+ }
357+ }
358+ x.wake()
359+}
19360
20361 // SurfaceGone forgets a destroyed wl_surface.
21-func (x *XWM) SurfaceGone(surfaceID uint64) { _ = surfaceID }
362+func (x *XWM) SurfaceGone(surfaceID uint64) {
363+ for i := 0; i < x.unpairedN; i++ {
364+ if x.unpaired[i].surface == surfaceID {
365+ x.unpairedN--
366+ x.unpaired[i] = x.unpaired[x.unpairedN]
367+ break
368+ }
369+ }
370+ for i := range x.tab.recs {
371+ if r := &x.tab.recs[i]; r.kind != kindFree && r.surface == surfaceID {
372+ r.surface = 0
373+ }
374+ }
375+ x.wake()
376+}
@@ -1,21 +1,376 @@
1 package xwm1 package xwm
2 2
3-import "bnema.dev/xturbo"3+import (
4+ "bnema.dev/xturbo"
5+ "bnema.dev/xturbo/xproto"
6+)
4 7
5-// beginMap starts the map flow of a managed window: properties, geometry,8+// ICCCM WM_STATE values.
6-// MapWindow. Also called by Attach for windows mapped before it.9+const (
7-func (x *XWM) beginMap(s Slot) { _ = s }10+ wmStateWithdrawn uint32 = 0
11+ wmStateNormal uint32 = 1
12+)
8 13
9-func (x *XWM) onMapRequest(ev *xturbo.Event) { _ = ev }14+// _NET_WM_STATE client message actions.
10-func (x *XWM) onMapNotify(ev *xturbo.Event) { _ = ev }15+const (
11-func (x *XWM) onUnmapNotify(ev *xturbo.Event) { _ = ev }16+ netStateRemove = 0
12-func (x *XWM) onClientMessage(ev *xturbo.Event) { _ = ev }17+ netStateAdd = 1
18+ netStateToggle = 2
19+)
13 20
14-func (x *XWM) onGeomReply(s Slot, rec *record, r *xturbo.Reply) { _, _, _ = s, rec, r }21+// beginMap starts the map flow of a managed window: every property, the
15-func (x *XWM) onClientIdsReply(s Slot, rec *record, r *xturbo.Reply) { _, _, _ = s, rec, r }22+// geometry and MapWindow go out in one batch (wlroots order). Also called by
23+// Attach for windows mapped before it.
24+func (x *XWM) beginMap(s Slot) {
25+ rec := x.tab.rec(s)
26+ if rec.kind != kindManaged || rec.mapping {
27+ return
28+ }
29+ rec.mapping, rec.ready = true, false
30+ rec.hasNetName = false
31+ rec.pending = propGeom
32+ xid := rec.xid
33+ for idx := 0; idx < propCount; idx++ {
34+ x.requestProp(s, idx)
35+ }
36+ b := x.conn.Buf()
37+ x.expect(xproto.GetGeometry(b, xproto.Drawable(xid)), kindGeom, x.tab.tag(s), 0)
38+ xproto.MapWindow(b, xproto.Window(xid))
39+}
16 40
17-// SetSerial records the serial of a wl_surface (xwayland_surface_v1.set_serial).41+func (x *XWM) onMapRequest(ev *xturbo.Event) {
18-func (x *XWM) SetSerial(surfaceID uint64, serial uint64) { _, _ = surfaceID, serial }42+ if s := x.tab.lookup(uint32(xproto.MapRequestOf(ev).Window())); s != 0 {
43+ x.beginMap(s)
44+ }
45+}
46+
47+func (x *XWM) onMapNotify(ev *xturbo.Event) {
48+ if s := x.tab.lookup(uint32(xproto.MapNotifyOf(ev).Window())); s != 0 {
49+ x.tab.rec(s).mapped = true
50+ }
51+}
52+
53+// onUnmapNotify ends the mapped life of a managed window; a later MapRequest
54+// starts over and re-reads the properties. Synthetic events (ICCCM unmap
55+// requests) are not an unmap.
56+func (x *XWM) onUnmapNotify(ev *xturbo.Event) {
57+ if ev.SendEvent {
58+ return
59+ }
60+ s := x.tab.lookup(uint32(xproto.UnmapNotifyOf(ev).Window()))
61+ if s == 0 {
62+ return
63+ }
64+ rec := x.tab.rec(s)
65+ rec.mapped = false
66+ if rec.kind != kindManaged {
67+ return
68+ }
69+ wasMapping := rec.mapping
70+ rec.ready, rec.mapping, rec.pending = false, false, 0
71+ rec.serial, rec.surface = 0, 0
72+ if wasMapping {
73+ xid := rec.xid
74+ x.setProp32Typed(xid, atomWMState, x.atoms[atomWMState], []uint32{wmStateWithdrawn, 0})
75+ xproto.DeleteProperty(x.conn.Buf(), xproto.Window(xid), xproto.Atom(x.atoms[atomNetWMState]))
76+ x.unlist(xid)
77+ }
78+ if rec.id != 0 {
79+ x.host.Dissociate(s, false)
80+ }
81+}
82+
83+// setProp32Typed is setProp32 with an explicit property type atom.
84+func (x *XWM) setProp32Typed(win uint32, prop int, typ uint32, vals []uint32) {
85+ x.setProp32(win, prop, xproto.Atom(typ), vals)
86+}
87+
88+// checkReady fires Ready once every awaited reply is in.
89+func (x *XWM) checkReady(s Slot) {
90+ rec := x.tab.rec(s)
91+ if rec == nil || rec.pending != 0 || rec.ready || !rec.mapping || rec.kind != kindManaged || x.host == nil {
92+ return
93+ }
94+ rec.ready = true
95+ info := x.info(s)
96+ xid := rec.xid
97+
98+ var state [2]uint32
99+ n := 0
100+ if rec.modal {
101+ state[n] = x.atoms[atomNetWMStateModal]
102+ n++
103+ }
104+ if rec.fullscreen {
105+ state[n] = x.atoms[atomNetWMStateFullscreen]
106+ n++
107+ }
108+ x.setProp32(xid, atomNetWMState, xproto.AtomAtom, state[:n])
109+ x.setProp32Typed(xid, atomWMState, x.atoms[atomWMState], []uint32{wmStateNormal, 0})
110+ x.clients = append(x.clients, xid)
111+ x.stacking = append(x.stacking, xid)
112+ x.publishLists()
113+
114+ x.host.Ready(s, info)
115+ x.tryPair(s)
116+}
117+
118+// info is what the host learns when a window is ready.
119+func (x *XWM) info(s Slot) WindowInfo {
120+ rec := x.tab.rec(s)
121+ parent := x.managedSlot(rec.transient)
122+ fixed := rec.minW > 0 && rec.minH > 0 && rec.minW == rec.maxW && rec.minH == rec.maxH
123+ info := WindowInfo{
124+ PID: int(rec.pid),
125+ Floating: floatingType(rec.wtype) || parent != 0 || fixed,
126+ Parent: parent,
127+ Modal: rec.modal,
128+ Fullscreen: rec.fullscreen,
129+ Title: rec.title,
130+ AppID: rec.class,
131+ }
132+ if info.Floating {
133+ info.Width, info.Height = int(rec.w), int(rec.h)
134+ }
135+ return info
136+}
137+
138+func (x *XWM) onGeomReply(s Slot, rec *record, r *xturbo.Reply) {
139+ if g, ok := xproto.ParseGetGeometryReply(r.Raw); ok {
140+ rec.x, rec.y, rec.w, rec.h = int32(g.X()), int32(g.Y()), uint32(g.Width()), uint32(g.Height())
141+ }
142+ rec.pending &^= propGeom
143+ x.checkReady(s)
144+}
145+
146+// publishLists rewrites _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING.
147+func (x *XWM) publishLists() {
148+ x.setProp32(x.root, atomNetClientList, xproto.AtomWindow, x.clients)
149+ x.setProp32(x.root, atomNetClientListStacking, xproto.AtomWindow, x.stacking)
150+}
151+
152+// unlist drops xid from both client lists and republishes them if it was in.
153+func (x *XWM) unlist(xid uint32) {
154+ was := len(x.clients)
155+ x.clients = removeID(x.clients, xid)
156+ x.stacking = removeID(x.stacking, xid)
157+ if len(x.clients) != was {
158+ x.publishLists()
159+ }
160+}
161+
162+func removeID(l []uint32, xid uint32) []uint32 {
163+ for i, v := range l {
164+ if v == xid {
165+ return append(l[:i], l[i+1:]...)
166+ }
167+ }
168+ return l
169+}
170+
171+// onClientMessage handles WL_SURFACE_SERIAL (pairing), the legacy
172+// WL_SURFACE_ID (counted, ignored) and _NET_WM_STATE requests.
173+func (x *XWM) onClientMessage(ev *xturbo.Event) {
174+ e := xproto.ClientMessageOf(ev)
175+ if e.Format() != 32 {
176+ return
177+ }
178+ typ := uint32(e.Type())
179+ switch typ {
180+ case x.atoms[atomWLSurfaceSerial]:
181+ s := x.tab.lookup(uint32(e.Window()))
182+ if s == 0 {
183+ return
184+ }
185+ rec := x.tab.rec(s)
186+ if rec.serial != 0 {
187+ x.log.Debug().Uint32("xid", rec.xid).Msg("duplicate WL_SURFACE_SERIAL ignored")
188+ return
189+ }
190+ d := e.Data().Data32()
191+ rec.serial = uint64(d[1])<<32 | uint64(d[0])
192+ x.tryPair(s)
193+ case x.atoms[atomWLSurfaceID]:
194+ x.stats.LegacyID++
195+ if x.stats.LegacyID == 1 {
196+ x.log.Warn().Msg("WL_SURFACE_ID received: Xwayland did not bind xwayland_shell_v1, window ignored")
197+ }
198+ case x.atoms[atomNetWMState]:
199+ x.onNetWMState(e)
200+ }
201+}
202+
203+// onNetWMState forwards fullscreen requests to the host and records them; the
204+// property itself is written when core answers with a configure.
205+func (x *XWM) onNetWMState(e xproto.ClientMessageEvent) {
206+ s := x.tab.lookup(uint32(e.Window()))
207+ if s == 0 {
208+ return
209+ }
210+ rec := x.tab.rec(s)
211+ if rec.kind != kindManaged {
212+ return
213+ }
214+ d := e.Data().Data32()
215+ apply := func(cur bool) bool {
216+ switch d[0] {
217+ case netStateRemove:
218+ return false
219+ case netStateAdd:
220+ return true
221+ case netStateToggle:
222+ return !cur
223+ }
224+ return cur
225+ }
226+ for _, a := range d[1:3] {
227+ switch {
228+ case a == 0:
229+ case a == x.atoms[atomNetWMStateModal]:
230+ rec.modal = apply(rec.modal)
231+ case a == x.atoms[atomNetWMStateFullscreen]:
232+ on := apply(rec.fullscreen)
233+ if on == rec.fullscreen {
234+ continue
235+ }
236+ rec.fullscreen = on
237+ if rec.ready {
238+ x.host.Fullscreen(s, on)
239+ rec = x.tab.rec(s)
240+ }
241+ }
242+ }
243+}
244+
245+// onReparentNotify keeps the table to root's direct children: a window moved
246+// away is forgotten, one moved into root is treated as newly created.
247+func (x *XWM) onReparentNotify(ev *xturbo.Event) {
248+ e := xproto.ReparentNotifyOf(ev)
249+ xid := uint32(e.Window())
250+ if xid == x.wmWin {
251+ return
252+ }
253+ s := x.tab.lookup(xid)
254+ if uint32(e.Parent()) != x.root {
255+ if s != 0 {
256+ x.dropWindow(s)
257+ }
258+ return
259+ }
260+ if s != 0 {
261+ return
262+ }
263+ s = x.insert(xid)
264+ if s == 0 {
265+ return
266+ }
267+ rec := x.tab.rec(s)
268+ rec.override = e.OverrideRedirect()
269+ rec.x, rec.y = int32(e.X()), int32(e.Y())
270+ if rec.override {
271+ rec.kind = kindUnmanaged
272+ x.stats.UnmanagedSeen++
273+ return
274+ }
275+ if x.host != nil {
276+ x.adopt(s)
277+ }
278+}
279+
280+// dropWindow forgets s: the host is told if it knew the window.
281+func (x *XWM) dropWindow(s Slot) {
282+ rec := x.tab.rec(s)
283+ xid := rec.xid
284+ if rec.kind == kindManaged {
285+ x.unlist(xid)
286+ if x.active == s {
287+ x.active = 0
288+ }
289+ if rec.id != 0 && x.host != nil {
290+ rec.id = 0
291+ x.host.Dissociate(s, true)
292+ }
293+ }
294+ x.tab.remove(s)
295+}
296+
297+// tryPair links the window to the wl_surface carrying its serial, once both
298+// are known and the window is ready (the host maps on the surface's first
299+// buffer, so it must know the window's info by then). Unmanaged windows keep
300+// the surface but are never associated.
301+func (x *XWM) tryPair(s Slot) {
302+ rec := x.tab.rec(s)
303+ if rec.serial == 0 || rec.surface != 0 {
304+ return
305+ }
306+ if rec.kind == kindManaged && !rec.ready {
307+ return
308+ }
309+ for i := 0; i < x.unpairedN; i++ {
310+ if x.unpaired[i].serial != rec.serial {
311+ continue
312+ }
313+ rec.surface = x.unpaired[i].surface
314+ x.unpairedN--
315+ x.unpaired[i] = x.unpaired[x.unpairedN]
316+ if rec.kind == kindManaged {
317+ x.log.Debug().Uint32("xid", rec.xid).Uint64("surface", rec.surface).Msg("x11 window paired")
318+ x.host.Associate(s, rec.surface)
319+ }
320+ return
321+ }
322+}
323+
324+// SetSerial records the serial of a wl_surface (xwayland_surface_v1.set_serial)
325+// and pairs it with the window announcing the same serial.
326+func (x *XWM) SetSerial(surfaceID uint64, serial uint64) {
327+ if serial == 0 || x.closed {
328+ return
329+ }
330+ for i := 0; i < x.unpairedN; i++ {
331+ if x.unpaired[i].serial == serial {
332+ x.unpaired[i].surface = surfaceID
333+ x.pairSerial(serial)
334+ return
335+ }
336+ }
337+ if x.unpairedN == unpairedCap {
338+ copy(x.unpaired[:], x.unpaired[1:])
339+ x.unpairedN--
340+ if !x.warnedUnpaired {
341+ x.warnedUnpaired = true
342+ x.log.Warn().Msg("too many unpaired surfaces, dropping the oldest")
343+ }
344+ }
345+ x.unpaired[x.unpairedN] = struct{ serial, surface uint64 }{serial, surfaceID}
346+ x.unpairedN++
347+ x.pairSerial(serial)
348+}
349+
350+// pairSerial offers a newly known surface serial to the windows.
351+func (x *XWM) pairSerial(serial uint64) {
352+ for i := range x.tab.recs {
353+ if r := &x.tab.recs[i]; r.kind != kindFree && r.serial == serial && r.surface == 0 {
354+ x.tryPair(Slot(i + 1))
355+ break
356+ }
357+ }
358+ x.wake()
359+}
19 360
20 // SurfaceGone forgets a destroyed wl_surface.361 // SurfaceGone forgets a destroyed wl_surface.
21-func (x *XWM) SurfaceGone(surfaceID uint64) { _ = surfaceID }362+func (x *XWM) SurfaceGone(surfaceID uint64) {
363+ for i := 0; i < x.unpairedN; i++ {
364+ if x.unpaired[i].surface == surfaceID {
365+ x.unpairedN--
366+ x.unpaired[i] = x.unpaired[x.unpairedN]
367+ break
368+ }
369+ }
370+ for i := range x.tab.recs {
371+ if r := &x.tab.recs[i]; r.kind != kindFree && r.surface == surfaceID {
372+ r.surface = 0
373+ }
374+ }
375+ x.wake()
376+}
A internal/adapters/wayland/xwm/mapflow_test.go +789 -0
@@ -0,0 +1,789 @@
1+package xwm
2+
3+import (
4+ "encoding/binary"
5+ "testing"
6+
7+ "bnema.dev/xturbo"
8+ "bnema.dev/xturbo/xproto"
9+ "github.com/bnema/neferwl/internal/ports"
10+ "github.com/stretchr/testify/mock"
11+ "github.com/stretchr/testify/require"
12+)
13+
14+// harness bundles an attached XWM, its peer and the host mock.
15+type harness struct {
16+ t *testing.T
17+ x *XWM
18+ p *xpeer
19+ h *mockhost
20+
21+ ready map[uint32]WindowInfo // by xid, filled by Ready
22+ next ports.WindowID
23+}
24+
25+func newHarness(t *testing.T, mutate func(p *xpeer)) *harness {
26+ t.Helper()
27+ x, p := bootPeer(t, mutate)
28+ hs := &harness{t: t, x: x, p: p, h: newMockhost(t), ready: map[uint32]WindowInfo{}}
29+ src := newMockfdSource(t)
30+ src.EXPECT().Remove().Maybe()
31+ src.EXPECT().SetWritable(mock.Anything).Return(nil).Maybe()
32+ post, _ := newPost()
33+ x.attach(src, hs.h, post)
34+ return hs
35+}
36+
37+func (hs *harness) expectCreated(win uint32) {
38+ hs.next++
39+ hs.h.EXPECT().Created(mock.Anything, win).Return(hs.next).Once()
40+}
41+
42+func (hs *harness) expectReady(win uint32) {
43+ hs.h.EXPECT().Ready(mock.Anything, mock.Anything).Run(func(_ Slot, info WindowInfo) {
44+ hs.ready[win] = info
45+ }).Once()
46+}
47+
48+func (hs *harness) create(win uint32) Slot {
49+ hs.t.Helper()
50+ hs.expectCreated(win)
51+ hs.p.createNotify(win, false)
52+ readUntil(hs.t, hs.x, func() bool { return hs.x.Slot(win) != 0 })
53+ return hs.x.Slot(win)
54+}
55+
56+// mapAndWait creates and maps win and waits for Ready.
57+func (hs *harness) mapAndWait(win uint32) Slot {
58+ hs.t.Helper()
59+ s := hs.create(win)
60+ hs.expectReady(win)
61+ hs.p.mapRequest(win)
62+ readUntil(hs.t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
63+ return s
64+}
65+
66+func (hs *harness) str(win uint32, atom string, typ uint32, s string) {
67+ hs.p.setProp(win, hs.p.atom(atom), typ, 8, []byte(s))
68+}
69+
70+func (hs *harness) words(win uint32, atom string, typ uint32, w ...uint32) {
71+ hs.p.setProp(win, hs.p.atom(atom), typ, 32, le32(w...))
72+}
73+
74+func (hs *harness) atoms(win uint32, atom string, names ...string) {
75+ var w []uint32
76+ for _, n := range names {
77+ w = append(w, hs.p.atom(n))
78+ }
79+ hs.words(win, atom, uint32(xproto.AtomAtom), w...)
80+}
81+
82+func (hs *harness) classic(win uint32) {
83+ hs.p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("inst\x00Class\x00"))
84+ hs.str(win, "_NET_WM_NAME", hs.p.atom("UTF8_STRING"), "net title")
85+ hs.p.setProp(win, uint32(xproto.AtomWmName), uint32(xproto.AtomString), 8, []byte("old title"))
86+ hs.words(win, "_NET_WM_PID", uint32(xproto.AtomCardinal), 4242)
87+ hs.p.set(func(p *xpeer) { p.geoms[win] = xpeerGeom{w: 300, h: 200} })
88+}
89+
90+func (hs *harness) count(match func(r xpeerReq) bool) int {
91+ n := 0
92+ for _, r := range hs.p.requests() {
93+ if match(r) {
94+ n++
95+ }
96+ }
97+ return n
98+}
99+
100+func (hs *harness) waitMap(win uint32) {
101+ hs.t.Helper()
102+ readUntil(hs.t, hs.x, func() bool {
103+ return hs.count(func(r xpeerReq) bool { return r.major == opMapWindow && r.u32(4) == win }) > 0
104+ })
105+}
106+
107+func TestMapFlowBatchesRequestsAndReadyFiresOnce(t *testing.T) {
108+ hs := newHarness(t, nil)
109+ const win = 0x500001
110+ hs.classic(win)
111+ hs.create(win)
112+ base := len(hs.p.requests())
113+
114+ hs.p.holdReplies()
115+ hs.p.mapRequest(win)
116+ hs.expectReady(win)
117+ hs.waitMap(win)
118+
119+ reqs := hs.p.requests()[base:]
120+ want := []uint32{
121+ uint32(xproto.AtomWmClass), uint32(xproto.AtomWmName), hs.p.atom("_NET_WM_NAME"), uint32(xproto.AtomWmHints),
122+ uint32(xproto.AtomWmNormalHints), hs.p.atom("WM_PROTOCOLS"), uint32(xproto.AtomWmTransientFor),
123+ hs.p.atom("_NET_WM_WINDOW_TYPE"), hs.p.atom("_NET_WM_STATE"), hs.p.atom("_NET_WM_PID"),
124+ }
125+ require.GreaterOrEqual(t, len(reqs), len(want)+2)
126+ for i, a := range want {
127+ require.EqualValues(t, opGetProperty, reqs[i].major, i)
128+ require.Equal(t, uint32(win), reqs[i].u32(4))
129+ require.Equal(t, a, reqs[i].u32(8), i)
130+ require.EqualValues(t, 1024, reqs[i].u32(20), "long length: one 4 KiB read")
131+ }
132+ require.EqualValues(t, opGetGeometry, reqs[len(want)].major)
133+ require.EqualValues(t, opMapWindow, reqs[len(want)+1].major, "MapWindow in the same batch")
134+
135+ hs.p.release() // every reply arrives after MapWindow went out
136+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
137+ info := hs.ready[win]
138+ require.Equal(t, WindowInfo{PID: 4242, Title: "net title", AppID: "Class"}, info, "_NET_WM_NAME wins, class is the second string")
139+
140+ state, ok := hs.p.prop(win, "WM_STATE")
141+ require.True(t, ok)
142+ require.Equal(t, []uint32{1, 0}, atomsOf(state))
143+ list, ok := hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
144+ require.True(t, ok)
145+ require.Equal(t, []uint32{win}, atomsOf(list))
146+}
147+
148+func TestMapFlowFloatingAndModal(t *testing.T) {
149+ hint := func(minw, minh, maxw, maxh uint32) []byte {
150+ return le32(hintMinSize|hintMaxSize, 0, 0, 0, 0, minw, minh, maxw, maxh, 0, 0, 0, 0, 0, 0, 0, 0, 0)
151+ }
152+ cases := []struct {
153+ name string
154+ setup func(hs *harness, win uint32)
155+ want func(info WindowInfo)
156+ }{
157+ {"dialog type", func(hs *harness, w uint32) {
158+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_DIALOG")
159+ }, func(i WindowInfo) {
160+ require.True(t, i.Floating)
161+ require.Equal(t, 300, i.Width)
162+ require.Equal(t, 200, i.Height)
163+ }},
164+ {"utility type", func(hs *harness, w uint32) {
165+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_UTILITY")
166+ }, func(i WindowInfo) { require.True(t, i.Floating) }},
167+ {"splash type", func(hs *harness, w uint32) {
168+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_SPLASH")
169+ }, func(i WindowInfo) { require.True(t, i.Floating) }},
170+ {"normal type", func(hs *harness, w uint32) {
171+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_NORMAL")
172+ }, func(i WindowInfo) { require.False(t, i.Floating); require.Zero(t, i.Width) }},
173+ {"fixed size", func(hs *harness, w uint32) {
174+ hs.p.setProp(w, uint32(xproto.AtomWmNormalHints), uint32(xproto.AtomWmSizeHints), 32, hint(200, 100, 200, 100))
175+ }, func(i WindowInfo) { require.True(t, i.Floating) }},
176+ {"resizable", func(hs *harness, w uint32) {
177+ hs.p.setProp(w, uint32(xproto.AtomWmNormalHints), uint32(xproto.AtomWmSizeHints), 32, hint(100, 100, 800, 600))
178+ }, func(i WindowInfo) { require.False(t, i.Floating) }},
179+ {"modal", func(hs *harness, w uint32) {
180+ hs.atoms(w, "_NET_WM_STATE", "_NET_WM_STATE_MODAL")
181+ }, func(i WindowInfo) { require.True(t, i.Modal); require.False(t, i.Fullscreen) }},
182+ {"fullscreen at map", func(hs *harness, w uint32) {
183+ hs.atoms(w, "_NET_WM_STATE", "_NET_WM_STATE_FULLSCREEN")
184+ }, func(i WindowInfo) { require.True(t, i.Fullscreen) }},
185+ }
186+ for _, c := range cases {
187+ t.Run(c.name, func(t *testing.T) {
188+ hs := newHarness(t, nil)
189+ const win = 0x500001
190+ hs.classic(win)
191+ c.setup(hs, win)
192+ hs.mapAndWait(win)
193+ c.want(hs.ready[win])
194+ })
195+ }
196+}
197+
198+func TestMapFlowTransientForManagedWindowFloats(t *testing.T) {
199+ hs := newHarness(t, nil)
200+ const parent, child = 0x500001, 0x500002
201+ hs.classic(parent)
202+ ps := hs.mapAndWait(parent)
203+ hs.classic(child)
204+ hs.p.setProp(child, uint32(xproto.AtomWmTransientFor), uint32(xproto.AtomWindow), 32, le32(parent))
205+ hs.mapAndWait(child)
206+ require.True(t, hs.ready[child].Floating)
207+ require.Equal(t, ps, hs.ready[child].Parent)
208+}
209+
210+func TestMapFlowTransientForUnknownWindowIsNotAParent(t *testing.T) {
211+ hs := newHarness(t, nil)
212+ const win = 0x500001
213+ hs.classic(win)
214+ hs.p.setProp(win, uint32(xproto.AtomWmTransientFor), uint32(xproto.AtomWindow), 32, le32(0x777777))
215+ hs.mapAndWait(win)
216+ require.Zero(t, hs.ready[win].Parent)
217+ require.False(t, hs.ready[win].Floating)
218+}
219+
220+func TestMapFlowClassFallsBackToInstance(t *testing.T) {
221+ hs := newHarness(t, nil)
222+ const win = 0x500001
223+ hs.classic(win)
224+ hs.p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("onlyinstance\x00"))
225+ hs.mapAndWait(win)
226+ require.Equal(t, "onlyinstance", hs.ready[win].AppID)
227+}
228+
229+func TestMapFlowTitleFallsBackToWMName(t *testing.T) {
230+ hs := newHarness(t, nil)
231+ const win = 0x500001
232+ hs.classic(win)
233+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_NAME"]) })
234+ hs.mapAndWait(win)
235+ require.Equal(t, "old title", hs.ready[win].Title)
236+}
237+
238+func TestMapFlowPIDFromXRes(t *testing.T) {
239+ hs := newHarness(t, func(p *xpeer) { p.pids = map[uint32]uint32{0x500001: 777} })
240+ const win = 0x500001
241+ hs.classic(win)
242+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_PID"]) })
243+ hs.create(win)
244+ hs.expectReady(win)
245+ hs.p.mapRequest(win)
246+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
247+ require.Equal(t, 777, hs.ready[win].PID, "Ready waited for the QueryClientIds reply")
248+ require.Equal(t, 1, hs.count(func(r xpeerReq) bool { return r.major == 144 && r.minor == 4 }))
249+ require.Zero(t, hs.x.tab.rec(hs.x.Slot(win)).pending)
250+}
251+
252+func TestMapFlowPIDUnknownStillReady(t *testing.T) {
253+ hs := newHarness(t, nil)
254+ const win = 0x500001
255+ hs.classic(win)
256+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_PID"]) })
257+ hs.mapAndWait(win)
258+ require.Zero(t, hs.ready[win].PID)
259+}
260+
261+func TestMapFlowNoXResAsksNothing(t *testing.T) {
262+ exts := fullPeerExts()
263+ delete(exts, "X-Resource")
264+ fd, p := startXPeer(t, exts)
265+ x, err := Boot(fd, testLog())
266+ require.NoError(t, err)
267+ t.Cleanup(x.CloseConn)
268+ h := newMockhost(t)
269+ src := newMockfdSource(t)
270+ src.EXPECT().Remove().Maybe()
271+ post, _ := newPost()
272+ x.attach(src, h, post)
273+ hs := &harness{t: t, x: x, p: p, h: h, ready: map[uint32]WindowInfo{}}
274+ const win = 0x500001
275+ hs.classic(win)
276+ p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_PID"]) })
277+ hs.mapAndWait(win)
278+ require.Zero(t, hs.count(func(r xpeerReq) bool { return r.major == 144 }))
279+}
280+
281+func TestMapFlowBadWindowOnPropertiesNeverReady(t *testing.T) {
282+ hs := newHarness(t, nil)
283+ const win = 0x500001
284+ hs.classic(win)
285+ hs.p.set(func(p *xpeer) { p.bad[win] = true })
286+ s := hs.create(win)
287+ hs.p.mapRequest(win)
288+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Errors >= uint64(propCount)+1 })
289+ require.Zero(t, hs.x.tab.rec(s).pending, "failed replies are no longer awaited")
290+ require.False(t, hs.x.tab.rec(s).ready)
291+ require.Zero(t, hs.x.Stats().Unattributed)
292+
293+ hs.h.EXPECT().Dissociate(s, true).Once()
294+ hs.p.destroyNotify(win)
295+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) == 0 })
296+}
297+
298+func TestMapRequestTwiceBeforeReadyAsksOnce(t *testing.T) {
299+ hs := newHarness(t, nil)
300+ const win = 0x500001
301+ hs.classic(win)
302+ hs.create(win)
303+ hs.p.holdReplies()
304+ hs.p.mapRequest(win)
305+ hs.p.mapRequest(win)
306+ hs.expectReady(win)
307+ hs.waitMap(win)
308+ hs.p.release()
309+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
310+ require.Equal(t, 1, hs.count(func(r xpeerReq) bool { return r.major == opMapWindow && r.u32(4) == win }))
311+}
312+
313+func TestMapNotifyMarksMapped(t *testing.T) {
314+ hs := newHarness(t, nil)
315+ const win = 0x500001
316+ hs.classic(win)
317+ s := hs.mapAndWait(win)
318+ hs.p.mapNotify(win)
319+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).mapped })
320+}
321+
322+// Property changes after Ready.
323+
324+func (hs *harness) notify(win uint32, atom string, deleted bool) {
325+ hs.t.Helper()
326+ before := hs.x.Stats().Replies
327+ hs.p.propertyNotify(win, hs.p.atom(atom), deleted)
328+ if deleted {
329+ hs.p.propertyNotify(win, hs.p.atom("WM_PROTOCOLS"), false) // sentinel: one reply follows
330+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
331+ return
332+ }
333+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
334+}
335+
336+func TestPropertyChangedTitleReachesHostOnce(t *testing.T) {
337+ hs := newHarness(t, nil)
338+ const win = 0x500001
339+ hs.classic(win)
340+ s := hs.mapAndWait(win)
341+
342+ hs.h.EXPECT().Title(s, "renamed").Once()
343+ hs.str(win, "_NET_WM_NAME", hs.p.atom("UTF8_STRING"), "renamed")
344+ hs.notify(win, "_NET_WM_NAME", false)
345+ require.Equal(t, "renamed", hs.x.tab.rec(s).title)
346+
347+ // Same value again: no host call (the mock fails on an unexpected one).
348+ hs.notify(win, "_NET_WM_NAME", false)
349+
350+ // WM_NAME is ignored while _NET_WM_NAME is set.
351+ hs.p.setProp(win, uint32(xproto.AtomWmName), uint32(xproto.AtomString), 8, []byte("legacy"))
352+ before := hs.x.Stats().Events
353+ hs.p.propertyNotify(win, uint32(xproto.AtomWmName), false)
354+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events > before })
355+ require.Equal(t, "renamed", hs.x.tab.rec(s).title)
356+}
357+
358+func TestPropertyNetNameDeletedFallsBackToWMName(t *testing.T) {
359+ hs := newHarness(t, nil)
360+ const win = 0x500001
361+ hs.classic(win)
362+ s := hs.mapAndWait(win)
363+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_NAME"]) })
364+ hs.h.EXPECT().Title(s, "old title").Once()
365+ hs.p.propertyNotify(win, hs.p.atom("_NET_WM_NAME"), true)
366+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).title == "old title" })
367+}
368+
369+func TestPropertyClassChangeReachesHost(t *testing.T) {
370+ hs := newHarness(t, nil)
371+ const win = 0x500001
372+ hs.classic(win)
373+ s := hs.mapAndWait(win)
374+ hs.h.EXPECT().AppID(s, "Other").Once()
375+ hs.p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("i\x00Other\x00"))
376+ hs.p.propertyNotify(win, uint32(xproto.AtomWmClass), false)
377+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).class == "Other" })
378+}
379+
380+func TestPropertyTransientChangeReachesHost(t *testing.T) {
381+ hs := newHarness(t, nil)
382+ const a, b = 0x500001, 0x500002
383+ hs.classic(a)
384+ as := hs.mapAndWait(a)
385+ hs.classic(b)
386+ bs := hs.mapAndWait(b)
387+ hs.h.EXPECT().Parent(bs, as).Once()
388+ hs.p.setProp(b, uint32(xproto.AtomWmTransientFor), uint32(xproto.AtomWindow), 32, le32(a))
389+ hs.p.propertyNotify(b, uint32(xproto.AtomWmTransientFor), false)
390+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(bs).transient == a })
391+}
392+
393+func TestPropertyFullscreenStateChangeReachesHost(t *testing.T) {
394+ hs := newHarness(t, nil)
395+ const win = 0x500001
396+ hs.classic(win)
397+ s := hs.mapAndWait(win)
398+ hs.h.EXPECT().Fullscreen(s, true).Once()
399+ hs.atoms(win, "_NET_WM_STATE", "_NET_WM_STATE_FULLSCREEN")
400+ hs.p.propertyNotify(win, hs.p.atom("_NET_WM_STATE"), false)
401+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).fullscreen })
402+}
403+
404+func TestPropertyInputProtocolsAndHints(t *testing.T) {
405+ hs := newHarness(t, nil)
406+ const win = 0x500001
407+ hs.classic(win)
408+ hs.atoms(win, "WM_PROTOCOLS", "WM_DELETE_WINDOW", "WM_TAKE_FOCUS")
409+ hs.p.setProp(win, uint32(xproto.AtomWmHints), uint32(xproto.AtomWmHints), 32, le32(hintInput, 0, 0, 0, 0, 0, 0, 0, 0))
410+ s := hs.mapAndWait(win)
411+ r := hs.x.tab.rec(s)
412+ require.True(t, r.deleteWindow)
413+ require.True(t, r.takeFocus)
414+ require.False(t, r.input, "WM_HINTS input = false")
415+
416+ // No WM_HINTS: input defaults to true.
417+ const other = 0x500002
418+ hs.classic(other)
419+ o := hs.mapAndWait(other)
420+ require.True(t, hs.x.tab.rec(o).input)
421+ require.False(t, hs.x.tab.rec(o).deleteWindow)
422+}
423+
424+func TestPropertyUnwatchedAtomAsksNothing(t *testing.T) {
425+ hs := newHarness(t, nil)
426+ const win = 0x500001
427+ hs.classic(win)
428+ hs.mapAndWait(win)
429+ n := len(hs.p.requests())
430+ hs.p.propertyNotify(win, hs.p.atom("_MOTIF_WM_HINTS"), false)
431+ hs.p.propertyNotify(0x999999, hs.p.atom("_NET_WM_NAME"), false) // unknown window
432+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 2 })
433+ require.Len(t, hs.p.requests(), n)
434+}
435+
436+// Pairing.
437+
438+func (hs *harness) serialMsg(win uint32, serial uint64) {
439+ hs.p.clientMessage(win, hs.p.atom("WL_SURFACE_SERIAL"), uint32(serial), uint32(serial>>32))
440+ hs.t.Helper()
441+ before := hs.x.Stats().Events
442+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Events > before })
443+}
444+
445+func TestPairingSerialBeforeSetSerial(t *testing.T) {
446+ hs := newHarness(t, nil)
447+ const win = 0x500001
448+ hs.classic(win)
449+ s := hs.mapAndWait(win)
450+ hs.serialMsg(win, 0x1_0000_0002)
451+ require.EqualValues(t, 0x1_0000_0002, hs.x.tab.rec(s).serial, "high word in data32[1]")
452+ hs.h.EXPECT().Associate(s, uint64(99)).Once()
453+ hs.x.SetSerial(99, 0x1_0000_0002)
454+ require.EqualValues(t, 99, hs.x.tab.rec(s).surface)
455+ require.Zero(t, hs.x.unpairedN)
456+}
457+
458+func TestPairingSetSerialBeforeSerial(t *testing.T) {
459+ hs := newHarness(t, nil)
460+ const win = 0x500001
461+ hs.classic(win)
462+ s := hs.mapAndWait(win)
463+ hs.x.SetSerial(99, 7)
464+ require.Equal(t, 1, hs.x.unpairedN)
465+ hs.h.EXPECT().Associate(s, uint64(99)).Once()
466+ hs.serialMsg(win, 7)
467+ require.Zero(t, hs.x.unpairedN)
468+}
469+
470+func TestPairingWaitsForReady(t *testing.T) {
471+ hs := newHarness(t, nil)
472+ const win = 0x500001
473+ hs.classic(win)
474+ s := hs.create(win)
475+ hs.x.SetSerial(5, 3)
476+ hs.serialMsg(win, 3) // not mapped yet: no Associate (the mock fails on one)
477+ require.Zero(t, hs.x.tab.rec(s).surface)
478+
479+ hs.h.EXPECT().Associate(s, uint64(5)).Once()
480+ hs.expectReady(win)
481+ hs.p.mapRequest(win)
482+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).surface != 0 })
483+}
484+
485+func TestPairingSerialForUnknownWindowIgnored(t *testing.T) {
486+ hs := newHarness(t, nil)
487+ hs.x.SetSerial(5, 3)
488+ hs.serialMsg(0x888888, 3)
489+ require.Equal(t, 1, hs.x.unpairedN, "stays unpaired")
490+}
491+
492+func TestPairingSurfaceGoneBeforePair(t *testing.T) {
493+ hs := newHarness(t, nil)
494+ const win = 0x500001
495+ hs.classic(win)
496+ s := hs.mapAndWait(win)
497+ hs.x.SetSerial(5, 3)
498+ hs.x.SurfaceGone(5)
499+ require.Zero(t, hs.x.unpairedN)
500+ hs.serialMsg(win, 3) // the surface is gone: nothing to pair with
501+ require.Zero(t, hs.x.tab.rec(s).surface)
502+}
503+
504+func TestPairingSurfaceGoneAfterPairClearsLink(t *testing.T) {
505+ hs := newHarness(t, nil)
506+ const win = 0x500001
507+ hs.classic(win)
508+ s := hs.mapAndWait(win)
509+ hs.serialMsg(win, 3)
510+ hs.h.EXPECT().Associate(s, uint64(5)).Once()
511+ hs.x.SetSerial(5, 3)
512+ hs.x.SurfaceGone(5)
513+ require.Zero(t, hs.x.tab.rec(s).surface)
514+}
515+
516+func TestPairingDuplicateSerialIgnored(t *testing.T) {
517+ hs := newHarness(t, nil)
518+ const win = 0x500001
519+ hs.classic(win)
520+ s := hs.mapAndWait(win)
521+ hs.serialMsg(win, 3)
522+ hs.serialMsg(win, 4)
523+ require.EqualValues(t, 3, hs.x.tab.rec(s).serial)
524+ hs.x.SetSerial(8, 4) // nobody announces 4: stays unpaired
525+ require.Equal(t, 1, hs.x.unpairedN)
526+}
527+
528+func TestPairingUnmanagedKeepsSurfaceWithoutAssociate(t *testing.T) {
529+ hs := newHarness(t, nil)
530+ hs.p.createNotify(0x500009, true)
531+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) != 0 })
532+ hs.serialMsg(0x500009, 3)
533+ hs.x.SetSerial(5, 3) // the mock fails on an Associate
534+ require.EqualValues(t, 5, hs.x.tab.rec(hs.x.Slot(0x500009)).surface)
535+}
536+
537+func TestPairingTableOverflowDropsOldest(t *testing.T) {
538+ hs := newHarness(t, nil)
539+ for i := uint64(1); i <= unpairedCap+1; i++ {
540+ hs.x.SetSerial(100+i, i)
541+ }
542+ require.Equal(t, unpairedCap, hs.x.unpairedN)
543+ for i := 0; i < hs.x.unpairedN; i++ {
544+ require.NotEqual(t, uint64(1), hs.x.unpaired[i].serial, "oldest dropped")
545+ }
546+}
547+
548+func TestPairingZeroSerialIgnored(t *testing.T) {
549+ hs := newHarness(t, nil)
550+ hs.x.SetSerial(5, 0)
551+ require.Zero(t, hs.x.unpairedN)
552+}
553+
554+func TestLegacySurfaceIDCountedAndIgnored(t *testing.T) {
555+ hs := newHarness(t, nil)
556+ const win = 0x500001
557+ hs.classic(win)
558+ s := hs.mapAndWait(win)
559+ hs.p.clientMessage(win, hs.p.atom("WL_SURFACE_ID"), 12)
560+ readUntil(t, hs.x, func() bool { return hs.x.Stats().LegacyID == 1 })
561+ require.Zero(t, hs.x.tab.rec(s).serial)
562+}
563+
564+func TestNetWMStateClientMessageForwardsFullscreen(t *testing.T) {
565+ hs := newHarness(t, nil)
566+ const win = 0x500001
567+ hs.classic(win)
568+ s := hs.mapAndWait(win)
569+ hs.h.EXPECT().Fullscreen(s, true).Once()
570+ hs.p.clientMessage(win, hs.p.atom("_NET_WM_STATE"), 1, hs.p.atom("_NET_WM_STATE_FULLSCREEN"))
571+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).fullscreen })
572+ hs.h.EXPECT().Fullscreen(s, false).Once() // toggle
573+ hs.p.clientMessage(win, hs.p.atom("_NET_WM_STATE"), 2, hs.p.atom("_NET_WM_STATE_FULLSCREEN"))
574+ readUntil(t, hs.x, func() bool { return !hs.x.tab.rec(s).fullscreen })
575+ // Not written back by the message itself.
576+ st, ok := hs.p.prop(win, "_NET_WM_STATE")
577+ require.True(t, ok)
578+ require.Empty(t, st.data)
579+}
580+
581+// Unmap, destroy, remap.
582+
583+func TestUnmapDissociatesAndRemapRereadsProperties(t *testing.T) {
584+ hs := newHarness(t, nil)
585+ const win = 0x500001
586+ hs.classic(win)
587+ s := hs.mapAndWait(win)
588+
589+ // A synthetic UnmapNotify (ICCCM withdraw request) is not an unmap.
590+ hs.p.unmapNotify(win, true)
591+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 2 })
592+ require.True(t, hs.x.tab.rec(s).ready)
593+
594+ hs.h.EXPECT().Dissociate(s, false).Once()
595+ hs.p.unmapNotify(win, false)
596+ readUntil(t, hs.x, func() bool { return !hs.x.tab.rec(s).ready })
597+ r := hs.x.tab.rec(s)
598+ require.False(t, r.mapping)
599+ require.Zero(t, r.serial)
600+ hs.p.waitReq(0, func(q xpeerReq) bool {
601+ return q.major == opSetInputFocus || q.major == opChangeProperty && q.u32(4) == win
602+ })
603+ readUntil(t, hs.x, func() bool {
604+ v, ok := hs.p.prop(win, "WM_STATE")
605+ return ok && len(v.data) == 8 && binary.LittleEndian.Uint32(v.data) == 0
606+ })
607+ _, ok := hs.p.prop(win, "_NET_WM_STATE")
608+ require.False(t, ok, "_NET_WM_STATE deleted")
609+ list, _ := hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
610+ require.Empty(t, list.data)
611+
612+ // Remap starts over.
613+ delete(hs.ready, win)
614+ hs.expectReady(win)
615+ n := hs.count(func(q xpeerReq) bool { return q.major == opGetProperty && q.u32(4) == win })
616+ hs.p.mapRequest(win)
617+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
618+ require.Equal(t, n+propCount, hs.count(func(q xpeerReq) bool { return q.major == opGetProperty && q.u32(4) == win }))
619+ list, _ = hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
620+ require.Equal(t, []uint32{win}, atomsOf(list))
621+}
622+
623+func TestDestroyWhileMappingNeverReady(t *testing.T) {
624+ hs := newHarness(t, nil)
625+ const win = 0x500001
626+ hs.classic(win)
627+ s := hs.create(win)
628+ hs.p.holdReplies()
629+ hs.p.mapRequest(win)
630+ hs.waitMap(win)
631+ hs.h.EXPECT().Dissociate(s, true).Once()
632+ hs.p.destroyNotify(win)
633+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) == 0 })
634+ replies := hs.x.Stats().Replies
635+ hs.p.release() // replies for a window that is gone: xturbo retired their slots
636+ readUntil(t, hs.x, func() bool { return hs.x.conn.Stats().DroppedReplies >= uint64(propCount) })
637+ require.Equal(t, replies, hs.x.Stats().Replies, "none reaches the XWM")
638+}
639+
640+func TestStaleRepliesAfterSlotReuseAreIgnored(t *testing.T) {
641+ hs := newHarness(t, nil)
642+ const old, fresh = 0x500001, 0x500002
643+ hs.classic(old)
644+ hs.classic(fresh)
645+ s := hs.create(old)
646+ hs.p.holdReplies()
647+ hs.p.mapRequest(old)
648+ hs.waitMap(old)
649+ hs.h.EXPECT().Dissociate(s, true).Once()
650+ hs.p.destroyNotify(old)
651+ readUntil(t, hs.x, func() bool { return hs.x.Slot(old) == 0 })
652+
653+ hs.expectCreated(fresh)
654+ hs.p.createNotify(fresh, false)
655+ readUntil(t, hs.x, func() bool { return hs.x.Slot(fresh) != 0 })
656+ require.Equal(t, s, hs.x.Slot(fresh), "the freed slot is reused")
657+
658+ hs.p.release()
659+ readUntil(t, hs.x, func() bool { return hs.x.conn.Stats().DroppedReplies >= uint64(propCount) })
660+
661+ // A reply that does get through with the old tag (slot reused, generation
662+ // bumped) is ignored.
663+ oldTag := s | Slot(hs.x.tab.rec(s).gen-1)<<16
664+ rep := make([]byte, 32)
665+ rep[0] = 1
666+ hs.x.onReply(&xturbo.Reply{Kind: kindProp, Args: [4]uint32{uint32(oldTag), propClass}, Raw: rep})
667+ hs.x.onReply(&xturbo.Reply{Kind: kindGeom, Args: [4]uint32{uint32(oldTag)}, Raw: rep})
668+ require.EqualValues(t, 2, hs.x.Stats().Stale)
669+ require.False(t, hs.x.tab.rec(s).ready, "stale replies must not complete the new window")
670+ require.Zero(t, hs.x.tab.rec(s).pending)
671+}
672+
673+// Reparenting.
674+
675+func TestReparentAwayFromRootRemovesManagedWindow(t *testing.T) {
676+ hs := newHarness(t, nil)
677+ const win = 0x500001
678+ hs.classic(win)
679+ s := hs.mapAndWait(win)
680+ hs.h.EXPECT().Dissociate(s, true).Once()
681+ hs.p.reparentNotify(win, 0x600000)
682+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) == 0 })
683+ list, _ := hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
684+ require.Empty(t, list.data)
685+ require.Zero(t, hs.x.tab.n)
686+}
687+
688+func TestReparentAwayOfUnmanagedWindowRemovesIt(t *testing.T) {
689+ hs := newHarness(t, nil)
690+ hs.p.createNotify(0x500009, true)
691+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) != 0 })
692+ hs.p.reparentNotify(0x500009, 0x600000)
693+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) == 0 })
694+}
695+
696+func TestReparentIntoRootAdoptsWindow(t *testing.T) {
697+ hs := newHarness(t, nil)
698+ const win = 0x500001
699+ hs.expectCreated(win)
700+ hs.p.reparentNotify(win, peerRoot)
701+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) != 0 })
702+ require.Equal(t, kindManaged, hs.x.tab.rec(hs.x.Slot(win)).kind)
703+
704+ // Already known: no second Created.
705+ hs.p.reparentNotify(win, peerRoot)
706+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 2 })
707+}
708+
709+func TestReparentIntoRootOfOverrideRedirectWindowStaysUnmanaged(t *testing.T) {
710+ hs := newHarness(t, nil)
711+ hs.p.event(21, func(b *[32]byte) {
712+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
713+ binary.LittleEndian.PutUint32(b[8:], 0x500009)
714+ binary.LittleEndian.PutUint32(b[12:], peerRoot)
715+ b[20] = 1
716+ })
717+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) != 0 })
718+ require.Equal(t, kindUnmanaged, hs.x.tab.rec(hs.x.Slot(0x500009)).kind)
719+}
720+
721+func TestReparentOfWMWindowIgnored(t *testing.T) {
722+ hs := newHarness(t, nil)
723+ hs.p.reparentNotify(hs.x.wmWin, peerRoot)
724+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 1 })
725+ require.Zero(t, hs.x.Slot(hs.x.wmWin))
726+}
727+
728+// Window known only before Attach: Attach replays the map flow.
729+func TestAttachMapsWindowsRequestedBeforeAttach(t *testing.T) {
730+ x, p := bootPeer(t, nil)
731+ const win = 0x500001
732+ p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("a\x00B\x00"))
733+ p.createNotify(win, false)
734+ p.mapRequest(win)
735+ readUntil(t, x, func() bool { return x.Stats().Events >= 2 })
736+ h := newMockhost(t)
737+ src := newMockfdSource(t)
738+ src.EXPECT().Remove().Maybe()
739+ post, _ := newPost()
740+ var got WindowInfo
741+ done := false
742+ h.EXPECT().Created(mock.Anything, uint32(win)).Return(ports.WindowID(1)).Once()
743+ h.EXPECT().Ready(mock.Anything, mock.Anything).Run(func(_ Slot, i WindowInfo) { got, done = i, true }).Once()
744+ x.attach(src, h, post)
745+ readUntil(t, x, func() bool { return done })
746+ require.Equal(t, "B", got.AppID)
747+}
748+
749+// Replies complete a map in any order (a server may answer the geometry
750+// before the properties it was asked for first): Ready fires on the last one.
751+func TestMapFlowReadyFiresOnLastReplyInAnyOrder(t *testing.T) {
752+ hs := newHarness(t, nil)
753+ const win = 0x500001
754+ hs.classic(win)
755+ s := hs.create(win)
756+ r := hs.x.tab.rec(s)
757+ r.mapping, r.pending = true, propMaskAll&^propClientIDs
758+ hs.expectReady(win)
759+ order := []int{propCount, propPID, propClass, propNetName, propName, propHints, propNormalHints, propProtocols, propTransientFor, propWindowType, propState}
760+ for i, bit := range order {
761+ require.False(t, hs.x.tab.rec(s).ready, i)
762+ if bit == propCount { // the geometry
763+ hs.x.tab.rec(s).w, hs.x.tab.rec(s).h = 300, 200
764+ hs.x.tab.rec(s).pending &^= propGeom
765+ hs.x.checkReady(s)
766+ continue
767+ }
768+ var val []byte
769+ var format uint8 = 8
770+ switch bit {
771+ case propClass:
772+ val = []byte("a\x00B\x00")
773+ case propNetName:
774+ val = []byte("t")
775+ case propPID:
776+ val, format = le32(5), 32
777+ default:
778+ format = 32
779+ }
780+ x := hs.x
781+ x.applyProp(s, bit, format, val)
782+ x.tab.rec(s).pending &^= 1 << bit
783+ x.checkReady(s)
784+ }
785+ require.True(t, hs.x.tab.rec(s).ready)
786+ require.Equal(t, "B", hs.ready[win].AppID)
787+ require.Equal(t, "t", hs.ready[win].Title)
788+ require.Equal(t, 5, hs.ready[win].PID)
789+}
@@ -0,0 +1,789 @@
1+package xwm
2+
3+import (
4+ "encoding/binary"
5+ "testing"
6+
7+ "bnema.dev/xturbo"
8+ "bnema.dev/xturbo/xproto"
9+ "github.com/bnema/neferwl/internal/ports"
10+ "github.com/stretchr/testify/mock"
11+ "github.com/stretchr/testify/require"
12+)
13+
14+// harness bundles an attached XWM, its peer and the host mock.
15+type harness struct {
16+ t *testing.T
17+ x *XWM
18+ p *xpeer
19+ h *mockhost
20+
21+ ready map[uint32]WindowInfo // by xid, filled by Ready
22+ next ports.WindowID
23+}
24+
25+func newHarness(t *testing.T, mutate func(p *xpeer)) *harness {
26+ t.Helper()
27+ x, p := bootPeer(t, mutate)
28+ hs := &harness{t: t, x: x, p: p, h: newMockhost(t), ready: map[uint32]WindowInfo{}}
29+ src := newMockfdSource(t)
30+ src.EXPECT().Remove().Maybe()
31+ src.EXPECT().SetWritable(mock.Anything).Return(nil).Maybe()
32+ post, _ := newPost()
33+ x.attach(src, hs.h, post)
34+ return hs
35+}
36+
37+func (hs *harness) expectCreated(win uint32) {
38+ hs.next++
39+ hs.h.EXPECT().Created(mock.Anything, win).Return(hs.next).Once()
40+}
41+
42+func (hs *harness) expectReady(win uint32) {
43+ hs.h.EXPECT().Ready(mock.Anything, mock.Anything).Run(func(_ Slot, info WindowInfo) {
44+ hs.ready[win] = info
45+ }).Once()
46+}
47+
48+func (hs *harness) create(win uint32) Slot {
49+ hs.t.Helper()
50+ hs.expectCreated(win)
51+ hs.p.createNotify(win, false)
52+ readUntil(hs.t, hs.x, func() bool { return hs.x.Slot(win) != 0 })
53+ return hs.x.Slot(win)
54+}
55+
56+// mapAndWait creates and maps win and waits for Ready.
57+func (hs *harness) mapAndWait(win uint32) Slot {
58+ hs.t.Helper()
59+ s := hs.create(win)
60+ hs.expectReady(win)
61+ hs.p.mapRequest(win)
62+ readUntil(hs.t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
63+ return s
64+}
65+
66+func (hs *harness) str(win uint32, atom string, typ uint32, s string) {
67+ hs.p.setProp(win, hs.p.atom(atom), typ, 8, []byte(s))
68+}
69+
70+func (hs *harness) words(win uint32, atom string, typ uint32, w ...uint32) {
71+ hs.p.setProp(win, hs.p.atom(atom), typ, 32, le32(w...))
72+}
73+
74+func (hs *harness) atoms(win uint32, atom string, names ...string) {
75+ var w []uint32
76+ for _, n := range names {
77+ w = append(w, hs.p.atom(n))
78+ }
79+ hs.words(win, atom, uint32(xproto.AtomAtom), w...)
80+}
81+
82+func (hs *harness) classic(win uint32) {
83+ hs.p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("inst\x00Class\x00"))
84+ hs.str(win, "_NET_WM_NAME", hs.p.atom("UTF8_STRING"), "net title")
85+ hs.p.setProp(win, uint32(xproto.AtomWmName), uint32(xproto.AtomString), 8, []byte("old title"))
86+ hs.words(win, "_NET_WM_PID", uint32(xproto.AtomCardinal), 4242)
87+ hs.p.set(func(p *xpeer) { p.geoms[win] = xpeerGeom{w: 300, h: 200} })
88+}
89+
90+func (hs *harness) count(match func(r xpeerReq) bool) int {
91+ n := 0
92+ for _, r := range hs.p.requests() {
93+ if match(r) {
94+ n++
95+ }
96+ }
97+ return n
98+}
99+
100+func (hs *harness) waitMap(win uint32) {
101+ hs.t.Helper()
102+ readUntil(hs.t, hs.x, func() bool {
103+ return hs.count(func(r xpeerReq) bool { return r.major == opMapWindow && r.u32(4) == win }) > 0
104+ })
105+}
106+
107+func TestMapFlowBatchesRequestsAndReadyFiresOnce(t *testing.T) {
108+ hs := newHarness(t, nil)
109+ const win = 0x500001
110+ hs.classic(win)
111+ hs.create(win)
112+ base := len(hs.p.requests())
113+
114+ hs.p.holdReplies()
115+ hs.p.mapRequest(win)
116+ hs.expectReady(win)
117+ hs.waitMap(win)
118+
119+ reqs := hs.p.requests()[base:]
120+ want := []uint32{
121+ uint32(xproto.AtomWmClass), uint32(xproto.AtomWmName), hs.p.atom("_NET_WM_NAME"), uint32(xproto.AtomWmHints),
122+ uint32(xproto.AtomWmNormalHints), hs.p.atom("WM_PROTOCOLS"), uint32(xproto.AtomWmTransientFor),
123+ hs.p.atom("_NET_WM_WINDOW_TYPE"), hs.p.atom("_NET_WM_STATE"), hs.p.atom("_NET_WM_PID"),
124+ }
125+ require.GreaterOrEqual(t, len(reqs), len(want)+2)
126+ for i, a := range want {
127+ require.EqualValues(t, opGetProperty, reqs[i].major, i)
128+ require.Equal(t, uint32(win), reqs[i].u32(4))
129+ require.Equal(t, a, reqs[i].u32(8), i)
130+ require.EqualValues(t, 1024, reqs[i].u32(20), "long length: one 4 KiB read")
131+ }
132+ require.EqualValues(t, opGetGeometry, reqs[len(want)].major)
133+ require.EqualValues(t, opMapWindow, reqs[len(want)+1].major, "MapWindow in the same batch")
134+
135+ hs.p.release() // every reply arrives after MapWindow went out
136+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
137+ info := hs.ready[win]
138+ require.Equal(t, WindowInfo{PID: 4242, Title: "net title", AppID: "Class"}, info, "_NET_WM_NAME wins, class is the second string")
139+
140+ state, ok := hs.p.prop(win, "WM_STATE")
141+ require.True(t, ok)
142+ require.Equal(t, []uint32{1, 0}, atomsOf(state))
143+ list, ok := hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
144+ require.True(t, ok)
145+ require.Equal(t, []uint32{win}, atomsOf(list))
146+}
147+
148+func TestMapFlowFloatingAndModal(t *testing.T) {
149+ hint := func(minw, minh, maxw, maxh uint32) []byte {
150+ return le32(hintMinSize|hintMaxSize, 0, 0, 0, 0, minw, minh, maxw, maxh, 0, 0, 0, 0, 0, 0, 0, 0, 0)
151+ }
152+ cases := []struct {
153+ name string
154+ setup func(hs *harness, win uint32)
155+ want func(info WindowInfo)
156+ }{
157+ {"dialog type", func(hs *harness, w uint32) {
158+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_DIALOG")
159+ }, func(i WindowInfo) {
160+ require.True(t, i.Floating)
161+ require.Equal(t, 300, i.Width)
162+ require.Equal(t, 200, i.Height)
163+ }},
164+ {"utility type", func(hs *harness, w uint32) {
165+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_UTILITY")
166+ }, func(i WindowInfo) { require.True(t, i.Floating) }},
167+ {"splash type", func(hs *harness, w uint32) {
168+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_SPLASH")
169+ }, func(i WindowInfo) { require.True(t, i.Floating) }},
170+ {"normal type", func(hs *harness, w uint32) {
171+ hs.atoms(w, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_NORMAL")
172+ }, func(i WindowInfo) { require.False(t, i.Floating); require.Zero(t, i.Width) }},
173+ {"fixed size", func(hs *harness, w uint32) {
174+ hs.p.setProp(w, uint32(xproto.AtomWmNormalHints), uint32(xproto.AtomWmSizeHints), 32, hint(200, 100, 200, 100))
175+ }, func(i WindowInfo) { require.True(t, i.Floating) }},
176+ {"resizable", func(hs *harness, w uint32) {
177+ hs.p.setProp(w, uint32(xproto.AtomWmNormalHints), uint32(xproto.AtomWmSizeHints), 32, hint(100, 100, 800, 600))
178+ }, func(i WindowInfo) { require.False(t, i.Floating) }},
179+ {"modal", func(hs *harness, w uint32) {
180+ hs.atoms(w, "_NET_WM_STATE", "_NET_WM_STATE_MODAL")
181+ }, func(i WindowInfo) { require.True(t, i.Modal); require.False(t, i.Fullscreen) }},
182+ {"fullscreen at map", func(hs *harness, w uint32) {
183+ hs.atoms(w, "_NET_WM_STATE", "_NET_WM_STATE_FULLSCREEN")
184+ }, func(i WindowInfo) { require.True(t, i.Fullscreen) }},
185+ }
186+ for _, c := range cases {
187+ t.Run(c.name, func(t *testing.T) {
188+ hs := newHarness(t, nil)
189+ const win = 0x500001
190+ hs.classic(win)
191+ c.setup(hs, win)
192+ hs.mapAndWait(win)
193+ c.want(hs.ready[win])
194+ })
195+ }
196+}
197+
198+func TestMapFlowTransientForManagedWindowFloats(t *testing.T) {
199+ hs := newHarness(t, nil)
200+ const parent, child = 0x500001, 0x500002
201+ hs.classic(parent)
202+ ps := hs.mapAndWait(parent)
203+ hs.classic(child)
204+ hs.p.setProp(child, uint32(xproto.AtomWmTransientFor), uint32(xproto.AtomWindow), 32, le32(parent))
205+ hs.mapAndWait(child)
206+ require.True(t, hs.ready[child].Floating)
207+ require.Equal(t, ps, hs.ready[child].Parent)
208+}
209+
210+func TestMapFlowTransientForUnknownWindowIsNotAParent(t *testing.T) {
211+ hs := newHarness(t, nil)
212+ const win = 0x500001
213+ hs.classic(win)
214+ hs.p.setProp(win, uint32(xproto.AtomWmTransientFor), uint32(xproto.AtomWindow), 32, le32(0x777777))
215+ hs.mapAndWait(win)
216+ require.Zero(t, hs.ready[win].Parent)
217+ require.False(t, hs.ready[win].Floating)
218+}
219+
220+func TestMapFlowClassFallsBackToInstance(t *testing.T) {
221+ hs := newHarness(t, nil)
222+ const win = 0x500001
223+ hs.classic(win)
224+ hs.p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("onlyinstance\x00"))
225+ hs.mapAndWait(win)
226+ require.Equal(t, "onlyinstance", hs.ready[win].AppID)
227+}
228+
229+func TestMapFlowTitleFallsBackToWMName(t *testing.T) {
230+ hs := newHarness(t, nil)
231+ const win = 0x500001
232+ hs.classic(win)
233+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_NAME"]) })
234+ hs.mapAndWait(win)
235+ require.Equal(t, "old title", hs.ready[win].Title)
236+}
237+
238+func TestMapFlowPIDFromXRes(t *testing.T) {
239+ hs := newHarness(t, func(p *xpeer) { p.pids = map[uint32]uint32{0x500001: 777} })
240+ const win = 0x500001
241+ hs.classic(win)
242+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_PID"]) })
243+ hs.create(win)
244+ hs.expectReady(win)
245+ hs.p.mapRequest(win)
246+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
247+ require.Equal(t, 777, hs.ready[win].PID, "Ready waited for the QueryClientIds reply")
248+ require.Equal(t, 1, hs.count(func(r xpeerReq) bool { return r.major == 144 && r.minor == 4 }))
249+ require.Zero(t, hs.x.tab.rec(hs.x.Slot(win)).pending)
250+}
251+
252+func TestMapFlowPIDUnknownStillReady(t *testing.T) {
253+ hs := newHarness(t, nil)
254+ const win = 0x500001
255+ hs.classic(win)
256+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_PID"]) })
257+ hs.mapAndWait(win)
258+ require.Zero(t, hs.ready[win].PID)
259+}
260+
261+func TestMapFlowNoXResAsksNothing(t *testing.T) {
262+ exts := fullPeerExts()
263+ delete(exts, "X-Resource")
264+ fd, p := startXPeer(t, exts)
265+ x, err := Boot(fd, testLog())
266+ require.NoError(t, err)
267+ t.Cleanup(x.CloseConn)
268+ h := newMockhost(t)
269+ src := newMockfdSource(t)
270+ src.EXPECT().Remove().Maybe()
271+ post, _ := newPost()
272+ x.attach(src, h, post)
273+ hs := &harness{t: t, x: x, p: p, h: h, ready: map[uint32]WindowInfo{}}
274+ const win = 0x500001
275+ hs.classic(win)
276+ p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_PID"]) })
277+ hs.mapAndWait(win)
278+ require.Zero(t, hs.count(func(r xpeerReq) bool { return r.major == 144 }))
279+}
280+
281+func TestMapFlowBadWindowOnPropertiesNeverReady(t *testing.T) {
282+ hs := newHarness(t, nil)
283+ const win = 0x500001
284+ hs.classic(win)
285+ hs.p.set(func(p *xpeer) { p.bad[win] = true })
286+ s := hs.create(win)
287+ hs.p.mapRequest(win)
288+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Errors >= uint64(propCount)+1 })
289+ require.Zero(t, hs.x.tab.rec(s).pending, "failed replies are no longer awaited")
290+ require.False(t, hs.x.tab.rec(s).ready)
291+ require.Zero(t, hs.x.Stats().Unattributed)
292+
293+ hs.h.EXPECT().Dissociate(s, true).Once()
294+ hs.p.destroyNotify(win)
295+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) == 0 })
296+}
297+
298+func TestMapRequestTwiceBeforeReadyAsksOnce(t *testing.T) {
299+ hs := newHarness(t, nil)
300+ const win = 0x500001
301+ hs.classic(win)
302+ hs.create(win)
303+ hs.p.holdReplies()
304+ hs.p.mapRequest(win)
305+ hs.p.mapRequest(win)
306+ hs.expectReady(win)
307+ hs.waitMap(win)
308+ hs.p.release()
309+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
310+ require.Equal(t, 1, hs.count(func(r xpeerReq) bool { return r.major == opMapWindow && r.u32(4) == win }))
311+}
312+
313+func TestMapNotifyMarksMapped(t *testing.T) {
314+ hs := newHarness(t, nil)
315+ const win = 0x500001
316+ hs.classic(win)
317+ s := hs.mapAndWait(win)
318+ hs.p.mapNotify(win)
319+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).mapped })
320+}
321+
322+// Property changes after Ready.
323+
324+func (hs *harness) notify(win uint32, atom string, deleted bool) {
325+ hs.t.Helper()
326+ before := hs.x.Stats().Replies
327+ hs.p.propertyNotify(win, hs.p.atom(atom), deleted)
328+ if deleted {
329+ hs.p.propertyNotify(win, hs.p.atom("WM_PROTOCOLS"), false) // sentinel: one reply follows
330+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
331+ return
332+ }
333+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
334+}
335+
336+func TestPropertyChangedTitleReachesHostOnce(t *testing.T) {
337+ hs := newHarness(t, nil)
338+ const win = 0x500001
339+ hs.classic(win)
340+ s := hs.mapAndWait(win)
341+
342+ hs.h.EXPECT().Title(s, "renamed").Once()
343+ hs.str(win, "_NET_WM_NAME", hs.p.atom("UTF8_STRING"), "renamed")
344+ hs.notify(win, "_NET_WM_NAME", false)
345+ require.Equal(t, "renamed", hs.x.tab.rec(s).title)
346+
347+ // Same value again: no host call (the mock fails on an unexpected one).
348+ hs.notify(win, "_NET_WM_NAME", false)
349+
350+ // WM_NAME is ignored while _NET_WM_NAME is set.
351+ hs.p.setProp(win, uint32(xproto.AtomWmName), uint32(xproto.AtomString), 8, []byte("legacy"))
352+ before := hs.x.Stats().Events
353+ hs.p.propertyNotify(win, uint32(xproto.AtomWmName), false)
354+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events > before })
355+ require.Equal(t, "renamed", hs.x.tab.rec(s).title)
356+}
357+
358+func TestPropertyNetNameDeletedFallsBackToWMName(t *testing.T) {
359+ hs := newHarness(t, nil)
360+ const win = 0x500001
361+ hs.classic(win)
362+ s := hs.mapAndWait(win)
363+ hs.p.set(func(p *xpeer) { delete(p.props[win], p.atoms["_NET_WM_NAME"]) })
364+ hs.h.EXPECT().Title(s, "old title").Once()
365+ hs.p.propertyNotify(win, hs.p.atom("_NET_WM_NAME"), true)
366+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).title == "old title" })
367+}
368+
369+func TestPropertyClassChangeReachesHost(t *testing.T) {
370+ hs := newHarness(t, nil)
371+ const win = 0x500001
372+ hs.classic(win)
373+ s := hs.mapAndWait(win)
374+ hs.h.EXPECT().AppID(s, "Other").Once()
375+ hs.p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("i\x00Other\x00"))
376+ hs.p.propertyNotify(win, uint32(xproto.AtomWmClass), false)
377+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).class == "Other" })
378+}
379+
380+func TestPropertyTransientChangeReachesHost(t *testing.T) {
381+ hs := newHarness(t, nil)
382+ const a, b = 0x500001, 0x500002
383+ hs.classic(a)
384+ as := hs.mapAndWait(a)
385+ hs.classic(b)
386+ bs := hs.mapAndWait(b)
387+ hs.h.EXPECT().Parent(bs, as).Once()
388+ hs.p.setProp(b, uint32(xproto.AtomWmTransientFor), uint32(xproto.AtomWindow), 32, le32(a))
389+ hs.p.propertyNotify(b, uint32(xproto.AtomWmTransientFor), false)
390+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(bs).transient == a })
391+}
392+
393+func TestPropertyFullscreenStateChangeReachesHost(t *testing.T) {
394+ hs := newHarness(t, nil)
395+ const win = 0x500001
396+ hs.classic(win)
397+ s := hs.mapAndWait(win)
398+ hs.h.EXPECT().Fullscreen(s, true).Once()
399+ hs.atoms(win, "_NET_WM_STATE", "_NET_WM_STATE_FULLSCREEN")
400+ hs.p.propertyNotify(win, hs.p.atom("_NET_WM_STATE"), false)
401+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).fullscreen })
402+}
403+
404+func TestPropertyInputProtocolsAndHints(t *testing.T) {
405+ hs := newHarness(t, nil)
406+ const win = 0x500001
407+ hs.classic(win)
408+ hs.atoms(win, "WM_PROTOCOLS", "WM_DELETE_WINDOW", "WM_TAKE_FOCUS")
409+ hs.p.setProp(win, uint32(xproto.AtomWmHints), uint32(xproto.AtomWmHints), 32, le32(hintInput, 0, 0, 0, 0, 0, 0, 0, 0))
410+ s := hs.mapAndWait(win)
411+ r := hs.x.tab.rec(s)
412+ require.True(t, r.deleteWindow)
413+ require.True(t, r.takeFocus)
414+ require.False(t, r.input, "WM_HINTS input = false")
415+
416+ // No WM_HINTS: input defaults to true.
417+ const other = 0x500002
418+ hs.classic(other)
419+ o := hs.mapAndWait(other)
420+ require.True(t, hs.x.tab.rec(o).input)
421+ require.False(t, hs.x.tab.rec(o).deleteWindow)
422+}
423+
424+func TestPropertyUnwatchedAtomAsksNothing(t *testing.T) {
425+ hs := newHarness(t, nil)
426+ const win = 0x500001
427+ hs.classic(win)
428+ hs.mapAndWait(win)
429+ n := len(hs.p.requests())
430+ hs.p.propertyNotify(win, hs.p.atom("_MOTIF_WM_HINTS"), false)
431+ hs.p.propertyNotify(0x999999, hs.p.atom("_NET_WM_NAME"), false) // unknown window
432+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 2 })
433+ require.Len(t, hs.p.requests(), n)
434+}
435+
436+// Pairing.
437+
438+func (hs *harness) serialMsg(win uint32, serial uint64) {
439+ hs.p.clientMessage(win, hs.p.atom("WL_SURFACE_SERIAL"), uint32(serial), uint32(serial>>32))
440+ hs.t.Helper()
441+ before := hs.x.Stats().Events
442+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Events > before })
443+}
444+
445+func TestPairingSerialBeforeSetSerial(t *testing.T) {
446+ hs := newHarness(t, nil)
447+ const win = 0x500001
448+ hs.classic(win)
449+ s := hs.mapAndWait(win)
450+ hs.serialMsg(win, 0x1_0000_0002)
451+ require.EqualValues(t, 0x1_0000_0002, hs.x.tab.rec(s).serial, "high word in data32[1]")
452+ hs.h.EXPECT().Associate(s, uint64(99)).Once()
453+ hs.x.SetSerial(99, 0x1_0000_0002)
454+ require.EqualValues(t, 99, hs.x.tab.rec(s).surface)
455+ require.Zero(t, hs.x.unpairedN)
456+}
457+
458+func TestPairingSetSerialBeforeSerial(t *testing.T) {
459+ hs := newHarness(t, nil)
460+ const win = 0x500001
461+ hs.classic(win)
462+ s := hs.mapAndWait(win)
463+ hs.x.SetSerial(99, 7)
464+ require.Equal(t, 1, hs.x.unpairedN)
465+ hs.h.EXPECT().Associate(s, uint64(99)).Once()
466+ hs.serialMsg(win, 7)
467+ require.Zero(t, hs.x.unpairedN)
468+}
469+
470+func TestPairingWaitsForReady(t *testing.T) {
471+ hs := newHarness(t, nil)
472+ const win = 0x500001
473+ hs.classic(win)
474+ s := hs.create(win)
475+ hs.x.SetSerial(5, 3)
476+ hs.serialMsg(win, 3) // not mapped yet: no Associate (the mock fails on one)
477+ require.Zero(t, hs.x.tab.rec(s).surface)
478+
479+ hs.h.EXPECT().Associate(s, uint64(5)).Once()
480+ hs.expectReady(win)
481+ hs.p.mapRequest(win)
482+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).surface != 0 })
483+}
484+
485+func TestPairingSerialForUnknownWindowIgnored(t *testing.T) {
486+ hs := newHarness(t, nil)
487+ hs.x.SetSerial(5, 3)
488+ hs.serialMsg(0x888888, 3)
489+ require.Equal(t, 1, hs.x.unpairedN, "stays unpaired")
490+}
491+
492+func TestPairingSurfaceGoneBeforePair(t *testing.T) {
493+ hs := newHarness(t, nil)
494+ const win = 0x500001
495+ hs.classic(win)
496+ s := hs.mapAndWait(win)
497+ hs.x.SetSerial(5, 3)
498+ hs.x.SurfaceGone(5)
499+ require.Zero(t, hs.x.unpairedN)
500+ hs.serialMsg(win, 3) // the surface is gone: nothing to pair with
501+ require.Zero(t, hs.x.tab.rec(s).surface)
502+}
503+
504+func TestPairingSurfaceGoneAfterPairClearsLink(t *testing.T) {
505+ hs := newHarness(t, nil)
506+ const win = 0x500001
507+ hs.classic(win)
508+ s := hs.mapAndWait(win)
509+ hs.serialMsg(win, 3)
510+ hs.h.EXPECT().Associate(s, uint64(5)).Once()
511+ hs.x.SetSerial(5, 3)
512+ hs.x.SurfaceGone(5)
513+ require.Zero(t, hs.x.tab.rec(s).surface)
514+}
515+
516+func TestPairingDuplicateSerialIgnored(t *testing.T) {
517+ hs := newHarness(t, nil)
518+ const win = 0x500001
519+ hs.classic(win)
520+ s := hs.mapAndWait(win)
521+ hs.serialMsg(win, 3)
522+ hs.serialMsg(win, 4)
523+ require.EqualValues(t, 3, hs.x.tab.rec(s).serial)
524+ hs.x.SetSerial(8, 4) // nobody announces 4: stays unpaired
525+ require.Equal(t, 1, hs.x.unpairedN)
526+}
527+
528+func TestPairingUnmanagedKeepsSurfaceWithoutAssociate(t *testing.T) {
529+ hs := newHarness(t, nil)
530+ hs.p.createNotify(0x500009, true)
531+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) != 0 })
532+ hs.serialMsg(0x500009, 3)
533+ hs.x.SetSerial(5, 3) // the mock fails on an Associate
534+ require.EqualValues(t, 5, hs.x.tab.rec(hs.x.Slot(0x500009)).surface)
535+}
536+
537+func TestPairingTableOverflowDropsOldest(t *testing.T) {
538+ hs := newHarness(t, nil)
539+ for i := uint64(1); i <= unpairedCap+1; i++ {
540+ hs.x.SetSerial(100+i, i)
541+ }
542+ require.Equal(t, unpairedCap, hs.x.unpairedN)
543+ for i := 0; i < hs.x.unpairedN; i++ {
544+ require.NotEqual(t, uint64(1), hs.x.unpaired[i].serial, "oldest dropped")
545+ }
546+}
547+
548+func TestPairingZeroSerialIgnored(t *testing.T) {
549+ hs := newHarness(t, nil)
550+ hs.x.SetSerial(5, 0)
551+ require.Zero(t, hs.x.unpairedN)
552+}
553+
554+func TestLegacySurfaceIDCountedAndIgnored(t *testing.T) {
555+ hs := newHarness(t, nil)
556+ const win = 0x500001
557+ hs.classic(win)
558+ s := hs.mapAndWait(win)
559+ hs.p.clientMessage(win, hs.p.atom("WL_SURFACE_ID"), 12)
560+ readUntil(t, hs.x, func() bool { return hs.x.Stats().LegacyID == 1 })
561+ require.Zero(t, hs.x.tab.rec(s).serial)
562+}
563+
564+func TestNetWMStateClientMessageForwardsFullscreen(t *testing.T) {
565+ hs := newHarness(t, nil)
566+ const win = 0x500001
567+ hs.classic(win)
568+ s := hs.mapAndWait(win)
569+ hs.h.EXPECT().Fullscreen(s, true).Once()
570+ hs.p.clientMessage(win, hs.p.atom("_NET_WM_STATE"), 1, hs.p.atom("_NET_WM_STATE_FULLSCREEN"))
571+ readUntil(t, hs.x, func() bool { return hs.x.tab.rec(s).fullscreen })
572+ hs.h.EXPECT().Fullscreen(s, false).Once() // toggle
573+ hs.p.clientMessage(win, hs.p.atom("_NET_WM_STATE"), 2, hs.p.atom("_NET_WM_STATE_FULLSCREEN"))
574+ readUntil(t, hs.x, func() bool { return !hs.x.tab.rec(s).fullscreen })
575+ // Not written back by the message itself.
576+ st, ok := hs.p.prop(win, "_NET_WM_STATE")
577+ require.True(t, ok)
578+ require.Empty(t, st.data)
579+}
580+
581+// Unmap, destroy, remap.
582+
583+func TestUnmapDissociatesAndRemapRereadsProperties(t *testing.T) {
584+ hs := newHarness(t, nil)
585+ const win = 0x500001
586+ hs.classic(win)
587+ s := hs.mapAndWait(win)
588+
589+ // A synthetic UnmapNotify (ICCCM withdraw request) is not an unmap.
590+ hs.p.unmapNotify(win, true)
591+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 2 })
592+ require.True(t, hs.x.tab.rec(s).ready)
593+
594+ hs.h.EXPECT().Dissociate(s, false).Once()
595+ hs.p.unmapNotify(win, false)
596+ readUntil(t, hs.x, func() bool { return !hs.x.tab.rec(s).ready })
597+ r := hs.x.tab.rec(s)
598+ require.False(t, r.mapping)
599+ require.Zero(t, r.serial)
600+ hs.p.waitReq(0, func(q xpeerReq) bool {
601+ return q.major == opSetInputFocus || q.major == opChangeProperty && q.u32(4) == win
602+ })
603+ readUntil(t, hs.x, func() bool {
604+ v, ok := hs.p.prop(win, "WM_STATE")
605+ return ok && len(v.data) == 8 && binary.LittleEndian.Uint32(v.data) == 0
606+ })
607+ _, ok := hs.p.prop(win, "_NET_WM_STATE")
608+ require.False(t, ok, "_NET_WM_STATE deleted")
609+ list, _ := hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
610+ require.Empty(t, list.data)
611+
612+ // Remap starts over.
613+ delete(hs.ready, win)
614+ hs.expectReady(win)
615+ n := hs.count(func(q xpeerReq) bool { return q.major == opGetProperty && q.u32(4) == win })
616+ hs.p.mapRequest(win)
617+ readUntil(t, hs.x, func() bool { _, ok := hs.ready[win]; return ok })
618+ require.Equal(t, n+propCount, hs.count(func(q xpeerReq) bool { return q.major == opGetProperty && q.u32(4) == win }))
619+ list, _ = hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
620+ require.Equal(t, []uint32{win}, atomsOf(list))
621+}
622+
623+func TestDestroyWhileMappingNeverReady(t *testing.T) {
624+ hs := newHarness(t, nil)
625+ const win = 0x500001
626+ hs.classic(win)
627+ s := hs.create(win)
628+ hs.p.holdReplies()
629+ hs.p.mapRequest(win)
630+ hs.waitMap(win)
631+ hs.h.EXPECT().Dissociate(s, true).Once()
632+ hs.p.destroyNotify(win)
633+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) == 0 })
634+ replies := hs.x.Stats().Replies
635+ hs.p.release() // replies for a window that is gone: xturbo retired their slots
636+ readUntil(t, hs.x, func() bool { return hs.x.conn.Stats().DroppedReplies >= uint64(propCount) })
637+ require.Equal(t, replies, hs.x.Stats().Replies, "none reaches the XWM")
638+}
639+
640+func TestStaleRepliesAfterSlotReuseAreIgnored(t *testing.T) {
641+ hs := newHarness(t, nil)
642+ const old, fresh = 0x500001, 0x500002
643+ hs.classic(old)
644+ hs.classic(fresh)
645+ s := hs.create(old)
646+ hs.p.holdReplies()
647+ hs.p.mapRequest(old)
648+ hs.waitMap(old)
649+ hs.h.EXPECT().Dissociate(s, true).Once()
650+ hs.p.destroyNotify(old)
651+ readUntil(t, hs.x, func() bool { return hs.x.Slot(old) == 0 })
652+
653+ hs.expectCreated(fresh)
654+ hs.p.createNotify(fresh, false)
655+ readUntil(t, hs.x, func() bool { return hs.x.Slot(fresh) != 0 })
656+ require.Equal(t, s, hs.x.Slot(fresh), "the freed slot is reused")
657+
658+ hs.p.release()
659+ readUntil(t, hs.x, func() bool { return hs.x.conn.Stats().DroppedReplies >= uint64(propCount) })
660+
661+ // A reply that does get through with the old tag (slot reused, generation
662+ // bumped) is ignored.
663+ oldTag := s | Slot(hs.x.tab.rec(s).gen-1)<<16
664+ rep := make([]byte, 32)
665+ rep[0] = 1
666+ hs.x.onReply(&xturbo.Reply{Kind: kindProp, Args: [4]uint32{uint32(oldTag), propClass}, Raw: rep})
667+ hs.x.onReply(&xturbo.Reply{Kind: kindGeom, Args: [4]uint32{uint32(oldTag)}, Raw: rep})
668+ require.EqualValues(t, 2, hs.x.Stats().Stale)
669+ require.False(t, hs.x.tab.rec(s).ready, "stale replies must not complete the new window")
670+ require.Zero(t, hs.x.tab.rec(s).pending)
671+}
672+
673+// Reparenting.
674+
675+func TestReparentAwayFromRootRemovesManagedWindow(t *testing.T) {
676+ hs := newHarness(t, nil)
677+ const win = 0x500001
678+ hs.classic(win)
679+ s := hs.mapAndWait(win)
680+ hs.h.EXPECT().Dissociate(s, true).Once()
681+ hs.p.reparentNotify(win, 0x600000)
682+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) == 0 })
683+ list, _ := hs.p.prop(peerRoot, "_NET_CLIENT_LIST")
684+ require.Empty(t, list.data)
685+ require.Zero(t, hs.x.tab.n)
686+}
687+
688+func TestReparentAwayOfUnmanagedWindowRemovesIt(t *testing.T) {
689+ hs := newHarness(t, nil)
690+ hs.p.createNotify(0x500009, true)
691+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) != 0 })
692+ hs.p.reparentNotify(0x500009, 0x600000)
693+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) == 0 })
694+}
695+
696+func TestReparentIntoRootAdoptsWindow(t *testing.T) {
697+ hs := newHarness(t, nil)
698+ const win = 0x500001
699+ hs.expectCreated(win)
700+ hs.p.reparentNotify(win, peerRoot)
701+ readUntil(t, hs.x, func() bool { return hs.x.Slot(win) != 0 })
702+ require.Equal(t, kindManaged, hs.x.tab.rec(hs.x.Slot(win)).kind)
703+
704+ // Already known: no second Created.
705+ hs.p.reparentNotify(win, peerRoot)
706+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 2 })
707+}
708+
709+func TestReparentIntoRootOfOverrideRedirectWindowStaysUnmanaged(t *testing.T) {
710+ hs := newHarness(t, nil)
711+ hs.p.event(21, func(b *[32]byte) {
712+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
713+ binary.LittleEndian.PutUint32(b[8:], 0x500009)
714+ binary.LittleEndian.PutUint32(b[12:], peerRoot)
715+ b[20] = 1
716+ })
717+ readUntil(t, hs.x, func() bool { return hs.x.Slot(0x500009) != 0 })
718+ require.Equal(t, kindUnmanaged, hs.x.tab.rec(hs.x.Slot(0x500009)).kind)
719+}
720+
721+func TestReparentOfWMWindowIgnored(t *testing.T) {
722+ hs := newHarness(t, nil)
723+ hs.p.reparentNotify(hs.x.wmWin, peerRoot)
724+ readUntil(t, hs.x, func() bool { return hs.x.Stats().Events >= 1 })
725+ require.Zero(t, hs.x.Slot(hs.x.wmWin))
726+}
727+
728+// Window known only before Attach: Attach replays the map flow.
729+func TestAttachMapsWindowsRequestedBeforeAttach(t *testing.T) {
730+ x, p := bootPeer(t, nil)
731+ const win = 0x500001
732+ p.setProp(win, uint32(xproto.AtomWmClass), uint32(xproto.AtomString), 8, []byte("a\x00B\x00"))
733+ p.createNotify(win, false)
734+ p.mapRequest(win)
735+ readUntil(t, x, func() bool { return x.Stats().Events >= 2 })
736+ h := newMockhost(t)
737+ src := newMockfdSource(t)
738+ src.EXPECT().Remove().Maybe()
739+ post, _ := newPost()
740+ var got WindowInfo
741+ done := false
742+ h.EXPECT().Created(mock.Anything, uint32(win)).Return(ports.WindowID(1)).Once()
743+ h.EXPECT().Ready(mock.Anything, mock.Anything).Run(func(_ Slot, i WindowInfo) { got, done = i, true }).Once()
744+ x.attach(src, h, post)
745+ readUntil(t, x, func() bool { return done })
746+ require.Equal(t, "B", got.AppID)
747+}
748+
749+// Replies complete a map in any order (a server may answer the geometry
750+// before the properties it was asked for first): Ready fires on the last one.
751+func TestMapFlowReadyFiresOnLastReplyInAnyOrder(t *testing.T) {
752+ hs := newHarness(t, nil)
753+ const win = 0x500001
754+ hs.classic(win)
755+ s := hs.create(win)
756+ r := hs.x.tab.rec(s)
757+ r.mapping, r.pending = true, propMaskAll&^propClientIDs
758+ hs.expectReady(win)
759+ order := []int{propCount, propPID, propClass, propNetName, propName, propHints, propNormalHints, propProtocols, propTransientFor, propWindowType, propState}
760+ for i, bit := range order {
761+ require.False(t, hs.x.tab.rec(s).ready, i)
762+ if bit == propCount { // the geometry
763+ hs.x.tab.rec(s).w, hs.x.tab.rec(s).h = 300, 200
764+ hs.x.tab.rec(s).pending &^= propGeom
765+ hs.x.checkReady(s)
766+ continue
767+ }
768+ var val []byte
769+ var format uint8 = 8
770+ switch bit {
771+ case propClass:
772+ val = []byte("a\x00B\x00")
773+ case propNetName:
774+ val = []byte("t")
775+ case propPID:
776+ val, format = le32(5), 32
777+ default:
778+ format = 32
779+ }
780+ x := hs.x
781+ x.applyProp(s, bit, format, val)
782+ x.tab.rec(s).pending &^= 1 << bit
783+ x.checkReady(s)
784+ }
785+ require.True(t, hs.x.tab.rec(s).ready)
786+ require.Equal(t, "B", hs.ready[win].AppID)
787+ require.Equal(t, "t", hs.ready[win].Title)
788+ require.Equal(t, 5, hs.ready[win].PID)
789+}
M internal/adapters/wayland/xwm/props.go +305 -7
@@ -1,12 +1,17 @@
11 package xwm
22
33 import (
4+ "bytes"
5+ "encoding/binary"
6+
47 "bnema.dev/xturbo"
8+ "bnema.dev/xturbo/res"
59 "bnema.dev/xturbo/xproto"
610 )
711
812 // Property indices (second Expect argument of kindProp) and their bits in
9-// record.pending. propGeom is the GetGeometry bit.
13+// record.pending. propGeom is the GetGeometry bit; propClientIDs is raised
14+// while a QueryClientIds reply stands in for a missing _NET_WM_PID.
1015 const (
1116 propClass = iota
1217 propName
@@ -22,14 +27,307 @@ const (
2227 )
2328
2429 const (
25- propGeom uint16 = 1 << propCount
30+ propGeom uint16 = 1 << propCount
31+ propClientIDs uint16 = propGeom << 1
32+ // propMaskAll is what a map waits for: every property and the geometry.
2633 propMaskAll uint16 = propGeom<<1 - 1
2734 )
2835
29-// onPropertyNotify handles a changed property of a managed window.
30-func (x *XWM) onPropertyNotify(ev *xturbo.Event) { _ = ev }
36+// WM_NORMAL_HINTS flags and WM_HINTS flags.
37+const (
38+ hintInput = 1 << 0
39+ hintMinSize = 1 << 4
40+ hintMaxSize = 1 << 5
41+)
42+
43+// maxPropWords bounds one GetProperty (4 KiB), so titles are truncated there.
44+const maxPropWords = 1024
45+
46+// propIndex maps a property atom to its index, or -1 when it is not watched.
47+func (x *XWM) propIndex(a uint32) int {
48+ switch a {
49+ case uint32(xproto.AtomWmClass):
50+ return propClass
51+ case uint32(xproto.AtomWmName):
52+ return propName
53+ case uint32(xproto.AtomWmHints):
54+ return propHints
55+ case uint32(xproto.AtomWmNormalHints):
56+ return propNormalHints
57+ case uint32(xproto.AtomWmTransientFor):
58+ return propTransientFor
59+ case x.atoms[atomNetWMName]:
60+ return propNetName
61+ case x.atoms[atomWMProtocols]:
62+ return propProtocols
63+ case x.atoms[atomNetWMWindowType]:
64+ return propWindowType
65+ case x.atoms[atomNetWMState]:
66+ return propState
67+ case x.atoms[atomNetWMPID]:
68+ return propPID
69+ }
70+ return -1
71+}
72+
73+func (x *XWM) propAtom(idx int) xproto.Atom {
74+ switch idx {
75+ case propClass:
76+ return xproto.AtomWmClass
77+ case propName:
78+ return xproto.AtomWmName
79+ case propHints:
80+ return xproto.AtomWmHints
81+ case propNormalHints:
82+ return xproto.AtomWmNormalHints
83+ case propTransientFor:
84+ return xproto.AtomWmTransientFor
85+ case propNetName:
86+ return xproto.Atom(x.atoms[atomNetWMName])
87+ case propProtocols:
88+ return xproto.Atom(x.atoms[atomWMProtocols])
89+ case propWindowType:
90+ return xproto.Atom(x.atoms[atomNetWMWindowType])
91+ case propState:
92+ return xproto.Atom(x.atoms[atomNetWMState])
93+ default:
94+ return xproto.Atom(x.atoms[atomNetWMPID])
95+ }
96+}
97+
98+// requestProp queues a GetProperty for property idx of s.
99+func (x *XWM) requestProp(s Slot, idx int) {
100+ rec := x.tab.rec(s)
101+ seq := xproto.GetProperty(x.conn.Buf(), false, xproto.Window(rec.xid), x.propAtom(idx), xproto.AtomAny, 0, maxPropWords)
102+ rec.pending |= 1 << idx
103+ x.expect(seq, kindProp, x.tab.tag(s), uint32(idx))
104+}
105+
106+// onPropertyNotify handles a changed property of a managed window that is
107+// being mapped or is mapped; unwatched atoms cost nothing.
108+func (x *XWM) onPropertyNotify(ev *xturbo.Event) {
109+ e := xproto.PropertyNotifyOf(ev)
110+ s := x.tab.lookup(uint32(e.Window()))
111+ if s == 0 {
112+ return
113+ }
114+ if rec := x.tab.rec(s); rec.kind != kindManaged || !rec.mapping {
115+ return
116+ }
117+ idx := x.propIndex(uint32(e.Atom()))
118+ if idx < 0 {
119+ return
120+ }
121+ if e.State() == xproto.PropertyDelete {
122+ x.applyProp(s, idx, 0, nil)
123+ return
124+ }
125+ x.requestProp(s, idx)
126+}
127+
128+// onPropReply decodes one GetProperty reply into the record. An absent
129+// property (format 0) clears its fields.
130+func (x *XWM) onPropReply(s Slot, _ *record, r *xturbo.Reply) {
131+ idx := int(r.Args[1])
132+ if idx < 0 || idx >= propCount {
133+ return
134+ }
135+ var format uint8
136+ var val []byte
137+ if rep, ok := xproto.ParseGetPropertyReply(r.Raw); ok {
138+ format, val = rep.Format(), rep.Value()
139+ }
140+ x.applyProp(s, idx, format, val)
141+ x.tab.rec(s).pending &^= 1 << idx
142+ x.checkReady(s)
143+}
144+
145+func word(b []byte, i int) uint32 { return binary.LittleEndian.Uint32(b[4*i:]) }
146+
147+// applyProp stores one decoded property. It does not allocate unless a string
148+// changed. Host calls come last: the record is not touched after them.
149+func (x *XWM) applyProp(s Slot, idx int, format uint8, val []byte) {
150+ rec := x.tab.rec(s)
151+ if idx == propClass || idx == propName || idx == propNetName {
152+ if format != 8 {
153+ val = nil
154+ }
155+ } else if format != 32 {
156+ val = nil
157+ }
158+ switch idx {
159+ case propClass:
160+ inst, class := val, []byte(nil)
161+ if i := bytes.IndexByte(val, 0); i >= 0 {
162+ inst, class = val[:i], val[i+1:]
163+ if j := bytes.IndexByte(class, 0); j >= 0 {
164+ class = class[:j]
165+ }
166+ }
167+ app := class
168+ if len(app) == 0 {
169+ app = inst
170+ }
171+ if rec.class == string(app) {
172+ return
173+ }
174+ rec.class = string(app)
175+ if rec.ready {
176+ x.host.AppID(s, rec.class)
177+ }
178+ case propName:
179+ if !rec.hasNetName {
180+ x.setTitle(s, val)
181+ }
182+ case propNetName:
183+ had := rec.hasNetName
184+ rec.hasNetName = len(val) > 0
185+ if rec.hasNetName {
186+ x.setTitle(s, val)
187+ } else if had && rec.mapping {
188+ x.requestProp(s, propName)
189+ }
190+ case propHints:
191+ rec.input = true
192+ if len(val) >= 8 && word(val, 0)&hintInput != 0 {
193+ rec.input = word(val, 1) != 0
194+ }
195+ case propNormalHints:
196+ rec.minW, rec.minH, rec.maxW, rec.maxH = 0, 0, 0, 0
197+ if len(val) >= 36 {
198+ fl := word(val, 0)
199+ if fl&hintMinSize != 0 {
200+ rec.minW, rec.minH = int32(word(val, 5)), int32(word(val, 6))
201+ }
202+ if fl&hintMaxSize != 0 {
203+ rec.maxW, rec.maxH = int32(word(val, 7)), int32(word(val, 8))
204+ }
205+ }
206+ case propProtocols:
207+ rec.deleteWindow, rec.takeFocus = false, false
208+ for i := 0; i+4 <= len(val); i += 4 {
209+ switch binary.LittleEndian.Uint32(val[i:]) {
210+ case x.atoms[atomWMDeleteWindow]:
211+ rec.deleteWindow = true
212+ case x.atoms[atomWMTakeFocus]:
213+ rec.takeFocus = true
214+ }
215+ }
216+ case propTransientFor:
217+ var t uint32
218+ if len(val) >= 4 {
219+ t = word(val, 0)
220+ }
221+ if t == rec.transient {
222+ return
223+ }
224+ rec.transient = t
225+ if rec.ready {
226+ x.host.Parent(s, x.managedSlot(t))
227+ }
228+ case propWindowType:
229+ rec.wtype = 0
230+ for i := 0; i+4 <= len(val); i += 4 {
231+ if t := x.windowType(binary.LittleEndian.Uint32(val[i:])); t != 0 {
232+ rec.wtype = t
233+ break
234+ }
235+ }
236+ case propState:
237+ var modal, fs bool
238+ for i := 0; i+4 <= len(val); i += 4 {
239+ switch binary.LittleEndian.Uint32(val[i:]) {
240+ case x.atoms[atomNetWMStateModal]:
241+ modal = true
242+ case x.atoms[atomNetWMStateFullscreen]:
243+ fs = true
244+ }
245+ }
246+ changed := fs != rec.fullscreen
247+ rec.modal, rec.fullscreen = modal, fs
248+ if changed && rec.ready {
249+ x.host.Fullscreen(s, fs)
250+ }
251+ case propPID:
252+ if len(val) >= 4 {
253+ rec.pid = int32(word(val, 0))
254+ return
255+ }
256+ rec.pid = 0
257+ if !rec.ready && rec.pending&propClientIDs == 0 && x.conn.Ext().Info(xturbo.ExtRes).Present {
258+ x.queryClientIDs(s)
259+ }
260+ }
261+}
262+
263+// setTitle stores a title when it differs; only a change allocates.
264+func (x *XWM) setTitle(s Slot, val []byte) {
265+ rec := x.tab.rec(s)
266+ if rec.title == string(val) {
267+ return
268+ }
269+ rec.title = string(val)
270+ if rec.ready {
271+ x.host.Title(s, rec.title)
272+ }
273+}
274+
275+// windowType returns the atom enum of a recognised _NET_WM_WINDOW_TYPE atom,
276+// or 0.
277+func (x *XWM) windowType(a uint32) uint8 {
278+ for t := atomNetWMWindowTypeNormal; t <= atomNetWMWindowTypeDND; t++ {
279+ if x.atoms[t] == a {
280+ return uint8(t)
281+ }
282+ }
283+ return 0
284+}
285+
286+// floatingType reports whether a window type floats by itself.
287+func floatingType(t uint8) bool {
288+ switch int(t) {
289+ case atomNetWMWindowTypeDialog, atomNetWMWindowTypeUtility, atomNetWMWindowTypeSplash, atomNetWMWindowTypeToolbar:
290+ return true
291+ }
292+ return false
293+}
294+
295+// managedSlot returns the slot of the managed window xid, or 0.
296+func (x *XWM) managedSlot(xid uint32) Slot {
297+ if xid == 0 {
298+ return 0
299+ }
300+ if s := x.tab.lookup(xid); s != 0 && x.tab.rec(s).kind == kindManaged {
301+ return s
302+ }
303+ return 0
304+}
31305
32-// onPropReply decodes one GetProperty reply into the record.
33-func (x *XWM) onPropReply(s Slot, rec *record, r *xturbo.Reply) { _, _, _ = s, rec, r }
306+// queryClientIDs asks XRes for the PID of the client owning s, which stands
307+// in for a missing _NET_WM_PID: Ready waits for it (propClientIDs).
308+func (x *XWM) queryClientIDs(s Slot) {
309+ rec := x.tab.rec(s)
310+ specs := [1]res.ClientIdSpec{{Client: rec.xid, Mask: res.ClientIdMaskLocalClientPID}}
311+ seq := res.QueryClientIds(x.conn.Buf(), specs[:])
312+ if seq == 0 {
313+ return
314+ }
315+ rec.pending |= propClientIDs
316+ x.expect(seq, kindClientIds, x.tab.tag(s), 0)
317+}
34318
35-var _ = xproto.AtomWmClass
319+// onClientIdsReply stores the PID XRes found and releases the map.
320+func (x *XWM) onClientIdsReply(s Slot, rec *record, r *xturbo.Reply) {
321+ if q, ok := res.ParseQueryClientIdsReply(r.Raw); ok {
322+ it := q.Ids()
323+ for it.Next() {
324+ v := it.Value()
325+ if v.Spec().Mask == res.ClientIdMaskLocalClientPID && v.Value().Len() > 0 {
326+ rec.pid = int32(v.Value().At(0))
327+ break
328+ }
329+ }
330+ }
331+ rec.pending &^= propClientIDs
332+ x.checkReady(s)
333+}
@@ -1,12 +1,17 @@
1 package xwm1 package xwm
2 2
3 import (3 import (
4+ "bytes"
5+ "encoding/binary"
6+
4 "bnema.dev/xturbo"7 "bnema.dev/xturbo"
8+ "bnema.dev/xturbo/res"
5 "bnema.dev/xturbo/xproto"9 "bnema.dev/xturbo/xproto"
6 )10 )
7 11
8 // Property indices (second Expect argument of kindProp) and their bits in12 // Property indices (second Expect argument of kindProp) and their bits in
9-// record.pending. propGeom is the GetGeometry bit.13+// record.pending. propGeom is the GetGeometry bit; propClientIDs is raised
14+// while a QueryClientIds reply stands in for a missing _NET_WM_PID.
10 const (15 const (
11 propClass = iota16 propClass = iota
12 propName17 propName
@@ -22,14 +27,307 @@ const (
22 )27 )
23 28
24 const (29 const (
25- propGeom uint16 = 1 << propCount30+ propGeom uint16 = 1 << propCount
31+ propClientIDs uint16 = propGeom << 1
32+ // propMaskAll is what a map waits for: every property and the geometry.
26 propMaskAll uint16 = propGeom<<1 - 133 propMaskAll uint16 = propGeom<<1 - 1
27 )34 )
28 35
29-// onPropertyNotify handles a changed property of a managed window.36+// WM_NORMAL_HINTS flags and WM_HINTS flags.
30-func (x *XWM) onPropertyNotify(ev *xturbo.Event) { _ = ev }37+const (
38+ hintInput = 1 << 0
39+ hintMinSize = 1 << 4
40+ hintMaxSize = 1 << 5
41+)
42+
43+// maxPropWords bounds one GetProperty (4 KiB), so titles are truncated there.
44+const maxPropWords = 1024
45+
46+// propIndex maps a property atom to its index, or -1 when it is not watched.
47+func (x *XWM) propIndex(a uint32) int {
48+ switch a {
49+ case uint32(xproto.AtomWmClass):
50+ return propClass
51+ case uint32(xproto.AtomWmName):
52+ return propName
53+ case uint32(xproto.AtomWmHints):
54+ return propHints
55+ case uint32(xproto.AtomWmNormalHints):
56+ return propNormalHints
57+ case uint32(xproto.AtomWmTransientFor):
58+ return propTransientFor
59+ case x.atoms[atomNetWMName]:
60+ return propNetName
61+ case x.atoms[atomWMProtocols]:
62+ return propProtocols
63+ case x.atoms[atomNetWMWindowType]:
64+ return propWindowType
65+ case x.atoms[atomNetWMState]:
66+ return propState
67+ case x.atoms[atomNetWMPID]:
68+ return propPID
69+ }
70+ return -1
71+}
72+
73+func (x *XWM) propAtom(idx int) xproto.Atom {
74+ switch idx {
75+ case propClass:
76+ return xproto.AtomWmClass
77+ case propName:
78+ return xproto.AtomWmName
79+ case propHints:
80+ return xproto.AtomWmHints
81+ case propNormalHints:
82+ return xproto.AtomWmNormalHints
83+ case propTransientFor:
84+ return xproto.AtomWmTransientFor
85+ case propNetName:
86+ return xproto.Atom(x.atoms[atomNetWMName])
87+ case propProtocols:
88+ return xproto.Atom(x.atoms[atomWMProtocols])
89+ case propWindowType:
90+ return xproto.Atom(x.atoms[atomNetWMWindowType])
91+ case propState:
92+ return xproto.Atom(x.atoms[atomNetWMState])
93+ default:
94+ return xproto.Atom(x.atoms[atomNetWMPID])
95+ }
96+}
97+
98+// requestProp queues a GetProperty for property idx of s.
99+func (x *XWM) requestProp(s Slot, idx int) {
100+ rec := x.tab.rec(s)
101+ seq := xproto.GetProperty(x.conn.Buf(), false, xproto.Window(rec.xid), x.propAtom(idx), xproto.AtomAny, 0, maxPropWords)
102+ rec.pending |= 1 << idx
103+ x.expect(seq, kindProp, x.tab.tag(s), uint32(idx))
104+}
105+
106+// onPropertyNotify handles a changed property of a managed window that is
107+// being mapped or is mapped; unwatched atoms cost nothing.
108+func (x *XWM) onPropertyNotify(ev *xturbo.Event) {
109+ e := xproto.PropertyNotifyOf(ev)
110+ s := x.tab.lookup(uint32(e.Window()))
111+ if s == 0 {
112+ return
113+ }
114+ if rec := x.tab.rec(s); rec.kind != kindManaged || !rec.mapping {
115+ return
116+ }
117+ idx := x.propIndex(uint32(e.Atom()))
118+ if idx < 0 {
119+ return
120+ }
121+ if e.State() == xproto.PropertyDelete {
122+ x.applyProp(s, idx, 0, nil)
123+ return
124+ }
125+ x.requestProp(s, idx)
126+}
127+
128+// onPropReply decodes one GetProperty reply into the record. An absent
129+// property (format 0) clears its fields.
130+func (x *XWM) onPropReply(s Slot, _ *record, r *xturbo.Reply) {
131+ idx := int(r.Args[1])
132+ if idx < 0 || idx >= propCount {
133+ return
134+ }
135+ var format uint8
136+ var val []byte
137+ if rep, ok := xproto.ParseGetPropertyReply(r.Raw); ok {
138+ format, val = rep.Format(), rep.Value()
139+ }
140+ x.applyProp(s, idx, format, val)
141+ x.tab.rec(s).pending &^= 1 << idx
142+ x.checkReady(s)
143+}
144+
145+func word(b []byte, i int) uint32 { return binary.LittleEndian.Uint32(b[4*i:]) }
146+
147+// applyProp stores one decoded property. It does not allocate unless a string
148+// changed. Host calls come last: the record is not touched after them.
149+func (x *XWM) applyProp(s Slot, idx int, format uint8, val []byte) {
150+ rec := x.tab.rec(s)
151+ if idx == propClass || idx == propName || idx == propNetName {
152+ if format != 8 {
153+ val = nil
154+ }
155+ } else if format != 32 {
156+ val = nil
157+ }
158+ switch idx {
159+ case propClass:
160+ inst, class := val, []byte(nil)
161+ if i := bytes.IndexByte(val, 0); i >= 0 {
162+ inst, class = val[:i], val[i+1:]
163+ if j := bytes.IndexByte(class, 0); j >= 0 {
164+ class = class[:j]
165+ }
166+ }
167+ app := class
168+ if len(app) == 0 {
169+ app = inst
170+ }
171+ if rec.class == string(app) {
172+ return
173+ }
174+ rec.class = string(app)
175+ if rec.ready {
176+ x.host.AppID(s, rec.class)
177+ }
178+ case propName:
179+ if !rec.hasNetName {
180+ x.setTitle(s, val)
181+ }
182+ case propNetName:
183+ had := rec.hasNetName
184+ rec.hasNetName = len(val) > 0
185+ if rec.hasNetName {
186+ x.setTitle(s, val)
187+ } else if had && rec.mapping {
188+ x.requestProp(s, propName)
189+ }
190+ case propHints:
191+ rec.input = true
192+ if len(val) >= 8 && word(val, 0)&hintInput != 0 {
193+ rec.input = word(val, 1) != 0
194+ }
195+ case propNormalHints:
196+ rec.minW, rec.minH, rec.maxW, rec.maxH = 0, 0, 0, 0
197+ if len(val) >= 36 {
198+ fl := word(val, 0)
199+ if fl&hintMinSize != 0 {
200+ rec.minW, rec.minH = int32(word(val, 5)), int32(word(val, 6))
201+ }
202+ if fl&hintMaxSize != 0 {
203+ rec.maxW, rec.maxH = int32(word(val, 7)), int32(word(val, 8))
204+ }
205+ }
206+ case propProtocols:
207+ rec.deleteWindow, rec.takeFocus = false, false
208+ for i := 0; i+4 <= len(val); i += 4 {
209+ switch binary.LittleEndian.Uint32(val[i:]) {
210+ case x.atoms[atomWMDeleteWindow]:
211+ rec.deleteWindow = true
212+ case x.atoms[atomWMTakeFocus]:
213+ rec.takeFocus = true
214+ }
215+ }
216+ case propTransientFor:
217+ var t uint32
218+ if len(val) >= 4 {
219+ t = word(val, 0)
220+ }
221+ if t == rec.transient {
222+ return
223+ }
224+ rec.transient = t
225+ if rec.ready {
226+ x.host.Parent(s, x.managedSlot(t))
227+ }
228+ case propWindowType:
229+ rec.wtype = 0
230+ for i := 0; i+4 <= len(val); i += 4 {
231+ if t := x.windowType(binary.LittleEndian.Uint32(val[i:])); t != 0 {
232+ rec.wtype = t
233+ break
234+ }
235+ }
236+ case propState:
237+ var modal, fs bool
238+ for i := 0; i+4 <= len(val); i += 4 {
239+ switch binary.LittleEndian.Uint32(val[i:]) {
240+ case x.atoms[atomNetWMStateModal]:
241+ modal = true
242+ case x.atoms[atomNetWMStateFullscreen]:
243+ fs = true
244+ }
245+ }
246+ changed := fs != rec.fullscreen
247+ rec.modal, rec.fullscreen = modal, fs
248+ if changed && rec.ready {
249+ x.host.Fullscreen(s, fs)
250+ }
251+ case propPID:
252+ if len(val) >= 4 {
253+ rec.pid = int32(word(val, 0))
254+ return
255+ }
256+ rec.pid = 0
257+ if !rec.ready && rec.pending&propClientIDs == 0 && x.conn.Ext().Info(xturbo.ExtRes).Present {
258+ x.queryClientIDs(s)
259+ }
260+ }
261+}
262+
263+// setTitle stores a title when it differs; only a change allocates.
264+func (x *XWM) setTitle(s Slot, val []byte) {
265+ rec := x.tab.rec(s)
266+ if rec.title == string(val) {
267+ return
268+ }
269+ rec.title = string(val)
270+ if rec.ready {
271+ x.host.Title(s, rec.title)
272+ }
273+}
274+
275+// windowType returns the atom enum of a recognised _NET_WM_WINDOW_TYPE atom,
276+// or 0.
277+func (x *XWM) windowType(a uint32) uint8 {
278+ for t := atomNetWMWindowTypeNormal; t <= atomNetWMWindowTypeDND; t++ {
279+ if x.atoms[t] == a {
280+ return uint8(t)
281+ }
282+ }
283+ return 0
284+}
285+
286+// floatingType reports whether a window type floats by itself.
287+func floatingType(t uint8) bool {
288+ switch int(t) {
289+ case atomNetWMWindowTypeDialog, atomNetWMWindowTypeUtility, atomNetWMWindowTypeSplash, atomNetWMWindowTypeToolbar:
290+ return true
291+ }
292+ return false
293+}
294+
295+// managedSlot returns the slot of the managed window xid, or 0.
296+func (x *XWM) managedSlot(xid uint32) Slot {
297+ if xid == 0 {
298+ return 0
299+ }
300+ if s := x.tab.lookup(xid); s != 0 && x.tab.rec(s).kind == kindManaged {
301+ return s
302+ }
303+ return 0
304+}
31 305
32-// onPropReply decodes one GetProperty reply into the record.306+// queryClientIDs asks XRes for the PID of the client owning s, which stands
33-func (x *XWM) onPropReply(s Slot, rec *record, r *xturbo.Reply) { _, _, _ = s, rec, r }307+// in for a missing _NET_WM_PID: Ready waits for it (propClientIDs).
308+func (x *XWM) queryClientIDs(s Slot) {
309+ rec := x.tab.rec(s)
310+ specs := [1]res.ClientIdSpec{{Client: rec.xid, Mask: res.ClientIdMaskLocalClientPID}}
311+ seq := res.QueryClientIds(x.conn.Buf(), specs[:])
312+ if seq == 0 {
313+ return
314+ }
315+ rec.pending |= propClientIDs
316+ x.expect(seq, kindClientIds, x.tab.tag(s), 0)
317+}
34 318
35-var _ = xproto.AtomWmClass319+// onClientIdsReply stores the PID XRes found and releases the map.
320+func (x *XWM) onClientIdsReply(s Slot, rec *record, r *xturbo.Reply) {
321+ if q, ok := res.ParseQueryClientIdsReply(r.Raw); ok {
322+ it := q.Ids()
323+ for it.Next() {
324+ v := it.Value()
325+ if v.Spec().Mask == res.ClientIdMaskLocalClientPID && v.Value().Len() > 0 {
326+ rec.pid = int32(v.Value().At(0))
327+ break
328+ }
329+ }
330+ }
331+ rec.pending &^= propClientIDs
332+ x.checkReady(s)
333+}
A internal/adapters/wayland/xwm/streams_test.go +178 -0
@@ -0,0 +1,178 @@
1+package xwm
2+
3+import (
4+ "encoding/hex"
5+ "os"
6+ "path/filepath"
7+ "runtime"
8+ "strings"
9+ "testing"
10+ "time"
11+
12+ "bnema.dev/xturbo/xproto"
13+ "github.com/stretchr/testify/require"
14+)
15+
16+// The recorded streams are the bytes an X server wrote to the XWM during a
17+// steady-state session of one mapped window, captured from the scripted xpeer
18+// (real encoded X packets: events, GetProperty replies). The scenario is
19+// deterministic, so a replay meets the request sequence numbers it recorded.
20+// They are not xtrace captures of a real Xwayland: xpeer speaks the same wire
21+// protocol and keeps the fixture reproducible. Regenerate with
22+//
23+// XWM_UPDATE_STREAMS=1 go test ./internal/adapters/wayland/xwm -run TestRecordedStreams
24+const (
25+ streamCycles = 400
26+ streamWarm = 20 // cycles replayed before measuring
27+)
28+
29+const streamWin = 0x500001
30+
31+type streamKind struct {
32+ name string
33+ // perCycle is Events+Replies the XWM sees per cycle.
34+ perCycle int
35+ cycle func(hs *harness)
36+}
37+
38+var streamKinds = []streamKind{
39+ // A title notification the XWM answers with one GetProperty whose reply
40+ // equals the stored title.
41+ {"property_unchanged", 2, func(hs *harness) {
42+ before := hs.x.Stats().Replies
43+ hs.p.propertyNotify(streamWin, hs.p.atom("_NET_WM_NAME"), false)
44+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
45+ }},
46+ // The same plus ConfigureNotify, FocusIn and MapNotify.
47+ {"steady", 5, func(hs *harness) {
48+ before := hs.x.Stats().Replies
49+ hs.p.propertyNotify(streamWin, hs.p.atom("_NET_WM_NAME"), false)
50+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
51+ events := hs.x.Stats().Events
52+ hs.p.configureNotify(streamWin)
53+ hs.p.focusIn(streamWin)
54+ hs.p.mapNotify(streamWin)
55+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Events >= events+3 })
56+ }},
57+}
58+
59+// streamScenario boots, maps one window and parks the peer behind a poke so
60+// nothing but the test writes to the XWM afterwards (an allocation count is
61+// process-wide). The poke is part of the scenario, recorded or replayed.
62+func streamScenario(t *testing.T) *harness {
63+ t.Helper()
64+ hs := newHarness(t, nil)
65+ hs.classic(streamWin)
66+ hs.mapAndWait(streamWin)
67+ return hs
68+}
69+
70+func poke(hs *harness) {
71+ hs.t.Helper()
72+ n := len(hs.p.requests())
73+ xproto.GetInputFocus(hs.x.conn.Buf())
74+ hs.x.flush()
75+ hs.p.waitReq(n, func(r xpeerReq) bool { return r.major == opGetInputFocus })
76+}
77+
78+func recordStream(t *testing.T, k streamKind) []byte {
79+ hs := streamScenario(t)
80+ poke(hs)
81+ hs.p.mu.Lock()
82+ hs.p.record = true
83+ hs.p.mu.Unlock()
84+ for i := 0; i < streamCycles; i++ {
85+ k.cycle(hs)
86+ }
87+ hs.p.mu.Lock()
88+ defer hs.p.mu.Unlock()
89+ hs.p.record = false
90+ return append([]byte(nil), hs.p.tap...)
91+}
92+
93+func streamPath(k streamKind) string {
94+ return filepath.Join("testdata", "streams", k.name+".hex")
95+}
96+
97+func loadStream(t *testing.T, k streamKind) []byte {
98+ t.Helper()
99+ text, err := os.ReadFile(streamPath(k))
100+ require.NoError(t, err)
101+ b, err := hex.DecodeString(strings.Join(strings.Fields(string(text)), ""))
102+ require.NoError(t, err)
103+ return b
104+}
105+
106+func encodeHex(b []byte) string {
107+ var sb strings.Builder
108+ for len(b) > 0 {
109+ n := min(32, len(b))
110+ sb.WriteString(hex.EncodeToString(b[:n]))
111+ sb.WriteByte('\n')
112+ b = b[n:]
113+ }
114+ return sb.String()
115+}
116+
117+func TestRecordedStreams(t *testing.T) {
118+ for _, k := range streamKinds {
119+ t.Run(k.name, func(t *testing.T) {
120+ got := recordStream(t, k)
121+ if os.Getenv("XWM_UPDATE_STREAMS") != "" {
122+ require.NoError(t, os.MkdirAll(filepath.Dir(streamPath(k)), 0o755))
123+ require.NoError(t, os.WriteFile(streamPath(k), []byte(encodeHex(got)), 0o644))
124+ }
125+ require.Equal(t, loadStream(t, k), got, "the committed stream no longer matches the protocol the XWM speaks")
126+ })
127+ }
128+}
129+
130+// replay feeds the recorded stream and reports the mallocs of the part after
131+// the warm-up cycles.
132+func replay(t *testing.T, k streamKind) (mallocs uint64, hs *harness) {
133+ t.Helper()
134+ stream := loadStream(t, k)
135+ hs = streamScenario(t)
136+ hs.p.pause()
137+ poke(hs)
138+ _, err := hs.p.f.Write(stream)
139+ require.NoError(t, err)
140+
141+ x := hs.x
142+ base := x.Stats().Events + x.Stats().Replies
143+ drive := func(until uint64) {
144+ deadline := time.Now().Add(10 * time.Second)
145+ for x.Stats().Events+x.Stats().Replies < until {
146+ if time.Now().After(deadline) { // no formatting arguments: boxing them would count as mallocs
147+ t.Fatal("stream stalled")
148+ }
149+ before := x.Stats().Events + x.Stats().Replies
150+ x.onReadable()
151+ if x.Stats().Events+x.Stats().Replies == before {
152+ runtime.Gosched()
153+ }
154+ }
155+ }
156+ drive(base + uint64(streamWarm*k.perCycle))
157+ target := base + uint64(streamCycles*k.perCycle)
158+
159+ var m0, m1 runtime.MemStats
160+ runtime.GC()
161+ runtime.ReadMemStats(&m0)
162+ drive(target)
163+ runtime.ReadMemStats(&m1)
164+ require.Equal(t, target, x.Stats().Events+x.Stats().Replies)
165+ return m1.Mallocs - m0.Mallocs, hs
166+}
167+
168+func TestPropertyUnchangedAllocations(t *testing.T) {
169+ m, hs := replay(t, streamKinds[0])
170+ require.Zero(t, m, "unchanged property cycles must not allocate")
171+ require.Equal(t, "net title", hs.x.tab.rec(hs.x.Slot(streamWin)).title)
172+}
173+
174+func TestDispatchAllocations(t *testing.T) {
175+ m, hs := replay(t, streamKinds[1])
176+ require.Zero(t, m, "steady-state event dispatch must not allocate")
177+ require.True(t, hs.x.tab.rec(hs.x.Slot(streamWin)).mapped)
178+}
@@ -0,0 +1,178 @@
1+package xwm
2+
3+import (
4+ "encoding/hex"
5+ "os"
6+ "path/filepath"
7+ "runtime"
8+ "strings"
9+ "testing"
10+ "time"
11+
12+ "bnema.dev/xturbo/xproto"
13+ "github.com/stretchr/testify/require"
14+)
15+
16+// The recorded streams are the bytes an X server wrote to the XWM during a
17+// steady-state session of one mapped window, captured from the scripted xpeer
18+// (real encoded X packets: events, GetProperty replies). The scenario is
19+// deterministic, so a replay meets the request sequence numbers it recorded.
20+// They are not xtrace captures of a real Xwayland: xpeer speaks the same wire
21+// protocol and keeps the fixture reproducible. Regenerate with
22+//
23+// XWM_UPDATE_STREAMS=1 go test ./internal/adapters/wayland/xwm -run TestRecordedStreams
24+const (
25+ streamCycles = 400
26+ streamWarm = 20 // cycles replayed before measuring
27+)
28+
29+const streamWin = 0x500001
30+
31+type streamKind struct {
32+ name string
33+ // perCycle is Events+Replies the XWM sees per cycle.
34+ perCycle int
35+ cycle func(hs *harness)
36+}
37+
38+var streamKinds = []streamKind{
39+ // A title notification the XWM answers with one GetProperty whose reply
40+ // equals the stored title.
41+ {"property_unchanged", 2, func(hs *harness) {
42+ before := hs.x.Stats().Replies
43+ hs.p.propertyNotify(streamWin, hs.p.atom("_NET_WM_NAME"), false)
44+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
45+ }},
46+ // The same plus ConfigureNotify, FocusIn and MapNotify.
47+ {"steady", 5, func(hs *harness) {
48+ before := hs.x.Stats().Replies
49+ hs.p.propertyNotify(streamWin, hs.p.atom("_NET_WM_NAME"), false)
50+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Replies > before })
51+ events := hs.x.Stats().Events
52+ hs.p.configureNotify(streamWin)
53+ hs.p.focusIn(streamWin)
54+ hs.p.mapNotify(streamWin)
55+ readUntil(hs.t, hs.x, func() bool { return hs.x.Stats().Events >= events+3 })
56+ }},
57+}
58+
59+// streamScenario boots, maps one window and parks the peer behind a poke so
60+// nothing but the test writes to the XWM afterwards (an allocation count is
61+// process-wide). The poke is part of the scenario, recorded or replayed.
62+func streamScenario(t *testing.T) *harness {
63+ t.Helper()
64+ hs := newHarness(t, nil)
65+ hs.classic(streamWin)
66+ hs.mapAndWait(streamWin)
67+ return hs
68+}
69+
70+func poke(hs *harness) {
71+ hs.t.Helper()
72+ n := len(hs.p.requests())
73+ xproto.GetInputFocus(hs.x.conn.Buf())
74+ hs.x.flush()
75+ hs.p.waitReq(n, func(r xpeerReq) bool { return r.major == opGetInputFocus })
76+}
77+
78+func recordStream(t *testing.T, k streamKind) []byte {
79+ hs := streamScenario(t)
80+ poke(hs)
81+ hs.p.mu.Lock()
82+ hs.p.record = true
83+ hs.p.mu.Unlock()
84+ for i := 0; i < streamCycles; i++ {
85+ k.cycle(hs)
86+ }
87+ hs.p.mu.Lock()
88+ defer hs.p.mu.Unlock()
89+ hs.p.record = false
90+ return append([]byte(nil), hs.p.tap...)
91+}
92+
93+func streamPath(k streamKind) string {
94+ return filepath.Join("testdata", "streams", k.name+".hex")
95+}
96+
97+func loadStream(t *testing.T, k streamKind) []byte {
98+ t.Helper()
99+ text, err := os.ReadFile(streamPath(k))
100+ require.NoError(t, err)
101+ b, err := hex.DecodeString(strings.Join(strings.Fields(string(text)), ""))
102+ require.NoError(t, err)
103+ return b
104+}
105+
106+func encodeHex(b []byte) string {
107+ var sb strings.Builder
108+ for len(b) > 0 {
109+ n := min(32, len(b))
110+ sb.WriteString(hex.EncodeToString(b[:n]))
111+ sb.WriteByte('\n')
112+ b = b[n:]
113+ }
114+ return sb.String()
115+}
116+
117+func TestRecordedStreams(t *testing.T) {
118+ for _, k := range streamKinds {
119+ t.Run(k.name, func(t *testing.T) {
120+ got := recordStream(t, k)
121+ if os.Getenv("XWM_UPDATE_STREAMS") != "" {
122+ require.NoError(t, os.MkdirAll(filepath.Dir(streamPath(k)), 0o755))
123+ require.NoError(t, os.WriteFile(streamPath(k), []byte(encodeHex(got)), 0o644))
124+ }
125+ require.Equal(t, loadStream(t, k), got, "the committed stream no longer matches the protocol the XWM speaks")
126+ })
127+ }
128+}
129+
130+// replay feeds the recorded stream and reports the mallocs of the part after
131+// the warm-up cycles.
132+func replay(t *testing.T, k streamKind) (mallocs uint64, hs *harness) {
133+ t.Helper()
134+ stream := loadStream(t, k)
135+ hs = streamScenario(t)
136+ hs.p.pause()
137+ poke(hs)
138+ _, err := hs.p.f.Write(stream)
139+ require.NoError(t, err)
140+
141+ x := hs.x
142+ base := x.Stats().Events + x.Stats().Replies
143+ drive := func(until uint64) {
144+ deadline := time.Now().Add(10 * time.Second)
145+ for x.Stats().Events+x.Stats().Replies < until {
146+ if time.Now().After(deadline) { // no formatting arguments: boxing them would count as mallocs
147+ t.Fatal("stream stalled")
148+ }
149+ before := x.Stats().Events + x.Stats().Replies
150+ x.onReadable()
151+ if x.Stats().Events+x.Stats().Replies == before {
152+ runtime.Gosched()
153+ }
154+ }
155+ }
156+ drive(base + uint64(streamWarm*k.perCycle))
157+ target := base + uint64(streamCycles*k.perCycle)
158+
159+ var m0, m1 runtime.MemStats
160+ runtime.GC()
161+ runtime.ReadMemStats(&m0)
162+ drive(target)
163+ runtime.ReadMemStats(&m1)
164+ require.Equal(t, target, x.Stats().Events+x.Stats().Replies)
165+ return m1.Mallocs - m0.Mallocs, hs
166+}
167+
168+func TestPropertyUnchangedAllocations(t *testing.T) {
169+ m, hs := replay(t, streamKinds[0])
170+ require.Zero(t, m, "unchanged property cycles must not allocate")
171+ require.Equal(t, "net title", hs.x.tab.rec(hs.x.Slot(streamWin)).title)
172+}
173+
174+func TestDispatchAllocations(t *testing.T) {
175+ m, hs := replay(t, streamKinds[1])
176+ require.Zero(t, m, "steady-state event dispatch must not allocate")
177+ require.True(t, hs.x.tab.rec(hs.x.Slot(streamWin)).mapped)
178+}
M internal/adapters/wayland/xwm/table.go +4 -2
@@ -1,4 +1,4 @@
1-//lint:file-ignore U1000 the record fields below are filled by the map, property, configure and focus flows that build on this skeleton
1+//lint:file-ignore U1000 pend, sent and stateSent are used by the configure flow
22
33 package xwm
44
@@ -202,7 +202,9 @@ func (t *table) lookup(xid uint32) Slot {
202202 }
203203 }
204204
205-// rec returns the record of s, or nil when s is out of range.
205+// rec returns the record of s, or nil when s is out of range. The pointer is
206+// valid only until the next insert, host call or anything else that can grow
207+// the table or free s: never hold it across those, fetch it again with rec(s).
206208 func (t *table) rec(s Slot) *record {
207209 if s == 0 || int(s) > len(t.recs) {
208210 return nil
@@ -1,4 +1,4 @@
1-//lint:file-ignore U1000 the record fields below are filled by the map, property, configure and focus flows that build on this skeleton1+//lint:file-ignore U1000 pend, sent and stateSent are used by the configure flow
2 2
3 package xwm3 package xwm
4 4
@@ -202,7 +202,9 @@ func (t *table) lookup(xid uint32) Slot {
202 }202 }
203 }203 }
204 204
205-// rec returns the record of s, or nil when s is out of range.205+// rec returns the record of s, or nil when s is out of range. The pointer is
206+// valid only until the next insert, host call or anything else that can grow
207+// the table or free s: never hold it across those, fetch it again with rec(s).
206 func (t *table) rec(s Slot) *record {208 func (t *table) rec(s Slot) *record {
207 if s == 0 || int(s) > len(t.recs) {209 if s == 0 || int(s) > len(t.recs) {
208 return nil210 return nil
A internal/adapters/wayland/xwm/testdata/streams/property_unchanged.hex +950 -0
@@ -0,0 +1,950 @@
1+1c005f0001005000f30300000000000000000000000000000000000000000000
2+0108600003000000ef0300000000000009000000000000000000000000000000
3+6e6574207469746c650000001c00600001005000f30300000000000000000000
4+0000000000000000000000000108610003000000ef0300000000000009000000
5+0000000000000000000000006e6574207469746c650000001c00610001005000
6+f303000000000000000000000000000000000000000000000108620003000000
7+ef03000000000000090000000000000000000000000000006e6574207469746c
8+650000001c00620001005000f303000000000000000000000000000000000000
9+000000000108630003000000ef03000000000000090000000000000000000000
10+000000006e6574207469746c650000001c00630001005000f303000000000000
11+000000000000000000000000000000000108640003000000ef03000000000000
12+090000000000000000000000000000006e6574207469746c650000001c006400
13+01005000f3030000000000000000000000000000000000000000000001086500
14+03000000ef03000000000000090000000000000000000000000000006e657420
15+7469746c650000001c00650001005000f3030000000000000000000000000000
16+00000000000000000108660003000000ef030000000000000900000000000000
17+00000000000000006e6574207469746c650000001c00660001005000f3030000
18+00000000000000000000000000000000000000000108670003000000ef030000
19+00000000090000000000000000000000000000006e6574207469746c65000000
20+1c00670001005000f30300000000000000000000000000000000000000000000
21+0108680003000000ef0300000000000009000000000000000000000000000000
22+6e6574207469746c650000001c00680001005000f30300000000000000000000
23+0000000000000000000000000108690003000000ef0300000000000009000000
24+0000000000000000000000006e6574207469746c650000001c00690001005000
25+f3030000000000000000000000000000000000000000000001086a0003000000
26+ef03000000000000090000000000000000000000000000006e6574207469746c
27+650000001c006a0001005000f303000000000000000000000000000000000000
28+0000000001086b0003000000ef03000000000000090000000000000000000000
29+000000006e6574207469746c650000001c006b0001005000f303000000000000
30+0000000000000000000000000000000001086c0003000000ef03000000000000
31+090000000000000000000000000000006e6574207469746c650000001c006c00
32+01005000f3030000000000000000000000000000000000000000000001086d00
33+03000000ef03000000000000090000000000000000000000000000006e657420
34+7469746c650000001c006d0001005000f3030000000000000000000000000000
35+000000000000000001086e0003000000ef030000000000000900000000000000
36+00000000000000006e6574207469746c650000001c006e0001005000f3030000
37+000000000000000000000000000000000000000001086f0003000000ef030000
38+00000000090000000000000000000000000000006e6574207469746c65000000
39+1c006f0001005000f30300000000000000000000000000000000000000000000
40+0108700003000000ef0300000000000009000000000000000000000000000000
41+6e6574207469746c650000001c00700001005000f30300000000000000000000
42+0000000000000000000000000108710003000000ef0300000000000009000000
43+0000000000000000000000006e6574207469746c650000001c00710001005000
44+f303000000000000000000000000000000000000000000000108720003000000
45+ef03000000000000090000000000000000000000000000006e6574207469746c
46+650000001c00720001005000f303000000000000000000000000000000000000
47+000000000108730003000000ef03000000000000090000000000000000000000
48+000000006e6574207469746c650000001c00730001005000f303000000000000
49+000000000000000000000000000000000108740003000000ef03000000000000
50+090000000000000000000000000000006e6574207469746c650000001c007400
51+01005000f3030000000000000000000000000000000000000000000001087500
52+03000000ef03000000000000090000000000000000000000000000006e657420
53+7469746c650000001c00750001005000f3030000000000000000000000000000
54+00000000000000000108760003000000ef030000000000000900000000000000
55+00000000000000006e6574207469746c650000001c00760001005000f3030000
56+00000000000000000000000000000000000000000108770003000000ef030000
57+00000000090000000000000000000000000000006e6574207469746c65000000
58+1c00770001005000f30300000000000000000000000000000000000000000000
59+0108780003000000ef0300000000000009000000000000000000000000000000
60+6e6574207469746c650000001c00780001005000f30300000000000000000000
61+0000000000000000000000000108790003000000ef0300000000000009000000
62+0000000000000000000000006e6574207469746c650000001c00790001005000
63+f3030000000000000000000000000000000000000000000001087a0003000000
64+ef03000000000000090000000000000000000000000000006e6574207469746c
65+650000001c007a0001005000f303000000000000000000000000000000000000
66+0000000001087b0003000000ef03000000000000090000000000000000000000
67+000000006e6574207469746c650000001c007b0001005000f303000000000000
68+0000000000000000000000000000000001087c0003000000ef03000000000000
69+090000000000000000000000000000006e6574207469746c650000001c007c00
70+01005000f3030000000000000000000000000000000000000000000001087d00
71+03000000ef03000000000000090000000000000000000000000000006e657420
72+7469746c650000001c007d0001005000f3030000000000000000000000000000
73+000000000000000001087e0003000000ef030000000000000900000000000000
74+00000000000000006e6574207469746c650000001c007e0001005000f3030000
75+000000000000000000000000000000000000000001087f0003000000ef030000
76+00000000090000000000000000000000000000006e6574207469746c65000000
77+1c007f0001005000f30300000000000000000000000000000000000000000000
78+0108800003000000ef0300000000000009000000000000000000000000000000
79+6e6574207469746c650000001c00800001005000f30300000000000000000000
80+0000000000000000000000000108810003000000ef0300000000000009000000
81+0000000000000000000000006e6574207469746c650000001c00810001005000
82+f303000000000000000000000000000000000000000000000108820003000000
83+ef03000000000000090000000000000000000000000000006e6574207469746c
84+650000001c00820001005000f303000000000000000000000000000000000000
85+000000000108830003000000ef03000000000000090000000000000000000000
86+000000006e6574207469746c650000001c00830001005000f303000000000000
87+000000000000000000000000000000000108840003000000ef03000000000000
88+090000000000000000000000000000006e6574207469746c650000001c008400
89+01005000f3030000000000000000000000000000000000000000000001088500
90+03000000ef03000000000000090000000000000000000000000000006e657420
91+7469746c650000001c00850001005000f3030000000000000000000000000000
92+00000000000000000108860003000000ef030000000000000900000000000000
93+00000000000000006e6574207469746c650000001c00860001005000f3030000
94+00000000000000000000000000000000000000000108870003000000ef030000
95+00000000090000000000000000000000000000006e6574207469746c65000000
96+1c00870001005000f30300000000000000000000000000000000000000000000
97+0108880003000000ef0300000000000009000000000000000000000000000000
98+6e6574207469746c650000001c00880001005000f30300000000000000000000
99+0000000000000000000000000108890003000000ef0300000000000009000000
100+0000000000000000000000006e6574207469746c650000001c00890001005000
101+f3030000000000000000000000000000000000000000000001088a0003000000
102+ef03000000000000090000000000000000000000000000006e6574207469746c
103+650000001c008a0001005000f303000000000000000000000000000000000000
104+0000000001088b0003000000ef03000000000000090000000000000000000000
105+000000006e6574207469746c650000001c008b0001005000f303000000000000
106+0000000000000000000000000000000001088c0003000000ef03000000000000
107+090000000000000000000000000000006e6574207469746c650000001c008c00
108+01005000f3030000000000000000000000000000000000000000000001088d00
109+03000000ef03000000000000090000000000000000000000000000006e657420
110+7469746c650000001c008d0001005000f3030000000000000000000000000000
111+000000000000000001088e0003000000ef030000000000000900000000000000
112+00000000000000006e6574207469746c650000001c008e0001005000f3030000
113+000000000000000000000000000000000000000001088f0003000000ef030000
114+00000000090000000000000000000000000000006e6574207469746c65000000
115+1c008f0001005000f30300000000000000000000000000000000000000000000
116+0108900003000000ef0300000000000009000000000000000000000000000000
117+6e6574207469746c650000001c00900001005000f30300000000000000000000
118+0000000000000000000000000108910003000000ef0300000000000009000000
119+0000000000000000000000006e6574207469746c650000001c00910001005000
120+f303000000000000000000000000000000000000000000000108920003000000
121+ef03000000000000090000000000000000000000000000006e6574207469746c
122+650000001c00920001005000f303000000000000000000000000000000000000
123+000000000108930003000000ef03000000000000090000000000000000000000
124+000000006e6574207469746c650000001c00930001005000f303000000000000
125+000000000000000000000000000000000108940003000000ef03000000000000
126+090000000000000000000000000000006e6574207469746c650000001c009400
127+01005000f3030000000000000000000000000000000000000000000001089500
128+03000000ef03000000000000090000000000000000000000000000006e657420
129+7469746c650000001c00950001005000f3030000000000000000000000000000
130+00000000000000000108960003000000ef030000000000000900000000000000
131+00000000000000006e6574207469746c650000001c00960001005000f3030000
132+00000000000000000000000000000000000000000108970003000000ef030000
133+00000000090000000000000000000000000000006e6574207469746c65000000
134+1c00970001005000f30300000000000000000000000000000000000000000000
135+0108980003000000ef0300000000000009000000000000000000000000000000
136+6e6574207469746c650000001c00980001005000f30300000000000000000000
137+0000000000000000000000000108990003000000ef0300000000000009000000
138+0000000000000000000000006e6574207469746c650000001c00990001005000
139+f3030000000000000000000000000000000000000000000001089a0003000000
140+ef03000000000000090000000000000000000000000000006e6574207469746c
141+650000001c009a0001005000f303000000000000000000000000000000000000
142+0000000001089b0003000000ef03000000000000090000000000000000000000
143+000000006e6574207469746c650000001c009b0001005000f303000000000000
144+0000000000000000000000000000000001089c0003000000ef03000000000000
145+090000000000000000000000000000006e6574207469746c650000001c009c00
146+01005000f3030000000000000000000000000000000000000000000001089d00
147+03000000ef03000000000000090000000000000000000000000000006e657420
148+7469746c650000001c009d0001005000f3030000000000000000000000000000
149+000000000000000001089e0003000000ef030000000000000900000000000000
150+00000000000000006e6574207469746c650000001c009e0001005000f3030000
151+000000000000000000000000000000000000000001089f0003000000ef030000
152+00000000090000000000000000000000000000006e6574207469746c65000000
153+1c009f0001005000f30300000000000000000000000000000000000000000000
154+0108a00003000000ef0300000000000009000000000000000000000000000000
155+6e6574207469746c650000001c00a00001005000f30300000000000000000000
156+0000000000000000000000000108a10003000000ef0300000000000009000000
157+0000000000000000000000006e6574207469746c650000001c00a10001005000
158+f303000000000000000000000000000000000000000000000108a20003000000
159+ef03000000000000090000000000000000000000000000006e6574207469746c
160+650000001c00a20001005000f303000000000000000000000000000000000000
161+000000000108a30003000000ef03000000000000090000000000000000000000
162+000000006e6574207469746c650000001c00a30001005000f303000000000000
163+000000000000000000000000000000000108a40003000000ef03000000000000
164+090000000000000000000000000000006e6574207469746c650000001c00a400
165+01005000f303000000000000000000000000000000000000000000000108a500
166+03000000ef03000000000000090000000000000000000000000000006e657420
167+7469746c650000001c00a50001005000f3030000000000000000000000000000
168+00000000000000000108a60003000000ef030000000000000900000000000000
169+00000000000000006e6574207469746c650000001c00a60001005000f3030000
170+00000000000000000000000000000000000000000108a70003000000ef030000
171+00000000090000000000000000000000000000006e6574207469746c65000000
172+1c00a70001005000f30300000000000000000000000000000000000000000000
173+0108a80003000000ef0300000000000009000000000000000000000000000000
174+6e6574207469746c650000001c00a80001005000f30300000000000000000000
175+0000000000000000000000000108a90003000000ef0300000000000009000000
176+0000000000000000000000006e6574207469746c650000001c00a90001005000
177+f303000000000000000000000000000000000000000000000108aa0003000000
178+ef03000000000000090000000000000000000000000000006e6574207469746c
179+650000001c00aa0001005000f303000000000000000000000000000000000000
180+000000000108ab0003000000ef03000000000000090000000000000000000000
181+000000006e6574207469746c650000001c00ab0001005000f303000000000000
182+000000000000000000000000000000000108ac0003000000ef03000000000000
183+090000000000000000000000000000006e6574207469746c650000001c00ac00
184+01005000f303000000000000000000000000000000000000000000000108ad00
185+03000000ef03000000000000090000000000000000000000000000006e657420
186+7469746c650000001c00ad0001005000f3030000000000000000000000000000
187+00000000000000000108ae0003000000ef030000000000000900000000000000
188+00000000000000006e6574207469746c650000001c00ae0001005000f3030000
189+00000000000000000000000000000000000000000108af0003000000ef030000
190+00000000090000000000000000000000000000006e6574207469746c65000000
191+1c00af0001005000f30300000000000000000000000000000000000000000000
192+0108b00003000000ef0300000000000009000000000000000000000000000000
193+6e6574207469746c650000001c00b00001005000f30300000000000000000000
194+0000000000000000000000000108b10003000000ef0300000000000009000000
195+0000000000000000000000006e6574207469746c650000001c00b10001005000
196+f303000000000000000000000000000000000000000000000108b20003000000
197+ef03000000000000090000000000000000000000000000006e6574207469746c
198+650000001c00b20001005000f303000000000000000000000000000000000000
199+000000000108b30003000000ef03000000000000090000000000000000000000
200+000000006e6574207469746c650000001c00b30001005000f303000000000000
201+000000000000000000000000000000000108b40003000000ef03000000000000
202+090000000000000000000000000000006e6574207469746c650000001c00b400
203+01005000f303000000000000000000000000000000000000000000000108b500
204+03000000ef03000000000000090000000000000000000000000000006e657420
205+7469746c650000001c00b50001005000f3030000000000000000000000000000
206+00000000000000000108b60003000000ef030000000000000900000000000000
207+00000000000000006e6574207469746c650000001c00b60001005000f3030000
208+00000000000000000000000000000000000000000108b70003000000ef030000
209+00000000090000000000000000000000000000006e6574207469746c65000000
210+1c00b70001005000f30300000000000000000000000000000000000000000000
211+0108b80003000000ef0300000000000009000000000000000000000000000000
212+6e6574207469746c650000001c00b80001005000f30300000000000000000000
213+0000000000000000000000000108b90003000000ef0300000000000009000000
214+0000000000000000000000006e6574207469746c650000001c00b90001005000
215+f303000000000000000000000000000000000000000000000108ba0003000000
216+ef03000000000000090000000000000000000000000000006e6574207469746c
217+650000001c00ba0001005000f303000000000000000000000000000000000000
218+000000000108bb0003000000ef03000000000000090000000000000000000000
219+000000006e6574207469746c650000001c00bb0001005000f303000000000000
220+000000000000000000000000000000000108bc0003000000ef03000000000000
221+090000000000000000000000000000006e6574207469746c650000001c00bc00
222+01005000f303000000000000000000000000000000000000000000000108bd00
223+03000000ef03000000000000090000000000000000000000000000006e657420
224+7469746c650000001c00bd0001005000f3030000000000000000000000000000
225+00000000000000000108be0003000000ef030000000000000900000000000000
226+00000000000000006e6574207469746c650000001c00be0001005000f3030000
227+00000000000000000000000000000000000000000108bf0003000000ef030000
228+00000000090000000000000000000000000000006e6574207469746c65000000
229+1c00bf0001005000f30300000000000000000000000000000000000000000000
230+0108c00003000000ef0300000000000009000000000000000000000000000000
231+6e6574207469746c650000001c00c00001005000f30300000000000000000000
232+0000000000000000000000000108c10003000000ef0300000000000009000000
233+0000000000000000000000006e6574207469746c650000001c00c10001005000
234+f303000000000000000000000000000000000000000000000108c20003000000
235+ef03000000000000090000000000000000000000000000006e6574207469746c
236+650000001c00c20001005000f303000000000000000000000000000000000000
237+000000000108c30003000000ef03000000000000090000000000000000000000
238+000000006e6574207469746c650000001c00c30001005000f303000000000000
239+000000000000000000000000000000000108c40003000000ef03000000000000
240+090000000000000000000000000000006e6574207469746c650000001c00c400
241+01005000f303000000000000000000000000000000000000000000000108c500
242+03000000ef03000000000000090000000000000000000000000000006e657420
243+7469746c650000001c00c50001005000f3030000000000000000000000000000
244+00000000000000000108c60003000000ef030000000000000900000000000000
245+00000000000000006e6574207469746c650000001c00c60001005000f3030000
246+00000000000000000000000000000000000000000108c70003000000ef030000
247+00000000090000000000000000000000000000006e6574207469746c65000000
248+1c00c70001005000f30300000000000000000000000000000000000000000000
249+0108c80003000000ef0300000000000009000000000000000000000000000000
250+6e6574207469746c650000001c00c80001005000f30300000000000000000000
251+0000000000000000000000000108c90003000000ef0300000000000009000000
252+0000000000000000000000006e6574207469746c650000001c00c90001005000
253+f303000000000000000000000000000000000000000000000108ca0003000000
254+ef03000000000000090000000000000000000000000000006e6574207469746c
255+650000001c00ca0001005000f303000000000000000000000000000000000000
256+000000000108cb0003000000ef03000000000000090000000000000000000000
257+000000006e6574207469746c650000001c00cb0001005000f303000000000000
258+000000000000000000000000000000000108cc0003000000ef03000000000000
259+090000000000000000000000000000006e6574207469746c650000001c00cc00
260+01005000f303000000000000000000000000000000000000000000000108cd00
261+03000000ef03000000000000090000000000000000000000000000006e657420
262+7469746c650000001c00cd0001005000f3030000000000000000000000000000
263+00000000000000000108ce0003000000ef030000000000000900000000000000
264+00000000000000006e6574207469746c650000001c00ce0001005000f3030000
265+00000000000000000000000000000000000000000108cf0003000000ef030000
266+00000000090000000000000000000000000000006e6574207469746c65000000
267+1c00cf0001005000f30300000000000000000000000000000000000000000000
268+0108d00003000000ef0300000000000009000000000000000000000000000000
269+6e6574207469746c650000001c00d00001005000f30300000000000000000000
270+0000000000000000000000000108d10003000000ef0300000000000009000000
271+0000000000000000000000006e6574207469746c650000001c00d10001005000
272+f303000000000000000000000000000000000000000000000108d20003000000
273+ef03000000000000090000000000000000000000000000006e6574207469746c
274+650000001c00d20001005000f303000000000000000000000000000000000000
275+000000000108d30003000000ef03000000000000090000000000000000000000
276+000000006e6574207469746c650000001c00d30001005000f303000000000000
277+000000000000000000000000000000000108d40003000000ef03000000000000
278+090000000000000000000000000000006e6574207469746c650000001c00d400
279+01005000f303000000000000000000000000000000000000000000000108d500
280+03000000ef03000000000000090000000000000000000000000000006e657420
281+7469746c650000001c00d50001005000f3030000000000000000000000000000
282+00000000000000000108d60003000000ef030000000000000900000000000000
283+00000000000000006e6574207469746c650000001c00d60001005000f3030000
284+00000000000000000000000000000000000000000108d70003000000ef030000
285+00000000090000000000000000000000000000006e6574207469746c65000000
286+1c00d70001005000f30300000000000000000000000000000000000000000000
287+0108d80003000000ef0300000000000009000000000000000000000000000000
288+6e6574207469746c650000001c00d80001005000f30300000000000000000000
289+0000000000000000000000000108d90003000000ef0300000000000009000000
290+0000000000000000000000006e6574207469746c650000001c00d90001005000
291+f303000000000000000000000000000000000000000000000108da0003000000
292+ef03000000000000090000000000000000000000000000006e6574207469746c
293+650000001c00da0001005000f303000000000000000000000000000000000000
294+000000000108db0003000000ef03000000000000090000000000000000000000
295+000000006e6574207469746c650000001c00db0001005000f303000000000000
296+000000000000000000000000000000000108dc0003000000ef03000000000000
297+090000000000000000000000000000006e6574207469746c650000001c00dc00
298+01005000f303000000000000000000000000000000000000000000000108dd00
299+03000000ef03000000000000090000000000000000000000000000006e657420
300+7469746c650000001c00dd0001005000f3030000000000000000000000000000
301+00000000000000000108de0003000000ef030000000000000900000000000000
302+00000000000000006e6574207469746c650000001c00de0001005000f3030000
303+00000000000000000000000000000000000000000108df0003000000ef030000
304+00000000090000000000000000000000000000006e6574207469746c65000000
305+1c00df0001005000f30300000000000000000000000000000000000000000000
306+0108e00003000000ef0300000000000009000000000000000000000000000000
307+6e6574207469746c650000001c00e00001005000f30300000000000000000000
308+0000000000000000000000000108e10003000000ef0300000000000009000000
309+0000000000000000000000006e6574207469746c650000001c00e10001005000
310+f303000000000000000000000000000000000000000000000108e20003000000
311+ef03000000000000090000000000000000000000000000006e6574207469746c
312+650000001c00e20001005000f303000000000000000000000000000000000000
313+000000000108e30003000000ef03000000000000090000000000000000000000
314+000000006e6574207469746c650000001c00e30001005000f303000000000000
315+000000000000000000000000000000000108e40003000000ef03000000000000
316+090000000000000000000000000000006e6574207469746c650000001c00e400
317+01005000f303000000000000000000000000000000000000000000000108e500
318+03000000ef03000000000000090000000000000000000000000000006e657420
319+7469746c650000001c00e50001005000f3030000000000000000000000000000
320+00000000000000000108e60003000000ef030000000000000900000000000000
321+00000000000000006e6574207469746c650000001c00e60001005000f3030000
322+00000000000000000000000000000000000000000108e70003000000ef030000
323+00000000090000000000000000000000000000006e6574207469746c65000000
324+1c00e70001005000f30300000000000000000000000000000000000000000000
325+0108e80003000000ef0300000000000009000000000000000000000000000000
326+6e6574207469746c650000001c00e80001005000f30300000000000000000000
327+0000000000000000000000000108e90003000000ef0300000000000009000000
328+0000000000000000000000006e6574207469746c650000001c00e90001005000
329+f303000000000000000000000000000000000000000000000108ea0003000000
330+ef03000000000000090000000000000000000000000000006e6574207469746c
331+650000001c00ea0001005000f303000000000000000000000000000000000000
332+000000000108eb0003000000ef03000000000000090000000000000000000000
333+000000006e6574207469746c650000001c00eb0001005000f303000000000000
334+000000000000000000000000000000000108ec0003000000ef03000000000000
335+090000000000000000000000000000006e6574207469746c650000001c00ec00
336+01005000f303000000000000000000000000000000000000000000000108ed00
337+03000000ef03000000000000090000000000000000000000000000006e657420
338+7469746c650000001c00ed0001005000f3030000000000000000000000000000
339+00000000000000000108ee0003000000ef030000000000000900000000000000
340+00000000000000006e6574207469746c650000001c00ee0001005000f3030000
341+00000000000000000000000000000000000000000108ef0003000000ef030000
342+00000000090000000000000000000000000000006e6574207469746c65000000
343+1c00ef0001005000f30300000000000000000000000000000000000000000000
344+0108f00003000000ef0300000000000009000000000000000000000000000000
345+6e6574207469746c650000001c00f00001005000f30300000000000000000000
346+0000000000000000000000000108f10003000000ef0300000000000009000000
347+0000000000000000000000006e6574207469746c650000001c00f10001005000
348+f303000000000000000000000000000000000000000000000108f20003000000
349+ef03000000000000090000000000000000000000000000006e6574207469746c
350+650000001c00f20001005000f303000000000000000000000000000000000000
351+000000000108f30003000000ef03000000000000090000000000000000000000
352+000000006e6574207469746c650000001c00f30001005000f303000000000000
353+000000000000000000000000000000000108f40003000000ef03000000000000
354+090000000000000000000000000000006e6574207469746c650000001c00f400
355+01005000f303000000000000000000000000000000000000000000000108f500
356+03000000ef03000000000000090000000000000000000000000000006e657420
357+7469746c650000001c00f50001005000f3030000000000000000000000000000
358+00000000000000000108f60003000000ef030000000000000900000000000000
359+00000000000000006e6574207469746c650000001c00f60001005000f3030000
360+00000000000000000000000000000000000000000108f70003000000ef030000
361+00000000090000000000000000000000000000006e6574207469746c65000000
362+1c00f70001005000f30300000000000000000000000000000000000000000000
363+0108f80003000000ef0300000000000009000000000000000000000000000000
364+6e6574207469746c650000001c00f80001005000f30300000000000000000000
365+0000000000000000000000000108f90003000000ef0300000000000009000000
366+0000000000000000000000006e6574207469746c650000001c00f90001005000
367+f303000000000000000000000000000000000000000000000108fa0003000000
368+ef03000000000000090000000000000000000000000000006e6574207469746c
369+650000001c00fa0001005000f303000000000000000000000000000000000000
370+000000000108fb0003000000ef03000000000000090000000000000000000000
371+000000006e6574207469746c650000001c00fb0001005000f303000000000000
372+000000000000000000000000000000000108fc0003000000ef03000000000000
373+090000000000000000000000000000006e6574207469746c650000001c00fc00
374+01005000f303000000000000000000000000000000000000000000000108fd00
375+03000000ef03000000000000090000000000000000000000000000006e657420
376+7469746c650000001c00fd0001005000f3030000000000000000000000000000
377+00000000000000000108fe0003000000ef030000000000000900000000000000
378+00000000000000006e6574207469746c650000001c00fe0001005000f3030000
379+00000000000000000000000000000000000000000108ff0003000000ef030000
380+00000000090000000000000000000000000000006e6574207469746c65000000
381+1c00ff0001005000f30300000000000000000000000000000000000000000000
382+0108000103000000ef0300000000000009000000000000000000000000000000
383+6e6574207469746c650000001c00000101005000f30300000000000000000000
384+0000000000000000000000000108010103000000ef0300000000000009000000
385+0000000000000000000000006e6574207469746c650000001c00010101005000
386+f303000000000000000000000000000000000000000000000108020103000000
387+ef03000000000000090000000000000000000000000000006e6574207469746c
388+650000001c00020101005000f303000000000000000000000000000000000000
389+000000000108030103000000ef03000000000000090000000000000000000000
390+000000006e6574207469746c650000001c00030101005000f303000000000000
391+000000000000000000000000000000000108040103000000ef03000000000000
392+090000000000000000000000000000006e6574207469746c650000001c000401
393+01005000f3030000000000000000000000000000000000000000000001080501
394+03000000ef03000000000000090000000000000000000000000000006e657420
395+7469746c650000001c00050101005000f3030000000000000000000000000000
396+00000000000000000108060103000000ef030000000000000900000000000000
397+00000000000000006e6574207469746c650000001c00060101005000f3030000
398+00000000000000000000000000000000000000000108070103000000ef030000
399+00000000090000000000000000000000000000006e6574207469746c65000000
400+1c00070101005000f30300000000000000000000000000000000000000000000
401+0108080103000000ef0300000000000009000000000000000000000000000000
402+6e6574207469746c650000001c00080101005000f30300000000000000000000
403+0000000000000000000000000108090103000000ef0300000000000009000000
404+0000000000000000000000006e6574207469746c650000001c00090101005000
405+f3030000000000000000000000000000000000000000000001080a0103000000
406+ef03000000000000090000000000000000000000000000006e6574207469746c
407+650000001c000a0101005000f303000000000000000000000000000000000000
408+0000000001080b0103000000ef03000000000000090000000000000000000000
409+000000006e6574207469746c650000001c000b0101005000f303000000000000
410+0000000000000000000000000000000001080c0103000000ef03000000000000
411+090000000000000000000000000000006e6574207469746c650000001c000c01
412+01005000f3030000000000000000000000000000000000000000000001080d01
413+03000000ef03000000000000090000000000000000000000000000006e657420
414+7469746c650000001c000d0101005000f3030000000000000000000000000000
415+000000000000000001080e0103000000ef030000000000000900000000000000
416+00000000000000006e6574207469746c650000001c000e0101005000f3030000
417+000000000000000000000000000000000000000001080f0103000000ef030000
418+00000000090000000000000000000000000000006e6574207469746c65000000
419+1c000f0101005000f30300000000000000000000000000000000000000000000
420+0108100103000000ef0300000000000009000000000000000000000000000000
421+6e6574207469746c650000001c00100101005000f30300000000000000000000
422+0000000000000000000000000108110103000000ef0300000000000009000000
423+0000000000000000000000006e6574207469746c650000001c00110101005000
424+f303000000000000000000000000000000000000000000000108120103000000
425+ef03000000000000090000000000000000000000000000006e6574207469746c
426+650000001c00120101005000f303000000000000000000000000000000000000
427+000000000108130103000000ef03000000000000090000000000000000000000
428+000000006e6574207469746c650000001c00130101005000f303000000000000
429+000000000000000000000000000000000108140103000000ef03000000000000
430+090000000000000000000000000000006e6574207469746c650000001c001401
431+01005000f3030000000000000000000000000000000000000000000001081501
432+03000000ef03000000000000090000000000000000000000000000006e657420
433+7469746c650000001c00150101005000f3030000000000000000000000000000
434+00000000000000000108160103000000ef030000000000000900000000000000
435+00000000000000006e6574207469746c650000001c00160101005000f3030000
436+00000000000000000000000000000000000000000108170103000000ef030000
437+00000000090000000000000000000000000000006e6574207469746c65000000
438+1c00170101005000f30300000000000000000000000000000000000000000000
439+0108180103000000ef0300000000000009000000000000000000000000000000
440+6e6574207469746c650000001c00180101005000f30300000000000000000000
441+0000000000000000000000000108190103000000ef0300000000000009000000
442+0000000000000000000000006e6574207469746c650000001c00190101005000
443+f3030000000000000000000000000000000000000000000001081a0103000000
444+ef03000000000000090000000000000000000000000000006e6574207469746c
445+650000001c001a0101005000f303000000000000000000000000000000000000
446+0000000001081b0103000000ef03000000000000090000000000000000000000
447+000000006e6574207469746c650000001c001b0101005000f303000000000000
448+0000000000000000000000000000000001081c0103000000ef03000000000000
449+090000000000000000000000000000006e6574207469746c650000001c001c01
450+01005000f3030000000000000000000000000000000000000000000001081d01
451+03000000ef03000000000000090000000000000000000000000000006e657420
452+7469746c650000001c001d0101005000f3030000000000000000000000000000
453+000000000000000001081e0103000000ef030000000000000900000000000000
454+00000000000000006e6574207469746c650000001c001e0101005000f3030000
455+000000000000000000000000000000000000000001081f0103000000ef030000
456+00000000090000000000000000000000000000006e6574207469746c65000000
457+1c001f0101005000f30300000000000000000000000000000000000000000000
458+0108200103000000ef0300000000000009000000000000000000000000000000
459+6e6574207469746c650000001c00200101005000f30300000000000000000000
460+0000000000000000000000000108210103000000ef0300000000000009000000
461+0000000000000000000000006e6574207469746c650000001c00210101005000
462+f303000000000000000000000000000000000000000000000108220103000000
463+ef03000000000000090000000000000000000000000000006e6574207469746c
464+650000001c00220101005000f303000000000000000000000000000000000000
465+000000000108230103000000ef03000000000000090000000000000000000000
466+000000006e6574207469746c650000001c00230101005000f303000000000000
467+000000000000000000000000000000000108240103000000ef03000000000000
468+090000000000000000000000000000006e6574207469746c650000001c002401
469+01005000f3030000000000000000000000000000000000000000000001082501
470+03000000ef03000000000000090000000000000000000000000000006e657420
471+7469746c650000001c00250101005000f3030000000000000000000000000000
472+00000000000000000108260103000000ef030000000000000900000000000000
473+00000000000000006e6574207469746c650000001c00260101005000f3030000
474+00000000000000000000000000000000000000000108270103000000ef030000
475+00000000090000000000000000000000000000006e6574207469746c65000000
476+1c00270101005000f30300000000000000000000000000000000000000000000
477+0108280103000000ef0300000000000009000000000000000000000000000000
478+6e6574207469746c650000001c00280101005000f30300000000000000000000
479+0000000000000000000000000108290103000000ef0300000000000009000000
480+0000000000000000000000006e6574207469746c650000001c00290101005000
481+f3030000000000000000000000000000000000000000000001082a0103000000
482+ef03000000000000090000000000000000000000000000006e6574207469746c
483+650000001c002a0101005000f303000000000000000000000000000000000000
484+0000000001082b0103000000ef03000000000000090000000000000000000000
485+000000006e6574207469746c650000001c002b0101005000f303000000000000
486+0000000000000000000000000000000001082c0103000000ef03000000000000
487+090000000000000000000000000000006e6574207469746c650000001c002c01
488+01005000f3030000000000000000000000000000000000000000000001082d01
489+03000000ef03000000000000090000000000000000000000000000006e657420
490+7469746c650000001c002d0101005000f3030000000000000000000000000000
491+000000000000000001082e0103000000ef030000000000000900000000000000
492+00000000000000006e6574207469746c650000001c002e0101005000f3030000
493+000000000000000000000000000000000000000001082f0103000000ef030000
494+00000000090000000000000000000000000000006e6574207469746c65000000
495+1c002f0101005000f30300000000000000000000000000000000000000000000
496+0108300103000000ef0300000000000009000000000000000000000000000000
497+6e6574207469746c650000001c00300101005000f30300000000000000000000
498+0000000000000000000000000108310103000000ef0300000000000009000000
499+0000000000000000000000006e6574207469746c650000001c00310101005000
500+f303000000000000000000000000000000000000000000000108320103000000
501+ef03000000000000090000000000000000000000000000006e6574207469746c
502+650000001c00320101005000f303000000000000000000000000000000000000
503+000000000108330103000000ef03000000000000090000000000000000000000
504+000000006e6574207469746c650000001c00330101005000f303000000000000
505+000000000000000000000000000000000108340103000000ef03000000000000
506+090000000000000000000000000000006e6574207469746c650000001c003401
507+01005000f3030000000000000000000000000000000000000000000001083501
508+03000000ef03000000000000090000000000000000000000000000006e657420
509+7469746c650000001c00350101005000f3030000000000000000000000000000
510+00000000000000000108360103000000ef030000000000000900000000000000
511+00000000000000006e6574207469746c650000001c00360101005000f3030000
512+00000000000000000000000000000000000000000108370103000000ef030000
513+00000000090000000000000000000000000000006e6574207469746c65000000
514+1c00370101005000f30300000000000000000000000000000000000000000000
515+0108380103000000ef0300000000000009000000000000000000000000000000
516+6e6574207469746c650000001c00380101005000f30300000000000000000000
517+0000000000000000000000000108390103000000ef0300000000000009000000
518+0000000000000000000000006e6574207469746c650000001c00390101005000
519+f3030000000000000000000000000000000000000000000001083a0103000000
520+ef03000000000000090000000000000000000000000000006e6574207469746c
521+650000001c003a0101005000f303000000000000000000000000000000000000
522+0000000001083b0103000000ef03000000000000090000000000000000000000
523+000000006e6574207469746c650000001c003b0101005000f303000000000000
524+0000000000000000000000000000000001083c0103000000ef03000000000000
525+090000000000000000000000000000006e6574207469746c650000001c003c01
526+01005000f3030000000000000000000000000000000000000000000001083d01
527+03000000ef03000000000000090000000000000000000000000000006e657420
528+7469746c650000001c003d0101005000f3030000000000000000000000000000
529+000000000000000001083e0103000000ef030000000000000900000000000000
530+00000000000000006e6574207469746c650000001c003e0101005000f3030000
531+000000000000000000000000000000000000000001083f0103000000ef030000
532+00000000090000000000000000000000000000006e6574207469746c65000000
533+1c003f0101005000f30300000000000000000000000000000000000000000000
534+0108400103000000ef0300000000000009000000000000000000000000000000
535+6e6574207469746c650000001c00400101005000f30300000000000000000000
536+0000000000000000000000000108410103000000ef0300000000000009000000
537+0000000000000000000000006e6574207469746c650000001c00410101005000
538+f303000000000000000000000000000000000000000000000108420103000000
539+ef03000000000000090000000000000000000000000000006e6574207469746c
540+650000001c00420101005000f303000000000000000000000000000000000000
541+000000000108430103000000ef03000000000000090000000000000000000000
542+000000006e6574207469746c650000001c00430101005000f303000000000000
543+000000000000000000000000000000000108440103000000ef03000000000000
544+090000000000000000000000000000006e6574207469746c650000001c004401
545+01005000f3030000000000000000000000000000000000000000000001084501
546+03000000ef03000000000000090000000000000000000000000000006e657420
547+7469746c650000001c00450101005000f3030000000000000000000000000000
548+00000000000000000108460103000000ef030000000000000900000000000000
549+00000000000000006e6574207469746c650000001c00460101005000f3030000
550+00000000000000000000000000000000000000000108470103000000ef030000
551+00000000090000000000000000000000000000006e6574207469746c65000000
552+1c00470101005000f30300000000000000000000000000000000000000000000
553+0108480103000000ef0300000000000009000000000000000000000000000000
554+6e6574207469746c650000001c00480101005000f30300000000000000000000
555+0000000000000000000000000108490103000000ef0300000000000009000000
556+0000000000000000000000006e6574207469746c650000001c00490101005000
557+f3030000000000000000000000000000000000000000000001084a0103000000
558+ef03000000000000090000000000000000000000000000006e6574207469746c
559+650000001c004a0101005000f303000000000000000000000000000000000000
560+0000000001084b0103000000ef03000000000000090000000000000000000000
561+000000006e6574207469746c650000001c004b0101005000f303000000000000
562+0000000000000000000000000000000001084c0103000000ef03000000000000
563+090000000000000000000000000000006e6574207469746c650000001c004c01
564+01005000f3030000000000000000000000000000000000000000000001084d01
565+03000000ef03000000000000090000000000000000000000000000006e657420
566+7469746c650000001c004d0101005000f3030000000000000000000000000000
567+000000000000000001084e0103000000ef030000000000000900000000000000
568+00000000000000006e6574207469746c650000001c004e0101005000f3030000
569+000000000000000000000000000000000000000001084f0103000000ef030000
570+00000000090000000000000000000000000000006e6574207469746c65000000
571+1c004f0101005000f30300000000000000000000000000000000000000000000
572+0108500103000000ef0300000000000009000000000000000000000000000000
573+6e6574207469746c650000001c00500101005000f30300000000000000000000
574+0000000000000000000000000108510103000000ef0300000000000009000000
575+0000000000000000000000006e6574207469746c650000001c00510101005000
576+f303000000000000000000000000000000000000000000000108520103000000
577+ef03000000000000090000000000000000000000000000006e6574207469746c
578+650000001c00520101005000f303000000000000000000000000000000000000
579+000000000108530103000000ef03000000000000090000000000000000000000
580+000000006e6574207469746c650000001c00530101005000f303000000000000
581+000000000000000000000000000000000108540103000000ef03000000000000
582+090000000000000000000000000000006e6574207469746c650000001c005401
583+01005000f3030000000000000000000000000000000000000000000001085501
584+03000000ef03000000000000090000000000000000000000000000006e657420
585+7469746c650000001c00550101005000f3030000000000000000000000000000
586+00000000000000000108560103000000ef030000000000000900000000000000
587+00000000000000006e6574207469746c650000001c00560101005000f3030000
588+00000000000000000000000000000000000000000108570103000000ef030000
589+00000000090000000000000000000000000000006e6574207469746c65000000
590+1c00570101005000f30300000000000000000000000000000000000000000000
591+0108580103000000ef0300000000000009000000000000000000000000000000
592+6e6574207469746c650000001c00580101005000f30300000000000000000000
593+0000000000000000000000000108590103000000ef0300000000000009000000
594+0000000000000000000000006e6574207469746c650000001c00590101005000
595+f3030000000000000000000000000000000000000000000001085a0103000000
596+ef03000000000000090000000000000000000000000000006e6574207469746c
597+650000001c005a0101005000f303000000000000000000000000000000000000
598+0000000001085b0103000000ef03000000000000090000000000000000000000
599+000000006e6574207469746c650000001c005b0101005000f303000000000000
600+0000000000000000000000000000000001085c0103000000ef03000000000000
601+090000000000000000000000000000006e6574207469746c650000001c005c01
602+01005000f3030000000000000000000000000000000000000000000001085d01
603+03000000ef03000000000000090000000000000000000000000000006e657420
604+7469746c650000001c005d0101005000f3030000000000000000000000000000
605+000000000000000001085e0103000000ef030000000000000900000000000000
606+00000000000000006e6574207469746c650000001c005e0101005000f3030000
607+000000000000000000000000000000000000000001085f0103000000ef030000
608+00000000090000000000000000000000000000006e6574207469746c65000000
609+1c005f0101005000f30300000000000000000000000000000000000000000000
610+0108600103000000ef0300000000000009000000000000000000000000000000
611+6e6574207469746c650000001c00600101005000f30300000000000000000000
612+0000000000000000000000000108610103000000ef0300000000000009000000
613+0000000000000000000000006e6574207469746c650000001c00610101005000
614+f303000000000000000000000000000000000000000000000108620103000000
615+ef03000000000000090000000000000000000000000000006e6574207469746c
616+650000001c00620101005000f303000000000000000000000000000000000000
617+000000000108630103000000ef03000000000000090000000000000000000000
618+000000006e6574207469746c650000001c00630101005000f303000000000000
619+000000000000000000000000000000000108640103000000ef03000000000000
620+090000000000000000000000000000006e6574207469746c650000001c006401
621+01005000f3030000000000000000000000000000000000000000000001086501
622+03000000ef03000000000000090000000000000000000000000000006e657420
623+7469746c650000001c00650101005000f3030000000000000000000000000000
624+00000000000000000108660103000000ef030000000000000900000000000000
625+00000000000000006e6574207469746c650000001c00660101005000f3030000
626+00000000000000000000000000000000000000000108670103000000ef030000
627+00000000090000000000000000000000000000006e6574207469746c65000000
628+1c00670101005000f30300000000000000000000000000000000000000000000
629+0108680103000000ef0300000000000009000000000000000000000000000000
630+6e6574207469746c650000001c00680101005000f30300000000000000000000
631+0000000000000000000000000108690103000000ef0300000000000009000000
632+0000000000000000000000006e6574207469746c650000001c00690101005000
633+f3030000000000000000000000000000000000000000000001086a0103000000
634+ef03000000000000090000000000000000000000000000006e6574207469746c
635+650000001c006a0101005000f303000000000000000000000000000000000000
636+0000000001086b0103000000ef03000000000000090000000000000000000000
637+000000006e6574207469746c650000001c006b0101005000f303000000000000
638+0000000000000000000000000000000001086c0103000000ef03000000000000
639+090000000000000000000000000000006e6574207469746c650000001c006c01
640+01005000f3030000000000000000000000000000000000000000000001086d01
641+03000000ef03000000000000090000000000000000000000000000006e657420
642+7469746c650000001c006d0101005000f3030000000000000000000000000000
643+000000000000000001086e0103000000ef030000000000000900000000000000
644+00000000000000006e6574207469746c650000001c006e0101005000f3030000
645+000000000000000000000000000000000000000001086f0103000000ef030000
646+00000000090000000000000000000000000000006e6574207469746c65000000
647+1c006f0101005000f30300000000000000000000000000000000000000000000
648+0108700103000000ef0300000000000009000000000000000000000000000000
649+6e6574207469746c650000001c00700101005000f30300000000000000000000
650+0000000000000000000000000108710103000000ef0300000000000009000000
651+0000000000000000000000006e6574207469746c650000001c00710101005000
652+f303000000000000000000000000000000000000000000000108720103000000
653+ef03000000000000090000000000000000000000000000006e6574207469746c
654+650000001c00720101005000f303000000000000000000000000000000000000
655+000000000108730103000000ef03000000000000090000000000000000000000
656+000000006e6574207469746c650000001c00730101005000f303000000000000
657+000000000000000000000000000000000108740103000000ef03000000000000
658+090000000000000000000000000000006e6574207469746c650000001c007401
659+01005000f3030000000000000000000000000000000000000000000001087501
660+03000000ef03000000000000090000000000000000000000000000006e657420
661+7469746c650000001c00750101005000f3030000000000000000000000000000
662+00000000000000000108760103000000ef030000000000000900000000000000
663+00000000000000006e6574207469746c650000001c00760101005000f3030000
664+00000000000000000000000000000000000000000108770103000000ef030000
665+00000000090000000000000000000000000000006e6574207469746c65000000
666+1c00770101005000f30300000000000000000000000000000000000000000000
667+0108780103000000ef0300000000000009000000000000000000000000000000
668+6e6574207469746c650000001c00780101005000f30300000000000000000000
669+0000000000000000000000000108790103000000ef0300000000000009000000
670+0000000000000000000000006e6574207469746c650000001c00790101005000
671+f3030000000000000000000000000000000000000000000001087a0103000000
672+ef03000000000000090000000000000000000000000000006e6574207469746c
673+650000001c007a0101005000f303000000000000000000000000000000000000
674+0000000001087b0103000000ef03000000000000090000000000000000000000
675+000000006e6574207469746c650000001c007b0101005000f303000000000000
676+0000000000000000000000000000000001087c0103000000ef03000000000000
677+090000000000000000000000000000006e6574207469746c650000001c007c01
678+01005000f3030000000000000000000000000000000000000000000001087d01
679+03000000ef03000000000000090000000000000000000000000000006e657420
680+7469746c650000001c007d0101005000f3030000000000000000000000000000
681+000000000000000001087e0103000000ef030000000000000900000000000000
682+00000000000000006e6574207469746c650000001c007e0101005000f3030000
683+000000000000000000000000000000000000000001087f0103000000ef030000
684+00000000090000000000000000000000000000006e6574207469746c65000000
685+1c007f0101005000f30300000000000000000000000000000000000000000000
686+0108800103000000ef0300000000000009000000000000000000000000000000
687+6e6574207469746c650000001c00800101005000f30300000000000000000000
688+0000000000000000000000000108810103000000ef0300000000000009000000
689+0000000000000000000000006e6574207469746c650000001c00810101005000
690+f303000000000000000000000000000000000000000000000108820103000000
691+ef03000000000000090000000000000000000000000000006e6574207469746c
692+650000001c00820101005000f303000000000000000000000000000000000000
693+000000000108830103000000ef03000000000000090000000000000000000000
694+000000006e6574207469746c650000001c00830101005000f303000000000000
695+000000000000000000000000000000000108840103000000ef03000000000000
696+090000000000000000000000000000006e6574207469746c650000001c008401
697+01005000f3030000000000000000000000000000000000000000000001088501
698+03000000ef03000000000000090000000000000000000000000000006e657420
699+7469746c650000001c00850101005000f3030000000000000000000000000000
700+00000000000000000108860103000000ef030000000000000900000000000000
701+00000000000000006e6574207469746c650000001c00860101005000f3030000
702+00000000000000000000000000000000000000000108870103000000ef030000
703+00000000090000000000000000000000000000006e6574207469746c65000000
704+1c00870101005000f30300000000000000000000000000000000000000000000
705+0108880103000000ef0300000000000009000000000000000000000000000000
706+6e6574207469746c650000001c00880101005000f30300000000000000000000
707+0000000000000000000000000108890103000000ef0300000000000009000000
708+0000000000000000000000006e6574207469746c650000001c00890101005000
709+f3030000000000000000000000000000000000000000000001088a0103000000
710+ef03000000000000090000000000000000000000000000006e6574207469746c
711+650000001c008a0101005000f303000000000000000000000000000000000000
712+0000000001088b0103000000ef03000000000000090000000000000000000000
713+000000006e6574207469746c650000001c008b0101005000f303000000000000
714+0000000000000000000000000000000001088c0103000000ef03000000000000
715+090000000000000000000000000000006e6574207469746c650000001c008c01
716+01005000f3030000000000000000000000000000000000000000000001088d01
717+03000000ef03000000000000090000000000000000000000000000006e657420
718+7469746c650000001c008d0101005000f3030000000000000000000000000000
719+000000000000000001088e0103000000ef030000000000000900000000000000
720+00000000000000006e6574207469746c650000001c008e0101005000f3030000
721+000000000000000000000000000000000000000001088f0103000000ef030000
722+00000000090000000000000000000000000000006e6574207469746c65000000
723+1c008f0101005000f30300000000000000000000000000000000000000000000
724+0108900103000000ef0300000000000009000000000000000000000000000000
725+6e6574207469746c650000001c00900101005000f30300000000000000000000
726+0000000000000000000000000108910103000000ef0300000000000009000000
727+0000000000000000000000006e6574207469746c650000001c00910101005000
728+f303000000000000000000000000000000000000000000000108920103000000
729+ef03000000000000090000000000000000000000000000006e6574207469746c
730+650000001c00920101005000f303000000000000000000000000000000000000
731+000000000108930103000000ef03000000000000090000000000000000000000
732+000000006e6574207469746c650000001c00930101005000f303000000000000
733+000000000000000000000000000000000108940103000000ef03000000000000
734+090000000000000000000000000000006e6574207469746c650000001c009401
735+01005000f3030000000000000000000000000000000000000000000001089501
736+03000000ef03000000000000090000000000000000000000000000006e657420
737+7469746c650000001c00950101005000f3030000000000000000000000000000
738+00000000000000000108960103000000ef030000000000000900000000000000
739+00000000000000006e6574207469746c650000001c00960101005000f3030000
740+00000000000000000000000000000000000000000108970103000000ef030000
741+00000000090000000000000000000000000000006e6574207469746c65000000
742+1c00970101005000f30300000000000000000000000000000000000000000000
743+0108980103000000ef0300000000000009000000000000000000000000000000
744+6e6574207469746c650000001c00980101005000f30300000000000000000000
745+0000000000000000000000000108990103000000ef0300000000000009000000
746+0000000000000000000000006e6574207469746c650000001c00990101005000
747+f3030000000000000000000000000000000000000000000001089a0103000000
748+ef03000000000000090000000000000000000000000000006e6574207469746c
749+650000001c009a0101005000f303000000000000000000000000000000000000
750+0000000001089b0103000000ef03000000000000090000000000000000000000
751+000000006e6574207469746c650000001c009b0101005000f303000000000000
752+0000000000000000000000000000000001089c0103000000ef03000000000000
753+090000000000000000000000000000006e6574207469746c650000001c009c01
754+01005000f3030000000000000000000000000000000000000000000001089d01
755+03000000ef03000000000000090000000000000000000000000000006e657420
756+7469746c650000001c009d0101005000f3030000000000000000000000000000
757+000000000000000001089e0103000000ef030000000000000900000000000000
758+00000000000000006e6574207469746c650000001c009e0101005000f3030000
759+000000000000000000000000000000000000000001089f0103000000ef030000
760+00000000090000000000000000000000000000006e6574207469746c65000000
761+1c009f0101005000f30300000000000000000000000000000000000000000000
762+0108a00103000000ef0300000000000009000000000000000000000000000000
763+6e6574207469746c650000001c00a00101005000f30300000000000000000000
764+0000000000000000000000000108a10103000000ef0300000000000009000000
765+0000000000000000000000006e6574207469746c650000001c00a10101005000
766+f303000000000000000000000000000000000000000000000108a20103000000
767+ef03000000000000090000000000000000000000000000006e6574207469746c
768+650000001c00a20101005000f303000000000000000000000000000000000000
769+000000000108a30103000000ef03000000000000090000000000000000000000
770+000000006e6574207469746c650000001c00a30101005000f303000000000000
771+000000000000000000000000000000000108a40103000000ef03000000000000
772+090000000000000000000000000000006e6574207469746c650000001c00a401
773+01005000f303000000000000000000000000000000000000000000000108a501
774+03000000ef03000000000000090000000000000000000000000000006e657420
775+7469746c650000001c00a50101005000f3030000000000000000000000000000
776+00000000000000000108a60103000000ef030000000000000900000000000000
777+00000000000000006e6574207469746c650000001c00a60101005000f3030000
778+00000000000000000000000000000000000000000108a70103000000ef030000
779+00000000090000000000000000000000000000006e6574207469746c65000000
780+1c00a70101005000f30300000000000000000000000000000000000000000000
781+0108a80103000000ef0300000000000009000000000000000000000000000000
782+6e6574207469746c650000001c00a80101005000f30300000000000000000000
783+0000000000000000000000000108a90103000000ef0300000000000009000000
784+0000000000000000000000006e6574207469746c650000001c00a90101005000
785+f303000000000000000000000000000000000000000000000108aa0103000000
786+ef03000000000000090000000000000000000000000000006e6574207469746c
787+650000001c00aa0101005000f303000000000000000000000000000000000000
788+000000000108ab0103000000ef03000000000000090000000000000000000000
789+000000006e6574207469746c650000001c00ab0101005000f303000000000000
790+000000000000000000000000000000000108ac0103000000ef03000000000000
791+090000000000000000000000000000006e6574207469746c650000001c00ac01
792+01005000f303000000000000000000000000000000000000000000000108ad01
793+03000000ef03000000000000090000000000000000000000000000006e657420
794+7469746c650000001c00ad0101005000f3030000000000000000000000000000
795+00000000000000000108ae0103000000ef030000000000000900000000000000
796+00000000000000006e6574207469746c650000001c00ae0101005000f3030000
797+00000000000000000000000000000000000000000108af0103000000ef030000
798+00000000090000000000000000000000000000006e6574207469746c65000000
799+1c00af0101005000f30300000000000000000000000000000000000000000000
800+0108b00103000000ef0300000000000009000000000000000000000000000000
801+6e6574207469746c650000001c00b00101005000f30300000000000000000000
802+0000000000000000000000000108b10103000000ef0300000000000009000000
803+0000000000000000000000006e6574207469746c650000001c00b10101005000
804+f303000000000000000000000000000000000000000000000108b20103000000
805+ef03000000000000090000000000000000000000000000006e6574207469746c
806+650000001c00b20101005000f303000000000000000000000000000000000000
807+000000000108b30103000000ef03000000000000090000000000000000000000
808+000000006e6574207469746c650000001c00b30101005000f303000000000000
809+000000000000000000000000000000000108b40103000000ef03000000000000
810+090000000000000000000000000000006e6574207469746c650000001c00b401
811+01005000f303000000000000000000000000000000000000000000000108b501
812+03000000ef03000000000000090000000000000000000000000000006e657420
813+7469746c650000001c00b50101005000f3030000000000000000000000000000
814+00000000000000000108b60103000000ef030000000000000900000000000000
815+00000000000000006e6574207469746c650000001c00b60101005000f3030000
816+00000000000000000000000000000000000000000108b70103000000ef030000
817+00000000090000000000000000000000000000006e6574207469746c65000000
818+1c00b70101005000f30300000000000000000000000000000000000000000000
819+0108b80103000000ef0300000000000009000000000000000000000000000000
820+6e6574207469746c650000001c00b80101005000f30300000000000000000000
821+0000000000000000000000000108b90103000000ef0300000000000009000000
822+0000000000000000000000006e6574207469746c650000001c00b90101005000
823+f303000000000000000000000000000000000000000000000108ba0103000000
824+ef03000000000000090000000000000000000000000000006e6574207469746c
825+650000001c00ba0101005000f303000000000000000000000000000000000000
826+000000000108bb0103000000ef03000000000000090000000000000000000000
827+000000006e6574207469746c650000001c00bb0101005000f303000000000000
828+000000000000000000000000000000000108bc0103000000ef03000000000000
829+090000000000000000000000000000006e6574207469746c650000001c00bc01
830+01005000f303000000000000000000000000000000000000000000000108bd01
831+03000000ef03000000000000090000000000000000000000000000006e657420
832+7469746c650000001c00bd0101005000f3030000000000000000000000000000
833+00000000000000000108be0103000000ef030000000000000900000000000000
834+00000000000000006e6574207469746c650000001c00be0101005000f3030000
835+00000000000000000000000000000000000000000108bf0103000000ef030000
836+00000000090000000000000000000000000000006e6574207469746c65000000
837+1c00bf0101005000f30300000000000000000000000000000000000000000000
838+0108c00103000000ef0300000000000009000000000000000000000000000000
839+6e6574207469746c650000001c00c00101005000f30300000000000000000000
840+0000000000000000000000000108c10103000000ef0300000000000009000000
841+0000000000000000000000006e6574207469746c650000001c00c10101005000
842+f303000000000000000000000000000000000000000000000108c20103000000
843+ef03000000000000090000000000000000000000000000006e6574207469746c
844+650000001c00c20101005000f303000000000000000000000000000000000000
845+000000000108c30103000000ef03000000000000090000000000000000000000
846+000000006e6574207469746c650000001c00c30101005000f303000000000000
847+000000000000000000000000000000000108c40103000000ef03000000000000
848+090000000000000000000000000000006e6574207469746c650000001c00c401
849+01005000f303000000000000000000000000000000000000000000000108c501
850+03000000ef03000000000000090000000000000000000000000000006e657420
851+7469746c650000001c00c50101005000f3030000000000000000000000000000
852+00000000000000000108c60103000000ef030000000000000900000000000000
853+00000000000000006e6574207469746c650000001c00c60101005000f3030000
854+00000000000000000000000000000000000000000108c70103000000ef030000
855+00000000090000000000000000000000000000006e6574207469746c65000000
856+1c00c70101005000f30300000000000000000000000000000000000000000000
857+0108c80103000000ef0300000000000009000000000000000000000000000000
858+6e6574207469746c650000001c00c80101005000f30300000000000000000000
859+0000000000000000000000000108c90103000000ef0300000000000009000000
860+0000000000000000000000006e6574207469746c650000001c00c90101005000
861+f303000000000000000000000000000000000000000000000108ca0103000000
862+ef03000000000000090000000000000000000000000000006e6574207469746c
863+650000001c00ca0101005000f303000000000000000000000000000000000000
864+000000000108cb0103000000ef03000000000000090000000000000000000000
865+000000006e6574207469746c650000001c00cb0101005000f303000000000000
866+000000000000000000000000000000000108cc0103000000ef03000000000000
867+090000000000000000000000000000006e6574207469746c650000001c00cc01
868+01005000f303000000000000000000000000000000000000000000000108cd01
869+03000000ef03000000000000090000000000000000000000000000006e657420
870+7469746c650000001c00cd0101005000f3030000000000000000000000000000
871+00000000000000000108ce0103000000ef030000000000000900000000000000
872+00000000000000006e6574207469746c650000001c00ce0101005000f3030000
873+00000000000000000000000000000000000000000108cf0103000000ef030000
874+00000000090000000000000000000000000000006e6574207469746c65000000
875+1c00cf0101005000f30300000000000000000000000000000000000000000000
876+0108d00103000000ef0300000000000009000000000000000000000000000000
877+6e6574207469746c650000001c00d00101005000f30300000000000000000000
878+0000000000000000000000000108d10103000000ef0300000000000009000000
879+0000000000000000000000006e6574207469746c650000001c00d10101005000
880+f303000000000000000000000000000000000000000000000108d20103000000
881+ef03000000000000090000000000000000000000000000006e6574207469746c
882+650000001c00d20101005000f303000000000000000000000000000000000000
883+000000000108d30103000000ef03000000000000090000000000000000000000
884+000000006e6574207469746c650000001c00d30101005000f303000000000000
885+000000000000000000000000000000000108d40103000000ef03000000000000
886+090000000000000000000000000000006e6574207469746c650000001c00d401
887+01005000f303000000000000000000000000000000000000000000000108d501
888+03000000ef03000000000000090000000000000000000000000000006e657420
889+7469746c650000001c00d50101005000f3030000000000000000000000000000
890+00000000000000000108d60103000000ef030000000000000900000000000000
891+00000000000000006e6574207469746c650000001c00d60101005000f3030000
892+00000000000000000000000000000000000000000108d70103000000ef030000
893+00000000090000000000000000000000000000006e6574207469746c65000000
894+1c00d70101005000f30300000000000000000000000000000000000000000000
895+0108d80103000000ef0300000000000009000000000000000000000000000000
896+6e6574207469746c650000001c00d80101005000f30300000000000000000000
897+0000000000000000000000000108d90103000000ef0300000000000009000000
898+0000000000000000000000006e6574207469746c650000001c00d90101005000
899+f303000000000000000000000000000000000000000000000108da0103000000
900+ef03000000000000090000000000000000000000000000006e6574207469746c
901+650000001c00da0101005000f303000000000000000000000000000000000000
902+000000000108db0103000000ef03000000000000090000000000000000000000
903+000000006e6574207469746c650000001c00db0101005000f303000000000000
904+000000000000000000000000000000000108dc0103000000ef03000000000000
905+090000000000000000000000000000006e6574207469746c650000001c00dc01
906+01005000f303000000000000000000000000000000000000000000000108dd01
907+03000000ef03000000000000090000000000000000000000000000006e657420
908+7469746c650000001c00dd0101005000f3030000000000000000000000000000
909+00000000000000000108de0103000000ef030000000000000900000000000000
910+00000000000000006e6574207469746c650000001c00de0101005000f3030000
911+00000000000000000000000000000000000000000108df0103000000ef030000
912+00000000090000000000000000000000000000006e6574207469746c65000000
913+1c00df0101005000f30300000000000000000000000000000000000000000000
914+0108e00103000000ef0300000000000009000000000000000000000000000000
915+6e6574207469746c650000001c00e00101005000f30300000000000000000000
916+0000000000000000000000000108e10103000000ef0300000000000009000000
917+0000000000000000000000006e6574207469746c650000001c00e10101005000
918+f303000000000000000000000000000000000000000000000108e20103000000
919+ef03000000000000090000000000000000000000000000006e6574207469746c
920+650000001c00e20101005000f303000000000000000000000000000000000000
921+000000000108e30103000000ef03000000000000090000000000000000000000
922+000000006e6574207469746c650000001c00e30101005000f303000000000000
923+000000000000000000000000000000000108e40103000000ef03000000000000
924+090000000000000000000000000000006e6574207469746c650000001c00e401
925+01005000f303000000000000000000000000000000000000000000000108e501
926+03000000ef03000000000000090000000000000000000000000000006e657420
927+7469746c650000001c00e50101005000f3030000000000000000000000000000
928+00000000000000000108e60103000000ef030000000000000900000000000000
929+00000000000000006e6574207469746c650000001c00e60101005000f3030000
930+00000000000000000000000000000000000000000108e70103000000ef030000
931+00000000090000000000000000000000000000006e6574207469746c65000000
932+1c00e70101005000f30300000000000000000000000000000000000000000000
933+0108e80103000000ef0300000000000009000000000000000000000000000000
934+6e6574207469746c650000001c00e80101005000f30300000000000000000000
935+0000000000000000000000000108e90103000000ef0300000000000009000000
936+0000000000000000000000006e6574207469746c650000001c00e90101005000
937+f303000000000000000000000000000000000000000000000108ea0103000000
938+ef03000000000000090000000000000000000000000000006e6574207469746c
939+650000001c00ea0101005000f303000000000000000000000000000000000000
940+000000000108eb0103000000ef03000000000000090000000000000000000000
941+000000006e6574207469746c650000001c00eb0101005000f303000000000000
942+000000000000000000000000000000000108ec0103000000ef03000000000000
943+090000000000000000000000000000006e6574207469746c650000001c00ec01
944+01005000f303000000000000000000000000000000000000000000000108ed01
945+03000000ef03000000000000090000000000000000000000000000006e657420
946+7469746c650000001c00ed0101005000f3030000000000000000000000000000
947+00000000000000000108ee0103000000ef030000000000000900000000000000
948+00000000000000006e6574207469746c650000001c00ee0101005000f3030000
949+00000000000000000000000000000000000000000108ef0103000000ef030000
950+00000000090000000000000000000000000000006e6574207469746c65000000
@@ -0,0 +1,950 @@
1+1c005f0001005000f30300000000000000000000000000000000000000000000
2+0108600003000000ef0300000000000009000000000000000000000000000000
3+6e6574207469746c650000001c00600001005000f30300000000000000000000
4+0000000000000000000000000108610003000000ef0300000000000009000000
5+0000000000000000000000006e6574207469746c650000001c00610001005000
6+f303000000000000000000000000000000000000000000000108620003000000
7+ef03000000000000090000000000000000000000000000006e6574207469746c
8+650000001c00620001005000f303000000000000000000000000000000000000
9+000000000108630003000000ef03000000000000090000000000000000000000
10+000000006e6574207469746c650000001c00630001005000f303000000000000
11+000000000000000000000000000000000108640003000000ef03000000000000
12+090000000000000000000000000000006e6574207469746c650000001c006400
13+01005000f3030000000000000000000000000000000000000000000001086500
14+03000000ef03000000000000090000000000000000000000000000006e657420
15+7469746c650000001c00650001005000f3030000000000000000000000000000
16+00000000000000000108660003000000ef030000000000000900000000000000
17+00000000000000006e6574207469746c650000001c00660001005000f3030000
18+00000000000000000000000000000000000000000108670003000000ef030000
19+00000000090000000000000000000000000000006e6574207469746c65000000
20+1c00670001005000f30300000000000000000000000000000000000000000000
21+0108680003000000ef0300000000000009000000000000000000000000000000
22+6e6574207469746c650000001c00680001005000f30300000000000000000000
23+0000000000000000000000000108690003000000ef0300000000000009000000
24+0000000000000000000000006e6574207469746c650000001c00690001005000
25+f3030000000000000000000000000000000000000000000001086a0003000000
26+ef03000000000000090000000000000000000000000000006e6574207469746c
27+650000001c006a0001005000f303000000000000000000000000000000000000
28+0000000001086b0003000000ef03000000000000090000000000000000000000
29+000000006e6574207469746c650000001c006b0001005000f303000000000000
30+0000000000000000000000000000000001086c0003000000ef03000000000000
31+090000000000000000000000000000006e6574207469746c650000001c006c00
32+01005000f3030000000000000000000000000000000000000000000001086d00
33+03000000ef03000000000000090000000000000000000000000000006e657420
34+7469746c650000001c006d0001005000f3030000000000000000000000000000
35+000000000000000001086e0003000000ef030000000000000900000000000000
36+00000000000000006e6574207469746c650000001c006e0001005000f3030000
37+000000000000000000000000000000000000000001086f0003000000ef030000
38+00000000090000000000000000000000000000006e6574207469746c65000000
39+1c006f0001005000f30300000000000000000000000000000000000000000000
40+0108700003000000ef0300000000000009000000000000000000000000000000
41+6e6574207469746c650000001c00700001005000f30300000000000000000000
42+0000000000000000000000000108710003000000ef0300000000000009000000
43+0000000000000000000000006e6574207469746c650000001c00710001005000
44+f303000000000000000000000000000000000000000000000108720003000000
45+ef03000000000000090000000000000000000000000000006e6574207469746c
46+650000001c00720001005000f303000000000000000000000000000000000000
47+000000000108730003000000ef03000000000000090000000000000000000000
48+000000006e6574207469746c650000001c00730001005000f303000000000000
49+000000000000000000000000000000000108740003000000ef03000000000000
50+090000000000000000000000000000006e6574207469746c650000001c007400
51+01005000f3030000000000000000000000000000000000000000000001087500
52+03000000ef03000000000000090000000000000000000000000000006e657420
53+7469746c650000001c00750001005000f3030000000000000000000000000000
54+00000000000000000108760003000000ef030000000000000900000000000000
55+00000000000000006e6574207469746c650000001c00760001005000f3030000
56+00000000000000000000000000000000000000000108770003000000ef030000
57+00000000090000000000000000000000000000006e6574207469746c65000000
58+1c00770001005000f30300000000000000000000000000000000000000000000
59+0108780003000000ef0300000000000009000000000000000000000000000000
60+6e6574207469746c650000001c00780001005000f30300000000000000000000
61+0000000000000000000000000108790003000000ef0300000000000009000000
62+0000000000000000000000006e6574207469746c650000001c00790001005000
63+f3030000000000000000000000000000000000000000000001087a0003000000
64+ef03000000000000090000000000000000000000000000006e6574207469746c
65+650000001c007a0001005000f303000000000000000000000000000000000000
66+0000000001087b0003000000ef03000000000000090000000000000000000000
67+000000006e6574207469746c650000001c007b0001005000f303000000000000
68+0000000000000000000000000000000001087c0003000000ef03000000000000
69+090000000000000000000000000000006e6574207469746c650000001c007c00
70+01005000f3030000000000000000000000000000000000000000000001087d00
71+03000000ef03000000000000090000000000000000000000000000006e657420
72+7469746c650000001c007d0001005000f3030000000000000000000000000000
73+000000000000000001087e0003000000ef030000000000000900000000000000
74+00000000000000006e6574207469746c650000001c007e0001005000f3030000
75+000000000000000000000000000000000000000001087f0003000000ef030000
76+00000000090000000000000000000000000000006e6574207469746c65000000
77+1c007f0001005000f30300000000000000000000000000000000000000000000
78+0108800003000000ef0300000000000009000000000000000000000000000000
79+6e6574207469746c650000001c00800001005000f30300000000000000000000
80+0000000000000000000000000108810003000000ef0300000000000009000000
81+0000000000000000000000006e6574207469746c650000001c00810001005000
82+f303000000000000000000000000000000000000000000000108820003000000
83+ef03000000000000090000000000000000000000000000006e6574207469746c
84+650000001c00820001005000f303000000000000000000000000000000000000
85+000000000108830003000000ef03000000000000090000000000000000000000
86+000000006e6574207469746c650000001c00830001005000f303000000000000
87+000000000000000000000000000000000108840003000000ef03000000000000
88+090000000000000000000000000000006e6574207469746c650000001c008400
89+01005000f3030000000000000000000000000000000000000000000001088500
90+03000000ef03000000000000090000000000000000000000000000006e657420
91+7469746c650000001c00850001005000f3030000000000000000000000000000
92+00000000000000000108860003000000ef030000000000000900000000000000
93+00000000000000006e6574207469746c650000001c00860001005000f3030000
94+00000000000000000000000000000000000000000108870003000000ef030000
95+00000000090000000000000000000000000000006e6574207469746c65000000
96+1c00870001005000f30300000000000000000000000000000000000000000000
97+0108880003000000ef0300000000000009000000000000000000000000000000
98+6e6574207469746c650000001c00880001005000f30300000000000000000000
99+0000000000000000000000000108890003000000ef0300000000000009000000
100+0000000000000000000000006e6574207469746c650000001c00890001005000
101+f3030000000000000000000000000000000000000000000001088a0003000000
102+ef03000000000000090000000000000000000000000000006e6574207469746c
103+650000001c008a0001005000f303000000000000000000000000000000000000
104+0000000001088b0003000000ef03000000000000090000000000000000000000
105+000000006e6574207469746c650000001c008b0001005000f303000000000000
106+0000000000000000000000000000000001088c0003000000ef03000000000000
107+090000000000000000000000000000006e6574207469746c650000001c008c00
108+01005000f3030000000000000000000000000000000000000000000001088d00
109+03000000ef03000000000000090000000000000000000000000000006e657420
110+7469746c650000001c008d0001005000f3030000000000000000000000000000
111+000000000000000001088e0003000000ef030000000000000900000000000000
112+00000000000000006e6574207469746c650000001c008e0001005000f3030000
113+000000000000000000000000000000000000000001088f0003000000ef030000
114+00000000090000000000000000000000000000006e6574207469746c65000000
115+1c008f0001005000f30300000000000000000000000000000000000000000000
116+0108900003000000ef0300000000000009000000000000000000000000000000
117+6e6574207469746c650000001c00900001005000f30300000000000000000000
118+0000000000000000000000000108910003000000ef0300000000000009000000
119+0000000000000000000000006e6574207469746c650000001c00910001005000
120+f303000000000000000000000000000000000000000000000108920003000000
121+ef03000000000000090000000000000000000000000000006e6574207469746c
122+650000001c00920001005000f303000000000000000000000000000000000000
123+000000000108930003000000ef03000000000000090000000000000000000000
124+000000006e6574207469746c650000001c00930001005000f303000000000000
125+000000000000000000000000000000000108940003000000ef03000000000000
126+090000000000000000000000000000006e6574207469746c650000001c009400
127+01005000f3030000000000000000000000000000000000000000000001089500
128+03000000ef03000000000000090000000000000000000000000000006e657420
129+7469746c650000001c00950001005000f3030000000000000000000000000000
130+00000000000000000108960003000000ef030000000000000900000000000000
131+00000000000000006e6574207469746c650000001c00960001005000f3030000
132+00000000000000000000000000000000000000000108970003000000ef030000
133+00000000090000000000000000000000000000006e6574207469746c65000000
134+1c00970001005000f30300000000000000000000000000000000000000000000
135+0108980003000000ef0300000000000009000000000000000000000000000000
136+6e6574207469746c650000001c00980001005000f30300000000000000000000
137+0000000000000000000000000108990003000000ef0300000000000009000000
138+0000000000000000000000006e6574207469746c650000001c00990001005000
139+f3030000000000000000000000000000000000000000000001089a0003000000
140+ef03000000000000090000000000000000000000000000006e6574207469746c
141+650000001c009a0001005000f303000000000000000000000000000000000000
142+0000000001089b0003000000ef03000000000000090000000000000000000000
143+000000006e6574207469746c650000001c009b0001005000f303000000000000
144+0000000000000000000000000000000001089c0003000000ef03000000000000
145+090000000000000000000000000000006e6574207469746c650000001c009c00
146+01005000f3030000000000000000000000000000000000000000000001089d00
147+03000000ef03000000000000090000000000000000000000000000006e657420
148+7469746c650000001c009d0001005000f3030000000000000000000000000000
149+000000000000000001089e0003000000ef030000000000000900000000000000
150+00000000000000006e6574207469746c650000001c009e0001005000f3030000
151+000000000000000000000000000000000000000001089f0003000000ef030000
152+00000000090000000000000000000000000000006e6574207469746c65000000
153+1c009f0001005000f30300000000000000000000000000000000000000000000
154+0108a00003000000ef0300000000000009000000000000000000000000000000
155+6e6574207469746c650000001c00a00001005000f30300000000000000000000
156+0000000000000000000000000108a10003000000ef0300000000000009000000
157+0000000000000000000000006e6574207469746c650000001c00a10001005000
158+f303000000000000000000000000000000000000000000000108a20003000000
159+ef03000000000000090000000000000000000000000000006e6574207469746c
160+650000001c00a20001005000f303000000000000000000000000000000000000
161+000000000108a30003000000ef03000000000000090000000000000000000000
162+000000006e6574207469746c650000001c00a30001005000f303000000000000
163+000000000000000000000000000000000108a40003000000ef03000000000000
164+090000000000000000000000000000006e6574207469746c650000001c00a400
165+01005000f303000000000000000000000000000000000000000000000108a500
166+03000000ef03000000000000090000000000000000000000000000006e657420
167+7469746c650000001c00a50001005000f3030000000000000000000000000000
168+00000000000000000108a60003000000ef030000000000000900000000000000
169+00000000000000006e6574207469746c650000001c00a60001005000f3030000
170+00000000000000000000000000000000000000000108a70003000000ef030000
171+00000000090000000000000000000000000000006e6574207469746c65000000
172+1c00a70001005000f30300000000000000000000000000000000000000000000
173+0108a80003000000ef0300000000000009000000000000000000000000000000
174+6e6574207469746c650000001c00a80001005000f30300000000000000000000
175+0000000000000000000000000108a90003000000ef0300000000000009000000
176+0000000000000000000000006e6574207469746c650000001c00a90001005000
177+f303000000000000000000000000000000000000000000000108aa0003000000
178+ef03000000000000090000000000000000000000000000006e6574207469746c
179+650000001c00aa0001005000f303000000000000000000000000000000000000
180+000000000108ab0003000000ef03000000000000090000000000000000000000
181+000000006e6574207469746c650000001c00ab0001005000f303000000000000
182+000000000000000000000000000000000108ac0003000000ef03000000000000
183+090000000000000000000000000000006e6574207469746c650000001c00ac00
184+01005000f303000000000000000000000000000000000000000000000108ad00
185+03000000ef03000000000000090000000000000000000000000000006e657420
186+7469746c650000001c00ad0001005000f3030000000000000000000000000000
187+00000000000000000108ae0003000000ef030000000000000900000000000000
188+00000000000000006e6574207469746c650000001c00ae0001005000f3030000
189+00000000000000000000000000000000000000000108af0003000000ef030000
190+00000000090000000000000000000000000000006e6574207469746c65000000
191+1c00af0001005000f30300000000000000000000000000000000000000000000
192+0108b00003000000ef0300000000000009000000000000000000000000000000
193+6e6574207469746c650000001c00b00001005000f30300000000000000000000
194+0000000000000000000000000108b10003000000ef0300000000000009000000
195+0000000000000000000000006e6574207469746c650000001c00b10001005000
196+f303000000000000000000000000000000000000000000000108b20003000000
197+ef03000000000000090000000000000000000000000000006e6574207469746c
198+650000001c00b20001005000f303000000000000000000000000000000000000
199+000000000108b30003000000ef03000000000000090000000000000000000000
200+000000006e6574207469746c650000001c00b30001005000f303000000000000
201+000000000000000000000000000000000108b40003000000ef03000000000000
202+090000000000000000000000000000006e6574207469746c650000001c00b400
203+01005000f303000000000000000000000000000000000000000000000108b500
204+03000000ef03000000000000090000000000000000000000000000006e657420
205+7469746c650000001c00b50001005000f3030000000000000000000000000000
206+00000000000000000108b60003000000ef030000000000000900000000000000
207+00000000000000006e6574207469746c650000001c00b60001005000f3030000
208+00000000000000000000000000000000000000000108b70003000000ef030000
209+00000000090000000000000000000000000000006e6574207469746c65000000
210+1c00b70001005000f30300000000000000000000000000000000000000000000
211+0108b80003000000ef0300000000000009000000000000000000000000000000
212+6e6574207469746c650000001c00b80001005000f30300000000000000000000
213+0000000000000000000000000108b90003000000ef0300000000000009000000
214+0000000000000000000000006e6574207469746c650000001c00b90001005000
215+f303000000000000000000000000000000000000000000000108ba0003000000
216+ef03000000000000090000000000000000000000000000006e6574207469746c
217+650000001c00ba0001005000f303000000000000000000000000000000000000
218+000000000108bb0003000000ef03000000000000090000000000000000000000
219+000000006e6574207469746c650000001c00bb0001005000f303000000000000
220+000000000000000000000000000000000108bc0003000000ef03000000000000
221+090000000000000000000000000000006e6574207469746c650000001c00bc00
222+01005000f303000000000000000000000000000000000000000000000108bd00
223+03000000ef03000000000000090000000000000000000000000000006e657420
224+7469746c650000001c00bd0001005000f3030000000000000000000000000000
225+00000000000000000108be0003000000ef030000000000000900000000000000
226+00000000000000006e6574207469746c650000001c00be0001005000f3030000
227+00000000000000000000000000000000000000000108bf0003000000ef030000
228+00000000090000000000000000000000000000006e6574207469746c65000000
229+1c00bf0001005000f30300000000000000000000000000000000000000000000
230+0108c00003000000ef0300000000000009000000000000000000000000000000
231+6e6574207469746c650000001c00c00001005000f30300000000000000000000
232+0000000000000000000000000108c10003000000ef0300000000000009000000
233+0000000000000000000000006e6574207469746c650000001c00c10001005000
234+f303000000000000000000000000000000000000000000000108c20003000000
235+ef03000000000000090000000000000000000000000000006e6574207469746c
236+650000001c00c20001005000f303000000000000000000000000000000000000
237+000000000108c30003000000ef03000000000000090000000000000000000000
238+000000006e6574207469746c650000001c00c30001005000f303000000000000
239+000000000000000000000000000000000108c40003000000ef03000000000000
240+090000000000000000000000000000006e6574207469746c650000001c00c400
241+01005000f303000000000000000000000000000000000000000000000108c500
242+03000000ef03000000000000090000000000000000000000000000006e657420
243+7469746c650000001c00c50001005000f3030000000000000000000000000000
244+00000000000000000108c60003000000ef030000000000000900000000000000
245+00000000000000006e6574207469746c650000001c00c60001005000f3030000
246+00000000000000000000000000000000000000000108c70003000000ef030000
247+00000000090000000000000000000000000000006e6574207469746c65000000
248+1c00c70001005000f30300000000000000000000000000000000000000000000
249+0108c80003000000ef0300000000000009000000000000000000000000000000
250+6e6574207469746c650000001c00c80001005000f30300000000000000000000
251+0000000000000000000000000108c90003000000ef0300000000000009000000
252+0000000000000000000000006e6574207469746c650000001c00c90001005000
253+f303000000000000000000000000000000000000000000000108ca0003000000
254+ef03000000000000090000000000000000000000000000006e6574207469746c
255+650000001c00ca0001005000f303000000000000000000000000000000000000
256+000000000108cb0003000000ef03000000000000090000000000000000000000
257+000000006e6574207469746c650000001c00cb0001005000f303000000000000
258+000000000000000000000000000000000108cc0003000000ef03000000000000
259+090000000000000000000000000000006e6574207469746c650000001c00cc00
260+01005000f303000000000000000000000000000000000000000000000108cd00
261+03000000ef03000000000000090000000000000000000000000000006e657420
262+7469746c650000001c00cd0001005000f3030000000000000000000000000000
263+00000000000000000108ce0003000000ef030000000000000900000000000000
264+00000000000000006e6574207469746c650000001c00ce0001005000f3030000
265+00000000000000000000000000000000000000000108cf0003000000ef030000
266+00000000090000000000000000000000000000006e6574207469746c65000000
267+1c00cf0001005000f30300000000000000000000000000000000000000000000
268+0108d00003000000ef0300000000000009000000000000000000000000000000
269+6e6574207469746c650000001c00d00001005000f30300000000000000000000
270+0000000000000000000000000108d10003000000ef0300000000000009000000
271+0000000000000000000000006e6574207469746c650000001c00d10001005000
272+f303000000000000000000000000000000000000000000000108d20003000000
273+ef03000000000000090000000000000000000000000000006e6574207469746c
274+650000001c00d20001005000f303000000000000000000000000000000000000
275+000000000108d30003000000ef03000000000000090000000000000000000000
276+000000006e6574207469746c650000001c00d30001005000f303000000000000
277+000000000000000000000000000000000108d40003000000ef03000000000000
278+090000000000000000000000000000006e6574207469746c650000001c00d400
279+01005000f303000000000000000000000000000000000000000000000108d500
280+03000000ef03000000000000090000000000000000000000000000006e657420
281+7469746c650000001c00d50001005000f3030000000000000000000000000000
282+00000000000000000108d60003000000ef030000000000000900000000000000
283+00000000000000006e6574207469746c650000001c00d60001005000f3030000
284+00000000000000000000000000000000000000000108d70003000000ef030000
285+00000000090000000000000000000000000000006e6574207469746c65000000
286+1c00d70001005000f30300000000000000000000000000000000000000000000
287+0108d80003000000ef0300000000000009000000000000000000000000000000
288+6e6574207469746c650000001c00d80001005000f30300000000000000000000
289+0000000000000000000000000108d90003000000ef0300000000000009000000
290+0000000000000000000000006e6574207469746c650000001c00d90001005000
291+f303000000000000000000000000000000000000000000000108da0003000000
292+ef03000000000000090000000000000000000000000000006e6574207469746c
293+650000001c00da0001005000f303000000000000000000000000000000000000
294+000000000108db0003000000ef03000000000000090000000000000000000000
295+000000006e6574207469746c650000001c00db0001005000f303000000000000
296+000000000000000000000000000000000108dc0003000000ef03000000000000
297+090000000000000000000000000000006e6574207469746c650000001c00dc00
298+01005000f303000000000000000000000000000000000000000000000108dd00
299+03000000ef03000000000000090000000000000000000000000000006e657420
300+7469746c650000001c00dd0001005000f3030000000000000000000000000000
301+00000000000000000108de0003000000ef030000000000000900000000000000
302+00000000000000006e6574207469746c650000001c00de0001005000f3030000
303+00000000000000000000000000000000000000000108df0003000000ef030000
304+00000000090000000000000000000000000000006e6574207469746c65000000
305+1c00df0001005000f30300000000000000000000000000000000000000000000
306+0108e00003000000ef0300000000000009000000000000000000000000000000
307+6e6574207469746c650000001c00e00001005000f30300000000000000000000
308+0000000000000000000000000108e10003000000ef0300000000000009000000
309+0000000000000000000000006e6574207469746c650000001c00e10001005000
310+f303000000000000000000000000000000000000000000000108e20003000000
311+ef03000000000000090000000000000000000000000000006e6574207469746c
312+650000001c00e20001005000f303000000000000000000000000000000000000
313+000000000108e30003000000ef03000000000000090000000000000000000000
314+000000006e6574207469746c650000001c00e30001005000f303000000000000
315+000000000000000000000000000000000108e40003000000ef03000000000000
316+090000000000000000000000000000006e6574207469746c650000001c00e400
317+01005000f303000000000000000000000000000000000000000000000108e500
318+03000000ef03000000000000090000000000000000000000000000006e657420
319+7469746c650000001c00e50001005000f3030000000000000000000000000000
320+00000000000000000108e60003000000ef030000000000000900000000000000
321+00000000000000006e6574207469746c650000001c00e60001005000f3030000
322+00000000000000000000000000000000000000000108e70003000000ef030000
323+00000000090000000000000000000000000000006e6574207469746c65000000
324+1c00e70001005000f30300000000000000000000000000000000000000000000
325+0108e80003000000ef0300000000000009000000000000000000000000000000
326+6e6574207469746c650000001c00e80001005000f30300000000000000000000
327+0000000000000000000000000108e90003000000ef0300000000000009000000
328+0000000000000000000000006e6574207469746c650000001c00e90001005000
329+f303000000000000000000000000000000000000000000000108ea0003000000
330+ef03000000000000090000000000000000000000000000006e6574207469746c
331+650000001c00ea0001005000f303000000000000000000000000000000000000
332+000000000108eb0003000000ef03000000000000090000000000000000000000
333+000000006e6574207469746c650000001c00eb0001005000f303000000000000
334+000000000000000000000000000000000108ec0003000000ef03000000000000
335+090000000000000000000000000000006e6574207469746c650000001c00ec00
336+01005000f303000000000000000000000000000000000000000000000108ed00
337+03000000ef03000000000000090000000000000000000000000000006e657420
338+7469746c650000001c00ed0001005000f3030000000000000000000000000000
339+00000000000000000108ee0003000000ef030000000000000900000000000000
340+00000000000000006e6574207469746c650000001c00ee0001005000f3030000
341+00000000000000000000000000000000000000000108ef0003000000ef030000
342+00000000090000000000000000000000000000006e6574207469746c65000000
343+1c00ef0001005000f30300000000000000000000000000000000000000000000
344+0108f00003000000ef0300000000000009000000000000000000000000000000
345+6e6574207469746c650000001c00f00001005000f30300000000000000000000
346+0000000000000000000000000108f10003000000ef0300000000000009000000
347+0000000000000000000000006e6574207469746c650000001c00f10001005000
348+f303000000000000000000000000000000000000000000000108f20003000000
349+ef03000000000000090000000000000000000000000000006e6574207469746c
350+650000001c00f20001005000f303000000000000000000000000000000000000
351+000000000108f30003000000ef03000000000000090000000000000000000000
352+000000006e6574207469746c650000001c00f30001005000f303000000000000
353+000000000000000000000000000000000108f40003000000ef03000000000000
354+090000000000000000000000000000006e6574207469746c650000001c00f400
355+01005000f303000000000000000000000000000000000000000000000108f500
356+03000000ef03000000000000090000000000000000000000000000006e657420
357+7469746c650000001c00f50001005000f3030000000000000000000000000000
358+00000000000000000108f60003000000ef030000000000000900000000000000
359+00000000000000006e6574207469746c650000001c00f60001005000f3030000
360+00000000000000000000000000000000000000000108f70003000000ef030000
361+00000000090000000000000000000000000000006e6574207469746c65000000
362+1c00f70001005000f30300000000000000000000000000000000000000000000
363+0108f80003000000ef0300000000000009000000000000000000000000000000
364+6e6574207469746c650000001c00f80001005000f30300000000000000000000
365+0000000000000000000000000108f90003000000ef0300000000000009000000
366+0000000000000000000000006e6574207469746c650000001c00f90001005000
367+f303000000000000000000000000000000000000000000000108fa0003000000
368+ef03000000000000090000000000000000000000000000006e6574207469746c
369+650000001c00fa0001005000f303000000000000000000000000000000000000
370+000000000108fb0003000000ef03000000000000090000000000000000000000
371+000000006e6574207469746c650000001c00fb0001005000f303000000000000
372+000000000000000000000000000000000108fc0003000000ef03000000000000
373+090000000000000000000000000000006e6574207469746c650000001c00fc00
374+01005000f303000000000000000000000000000000000000000000000108fd00
375+03000000ef03000000000000090000000000000000000000000000006e657420
376+7469746c650000001c00fd0001005000f3030000000000000000000000000000
377+00000000000000000108fe0003000000ef030000000000000900000000000000
378+00000000000000006e6574207469746c650000001c00fe0001005000f3030000
379+00000000000000000000000000000000000000000108ff0003000000ef030000
380+00000000090000000000000000000000000000006e6574207469746c65000000
381+1c00ff0001005000f30300000000000000000000000000000000000000000000
382+0108000103000000ef0300000000000009000000000000000000000000000000
383+6e6574207469746c650000001c00000101005000f30300000000000000000000
384+0000000000000000000000000108010103000000ef0300000000000009000000
385+0000000000000000000000006e6574207469746c650000001c00010101005000
386+f303000000000000000000000000000000000000000000000108020103000000
387+ef03000000000000090000000000000000000000000000006e6574207469746c
388+650000001c00020101005000f303000000000000000000000000000000000000
389+000000000108030103000000ef03000000000000090000000000000000000000
390+000000006e6574207469746c650000001c00030101005000f303000000000000
391+000000000000000000000000000000000108040103000000ef03000000000000
392+090000000000000000000000000000006e6574207469746c650000001c000401
393+01005000f3030000000000000000000000000000000000000000000001080501
394+03000000ef03000000000000090000000000000000000000000000006e657420
395+7469746c650000001c00050101005000f3030000000000000000000000000000
396+00000000000000000108060103000000ef030000000000000900000000000000
397+00000000000000006e6574207469746c650000001c00060101005000f3030000
398+00000000000000000000000000000000000000000108070103000000ef030000
399+00000000090000000000000000000000000000006e6574207469746c65000000
400+1c00070101005000f30300000000000000000000000000000000000000000000
401+0108080103000000ef0300000000000009000000000000000000000000000000
402+6e6574207469746c650000001c00080101005000f30300000000000000000000
403+0000000000000000000000000108090103000000ef0300000000000009000000
404+0000000000000000000000006e6574207469746c650000001c00090101005000
405+f3030000000000000000000000000000000000000000000001080a0103000000
406+ef03000000000000090000000000000000000000000000006e6574207469746c
407+650000001c000a0101005000f303000000000000000000000000000000000000
408+0000000001080b0103000000ef03000000000000090000000000000000000000
409+000000006e6574207469746c650000001c000b0101005000f303000000000000
410+0000000000000000000000000000000001080c0103000000ef03000000000000
411+090000000000000000000000000000006e6574207469746c650000001c000c01
412+01005000f3030000000000000000000000000000000000000000000001080d01
413+03000000ef03000000000000090000000000000000000000000000006e657420
414+7469746c650000001c000d0101005000f3030000000000000000000000000000
415+000000000000000001080e0103000000ef030000000000000900000000000000
416+00000000000000006e6574207469746c650000001c000e0101005000f3030000
417+000000000000000000000000000000000000000001080f0103000000ef030000
418+00000000090000000000000000000000000000006e6574207469746c65000000
419+1c000f0101005000f30300000000000000000000000000000000000000000000
420+0108100103000000ef0300000000000009000000000000000000000000000000
421+6e6574207469746c650000001c00100101005000f30300000000000000000000
422+0000000000000000000000000108110103000000ef0300000000000009000000
423+0000000000000000000000006e6574207469746c650000001c00110101005000
424+f303000000000000000000000000000000000000000000000108120103000000
425+ef03000000000000090000000000000000000000000000006e6574207469746c
426+650000001c00120101005000f303000000000000000000000000000000000000
427+000000000108130103000000ef03000000000000090000000000000000000000
428+000000006e6574207469746c650000001c00130101005000f303000000000000
429+000000000000000000000000000000000108140103000000ef03000000000000
430+090000000000000000000000000000006e6574207469746c650000001c001401
431+01005000f3030000000000000000000000000000000000000000000001081501
432+03000000ef03000000000000090000000000000000000000000000006e657420
433+7469746c650000001c00150101005000f3030000000000000000000000000000
434+00000000000000000108160103000000ef030000000000000900000000000000
435+00000000000000006e6574207469746c650000001c00160101005000f3030000
436+00000000000000000000000000000000000000000108170103000000ef030000
437+00000000090000000000000000000000000000006e6574207469746c65000000
438+1c00170101005000f30300000000000000000000000000000000000000000000
439+0108180103000000ef0300000000000009000000000000000000000000000000
440+6e6574207469746c650000001c00180101005000f30300000000000000000000
441+0000000000000000000000000108190103000000ef0300000000000009000000
442+0000000000000000000000006e6574207469746c650000001c00190101005000
443+f3030000000000000000000000000000000000000000000001081a0103000000
444+ef03000000000000090000000000000000000000000000006e6574207469746c
445+650000001c001a0101005000f303000000000000000000000000000000000000
446+0000000001081b0103000000ef03000000000000090000000000000000000000
447+000000006e6574207469746c650000001c001b0101005000f303000000000000
448+0000000000000000000000000000000001081c0103000000ef03000000000000
449+090000000000000000000000000000006e6574207469746c650000001c001c01
450+01005000f3030000000000000000000000000000000000000000000001081d01
451+03000000ef03000000000000090000000000000000000000000000006e657420
452+7469746c650000001c001d0101005000f3030000000000000000000000000000
453+000000000000000001081e0103000000ef030000000000000900000000000000
454+00000000000000006e6574207469746c650000001c001e0101005000f3030000
455+000000000000000000000000000000000000000001081f0103000000ef030000
456+00000000090000000000000000000000000000006e6574207469746c65000000
457+1c001f0101005000f30300000000000000000000000000000000000000000000
458+0108200103000000ef0300000000000009000000000000000000000000000000
459+6e6574207469746c650000001c00200101005000f30300000000000000000000
460+0000000000000000000000000108210103000000ef0300000000000009000000
461+0000000000000000000000006e6574207469746c650000001c00210101005000
462+f303000000000000000000000000000000000000000000000108220103000000
463+ef03000000000000090000000000000000000000000000006e6574207469746c
464+650000001c00220101005000f303000000000000000000000000000000000000
465+000000000108230103000000ef03000000000000090000000000000000000000
466+000000006e6574207469746c650000001c00230101005000f303000000000000
467+000000000000000000000000000000000108240103000000ef03000000000000
468+090000000000000000000000000000006e6574207469746c650000001c002401
469+01005000f3030000000000000000000000000000000000000000000001082501
470+03000000ef03000000000000090000000000000000000000000000006e657420
471+7469746c650000001c00250101005000f3030000000000000000000000000000
472+00000000000000000108260103000000ef030000000000000900000000000000
473+00000000000000006e6574207469746c650000001c00260101005000f3030000
474+00000000000000000000000000000000000000000108270103000000ef030000
475+00000000090000000000000000000000000000006e6574207469746c65000000
476+1c00270101005000f30300000000000000000000000000000000000000000000
477+0108280103000000ef0300000000000009000000000000000000000000000000
478+6e6574207469746c650000001c00280101005000f30300000000000000000000
479+0000000000000000000000000108290103000000ef0300000000000009000000
480+0000000000000000000000006e6574207469746c650000001c00290101005000
481+f3030000000000000000000000000000000000000000000001082a0103000000
482+ef03000000000000090000000000000000000000000000006e6574207469746c
483+650000001c002a0101005000f303000000000000000000000000000000000000
484+0000000001082b0103000000ef03000000000000090000000000000000000000
485+000000006e6574207469746c650000001c002b0101005000f303000000000000
486+0000000000000000000000000000000001082c0103000000ef03000000000000
487+090000000000000000000000000000006e6574207469746c650000001c002c01
488+01005000f3030000000000000000000000000000000000000000000001082d01
489+03000000ef03000000000000090000000000000000000000000000006e657420
490+7469746c650000001c002d0101005000f3030000000000000000000000000000
491+000000000000000001082e0103000000ef030000000000000900000000000000
492+00000000000000006e6574207469746c650000001c002e0101005000f3030000
493+000000000000000000000000000000000000000001082f0103000000ef030000
494+00000000090000000000000000000000000000006e6574207469746c65000000
495+1c002f0101005000f30300000000000000000000000000000000000000000000
496+0108300103000000ef0300000000000009000000000000000000000000000000
497+6e6574207469746c650000001c00300101005000f30300000000000000000000
498+0000000000000000000000000108310103000000ef0300000000000009000000
499+0000000000000000000000006e6574207469746c650000001c00310101005000
500+f303000000000000000000000000000000000000000000000108320103000000
501+ef03000000000000090000000000000000000000000000006e6574207469746c
502+650000001c00320101005000f303000000000000000000000000000000000000
503+000000000108330103000000ef03000000000000090000000000000000000000
504+000000006e6574207469746c650000001c00330101005000f303000000000000
505+000000000000000000000000000000000108340103000000ef03000000000000
506+090000000000000000000000000000006e6574207469746c650000001c003401
507+01005000f3030000000000000000000000000000000000000000000001083501
508+03000000ef03000000000000090000000000000000000000000000006e657420
509+7469746c650000001c00350101005000f3030000000000000000000000000000
510+00000000000000000108360103000000ef030000000000000900000000000000
511+00000000000000006e6574207469746c650000001c00360101005000f3030000
512+00000000000000000000000000000000000000000108370103000000ef030000
513+00000000090000000000000000000000000000006e6574207469746c65000000
514+1c00370101005000f30300000000000000000000000000000000000000000000
515+0108380103000000ef0300000000000009000000000000000000000000000000
516+6e6574207469746c650000001c00380101005000f30300000000000000000000
517+0000000000000000000000000108390103000000ef0300000000000009000000
518+0000000000000000000000006e6574207469746c650000001c00390101005000
519+f3030000000000000000000000000000000000000000000001083a0103000000
520+ef03000000000000090000000000000000000000000000006e6574207469746c
521+650000001c003a0101005000f303000000000000000000000000000000000000
522+0000000001083b0103000000ef03000000000000090000000000000000000000
523+000000006e6574207469746c650000001c003b0101005000f303000000000000
524+0000000000000000000000000000000001083c0103000000ef03000000000000
525+090000000000000000000000000000006e6574207469746c650000001c003c01
526+01005000f3030000000000000000000000000000000000000000000001083d01
527+03000000ef03000000000000090000000000000000000000000000006e657420
528+7469746c650000001c003d0101005000f3030000000000000000000000000000
529+000000000000000001083e0103000000ef030000000000000900000000000000
530+00000000000000006e6574207469746c650000001c003e0101005000f3030000
531+000000000000000000000000000000000000000001083f0103000000ef030000
532+00000000090000000000000000000000000000006e6574207469746c65000000
533+1c003f0101005000f30300000000000000000000000000000000000000000000
534+0108400103000000ef0300000000000009000000000000000000000000000000
535+6e6574207469746c650000001c00400101005000f30300000000000000000000
536+0000000000000000000000000108410103000000ef0300000000000009000000
537+0000000000000000000000006e6574207469746c650000001c00410101005000
538+f303000000000000000000000000000000000000000000000108420103000000
539+ef03000000000000090000000000000000000000000000006e6574207469746c
540+650000001c00420101005000f303000000000000000000000000000000000000
541+000000000108430103000000ef03000000000000090000000000000000000000
542+000000006e6574207469746c650000001c00430101005000f303000000000000
543+000000000000000000000000000000000108440103000000ef03000000000000
544+090000000000000000000000000000006e6574207469746c650000001c004401
545+01005000f3030000000000000000000000000000000000000000000001084501
546+03000000ef03000000000000090000000000000000000000000000006e657420
547+7469746c650000001c00450101005000f3030000000000000000000000000000
548+00000000000000000108460103000000ef030000000000000900000000000000
549+00000000000000006e6574207469746c650000001c00460101005000f3030000
550+00000000000000000000000000000000000000000108470103000000ef030000
551+00000000090000000000000000000000000000006e6574207469746c65000000
552+1c00470101005000f30300000000000000000000000000000000000000000000
553+0108480103000000ef0300000000000009000000000000000000000000000000
554+6e6574207469746c650000001c00480101005000f30300000000000000000000
555+0000000000000000000000000108490103000000ef0300000000000009000000
556+0000000000000000000000006e6574207469746c650000001c00490101005000
557+f3030000000000000000000000000000000000000000000001084a0103000000
558+ef03000000000000090000000000000000000000000000006e6574207469746c
559+650000001c004a0101005000f303000000000000000000000000000000000000
560+0000000001084b0103000000ef03000000000000090000000000000000000000
561+000000006e6574207469746c650000001c004b0101005000f303000000000000
562+0000000000000000000000000000000001084c0103000000ef03000000000000
563+090000000000000000000000000000006e6574207469746c650000001c004c01
564+01005000f3030000000000000000000000000000000000000000000001084d01
565+03000000ef03000000000000090000000000000000000000000000006e657420
566+7469746c650000001c004d0101005000f3030000000000000000000000000000
567+000000000000000001084e0103000000ef030000000000000900000000000000
568+00000000000000006e6574207469746c650000001c004e0101005000f3030000
569+000000000000000000000000000000000000000001084f0103000000ef030000
570+00000000090000000000000000000000000000006e6574207469746c65000000
571+1c004f0101005000f30300000000000000000000000000000000000000000000
572+0108500103000000ef0300000000000009000000000000000000000000000000
573+6e6574207469746c650000001c00500101005000f30300000000000000000000
574+0000000000000000000000000108510103000000ef0300000000000009000000
575+0000000000000000000000006e6574207469746c650000001c00510101005000
576+f303000000000000000000000000000000000000000000000108520103000000
577+ef03000000000000090000000000000000000000000000006e6574207469746c
578+650000001c00520101005000f303000000000000000000000000000000000000
579+000000000108530103000000ef03000000000000090000000000000000000000
580+000000006e6574207469746c650000001c00530101005000f303000000000000
581+000000000000000000000000000000000108540103000000ef03000000000000
582+090000000000000000000000000000006e6574207469746c650000001c005401
583+01005000f3030000000000000000000000000000000000000000000001085501
584+03000000ef03000000000000090000000000000000000000000000006e657420
585+7469746c650000001c00550101005000f3030000000000000000000000000000
586+00000000000000000108560103000000ef030000000000000900000000000000
587+00000000000000006e6574207469746c650000001c00560101005000f3030000
588+00000000000000000000000000000000000000000108570103000000ef030000
589+00000000090000000000000000000000000000006e6574207469746c65000000
590+1c00570101005000f30300000000000000000000000000000000000000000000
591+0108580103000000ef0300000000000009000000000000000000000000000000
592+6e6574207469746c650000001c00580101005000f30300000000000000000000
593+0000000000000000000000000108590103000000ef0300000000000009000000
594+0000000000000000000000006e6574207469746c650000001c00590101005000
595+f3030000000000000000000000000000000000000000000001085a0103000000
596+ef03000000000000090000000000000000000000000000006e6574207469746c
597+650000001c005a0101005000f303000000000000000000000000000000000000
598+0000000001085b0103000000ef03000000000000090000000000000000000000
599+000000006e6574207469746c650000001c005b0101005000f303000000000000
600+0000000000000000000000000000000001085c0103000000ef03000000000000
601+090000000000000000000000000000006e6574207469746c650000001c005c01
602+01005000f3030000000000000000000000000000000000000000000001085d01
603+03000000ef03000000000000090000000000000000000000000000006e657420
604+7469746c650000001c005d0101005000f3030000000000000000000000000000
605+000000000000000001085e0103000000ef030000000000000900000000000000
606+00000000000000006e6574207469746c650000001c005e0101005000f3030000
607+000000000000000000000000000000000000000001085f0103000000ef030000
608+00000000090000000000000000000000000000006e6574207469746c65000000
609+1c005f0101005000f30300000000000000000000000000000000000000000000
610+0108600103000000ef0300000000000009000000000000000000000000000000
611+6e6574207469746c650000001c00600101005000f30300000000000000000000
612+0000000000000000000000000108610103000000ef0300000000000009000000
613+0000000000000000000000006e6574207469746c650000001c00610101005000
614+f303000000000000000000000000000000000000000000000108620103000000
615+ef03000000000000090000000000000000000000000000006e6574207469746c
616+650000001c00620101005000f303000000000000000000000000000000000000
617+000000000108630103000000ef03000000000000090000000000000000000000
618+000000006e6574207469746c650000001c00630101005000f303000000000000
619+000000000000000000000000000000000108640103000000ef03000000000000
620+090000000000000000000000000000006e6574207469746c650000001c006401
621+01005000f3030000000000000000000000000000000000000000000001086501
622+03000000ef03000000000000090000000000000000000000000000006e657420
623+7469746c650000001c00650101005000f3030000000000000000000000000000
624+00000000000000000108660103000000ef030000000000000900000000000000
625+00000000000000006e6574207469746c650000001c00660101005000f3030000
626+00000000000000000000000000000000000000000108670103000000ef030000
627+00000000090000000000000000000000000000006e6574207469746c65000000
628+1c00670101005000f30300000000000000000000000000000000000000000000
629+0108680103000000ef0300000000000009000000000000000000000000000000
630+6e6574207469746c650000001c00680101005000f30300000000000000000000
631+0000000000000000000000000108690103000000ef0300000000000009000000
632+0000000000000000000000006e6574207469746c650000001c00690101005000
633+f3030000000000000000000000000000000000000000000001086a0103000000
634+ef03000000000000090000000000000000000000000000006e6574207469746c
635+650000001c006a0101005000f303000000000000000000000000000000000000
636+0000000001086b0103000000ef03000000000000090000000000000000000000
637+000000006e6574207469746c650000001c006b0101005000f303000000000000
638+0000000000000000000000000000000001086c0103000000ef03000000000000
639+090000000000000000000000000000006e6574207469746c650000001c006c01
640+01005000f3030000000000000000000000000000000000000000000001086d01
641+03000000ef03000000000000090000000000000000000000000000006e657420
642+7469746c650000001c006d0101005000f3030000000000000000000000000000
643+000000000000000001086e0103000000ef030000000000000900000000000000
644+00000000000000006e6574207469746c650000001c006e0101005000f3030000
645+000000000000000000000000000000000000000001086f0103000000ef030000
646+00000000090000000000000000000000000000006e6574207469746c65000000
647+1c006f0101005000f30300000000000000000000000000000000000000000000
648+0108700103000000ef0300000000000009000000000000000000000000000000
649+6e6574207469746c650000001c00700101005000f30300000000000000000000
650+0000000000000000000000000108710103000000ef0300000000000009000000
651+0000000000000000000000006e6574207469746c650000001c00710101005000
652+f303000000000000000000000000000000000000000000000108720103000000
653+ef03000000000000090000000000000000000000000000006e6574207469746c
654+650000001c00720101005000f303000000000000000000000000000000000000
655+000000000108730103000000ef03000000000000090000000000000000000000
656+000000006e6574207469746c650000001c00730101005000f303000000000000
657+000000000000000000000000000000000108740103000000ef03000000000000
658+090000000000000000000000000000006e6574207469746c650000001c007401
659+01005000f3030000000000000000000000000000000000000000000001087501
660+03000000ef03000000000000090000000000000000000000000000006e657420
661+7469746c650000001c00750101005000f3030000000000000000000000000000
662+00000000000000000108760103000000ef030000000000000900000000000000
663+00000000000000006e6574207469746c650000001c00760101005000f3030000
664+00000000000000000000000000000000000000000108770103000000ef030000
665+00000000090000000000000000000000000000006e6574207469746c65000000
666+1c00770101005000f30300000000000000000000000000000000000000000000
667+0108780103000000ef0300000000000009000000000000000000000000000000
668+6e6574207469746c650000001c00780101005000f30300000000000000000000
669+0000000000000000000000000108790103000000ef0300000000000009000000
670+0000000000000000000000006e6574207469746c650000001c00790101005000
671+f3030000000000000000000000000000000000000000000001087a0103000000
672+ef03000000000000090000000000000000000000000000006e6574207469746c
673+650000001c007a0101005000f303000000000000000000000000000000000000
674+0000000001087b0103000000ef03000000000000090000000000000000000000
675+000000006e6574207469746c650000001c007b0101005000f303000000000000
676+0000000000000000000000000000000001087c0103000000ef03000000000000
677+090000000000000000000000000000006e6574207469746c650000001c007c01
678+01005000f3030000000000000000000000000000000000000000000001087d01
679+03000000ef03000000000000090000000000000000000000000000006e657420
680+7469746c650000001c007d0101005000f3030000000000000000000000000000
681+000000000000000001087e0103000000ef030000000000000900000000000000
682+00000000000000006e6574207469746c650000001c007e0101005000f3030000
683+000000000000000000000000000000000000000001087f0103000000ef030000
684+00000000090000000000000000000000000000006e6574207469746c65000000
685+1c007f0101005000f30300000000000000000000000000000000000000000000
686+0108800103000000ef0300000000000009000000000000000000000000000000
687+6e6574207469746c650000001c00800101005000f30300000000000000000000
688+0000000000000000000000000108810103000000ef0300000000000009000000
689+0000000000000000000000006e6574207469746c650000001c00810101005000
690+f303000000000000000000000000000000000000000000000108820103000000
691+ef03000000000000090000000000000000000000000000006e6574207469746c
692+650000001c00820101005000f303000000000000000000000000000000000000
693+000000000108830103000000ef03000000000000090000000000000000000000
694+000000006e6574207469746c650000001c00830101005000f303000000000000
695+000000000000000000000000000000000108840103000000ef03000000000000
696+090000000000000000000000000000006e6574207469746c650000001c008401
697+01005000f3030000000000000000000000000000000000000000000001088501
698+03000000ef03000000000000090000000000000000000000000000006e657420
699+7469746c650000001c00850101005000f3030000000000000000000000000000
700+00000000000000000108860103000000ef030000000000000900000000000000
701+00000000000000006e6574207469746c650000001c00860101005000f3030000
702+00000000000000000000000000000000000000000108870103000000ef030000
703+00000000090000000000000000000000000000006e6574207469746c65000000
704+1c00870101005000f30300000000000000000000000000000000000000000000
705+0108880103000000ef0300000000000009000000000000000000000000000000
706+6e6574207469746c650000001c00880101005000f30300000000000000000000
707+0000000000000000000000000108890103000000ef0300000000000009000000
708+0000000000000000000000006e6574207469746c650000001c00890101005000
709+f3030000000000000000000000000000000000000000000001088a0103000000
710+ef03000000000000090000000000000000000000000000006e6574207469746c
711+650000001c008a0101005000f303000000000000000000000000000000000000
712+0000000001088b0103000000ef03000000000000090000000000000000000000
713+000000006e6574207469746c650000001c008b0101005000f303000000000000
714+0000000000000000000000000000000001088c0103000000ef03000000000000
715+090000000000000000000000000000006e6574207469746c650000001c008c01
716+01005000f3030000000000000000000000000000000000000000000001088d01
717+03000000ef03000000000000090000000000000000000000000000006e657420
718+7469746c650000001c008d0101005000f3030000000000000000000000000000
719+000000000000000001088e0103000000ef030000000000000900000000000000
720+00000000000000006e6574207469746c650000001c008e0101005000f3030000
721+000000000000000000000000000000000000000001088f0103000000ef030000
722+00000000090000000000000000000000000000006e6574207469746c65000000
723+1c008f0101005000f30300000000000000000000000000000000000000000000
724+0108900103000000ef0300000000000009000000000000000000000000000000
725+6e6574207469746c650000001c00900101005000f30300000000000000000000
726+0000000000000000000000000108910103000000ef0300000000000009000000
727+0000000000000000000000006e6574207469746c650000001c00910101005000
728+f303000000000000000000000000000000000000000000000108920103000000
729+ef03000000000000090000000000000000000000000000006e6574207469746c
730+650000001c00920101005000f303000000000000000000000000000000000000
731+000000000108930103000000ef03000000000000090000000000000000000000
732+000000006e6574207469746c650000001c00930101005000f303000000000000
733+000000000000000000000000000000000108940103000000ef03000000000000
734+090000000000000000000000000000006e6574207469746c650000001c009401
735+01005000f3030000000000000000000000000000000000000000000001089501
736+03000000ef03000000000000090000000000000000000000000000006e657420
737+7469746c650000001c00950101005000f3030000000000000000000000000000
738+00000000000000000108960103000000ef030000000000000900000000000000
739+00000000000000006e6574207469746c650000001c00960101005000f3030000
740+00000000000000000000000000000000000000000108970103000000ef030000
741+00000000090000000000000000000000000000006e6574207469746c65000000
742+1c00970101005000f30300000000000000000000000000000000000000000000
743+0108980103000000ef0300000000000009000000000000000000000000000000
744+6e6574207469746c650000001c00980101005000f30300000000000000000000
745+0000000000000000000000000108990103000000ef0300000000000009000000
746+0000000000000000000000006e6574207469746c650000001c00990101005000
747+f3030000000000000000000000000000000000000000000001089a0103000000
748+ef03000000000000090000000000000000000000000000006e6574207469746c
749+650000001c009a0101005000f303000000000000000000000000000000000000
750+0000000001089b0103000000ef03000000000000090000000000000000000000
751+000000006e6574207469746c650000001c009b0101005000f303000000000000
752+0000000000000000000000000000000001089c0103000000ef03000000000000
753+090000000000000000000000000000006e6574207469746c650000001c009c01
754+01005000f3030000000000000000000000000000000000000000000001089d01
755+03000000ef03000000000000090000000000000000000000000000006e657420
756+7469746c650000001c009d0101005000f3030000000000000000000000000000
757+000000000000000001089e0103000000ef030000000000000900000000000000
758+00000000000000006e6574207469746c650000001c009e0101005000f3030000
759+000000000000000000000000000000000000000001089f0103000000ef030000
760+00000000090000000000000000000000000000006e6574207469746c65000000
761+1c009f0101005000f30300000000000000000000000000000000000000000000
762+0108a00103000000ef0300000000000009000000000000000000000000000000
763+6e6574207469746c650000001c00a00101005000f30300000000000000000000
764+0000000000000000000000000108a10103000000ef0300000000000009000000
765+0000000000000000000000006e6574207469746c650000001c00a10101005000
766+f303000000000000000000000000000000000000000000000108a20103000000
767+ef03000000000000090000000000000000000000000000006e6574207469746c
768+650000001c00a20101005000f303000000000000000000000000000000000000
769+000000000108a30103000000ef03000000000000090000000000000000000000
770+000000006e6574207469746c650000001c00a30101005000f303000000000000
771+000000000000000000000000000000000108a40103000000ef03000000000000
772+090000000000000000000000000000006e6574207469746c650000001c00a401
773+01005000f303000000000000000000000000000000000000000000000108a501
774+03000000ef03000000000000090000000000000000000000000000006e657420
775+7469746c650000001c00a50101005000f3030000000000000000000000000000
776+00000000000000000108a60103000000ef030000000000000900000000000000
777+00000000000000006e6574207469746c650000001c00a60101005000f3030000
778+00000000000000000000000000000000000000000108a70103000000ef030000
779+00000000090000000000000000000000000000006e6574207469746c65000000
780+1c00a70101005000f30300000000000000000000000000000000000000000000
781+0108a80103000000ef0300000000000009000000000000000000000000000000
782+6e6574207469746c650000001c00a80101005000f30300000000000000000000
783+0000000000000000000000000108a90103000000ef0300000000000009000000
784+0000000000000000000000006e6574207469746c650000001c00a90101005000
785+f303000000000000000000000000000000000000000000000108aa0103000000
786+ef03000000000000090000000000000000000000000000006e6574207469746c
787+650000001c00aa0101005000f303000000000000000000000000000000000000
788+000000000108ab0103000000ef03000000000000090000000000000000000000
789+000000006e6574207469746c650000001c00ab0101005000f303000000000000
790+000000000000000000000000000000000108ac0103000000ef03000000000000
791+090000000000000000000000000000006e6574207469746c650000001c00ac01
792+01005000f303000000000000000000000000000000000000000000000108ad01
793+03000000ef03000000000000090000000000000000000000000000006e657420
794+7469746c650000001c00ad0101005000f3030000000000000000000000000000
795+00000000000000000108ae0103000000ef030000000000000900000000000000
796+00000000000000006e6574207469746c650000001c00ae0101005000f3030000
797+00000000000000000000000000000000000000000108af0103000000ef030000
798+00000000090000000000000000000000000000006e6574207469746c65000000
799+1c00af0101005000f30300000000000000000000000000000000000000000000
800+0108b00103000000ef0300000000000009000000000000000000000000000000
801+6e6574207469746c650000001c00b00101005000f30300000000000000000000
802+0000000000000000000000000108b10103000000ef0300000000000009000000
803+0000000000000000000000006e6574207469746c650000001c00b10101005000
804+f303000000000000000000000000000000000000000000000108b20103000000
805+ef03000000000000090000000000000000000000000000006e6574207469746c
806+650000001c00b20101005000f303000000000000000000000000000000000000
807+000000000108b30103000000ef03000000000000090000000000000000000000
808+000000006e6574207469746c650000001c00b30101005000f303000000000000
809+000000000000000000000000000000000108b40103000000ef03000000000000
810+090000000000000000000000000000006e6574207469746c650000001c00b401
811+01005000f303000000000000000000000000000000000000000000000108b501
812+03000000ef03000000000000090000000000000000000000000000006e657420
813+7469746c650000001c00b50101005000f3030000000000000000000000000000
814+00000000000000000108b60103000000ef030000000000000900000000000000
815+00000000000000006e6574207469746c650000001c00b60101005000f3030000
816+00000000000000000000000000000000000000000108b70103000000ef030000
817+00000000090000000000000000000000000000006e6574207469746c65000000
818+1c00b70101005000f30300000000000000000000000000000000000000000000
819+0108b80103000000ef0300000000000009000000000000000000000000000000
820+6e6574207469746c650000001c00b80101005000f30300000000000000000000
821+0000000000000000000000000108b90103000000ef0300000000000009000000
822+0000000000000000000000006e6574207469746c650000001c00b90101005000
823+f303000000000000000000000000000000000000000000000108ba0103000000
824+ef03000000000000090000000000000000000000000000006e6574207469746c
825+650000001c00ba0101005000f303000000000000000000000000000000000000
826+000000000108bb0103000000ef03000000000000090000000000000000000000
827+000000006e6574207469746c650000001c00bb0101005000f303000000000000
828+000000000000000000000000000000000108bc0103000000ef03000000000000
829+090000000000000000000000000000006e6574207469746c650000001c00bc01
830+01005000f303000000000000000000000000000000000000000000000108bd01
831+03000000ef03000000000000090000000000000000000000000000006e657420
832+7469746c650000001c00bd0101005000f3030000000000000000000000000000
833+00000000000000000108be0103000000ef030000000000000900000000000000
834+00000000000000006e6574207469746c650000001c00be0101005000f3030000
835+00000000000000000000000000000000000000000108bf0103000000ef030000
836+00000000090000000000000000000000000000006e6574207469746c65000000
837+1c00bf0101005000f30300000000000000000000000000000000000000000000
838+0108c00103000000ef0300000000000009000000000000000000000000000000
839+6e6574207469746c650000001c00c00101005000f30300000000000000000000
840+0000000000000000000000000108c10103000000ef0300000000000009000000
841+0000000000000000000000006e6574207469746c650000001c00c10101005000
842+f303000000000000000000000000000000000000000000000108c20103000000
843+ef03000000000000090000000000000000000000000000006e6574207469746c
844+650000001c00c20101005000f303000000000000000000000000000000000000
845+000000000108c30103000000ef03000000000000090000000000000000000000
846+000000006e6574207469746c650000001c00c30101005000f303000000000000
847+000000000000000000000000000000000108c40103000000ef03000000000000
848+090000000000000000000000000000006e6574207469746c650000001c00c401
849+01005000f303000000000000000000000000000000000000000000000108c501
850+03000000ef03000000000000090000000000000000000000000000006e657420
851+7469746c650000001c00c50101005000f3030000000000000000000000000000
852+00000000000000000108c60103000000ef030000000000000900000000000000
853+00000000000000006e6574207469746c650000001c00c60101005000f3030000
854+00000000000000000000000000000000000000000108c70103000000ef030000
855+00000000090000000000000000000000000000006e6574207469746c65000000
856+1c00c70101005000f30300000000000000000000000000000000000000000000
857+0108c80103000000ef0300000000000009000000000000000000000000000000
858+6e6574207469746c650000001c00c80101005000f30300000000000000000000
859+0000000000000000000000000108c90103000000ef0300000000000009000000
860+0000000000000000000000006e6574207469746c650000001c00c90101005000
861+f303000000000000000000000000000000000000000000000108ca0103000000
862+ef03000000000000090000000000000000000000000000006e6574207469746c
863+650000001c00ca0101005000f303000000000000000000000000000000000000
864+000000000108cb0103000000ef03000000000000090000000000000000000000
865+000000006e6574207469746c650000001c00cb0101005000f303000000000000
866+000000000000000000000000000000000108cc0103000000ef03000000000000
867+090000000000000000000000000000006e6574207469746c650000001c00cc01
868+01005000f303000000000000000000000000000000000000000000000108cd01
869+03000000ef03000000000000090000000000000000000000000000006e657420
870+7469746c650000001c00cd0101005000f3030000000000000000000000000000
871+00000000000000000108ce0103000000ef030000000000000900000000000000
872+00000000000000006e6574207469746c650000001c00ce0101005000f3030000
873+00000000000000000000000000000000000000000108cf0103000000ef030000
874+00000000090000000000000000000000000000006e6574207469746c65000000
875+1c00cf0101005000f30300000000000000000000000000000000000000000000
876+0108d00103000000ef0300000000000009000000000000000000000000000000
877+6e6574207469746c650000001c00d00101005000f30300000000000000000000
878+0000000000000000000000000108d10103000000ef0300000000000009000000
879+0000000000000000000000006e6574207469746c650000001c00d10101005000
880+f303000000000000000000000000000000000000000000000108d20103000000
881+ef03000000000000090000000000000000000000000000006e6574207469746c
882+650000001c00d20101005000f303000000000000000000000000000000000000
883+000000000108d30103000000ef03000000000000090000000000000000000000
884+000000006e6574207469746c650000001c00d30101005000f303000000000000
885+000000000000000000000000000000000108d40103000000ef03000000000000
886+090000000000000000000000000000006e6574207469746c650000001c00d401
887+01005000f303000000000000000000000000000000000000000000000108d501
888+03000000ef03000000000000090000000000000000000000000000006e657420
889+7469746c650000001c00d50101005000f3030000000000000000000000000000
890+00000000000000000108d60103000000ef030000000000000900000000000000
891+00000000000000006e6574207469746c650000001c00d60101005000f3030000
892+00000000000000000000000000000000000000000108d70103000000ef030000
893+00000000090000000000000000000000000000006e6574207469746c65000000
894+1c00d70101005000f30300000000000000000000000000000000000000000000
895+0108d80103000000ef0300000000000009000000000000000000000000000000
896+6e6574207469746c650000001c00d80101005000f30300000000000000000000
897+0000000000000000000000000108d90103000000ef0300000000000009000000
898+0000000000000000000000006e6574207469746c650000001c00d90101005000
899+f303000000000000000000000000000000000000000000000108da0103000000
900+ef03000000000000090000000000000000000000000000006e6574207469746c
901+650000001c00da0101005000f303000000000000000000000000000000000000
902+000000000108db0103000000ef03000000000000090000000000000000000000
903+000000006e6574207469746c650000001c00db0101005000f303000000000000
904+000000000000000000000000000000000108dc0103000000ef03000000000000
905+090000000000000000000000000000006e6574207469746c650000001c00dc01
906+01005000f303000000000000000000000000000000000000000000000108dd01
907+03000000ef03000000000000090000000000000000000000000000006e657420
908+7469746c650000001c00dd0101005000f3030000000000000000000000000000
909+00000000000000000108de0103000000ef030000000000000900000000000000
910+00000000000000006e6574207469746c650000001c00de0101005000f3030000
911+00000000000000000000000000000000000000000108df0103000000ef030000
912+00000000090000000000000000000000000000006e6574207469746c65000000
913+1c00df0101005000f30300000000000000000000000000000000000000000000
914+0108e00103000000ef0300000000000009000000000000000000000000000000
915+6e6574207469746c650000001c00e00101005000f30300000000000000000000
916+0000000000000000000000000108e10103000000ef0300000000000009000000
917+0000000000000000000000006e6574207469746c650000001c00e10101005000
918+f303000000000000000000000000000000000000000000000108e20103000000
919+ef03000000000000090000000000000000000000000000006e6574207469746c
920+650000001c00e20101005000f303000000000000000000000000000000000000
921+000000000108e30103000000ef03000000000000090000000000000000000000
922+000000006e6574207469746c650000001c00e30101005000f303000000000000
923+000000000000000000000000000000000108e40103000000ef03000000000000
924+090000000000000000000000000000006e6574207469746c650000001c00e401
925+01005000f303000000000000000000000000000000000000000000000108e501
926+03000000ef03000000000000090000000000000000000000000000006e657420
927+7469746c650000001c00e50101005000f3030000000000000000000000000000
928+00000000000000000108e60103000000ef030000000000000900000000000000
929+00000000000000006e6574207469746c650000001c00e60101005000f3030000
930+00000000000000000000000000000000000000000108e70103000000ef030000
931+00000000090000000000000000000000000000006e6574207469746c65000000
932+1c00e70101005000f30300000000000000000000000000000000000000000000
933+0108e80103000000ef0300000000000009000000000000000000000000000000
934+6e6574207469746c650000001c00e80101005000f30300000000000000000000
935+0000000000000000000000000108e90103000000ef0300000000000009000000
936+0000000000000000000000006e6574207469746c650000001c00e90101005000
937+f303000000000000000000000000000000000000000000000108ea0103000000
938+ef03000000000000090000000000000000000000000000006e6574207469746c
939+650000001c00ea0101005000f303000000000000000000000000000000000000
940+000000000108eb0103000000ef03000000000000090000000000000000000000
941+000000006e6574207469746c650000001c00eb0101005000f303000000000000
942+000000000000000000000000000000000108ec0103000000ef03000000000000
943+090000000000000000000000000000006e6574207469746c650000001c00ec01
944+01005000f303000000000000000000000000000000000000000000000108ed01
945+03000000ef03000000000000090000000000000000000000000000006e657420
946+7469746c650000001c00ed0101005000f3030000000000000000000000000000
947+00000000000000000108ee0103000000ef030000000000000900000000000000
948+00000000000000006e6574207469746c650000001c00ee0101005000f3030000
949+00000000000000000000000000000000000000000108ef0103000000ef030000
950+00000000090000000000000000000000000000006e6574207469746c65000000
A internal/adapters/wayland/xwm/testdata/streams/steady.hex +2150 -0 large diff (2150 lines changed): collapsed
M internal/adapters/wayland/xwm/xpeer_test.go +135 -3
@@ -18,6 +18,7 @@ const (
1818 opChangeWindowAttributes = 2
1919 opGetWindowAttributes = 3
2020 opMapWindow = 8
21+ opDeleteProperty = 19
2122 opConfigureWindow = 12
2223 opGetGeometry = 14
2324 opQueryTree = 15
@@ -80,7 +81,13 @@ type xpeer struct {
8081 geoms map[uint32]xpeerGeom
8182 attrs map[uint32][2]byte // window -> {overrideRedirect, mapState}
8283 children []uint32
83- denyRoot bool // answer the root event-mask request with BadAccess
84+ denyRoot bool // answer the root event-mask request with BadAccess
85+ pids map[uint32]uint32 // XRes QueryClientIds answers, by window
86+ bad map[uint32]bool // windows whose GetProperty/GetGeometry fail with BadWindow
87+ hold bool // keep replies until release
88+ held [][]byte
89+ tap []byte // everything written to the client, when recording
90+ record bool
8491
8592 gate atomic.Pointer[chan struct{}] // closed = reading allowed
8693 done chan struct{}
@@ -108,12 +115,14 @@ func startXPeer(t *testing.T, exts map[string]xpeerExt) (int, *xpeer) {
108115 t: t, f: os.NewFile(uintptr(fds[1]), "xpeer"),
109116 exts: exts, atoms: map[string]uint32{}, nextAtom: 1000,
110117 props: map[uint32]map[uint32]xpeerProp{}, geoms: map[uint32]xpeerGeom{}, attrs: map[uint32][2]byte{},
118+ pids: map[uint32]uint32{}, bad: map[uint32]bool{},
111119 done: make(chan struct{}),
112120 }
113121 open := make(chan struct{})
114122 close(open)
115123 p.gate.Store(&open)
116124 t.Cleanup(func() {
125+ p.resume() // a paused peer would never see the close
117126 p.f.Close()
118127 <-p.done
119128 })
@@ -179,7 +188,12 @@ func (p *xpeer) serve() {
179188 }
180189
181190 // write sends bytes to the client. The caller holds p.mu.
182-func (p *xpeer) write(b []byte) { _, _ = p.f.Write(b) }
191+func (p *xpeer) write(b []byte) {
192+ if p.record {
193+ p.tap = append(p.tap, b...)
194+ }
195+ _, _ = p.f.Write(b)
196+}
183197
184198 func (p *xpeer) reply(data uint8, body []byte) {
185199 words := (max(len(body), 24) - 24 + 3) / 4
@@ -188,9 +202,31 @@ func (p *xpeer) reply(data uint8, body []byte) {
188202 binary.LittleEndian.PutUint16(b[2:], p.seq)
189203 binary.LittleEndian.PutUint32(b[4:], uint32(words))
190204 copy(b[8:], body)
205+ if p.hold {
206+ p.held = append(p.held, b)
207+ return
208+ }
191209 p.write(b)
192210 }
193211
212+// release sends the held replies, in request order as a real server does.
213+func (p *xpeer) release() {
214+ p.mu.Lock()
215+ defer p.mu.Unlock()
216+ p.hold = false
217+ for _, b := range p.held {
218+ p.write(b)
219+ }
220+ p.held = nil
221+}
222+
223+// holdReplies keeps the replies of the following requests back.
224+func (p *xpeer) holdReplies() {
225+ p.mu.Lock()
226+ p.hold = true
227+ p.mu.Unlock()
228+}
229+
194230 func (p *xpeer) errorPacket(code, major uint8, bad uint32) {
195231 b := make([]byte, 32)
196232 b[1] = code
@@ -252,6 +288,8 @@ func (p *xpeer) answer(r xpeerReq, body []byte) {
252288 b[18] = a[1] // map_state is byte 26 of the reply
253289 b[19] = a[0] // override_redirect, byte 27
254290 p.reply(0, b)
291+ case (r.major == opGetGeometry || r.major == opGetProperty) && p.bad[binary.LittleEndian.Uint32(body)]:
292+ p.errorPacket(3, r.major, binary.LittleEndian.Uint32(body)) // BadWindow
255293 case r.major == opGetGeometry:
256294 g := p.geoms[binary.LittleEndian.Uint32(body)]
257295 b := make([]byte, 24)
@@ -272,6 +310,8 @@ func (p *xpeer) answer(r xpeerReq, body []byte) {
272310 typ: binary.LittleEndian.Uint32(body[8:]), format: format,
273311 data: append([]byte(nil), body[20:20+n*int(format)/8]...),
274312 }
313+ case r.major == opDeleteProperty:
314+ delete(p.props[binary.LittleEndian.Uint32(body)], binary.LittleEndian.Uint32(body[4:]))
275315 case r.major == opGetProperty:
276316 win, prop := binary.LittleEndian.Uint32(body), binary.LittleEndian.Uint32(body[4:])
277317 v, ok := p.props[win][prop]
@@ -300,8 +340,22 @@ func (p *xpeer) answer(r xpeerReq, body []byte) {
300340 p.reply(0, le32(5, 4))
301341 }
302342 case "X-Resource":
303- if r.minor == 0 {
343+ switch r.minor {
344+ case 0:
304345 p.reply(0, []byte{1, 0, 2, 0})
346+ case 4: // QueryClientIds: one spec, answered when a pid is scripted
347+ pid, ok := p.pids[r.u32(8)]
348+ if !ok {
349+ p.reply(0, le32(0))
350+ break
351+ }
352+ b := make([]byte, 40)
353+ binary.LittleEndian.PutUint32(b[0:], 1)
354+ binary.LittleEndian.PutUint32(b[24:], r.u32(8))
355+ binary.LittleEndian.PutUint32(b[28:], 2) // LocalClientPID
356+ binary.LittleEndian.PutUint32(b[32:], 4)
357+ binary.LittleEndian.PutUint32(b[36:], pid)
358+ p.reply(0, b)
305359 }
306360 }
307361 }
@@ -409,3 +463,81 @@ func (p *xpeer) hangup() {
409463 _ = rc.Control(func(fd uintptr) { _ = unix.Shutdown(int(fd), unix.SHUT_RDWR) })
410464 }
411465 }
466+
467+// setProp scripts a window property for GetProperty.
468+func (p *xpeer) setProp(win, atom, typ uint32, format uint8, data []byte) {
469+ p.mu.Lock()
470+ defer p.mu.Unlock()
471+ if p.props[win] == nil {
472+ p.props[win] = map[uint32]xpeerProp{}
473+ }
474+ p.props[win][atom] = xpeerProp{typ: typ, format: format, data: data}
475+}
476+
477+func (p *xpeer) mapRequest(win uint32) {
478+ p.event(20, func(b *[32]byte) {
479+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
480+ binary.LittleEndian.PutUint32(b[8:], win)
481+ })
482+}
483+
484+func (p *xpeer) mapNotify(win uint32) {
485+ p.event(19, func(b *[32]byte) {
486+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
487+ binary.LittleEndian.PutUint32(b[8:], win)
488+ })
489+}
490+
491+func (p *xpeer) unmapNotify(win uint32, synthetic bool) {
492+ p.event(18, func(b *[32]byte) {
493+ if synthetic {
494+ b[0] |= 0x80
495+ }
496+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
497+ binary.LittleEndian.PutUint32(b[8:], win)
498+ })
499+}
500+
501+func (p *xpeer) propertyNotify(win, atom uint32, deleted bool) {
502+ p.event(28, func(b *[32]byte) {
503+ binary.LittleEndian.PutUint32(b[4:], win)
504+ binary.LittleEndian.PutUint32(b[8:], atom)
505+ if deleted {
506+ b[16] = 1
507+ }
508+ })
509+}
510+
511+func (p *xpeer) reparentNotify(win, parent uint32) {
512+ p.event(21, func(b *[32]byte) {
513+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
514+ binary.LittleEndian.PutUint32(b[8:], win)
515+ binary.LittleEndian.PutUint32(b[12:], parent)
516+ })
517+}
518+
519+func (p *xpeer) clientMessage(win, typ uint32, data ...uint32) {
520+ p.event(33, func(b *[32]byte) {
521+ b[1] = 32
522+ binary.LittleEndian.PutUint32(b[4:], win)
523+ binary.LittleEndian.PutUint32(b[8:], typ)
524+ for i, d := range data {
525+ binary.LittleEndian.PutUint32(b[12+4*i:], d)
526+ }
527+ })
528+}
529+
530+func (p *xpeer) focusIn(win uint32) {
531+ p.event(9, func(b *[32]byte) { binary.LittleEndian.PutUint32(b[4:], win) })
532+}
533+
534+func (p *xpeer) configureNotify(win uint32) {
535+ p.event(22, func(b *[32]byte) {
536+ binary.LittleEndian.PutUint32(b[4:], win)
537+ binary.LittleEndian.PutUint32(b[8:], win)
538+ binary.LittleEndian.PutUint16(b[20:], 10)
539+ binary.LittleEndian.PutUint16(b[22:], 20)
540+ binary.LittleEndian.PutUint16(b[24:], 300)
541+ binary.LittleEndian.PutUint16(b[26:], 200)
542+ })
543+}
@@ -18,6 +18,7 @@ const (
18 opChangeWindowAttributes = 218 opChangeWindowAttributes = 2
19 opGetWindowAttributes = 319 opGetWindowAttributes = 3
20 opMapWindow = 820 opMapWindow = 8
21+ opDeleteProperty = 19
21 opConfigureWindow = 1222 opConfigureWindow = 12
22 opGetGeometry = 1423 opGetGeometry = 14
23 opQueryTree = 1524 opQueryTree = 15
@@ -80,7 +81,13 @@ type xpeer struct {
80 geoms map[uint32]xpeerGeom81 geoms map[uint32]xpeerGeom
81 attrs map[uint32][2]byte // window -> {overrideRedirect, mapState}82 attrs map[uint32][2]byte // window -> {overrideRedirect, mapState}
82 children []uint3283 children []uint32
83- denyRoot bool // answer the root event-mask request with BadAccess84+ denyRoot bool // answer the root event-mask request with BadAccess
85+ pids map[uint32]uint32 // XRes QueryClientIds answers, by window
86+ bad map[uint32]bool // windows whose GetProperty/GetGeometry fail with BadWindow
87+ hold bool // keep replies until release
88+ held [][]byte
89+ tap []byte // everything written to the client, when recording
90+ record bool
84 91
85 gate atomic.Pointer[chan struct{}] // closed = reading allowed92 gate atomic.Pointer[chan struct{}] // closed = reading allowed
86 done chan struct{}93 done chan struct{}
@@ -108,12 +115,14 @@ func startXPeer(t *testing.T, exts map[string]xpeerExt) (int, *xpeer) {
108 t: t, f: os.NewFile(uintptr(fds[1]), "xpeer"),115 t: t, f: os.NewFile(uintptr(fds[1]), "xpeer"),
109 exts: exts, atoms: map[string]uint32{}, nextAtom: 1000,116 exts: exts, atoms: map[string]uint32{}, nextAtom: 1000,
110 props: map[uint32]map[uint32]xpeerProp{}, geoms: map[uint32]xpeerGeom{}, attrs: map[uint32][2]byte{},117 props: map[uint32]map[uint32]xpeerProp{}, geoms: map[uint32]xpeerGeom{}, attrs: map[uint32][2]byte{},
118+ pids: map[uint32]uint32{}, bad: map[uint32]bool{},
111 done: make(chan struct{}),119 done: make(chan struct{}),
112 }120 }
113 open := make(chan struct{})121 open := make(chan struct{})
114 close(open)122 close(open)
115 p.gate.Store(&open)123 p.gate.Store(&open)
116 t.Cleanup(func() {124 t.Cleanup(func() {
125+ p.resume() // a paused peer would never see the close
117 p.f.Close()126 p.f.Close()
118 <-p.done127 <-p.done
119 })128 })
@@ -179,7 +188,12 @@ func (p *xpeer) serve() {
179 }188 }
180 189
181 // write sends bytes to the client. The caller holds p.mu.190 // write sends bytes to the client. The caller holds p.mu.
182-func (p *xpeer) write(b []byte) { _, _ = p.f.Write(b) }191+func (p *xpeer) write(b []byte) {
192+ if p.record {
193+ p.tap = append(p.tap, b...)
194+ }
195+ _, _ = p.f.Write(b)
196+}
183 197
184 func (p *xpeer) reply(data uint8, body []byte) {198 func (p *xpeer) reply(data uint8, body []byte) {
185 words := (max(len(body), 24) - 24 + 3) / 4199 words := (max(len(body), 24) - 24 + 3) / 4
@@ -188,9 +202,31 @@ func (p *xpeer) reply(data uint8, body []byte) {
188 binary.LittleEndian.PutUint16(b[2:], p.seq)202 binary.LittleEndian.PutUint16(b[2:], p.seq)
189 binary.LittleEndian.PutUint32(b[4:], uint32(words))203 binary.LittleEndian.PutUint32(b[4:], uint32(words))
190 copy(b[8:], body)204 copy(b[8:], body)
205+ if p.hold {
206+ p.held = append(p.held, b)
207+ return
208+ }
191 p.write(b)209 p.write(b)
192 }210 }
193 211
212+// release sends the held replies, in request order as a real server does.
213+func (p *xpeer) release() {
214+ p.mu.Lock()
215+ defer p.mu.Unlock()
216+ p.hold = false
217+ for _, b := range p.held {
218+ p.write(b)
219+ }
220+ p.held = nil
221+}
222+
223+// holdReplies keeps the replies of the following requests back.
224+func (p *xpeer) holdReplies() {
225+ p.mu.Lock()
226+ p.hold = true
227+ p.mu.Unlock()
228+}
229+
194 func (p *xpeer) errorPacket(code, major uint8, bad uint32) {230 func (p *xpeer) errorPacket(code, major uint8, bad uint32) {
195 b := make([]byte, 32)231 b := make([]byte, 32)
196 b[1] = code232 b[1] = code
@@ -252,6 +288,8 @@ func (p *xpeer) answer(r xpeerReq, body []byte) {
252 b[18] = a[1] // map_state is byte 26 of the reply288 b[18] = a[1] // map_state is byte 26 of the reply
253 b[19] = a[0] // override_redirect, byte 27289 b[19] = a[0] // override_redirect, byte 27
254 p.reply(0, b)290 p.reply(0, b)
291+ case (r.major == opGetGeometry || r.major == opGetProperty) && p.bad[binary.LittleEndian.Uint32(body)]:
292+ p.errorPacket(3, r.major, binary.LittleEndian.Uint32(body)) // BadWindow
255 case r.major == opGetGeometry:293 case r.major == opGetGeometry:
256 g := p.geoms[binary.LittleEndian.Uint32(body)]294 g := p.geoms[binary.LittleEndian.Uint32(body)]
257 b := make([]byte, 24)295 b := make([]byte, 24)
@@ -272,6 +310,8 @@ func (p *xpeer) answer(r xpeerReq, body []byte) {
272 typ: binary.LittleEndian.Uint32(body[8:]), format: format,310 typ: binary.LittleEndian.Uint32(body[8:]), format: format,
273 data: append([]byte(nil), body[20:20+n*int(format)/8]...),311 data: append([]byte(nil), body[20:20+n*int(format)/8]...),
274 }312 }
313+ case r.major == opDeleteProperty:
314+ delete(p.props[binary.LittleEndian.Uint32(body)], binary.LittleEndian.Uint32(body[4:]))
275 case r.major == opGetProperty:315 case r.major == opGetProperty:
276 win, prop := binary.LittleEndian.Uint32(body), binary.LittleEndian.Uint32(body[4:])316 win, prop := binary.LittleEndian.Uint32(body), binary.LittleEndian.Uint32(body[4:])
277 v, ok := p.props[win][prop]317 v, ok := p.props[win][prop]
@@ -300,8 +340,22 @@ func (p *xpeer) answer(r xpeerReq, body []byte) {
300 p.reply(0, le32(5, 4))340 p.reply(0, le32(5, 4))
301 }341 }
302 case "X-Resource":342 case "X-Resource":
303- if r.minor == 0 {343+ switch r.minor {
344+ case 0:
304 p.reply(0, []byte{1, 0, 2, 0})345 p.reply(0, []byte{1, 0, 2, 0})
346+ case 4: // QueryClientIds: one spec, answered when a pid is scripted
347+ pid, ok := p.pids[r.u32(8)]
348+ if !ok {
349+ p.reply(0, le32(0))
350+ break
351+ }
352+ b := make([]byte, 40)
353+ binary.LittleEndian.PutUint32(b[0:], 1)
354+ binary.LittleEndian.PutUint32(b[24:], r.u32(8))
355+ binary.LittleEndian.PutUint32(b[28:], 2) // LocalClientPID
356+ binary.LittleEndian.PutUint32(b[32:], 4)
357+ binary.LittleEndian.PutUint32(b[36:], pid)
358+ p.reply(0, b)
305 }359 }
306 }360 }
307 }361 }
@@ -409,3 +463,81 @@ func (p *xpeer) hangup() {
409 _ = rc.Control(func(fd uintptr) { _ = unix.Shutdown(int(fd), unix.SHUT_RDWR) })463 _ = rc.Control(func(fd uintptr) { _ = unix.Shutdown(int(fd), unix.SHUT_RDWR) })
410 }464 }
411 }465 }
466+
467+// setProp scripts a window property for GetProperty.
468+func (p *xpeer) setProp(win, atom, typ uint32, format uint8, data []byte) {
469+ p.mu.Lock()
470+ defer p.mu.Unlock()
471+ if p.props[win] == nil {
472+ p.props[win] = map[uint32]xpeerProp{}
473+ }
474+ p.props[win][atom] = xpeerProp{typ: typ, format: format, data: data}
475+}
476+
477+func (p *xpeer) mapRequest(win uint32) {
478+ p.event(20, func(b *[32]byte) {
479+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
480+ binary.LittleEndian.PutUint32(b[8:], win)
481+ })
482+}
483+
484+func (p *xpeer) mapNotify(win uint32) {
485+ p.event(19, func(b *[32]byte) {
486+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
487+ binary.LittleEndian.PutUint32(b[8:], win)
488+ })
489+}
490+
491+func (p *xpeer) unmapNotify(win uint32, synthetic bool) {
492+ p.event(18, func(b *[32]byte) {
493+ if synthetic {
494+ b[0] |= 0x80
495+ }
496+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
497+ binary.LittleEndian.PutUint32(b[8:], win)
498+ })
499+}
500+
501+func (p *xpeer) propertyNotify(win, atom uint32, deleted bool) {
502+ p.event(28, func(b *[32]byte) {
503+ binary.LittleEndian.PutUint32(b[4:], win)
504+ binary.LittleEndian.PutUint32(b[8:], atom)
505+ if deleted {
506+ b[16] = 1
507+ }
508+ })
509+}
510+
511+func (p *xpeer) reparentNotify(win, parent uint32) {
512+ p.event(21, func(b *[32]byte) {
513+ binary.LittleEndian.PutUint32(b[4:], peerRoot)
514+ binary.LittleEndian.PutUint32(b[8:], win)
515+ binary.LittleEndian.PutUint32(b[12:], parent)
516+ })
517+}
518+
519+func (p *xpeer) clientMessage(win, typ uint32, data ...uint32) {
520+ p.event(33, func(b *[32]byte) {
521+ b[1] = 32
522+ binary.LittleEndian.PutUint32(b[4:], win)
523+ binary.LittleEndian.PutUint32(b[8:], typ)
524+ for i, d := range data {
525+ binary.LittleEndian.PutUint32(b[12+4*i:], d)
526+ }
527+ })
528+}
529+
530+func (p *xpeer) focusIn(win uint32) {
531+ p.event(9, func(b *[32]byte) { binary.LittleEndian.PutUint32(b[4:], win) })
532+}
533+
534+func (p *xpeer) configureNotify(win uint32) {
535+ p.event(22, func(b *[32]byte) {
536+ binary.LittleEndian.PutUint32(b[4:], win)
537+ binary.LittleEndian.PutUint32(b[8:], win)
538+ binary.LittleEndian.PutUint16(b[20:], 10)
539+ binary.LittleEndian.PutUint16(b[22:], 20)
540+ binary.LittleEndian.PutUint16(b[24:], 300)
541+ binary.LittleEndian.PutUint16(b[26:], 200)
542+ })
543+}
M internal/adapters/wayland/xwm/xwm.go +2 -1
@@ -1,4 +1,4 @@
1-//lint:file-ignore U1000 pairing, focus and stacking state is used by the flows that build on this skeleton
1+//lint:file-ignore U1000 refocus and listsDirty are used by the focus and configure flows
22
33 // Package xwm is the built-in X11 window manager for Xwayland: it speaks the
44 // X protocol over the -wm socket with xturbo and reports managed windows to
@@ -66,6 +66,7 @@ type XWM struct {
6666 posted, writable, closed bool
6767 bootErr error
6868 warnedRing, warnedFull bool
69+ warnedUnpaired bool
6970
7071 scratch []byte // encode buffer for 32-bit property values, 4 bytes per slot
7172
@@ -1,4 +1,4 @@
1-//lint:file-ignore U1000 pairing, focus and stacking state is used by the flows that build on this skeleton1+//lint:file-ignore U1000 refocus and listsDirty are used by the focus and configure flows
2 2
3 // Package xwm is the built-in X11 window manager for Xwayland: it speaks the3 // Package xwm is the built-in X11 window manager for Xwayland: it speaks the
4 // X protocol over the -wm socket with xturbo and reports managed windows to4 // X protocol over the -wm socket with xturbo and reports managed windows to
@@ -66,6 +66,7 @@ type XWM struct {
66 posted, writable, closed bool66 posted, writable, closed bool
67 bootErr error67 bootErr error
68 warnedRing, warnedFull bool68 warnedRing, warnedFull bool
69+ warnedUnpaired bool
69 70
70 scratch []byte // encode buffer for 32-bit property values, 4 bytes per slot71 scratch []byte // encode buffer for 32-bit property values, 4 bytes per slot
71 72

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close