From 1728be2d63b4cff8becdfa155c269f751737278c Mon Sep 17 00:00:00 2001 From: Benny Tordrup Date: Thu, 11 May 2023 16:14:48 +0200 Subject: [PATCH 1/4] Implementing an alternate way of finding editor handle for new Notepad * Adding needed interop definitions to enumerate child windows * If first attempt using FindWindowEx with notepad process MainWindowHandle does not find editor handle, the search is done by enumerating child window handles for the notepad process MainWindowHandle. --- .../Notepad/Interop/NotepadTextWriter.cs | 46 ++++++++++++++++++- .../Sinks/Notepad/Interop/User32.cs | 11 +++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs index 651d260..5618712 100644 --- a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs +++ b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs @@ -14,12 +14,14 @@ // #endregion +using Serilog.Debugging; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; -using Serilog.Debugging; namespace Serilog.Sinks.Notepad.Interop { @@ -120,6 +122,13 @@ private static IntPtr FindNotepadEditorHandle(IntPtr notepadWindowHandle) return richEditHandle; } + // Issue #59 - Alternate way of finding the RichEditD2DPT class: + if (FindEditorHandleThroughChildWindows(notepadWindowHandle) is var childRichEditHandle + && childRichEditHandle != IntPtr.Zero) + { + return childRichEditHandle; + } + return User32.FindWindowEx(notepadWindowHandle, IntPtr.Zero, "Edit", null); } @@ -130,5 +139,40 @@ private void EnsureNotDisposed() throw new ObjectDisposedException(GetType().Name); } } + + private static bool EnumWindow(IntPtr handle, IntPtr pointer) + { + GCHandle gch = GCHandle.FromIntPtr(pointer); + List list = gch.Target as List; + if (list == null) + { + throw new InvalidCastException("GCHandle Target could not be cast as List"); + } + + // We only want windows of class RichEditD2DPT. + if (User32.FindWindowEx(handle, IntPtr.Zero, "RichEditD2DPT", null) != IntPtr.Zero) + { + list.Add(handle); + } + + return true; + } + + private static IntPtr FindEditorHandleThroughChildWindows(IntPtr notepadWindowHandle) + { + List result = new List(); + GCHandle listHandle = GCHandle.Alloc(result); + try + { + User32.Win32Callback childProc = new User32.Win32Callback(EnumWindow); + User32.EnumChildWindows(notepadWindowHandle, childProc, GCHandle.ToIntPtr(listHandle)); + } + finally + { + if (listHandle.IsAllocated) + listHandle.Free(); + } + return result.FirstOrDefault(); + } } } diff --git a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs index f417510..5ab2be0 100644 --- a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs +++ b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs @@ -35,5 +35,16 @@ internal class User32 [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam); + + + [DllImport("user32.dll")] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + // Needed for EnumChildWindows for registering a call back function. + public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam); + + [DllImport("user32.Dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumChildWindows(IntPtr parentHandle, Win32Callback callback, IntPtr lParam); } } From 65f53998dbfdea2c8f345a28f5ce618739532119 Mon Sep 17 00:00:00 2001 From: Benny Tordrup Date: Fri, 12 May 2023 14:17:20 +0200 Subject: [PATCH 2/4] Changes to handle changes in Notepad that allows multiple documents. --- .../Notepad/Interop/NotepadTextWriter.cs | 21 +++++++++++++------ .../Sinks/Notepad/Interop/User32.cs | 9 ++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs index 5618712..41cb29c 100644 --- a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs +++ b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs @@ -123,10 +123,10 @@ private static IntPtr FindNotepadEditorHandle(IntPtr notepadWindowHandle) } // Issue #59 - Alternate way of finding the RichEditD2DPT class: - if (FindEditorHandleThroughChildWindows(notepadWindowHandle) is var childRichEditHandle - && childRichEditHandle != IntPtr.Zero) + if (FindEditorHandleThroughChildWindows(notepadWindowHandle) is var richEditHandleFromChildren + && richEditHandleFromChildren != IntPtr.Zero) { - return childRichEditHandle; + return richEditHandleFromChildren; } return User32.FindWindowEx(notepadWindowHandle, IntPtr.Zero, "Edit", null); @@ -140,6 +140,13 @@ private void EnsureNotDisposed() } } + private static string GetClassNameFromWindow(IntPtr handle) + { + StringBuilder sb = new StringBuilder(256); + var ret = User32.GetClassName(handle, sb, sb.Capacity); + return ret != 0 ? sb.ToString() : string.Empty; + } + private static bool EnumWindow(IntPtr handle, IntPtr pointer) { GCHandle gch = GCHandle.FromIntPtr(pointer); @@ -149,10 +156,12 @@ private static bool EnumWindow(IntPtr handle, IntPtr pointer) throw new InvalidCastException("GCHandle Target could not be cast as List"); } - // We only want windows of class RichEditD2DPT. - if (User32.FindWindowEx(handle, IntPtr.Zero, "RichEditD2DPT", null) != IntPtr.Zero) + if (string.Equals(GetClassNameFromWindow(handle), "RichEditD2DPT", StringComparison.OrdinalIgnoreCase)) { list.Add(handle); + + // Stop enumerating - we found the one. + return false; } return true; @@ -160,7 +169,7 @@ private static bool EnumWindow(IntPtr handle, IntPtr pointer) private static IntPtr FindEditorHandleThroughChildWindows(IntPtr notepadWindowHandle) { - List result = new List(); + List result = new List(1); GCHandle listHandle = GCHandle.Alloc(result); try { diff --git a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs index 5ab2be0..ad84f1a 100644 --- a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs +++ b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs @@ -37,14 +37,15 @@ internal class User32 public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam); - [DllImport("user32.dll")] - public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - // Needed for EnumChildWindows for registering a call back function. public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam); - [DllImport("user32.Dll")] + [DllImport("user32.Dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool EnumChildWindows(IntPtr parentHandle, Win32Callback callback, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount); + } } From 0084257f6ae46a5d43b44340c3de936df43b96d8 Mon Sep 17 00:00:00 2001 From: Benny Tordrup Date: Fri, 12 May 2023 15:33:51 +0200 Subject: [PATCH 3/4] In some situations, a notepad window opened after logging start would not receive log messages. --- sample/ConsoleDemo/Program.cs | 18 +++++++++++++----- .../Sinks/Notepad/Interop/NotepadTextWriter.cs | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/sample/ConsoleDemo/Program.cs b/sample/ConsoleDemo/Program.cs index 42102d7..3c0b2a9 100644 --- a/sample/ConsoleDemo/Program.cs +++ b/sample/ConsoleDemo/Program.cs @@ -14,9 +14,9 @@ // #endregion +using Serilog; using System; using System.Threading; -using Serilog; namespace ConsoleDemo { @@ -33,15 +33,23 @@ private static void Main(string[] args) try { - Console.WriteLine("Open a `notepad.exe` instance and press to continue..."); - Console.ReadLine(); + //Console.WriteLine("Open a `notepad.exe` instance and press to continue..."); + //Console.ReadLine(); Console.WriteLine("Writing messages to the most recent Notepad you opened..."); Log.Debug("Getting started"); - Log.Information("Hello {Name} from thread {ThreadId}", Environment.GetEnvironmentVariable("USERNAME"), - Thread.CurrentThread.ManagedThreadId); + var startTime = DateTime.Now; + + while (DateTime.Now - startTime < TimeSpan.FromMinutes(1)) + { + + Log.Information("Hello {Name} from thread {ThreadId}", Environment.GetEnvironmentVariable("USERNAME"), + Thread.CurrentThread.ManagedThreadId); + + Thread.Sleep(1000); + } Log.Warning("No coins remain at position {@Position}", new { Lat = 25, Long = 134 }); diff --git a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs index 41cb29c..3e812c2 100644 --- a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs +++ b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs @@ -57,7 +57,10 @@ public override void Flush() // No instances of Notepad found... Nothing to do return; } + } + if (_currentNotepadEditorHandle == IntPtr.Zero) + { var notepadWindowHandle = currentNotepadProcess.MainWindowHandle; var notepadEditorHandle = FindNotepadEditorHandle(notepadWindowHandle); From ab9ac54dac0b74069dc1374051f39d49a8ab6d76 Mon Sep 17 00:00:00 2001 From: Benny Tordrup Date: Mon, 15 May 2023 13:50:45 +0100 Subject: [PATCH 4/4] Sometimes, the editor handle seems not correct. So if the text length does not change by sending text to it, it is cleared in order to reget it. --- .../Sinks/Notepad/Interop/NotepadTextWriter.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs index 3e812c2..54af0d0 100644 --- a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs +++ b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs @@ -85,7 +85,16 @@ public override void Flush() // Write the log message to Notepad User32.SendMessage(_currentNotepadEditorHandle, User32.EM_REPLACESEL, (IntPtr)1, message); - buffer.Clear(); + // Get how many characters are in the Notepad editor after putting in new text + var textLengthAfter = User32.SendMessage(_currentNotepadEditorHandle, User32.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero); + + // If no change in textLength, reset editor handle to try to find it again. + if (textLengthAfter == textLength) + _currentNotepadEditorHandle = IntPtr.Zero; + + // Otherwise, we clear the buffer + else + buffer.Clear(); } protected override void Dispose(bool disposing)