diff --git a/Notepad.Extensions.Logging/NativeMethods.cs b/Notepad.Extensions.Logging/NativeMethods.cs
new file mode 100644
index 0000000..873b5de
--- /dev/null
+++ b/Notepad.Extensions.Logging/NativeMethods.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Notepad.Extensions.Logging
+{
+ static class NativeMethods
+ {
+ [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
+
+ [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow);
+
+ public const int EM_REPLACESEL = 0x00C2;
+
+ [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
+
+ public const int SCI_ADDTEXT = 2001;
+
+ [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
+
+ public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ public static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lParam);
+
+ [DllImport("User32.dll")]
+ public static extern int GetWindowText(IntPtr hWndParent, StringBuilder sb, int maxCount);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, int flAllocationType, int flProtect);
+
+ public const int MEM_COMMIT = 0x00001000;
+ public const int MEM_RESERVE = 0x00002000;
+ public const int MEM_RELEASE = 0x8000;
+ public const int PAGE_READWRITE = 0x04;
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, int dwFreeType);
+
+ [DllImport("User32.dll")]
+ public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern int WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesWritten);
+ }
+}
diff --git a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj
index b7eefed..427e3ed 100644
--- a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj
+++ b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj
@@ -10,6 +10,8 @@
git
Apache-2.0
true
+ true
+ 1.0.1
diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs
index 228046b..bf17382 100644
--- a/Notepad.Extensions.Logging/NotepadLogger.cs
+++ b/Notepad.Extensions.Logging/NotepadLogger.cs
@@ -1,9 +1,14 @@
using System;
+using System.Buffers;
+using System.ComponentModel;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
+using static Notepad.Extensions.Logging.NativeMethods;
-namespace Microsoft.Extensions.Logging
+namespace Notepad.Extensions.Logging
{
class NotepadLogger : ILogger
{
@@ -88,41 +93,58 @@ namespace Microsoft.Extensions.Logging
void WriteToNotepad(string message)
{
- IntPtr hwnd = FindNotepadWindow();
+ var (kind, hwnd) = WindowFinder.FindNotepadWindow();
+ switch (kind)
+ {
+ case WindowKind.Notepad:
+ SendMessage(hwnd, EM_REPLACESEL, (IntPtr)1, message);
+ break;
- if (hwnd.Equals(IntPtr.Zero))
+ case WindowKind.NotepadPlusPlus:
+ {
+ WriteToNotepadPlusPlus(hwnd, message);
+ break;
+ }
+ }
+ }
+
+ unsafe static void WriteToNotepadPlusPlus(IntPtr hwnd, string message)
+ {
+ var dataLength = Encoding.UTF8.GetByteCount(message);
+
+ //
+ // HERE BE DRAGONS
+ // We need to copy the message into Notepad++'s memory so that it can read it.
+ // Look away now, before its too late.
+ //
+
+ /* unused thread ID */ _ = GetWindowThreadProcessId(hwnd, out var remoteProcessId);
+ using var remoteProcess = Process.GetProcessById(remoteProcessId);
+ var mem = VirtualAllocEx(remoteProcess.Handle, IntPtr.Zero, (IntPtr)dataLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
+ if (mem == IntPtr.Zero)
{
return;
}
- IntPtr edit = NativeMethods.FindWindowEx(hwnd, IntPtr.Zero, "EDIT", null);
- NativeMethods.SendMessage(edit, NativeMethods.EM_REPLACESEL, (IntPtr)1, message);
- }
-
- IntPtr FindNotepadWindow()
- {
- IntPtr hwnd;
-
- hwnd = NativeMethods.FindWindow(null, windowName);
- if (hwnd.Equals(IntPtr.Zero))
+ try
{
- hwnd = NativeMethods.FindWindow(null, changedWindowName);
+ var data = ArrayPool.Shared.Rent(dataLength);
+ try
+ {
+ var idx = Encoding.UTF8.GetBytes(message, 0, message.Length, data, 0);
+
+ WriteProcessMemory(remoteProcess.Handle, mem, data, (IntPtr)dataLength, out var bytesWritten);
+ SendMessage(hwnd, SCI_ADDTEXT, (IntPtr)dataLength, mem);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(data);
+ }
+ }
+ finally
+ {
+ VirtualFreeEx(remoteProcess.Handle, IntPtr.Zero, IntPtr.Zero, MEM_RELEASE);
}
- return hwnd;
}
}
-
- static class NativeMethods
- {
- [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
-
- [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow);
-
- public const int EM_REPLACESEL = 0x00C2;
-
- [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
- }
}
diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs
index f291622..4c390df 100644
--- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs
+++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs
@@ -1,9 +1,10 @@
using System;
using System.Text;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
-namespace Microsoft.Extensions.Logging
+namespace Notepad.Extensions.Logging
{
[ProviderAlias("Notepad")]
class NotepadLoggerProvider : ILoggerProvider
diff --git a/Notepad.Extensions.Logging/NullDisposable.cs b/Notepad.Extensions.Logging/NullDisposable.cs
index 4e3e782..a8b7db3 100644
--- a/Notepad.Extensions.Logging/NullDisposable.cs
+++ b/Notepad.Extensions.Logging/NullDisposable.cs
@@ -1,6 +1,6 @@
using System;
-namespace Microsoft.Extensions.Logging
+namespace Notepad.Extensions.Logging
{
class NullDisposable : IDisposable
{
diff --git a/Notepad.Extensions.Logging/WindowFinder.cs b/Notepad.Extensions.Logging/WindowFinder.cs
new file mode 100644
index 0000000..b716ec9
--- /dev/null
+++ b/Notepad.Extensions.Logging/WindowFinder.cs
@@ -0,0 +1,89 @@
+using System;
+using System.ComponentModel;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Notepad.Extensions.Logging
+{
+ static class WindowFinder
+ {
+ public static (WindowKind kind, IntPtr hwnd) FindNotepadWindow()
+ {
+ sb ??= new StringBuilder(4096);
+
+ try
+ {
+ FindMainWindow();
+ return (windowKind, handle);
+ }
+ finally
+ {
+ handle = IntPtr.Zero;
+ sb.Clear();
+ windowKind = WindowKind.Invalid;
+ }
+ }
+
+ static IntPtr FindMainWindow()
+ {
+ NativeMethods.EnumWindows(enumWindowsDelegate, IntPtr.Zero);
+ return handle;
+ }
+
+ static NativeMethods.EnumWindowsDelegate enumWindowsDelegate = new NativeMethods.EnumWindowsDelegate(EnumWindowsCallback);
+
+ static bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
+ {
+ var result = NativeMethods.GetWindowText(hWnd, sb, sb.Capacity);
+ if (result < 0)
+ {
+ throw new Win32Exception(result);
+ }
+
+ WindowFinder.handle = hWnd;
+
+ if (sb.Length > 0 && sb[0] == '*')
+ {
+ // Notepad and Notepad++ both mark dirty documents by adding a leading asterisk to the window name.
+ sb.Remove(0, 1);
+ }
+
+ if (IsKnownNotepadWindow(sb.ToString()))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ [ThreadStatic]
+ static IntPtr handle;
+
+ [ThreadStatic]
+ static WindowKind windowKind;
+
+ [ThreadStatic]
+ static StringBuilder sb;
+
+ static Regex notepadPlusPlusRegex = new Regex(@"^new \d+ - Notepad\+\+$", RegexOptions.Compiled);
+
+ static bool IsKnownNotepadWindow(string titleText)
+ {
+ switch (titleText)
+ {
+ case "Untitled - Notepad":
+ windowKind = WindowKind.Notepad;
+ handle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "EDIT", null);
+ return true;
+ }
+
+ if (notepadPlusPlusRegex.IsMatch(titleText))
+ {
+ windowKind = WindowKind.NotepadPlusPlus;
+ handle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "Scintilla", null);
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Notepad.Extensions.Logging/WindowKind.cs b/Notepad.Extensions.Logging/WindowKind.cs
new file mode 100644
index 0000000..d79e383
--- /dev/null
+++ b/Notepad.Extensions.Logging/WindowKind.cs
@@ -0,0 +1,9 @@
+namespace Notepad.Extensions.Logging
+{
+ public enum WindowKind
+ {
+ Invalid,
+ Notepad,
+ NotepadPlusPlus
+ }
+}