diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d46bd92..39a62ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,31 +33,31 @@ jobs: runs-on: ${{ matrix.job.os }} steps: - name: Setup netcoreapp3.1 - uses: actions/setup-dotnet@v3.2.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: "3.1.425" - name: Setup net5.0 - uses: actions/setup-dotnet@v3.2.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: "5.0.408" - name: Setup net6.0 - uses: actions/setup-dotnet@v3.2.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: "6.0.403" - name: Setup net7.0 - uses: actions/setup-dotnet@v3.2.0 + uses: actions/setup-dotnet@v4.0.1 with: dotnet-version: "7.0.100" - name: Run dotnet --info run: dotnet --info - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@v4.1.7 with: fetch-depth: 0 - name: Build run: ${{ matrix.job.build }} --verbosity=diagnostic --target=pack - name: Publish artifacts if: matrix.job.push && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')) - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@v4.3.6 with: if-no-files-found: warn name: package 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 651d260..87b5972 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 { @@ -42,45 +44,70 @@ public override void Flush() base.Flush(); - var currentNotepadProcess = _currentNotepadProcess; - var targetNotepadProcess = _notepadProcessFinderFunc(); + var attempts = 0; + var succeeded = false; + StringBuilder buffer = null; - if (currentNotepadProcess is null || targetNotepadProcess is null || currentNotepadProcess.Id != targetNotepadProcess.Id) + do { - _currentNotepadProcess = currentNotepadProcess = targetNotepadProcess; - _currentNotepadEditorHandle = IntPtr.Zero; + var currentNotepadProcess = _currentNotepadProcess; + var targetNotepadProcess = _notepadProcessFinderFunc(); - if (currentNotepadProcess is null || currentNotepadProcess.HasExited) + if (currentNotepadProcess is null || targetNotepadProcess is null || currentNotepadProcess.Id != targetNotepadProcess.Id) { - // No instances of Notepad found... Nothing to do - return; + _currentNotepadProcess = currentNotepadProcess = targetNotepadProcess; + _currentNotepadEditorHandle = IntPtr.Zero; + + if (currentNotepadProcess is null || currentNotepadProcess.HasExited) + { + // No instances of Notepad found... Nothing to do + return; + } } - var notepadWindowHandle = currentNotepadProcess.MainWindowHandle; - - var notepadEditorHandle = FindNotepadEditorHandle(notepadWindowHandle); - if (notepadEditorHandle == IntPtr.Zero) + if (_currentNotepadEditorHandle == IntPtr.Zero) { - SelfLog.WriteLine($"Unable to access a Notepad Editor on process {currentNotepadProcess.ProcessName} ({currentNotepadProcess.Id})"); - return; + var notepadWindowHandle = currentNotepadProcess.MainWindowHandle; + + var notepadEditorHandle = FindNotepadEditorHandle(notepadWindowHandle); + if (notepadEditorHandle == IntPtr.Zero) + { + SelfLog.WriteLine($"Unable to access a Notepad Editor on process {currentNotepadProcess.ProcessName} ({currentNotepadProcess.Id})"); + return; + } + + _currentNotepadEditorHandle = notepadEditorHandle; } - _currentNotepadEditorHandle = notepadEditorHandle; - } + // Get how many characters are in the Notepad editor already + var textLength = User32.SendMessage(_currentNotepadEditorHandle, User32.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero); + + // Set the caret position to the end of the text + User32.SendMessage(_currentNotepadEditorHandle, User32.EM_SETSEL, (IntPtr)textLength, (IntPtr)textLength); + + buffer = base.GetStringBuilder(); + var message = buffer.ToString(); - // Get how many characters are in the Notepad editor already - var textLength = User32.SendMessage(_currentNotepadEditorHandle, User32.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero); + // Write the log message to Notepad + User32.SendMessage(_currentNotepadEditorHandle, User32.EM_REPLACESEL, (IntPtr)1, message); - // Set the caret position to the end of the text - User32.SendMessage(_currentNotepadEditorHandle, User32.EM_SETSEL, (IntPtr)textLength, (IntPtr)textLength); + // 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); - var buffer = base.GetStringBuilder(); - var message = buffer.ToString(); + // Determine if the write succeeded. This will break the loop. + succeeded = textLengthAfter > textLength; - // Write the log message to Notepad - User32.SendMessage(_currentNotepadEditorHandle, User32.EM_REPLACESEL, (IntPtr)1, message); + // If no change in the text length, reset editor handle to try to find it again. + if (!succeeded) + { + _currentNotepadEditorHandle = IntPtr.Zero; + attempts++; + } + } + while (!succeeded && attempts < 3); - buffer.Clear(); + if (buffer != null) + buffer.Clear(); } protected override void Dispose(bool disposing) @@ -120,6 +147,13 @@ private static IntPtr FindNotepadEditorHandle(IntPtr notepadWindowHandle) return richEditHandle; } + // Issue #59 - Alternate way of finding the RichEditD2DPT class: + if (FindEditorHandleThroughChildWindows(notepadWindowHandle) is var richEditHandleFromChildren + && richEditHandleFromChildren != IntPtr.Zero) + { + return richEditHandleFromChildren; + } + return User32.FindWindowEx(notepadWindowHandle, IntPtr.Zero, "Edit", null); } @@ -130,5 +164,49 @@ private void EnsureNotDisposed() throw new ObjectDisposedException(GetType().Name); } } + + 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); + List list = gch.Target as List; + if (list == null) + { + throw new InvalidCastException("GCHandle Target could not be cast as List"); + } + + if (string.Equals(GetClassNameFromWindow(handle), "RichEditD2DPT", StringComparison.OrdinalIgnoreCase)) + { + list.Add(handle); + + // Stop enumerating - we found the one. + return false; + } + + return true; + } + + private static IntPtr FindEditorHandleThroughChildWindows(IntPtr notepadWindowHandle) + { + List result = new List(1); + 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..ad84f1a 100644 --- a/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs +++ b/src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs @@ -35,5 +35,17 @@ 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); + + + // Needed for EnumChildWindows for registering a call back function. + public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam); + + [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); + } }