• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dapplo / Dapplo.Windows / 22231723592

20 Feb 2026 04:16PM UTC coverage: 33.416% (-0.07%) from 33.49%
22231723592

push

github

Lakritzator
Small fixes for tests and namespaces

611 of 1948 branches covered (31.37%)

Branch coverage included in aggregate %.

2 of 8 new or added lines in 2 files covered. (25.0%)

231 existing lines in 16 files now uncovered.

1668 of 4872 relevant lines covered (34.24%)

30.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.75
/src/Dapplo.Windows.Messages/SharedMessageWindow.cs
1
// Copyright (c) Dapplo and contributors. All rights reserved.
2
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3

4
using Dapplo.Windows.Messages.Enumerations;
5
using Dapplo.Windows.Messages.Native;
6
using Dapplo.Windows.Messages.Structs;
7
using System;
8
using System.Reactive.Disposables;
9
using System.Reactive.Linq;
10
using System.Reactive.Subjects;
11
using System.Runtime.InteropServices;
12
using System.Threading;
13

14
namespace Dapplo.Windows.Messages;
15

16
/// <summary>
17
/// Provides functionality to create an observable stream of Windows messages using a message-only window on a dedicated thread.
18
/// </summary>
19
public static class SharedMessageWindow
20
{
21
    #region PInvokes
22
    [DllImport("user32", SetLastError = true, CharSet = CharSet.Auto)]
23
    private static extern ushort RegisterClassEx(ref WNDCLASSEX lpwc);
24

25
    [DllImport("user32", SetLastError = true, CharSet = CharSet.Auto)]
26
    private static extern bool UnregisterClass(string lpClassName, nint hInstance);
27

28
    [DllImport("user32", SetLastError = true, CharSet = CharSet.Auto)]
29
    private static extern nint CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, nint hWndParent, nint hMenu, nint hInstance, nint lpParam);
30

31
    [DllImport("user32")]
32
    private static extern bool GetMessage(out Msg lpMsg, nint hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
33

34
    [DllImport("user32")]
35
    private static extern bool TranslateMessage(ref Msg lpMsg);
36

37
    [DllImport("user32")]
38
    private static extern nint DispatchMessage(ref Msg lpMsg);
39

40
    [DllImport("user32")]
41
    private static extern nint DefWindowProc(nint hWnd, uint msg, nint wParam, nint lParam);
42

43
    [DllImport("user32")]
44
    private static extern void PostQuitMessage(int nExitCode);
45

46
    [DllImport("user32")]
47
    private static extern bool PostMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
48

49
    [DllImport("kernel32")]
50
    private static extern nint GetModuleHandle(string? lpModuleName);
51
    #endregion
52

53
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
54
    private struct WNDCLASSEX
55
    {
56
        public uint cbSize; public uint style; public WndProc lpfnWndProc; public int cbClsExtra;
57
        public int cbWndExtra; public nint hInstance; public nint hIcon; public nint hCursor;
58
        public nint hbrBackground; public string lpszMenuName; public string lpszClassName; public nint hIconSm;
59
    }
60

61
    private static IObservable<WindowMessage>? _sharedStream;
62
    private static readonly object _lock = new();
1✔
63
    private static readonly BehaviorSubject<nint> _handleSubject = new(0);
1✔
64

65
    /// <summary>
66
    /// Gets the current handle of the message window, or zero if no window is currently active.
67
    /// </summary>
68
    public static nint Handle => _handleSubject.Value;
23✔
69

70
    /// <summary>
71
    /// Gets an observable sequence that emits whenever the window handle is non-zero.
72
    /// If the handle is already non-zero when subscribed, it emits immediately.
73
    /// Use <see cref="System.Reactive.Linq.Observable.First{TSource}"/> or
74
    /// <see cref="System.Reactive.Linq.Observable.FirstAsync{TSource}(IObservable{TSource})"/>
75
    /// to obtain only the first available handle.
76
    /// </summary>
77
    public static IObservable<nint> WhenHandleAvailable => _handleSubject.Where(h => h != 0);
×
78

79
    /// <summary>
80
    /// Gets an observable sequence of all window messages received by the application.
81
    /// </summary>
82
    /// <remarks>The returned observable is shared among all subscribers. Subscribing to this property allows
83
    /// monitoring of window messages as they occur. Unsubscribing from all observers will automatically release
84
    /// resources associated with the stream.</remarks>
85
    public static IObservable<WindowMessage> Messages
86
    {
87
        get
88
        {
89
            lock (_lock)
14✔
90
            {
91
                if (_sharedStream == null)
14✔
92
                {
93
                    _sharedStream = CreateBaseStream()
1✔
94
                        .Publish()
1✔
95
                        .RefCount();
1✔
96
                }
97
                return _sharedStream;
14✔
98
            }
99
        }
14✔
100
    }
101

102
    /// <summary>
103
    /// Subscribes to the shared message loop.
104
    /// </summary>
105
    /// <param name="onSetup">Invoked with the HWND when the window is created (or immediately if it already exists).</param>
106
    /// <param name="onTeardown">Invoked with the HWND when the window is destroyed OR when the subscription is disposed.</param>
107
    public static IObservable<WindowMessage> Listen(Action<nint> onSetup = null, Action<nint> onTeardown = null)
108
    {
109
        if (onSetup == null && onTeardown == null)
14!
110
        {
111
            return Messages;
14✔
112
        }
113

114
        return Observable.Create<WindowMessage>(observer =>
×
115
        {
×
116
            nint activeHwnd = 0;
×
117
            object handleLock = new();
×
118

×
119
            // Manage the Handle Lifecycle
×
120
            var handleSubscription = _handleSubject.Subscribe(hwnd =>
×
121
            {
×
122
                lock (handleLock)
×
123
                {
×
124
                    if (hwnd != 0)
×
125
                    {
×
126
                        activeHwnd = hwnd;
×
127
                        onSetup?.Invoke(activeHwnd);
×
128
                    }
×
129
                    else if (activeHwnd != 0)
×
130
                    {
×
131
                        // Window was destroyed
×
132
                        onTeardown?.Invoke(activeHwnd);
×
133
                        activeHwnd = 0;
×
134
                    }
×
135
                }
×
136
            });
×
137

×
138
            // Pass through the messages
×
139
            var messageSubscription = Messages.Subscribe(observer);
×
140

×
141
            // Ensure teardown runs if the consumer drops the subscription early
×
UNCOV
142
            return Disposable.Create(() =>
×
UNCOV
143
            {
×
UNCOV
144
                lock (handleLock)
×
UNCOV
145
                {
×
UNCOV
146
                    if (activeHwnd != 0)
×
UNCOV
147
                    {
×
UNCOV
148
                        onTeardown?.Invoke(activeHwnd);
×
UNCOV
149
                        activeHwnd = 0;
×
UNCOV
150
                    }
×
UNCOV
151
                }
×
UNCOV
152
                handleSubscription.Dispose();
×
UNCOV
153
                messageSubscription.Dispose();
×
154
            });
×
155
        });
×
156
    }
157

158
    /// <summary>
159
    /// Creates an observable sequence that emits Windows messages for a message-only window with the specified class name.
160
    /// </summary>
161
    /// <remarks>The window is created on a dedicated background thread with a single-threaded apartment
162
    /// state. Disposal of the returned observable triggers destruction of the window and completion of the sequence.
163
    /// This method is useful for scenarios requiring low-level message processing without a visible UI window.</remarks>
164
    /// Can be null if no callback is needed.</param>
165
    /// <returns>An observable sequence of Windows messages received by the created message-only window. The sequence completes when the window is destroyed.</returns>
166
    private static IObservable<WindowMessage> CreateBaseStream()
167
    {
168
        return Observable.Create<WindowMessage>(observer =>
1✔
169
        {
1✔
170
            nint hwnd = 0;
13✔
171
            string className = "MsgLoop_" + Guid.NewGuid().ToString("N");
13✔
172
            WndProc? wndProcDelegate = null;
13✔
173

1✔
174
            var thread = new Thread(() =>
13✔
175
            {
13✔
176
                wndProcDelegate = (hWnd, msg, wParam, lParam) =>
13✔
177
                {
13✔
178
                    if (msg == (uint)WindowsMessages.WM_DESTROY)
66✔
179
                    {
13✔
180
                        PostQuitMessage(0);
13✔
181
                        return 0;
13✔
182
                    }
13✔
183

13✔
184
                    observer.OnNext(new WindowMessage(hWnd, msg, wParam, lParam));
53✔
185
                    return DefWindowProc(hWnd, msg, wParam, lParam);
53✔
186
                };
13✔
187

13✔
188
                // Register Class
13✔
189
                var wndClass = new WNDCLASSEX
13✔
190
                {
13✔
191
                    cbSize = (uint)Marshal.SizeOf<WNDCLASSEX>(),
13✔
192
                    lpfnWndProc = wndProcDelegate,
13✔
193
                    hInstance = GetModuleHandle(null),
13✔
194
                    lpszClassName = className
13✔
195
                };
13✔
196

13✔
197
                RegisterClassEx(ref wndClass);
13✔
198

13✔
199
                hwnd = CreateWindowEx(0, className, "NativeMessageWindow", 0, 0, 0, 0, 0, -3, 0, wndClass.hInstance, 0);
13✔
200

13✔
201
                // Trigger External Registrations
13✔
202
                if (hwnd != 0)
13✔
203
                {
13✔
204
                    _handleSubject.OnNext(hwnd);
13✔
205
                }
13✔
206

13✔
207
                while (GetMessage(out var msg, 0, 0, 0))
26✔
208
                {
13✔
209
                    TranslateMessage(ref msg);
13✔
210
                    DispatchMessage(ref msg);
13✔
211
                }
13✔
212

13✔
213
                // Trigger External Cleanup
13✔
214
                if (hwnd != 0)
13✔
215
                {
13✔
216
                    _handleSubject.OnNext(0);
13✔
217
                }
13✔
218
                
13✔
219
                UnregisterClass(className, wndClass.hInstance);
13✔
220
            });
26✔
221

1✔
222
            thread.SetApartmentState(ApartmentState.STA);
13✔
223
            thread.IsBackground = true;
13✔
224
            thread.Start();
13✔
225

1✔
226
            return Disposable.Create(() =>
13✔
227
            {
13✔
228
                if (hwnd != 0)
13✔
229
                {
13✔
230
                    PostMessage(hwnd, (uint)WindowsMessages.WM_DESTROY, 0, 0);
13✔
231
                }
13✔
232
            });
26✔
233
        });
1✔
234
    }
235
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc