Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to support writing to new Notepad on Windows 11 #60

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions sample/ConsoleDemo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
//
#endregion

using Serilog;
using System;
using System.Threading;
using Serilog;

namespace ConsoleDemo
{
Expand All @@ -33,15 +33,23 @@ private static void Main(string[] args)

try
{
Console.WriteLine("Open a `notepad.exe` instance and press <enter> to continue...");
Console.ReadLine();
//Console.WriteLine("Open a `notepad.exe` instance and press <enter> 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 });

Expand Down
130 changes: 104 additions & 26 deletions src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
bstordrup marked this conversation as resolved.
Show resolved Hide resolved

// 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)
Expand Down Expand Up @@ -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);
}

Expand All @@ -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<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}

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<IntPtr> result = new List<IntPtr>(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();
}
}
}
12 changes: 12 additions & 0 deletions src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}
}