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

dapplo / Dapplo.Windows / 22363910836

24 Feb 2026 06:11PM UTC coverage: 31.828% (-0.9%) from 32.763%
22363910836

push

github

web-flow
Comment out WpfFact attribute in ClipboardTests

Comment out the WpfFact attribute for the TestClipboardMonitor_DelayedRender method.

602 of 2002 branches covered (30.07%)

Branch coverage included in aggregate %.

1647 of 5064 relevant lines covered (32.52%)

29.92 hits per line

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

55.63
/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 nuint DefWindowProc(nint hWnd, WindowsMessages 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 of all window messages received by the application.
72
    /// </summary>
73
    /// <remarks>The returned observable is shared among all subscribers. Subscribing to this property allows
74
    /// monitoring of window messages as they occur. Unsubscribing from all observers will automatically release
75
    /// resources associated with the stream.</remarks>
76
    public static IObservable<WindowMessage> Messages
77
    {
78
        get
79
        {
80
            lock (_lock)
14✔
81
            {
82
                if (_sharedStream == null)
14✔
83
                {
84
                    _sharedStream = CreateBaseStream()
1✔
85
                        .Publish()
1✔
86
                        .RefCount();
1✔
87
                }
88
                return _sharedStream;
14✔
89
            }
90
        }
14✔
91
    }
92

93
    /// <summary>
94
    /// Subscribes to the shared message loop.
95
    /// </summary>
96
    /// <param name="onSetup">Invoked with the HWND when the window is created (or immediately if it already exists).</param>
97
    /// <param name="onTeardown">Invoked with the HWND when the window is destroyed OR when the subscription is disposed.</param>
98
    public static IObservable<WindowMessage> Listen(Action<nint> onSetup = null, Action<nint> onTeardown = null)
99
    {
100
        if (onSetup == null && onTeardown == null)
14!
101
        {
102
            return Messages;
14✔
103
        }
104

105
        return Observable.Create<WindowMessage>(observer =>
×
106
        {
×
107
            nint activeHwnd = 0;
×
108
            object handleLock = new();
×
109

×
110
            // Manage the Handle Lifecycle
×
111
            var handleSubscription = _handleSubject.Subscribe(hwnd =>
×
112
            {
×
113
                lock (handleLock)
×
114
                {
×
115
                    if (hwnd != 0)
×
116
                    {
×
117
                        activeHwnd = hwnd;
×
118
                        onSetup?.Invoke(activeHwnd);
×
119
                    }
×
120
                    else if (activeHwnd != 0)
×
121
                    {
×
122
                        // Window was destroyed
×
123
                        onTeardown?.Invoke(activeHwnd);
×
124
                        activeHwnd = 0;
×
125
                    }
×
126
                }
×
127
            });
×
128

×
129
            // Pass through the messages
×
130
            var messageSubscription = Messages.Subscribe(observer);
×
131

×
132
            // Ensure teardown runs if the consumer drops the subscription early
×
133
            return Disposable.Create(() =>
×
134
            {
×
135
                lock (handleLock)
×
136
                {
×
137
                    if (activeHwnd != 0)
×
138
                    {
×
139
                        onTeardown?.Invoke(activeHwnd);
×
140
                        activeHwnd = 0;
×
141
                    }
×
142
                }
×
143
                handleSubscription.Dispose();
×
144
                messageSubscription.Dispose();
×
145
            });
×
146
        });
×
147
    }
148

149
    /// <summary>
150
    /// Creates an observable sequence that emits Windows messages for a message-only window with the specified class name.
151
    /// </summary>
152
    /// <remarks>The window is created on a dedicated background thread with a single-threaded apartment
153
    /// state. Disposal of the returned observable triggers destruction of the window and completion of the sequence.
154
    /// This method is useful for scenarios requiring low-level message processing without a visible UI window.</remarks>
155
    /// <returns>An observable sequence of Windows messages received by the created message-only window. The sequence completes when the window is destroyed.</returns>
156
    private static IObservable<WindowMessage> CreateBaseStream()
157
    {
158
        return Observable.Create<WindowMessage>(observer =>
1✔
159
        {
1✔
160
            nint hwnd = 0;
1✔
161
            string className = "MsgLoop_" + Guid.NewGuid().ToString("N");
1✔
162
            WndProc wndProcDelegate = null;
1✔
163

1✔
164
            var thread = new Thread(() =>
1✔
165
            {
1✔
166
                wndProcDelegate = (hWnd, msg, wParam, lParam) =>
1✔
167
                {
1✔
168
                    if (msg == WindowsMessages.WM_DESTROY)
14!
169
                    {
1✔
170
                        PostQuitMessage(0);
×
171
                        return 0;
×
172
                    }
1✔
173

1✔
174
                    var windowMessage = new WindowMessage(hWnd, msg, wParam, lParam);
14✔
175
                    observer.OnNext(windowMessage);
14✔
176
                    // Check if any subscriber claimed the message
1✔
177
                    if (windowMessage.Handled)
14!
178
                    {
1✔
179
                        return (nuint)windowMessage.Result; // Return the custom value defined by the listener
×
180
                    }
1✔
181
                    return DefWindowProc(hWnd, msg, wParam, lParam);
14✔
182
                };
1✔
183

1✔
184
                // Register Class
1✔
185
                var wndClass = new WNDCLASSEX
1✔
186
                {
1✔
187
                    cbSize = (uint)Marshal.SizeOf<WNDCLASSEX>(),
1✔
188
                    lpfnWndProc = wndProcDelegate,
1✔
189
                    hInstance = GetModuleHandle(null),
1✔
190
                    lpszClassName = className
1✔
191
                };
1✔
192

1✔
193
                RegisterClassEx(ref wndClass);
1✔
194

1✔
195
                hwnd = CreateWindowEx(0, className, "MsgWnd", 0, 0, 0, 0, 0, 0, 0, wndClass.hInstance, 0);
1✔
196

1✔
197
                // Trigger External Registrations
1✔
198
                if (hwnd != 0)
1✔
199
                {
1✔
200
                    _handleSubject.OnNext(hwnd);
1✔
201
                }
1✔
202

1✔
203
                while (GetMessage(out var msg, 0, 0, 0))
2!
204
                {
1✔
205
                    TranslateMessage(ref msg);
1✔
206
                    DispatchMessage(ref msg);
1✔
207
                }
1✔
208

1✔
209
                // Trigger External Cleanup
1✔
210
                if (hwnd != 0)
×
211
                {
1✔
212
                    _handleSubject.OnNext(0);
×
213
                }
1✔
214
                
1✔
215
                UnregisterClass(className, wndClass.hInstance);
×
216
            });
1✔
217

1✔
218
            thread.SetApartmentState(ApartmentState.STA);
1✔
219
            thread.IsBackground = true;
1✔
220
            thread.Start();
1✔
221

1✔
222
            return Disposable.Create(() =>
1✔
223
            {
1✔
224
                if (hwnd != 0)
×
225
                {
1✔
226
                    PostMessage(hwnd, (uint)WindowsMessages.WM_DESTROY, 0, 0);
×
227
                }
1✔
228
            });
1✔
229
        });
1✔
230
    }
231
}
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