#nullable enable
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
public static class Todo
{ [Conditional("DEBUG")] public static void TODO(
string message,
[CallerFilePath] string? file = null,
[CallerLineNumber] int line = 0,
[CallerMemberName] string? member = null)
{ TodoImpl.Log(message, file, line, member);
}
}
internal static class TodoImpl
{ private static readonly ConcurrentDictionary<string, bool> _seen = new();
private static readonly ConcurrentQueue<TodoEntry> _pending = new();
private static volatile IntPtr _hwnd = IntPtr.Zero;
private static Thread? _uiThread;
private static readonly object _uiLock = new();
internal static void Log(string message, string? file, int line, string? member)
{#if !DEBUG
return;
#else
try
{ message = message ?? string.Empty;
if (message.Length > 4096) message = message.Substring(0, 4096) + " …";
file ??= "unknown";
member ??= "unknown";
if (line < 0) line = 0;
if (!TryMarkSeen(message, file, line, member))
return;
var entry = TodoEntry.Create(message, file, line, member);
if (IsWindowsDesktop())
{ EnsureWindowSafe();
if (_hwnd != IntPtr.Zero)
{ _pending.Enqueue(entry);
PostMessage(_hwnd, WM_TODO_APPEND, IntPtr.Zero, IntPtr.Zero);
return;
}
}
Console.WriteLine(entry.ToConsoleLine());
}
catch (Exception ex)
{ SafeReportInternalError("Log", ex); }
#endif
}
private static bool TryMarkSeen(string message, string file, int line, string member)
{ try
{ string key = $"{file}|{line}|{member}|{message}"; return _seen.TryAdd(key, true);
}
catch (Exception ex)
{ SafeReportInternalError("TryMarkSeen", ex); return true;
}
}
private static bool IsWindowsDesktop()
{ try { return OperatingSystem.IsWindows() && Environment.UserInteractive; } catch { return false; } }
private static void EnsureWindowSafe()
{ try
{ if (_hwnd != IntPtr.Zero) return;
lock (_uiLock)
{ if (_hwnd != IntPtr.Zero) return;
if (_uiThread != null) return;
_uiThread = new Thread(TodoWindowThread)
{ IsBackground = true,
Name = "TodoStickyNoteUI"
};
_uiThread.SetApartmentState(ApartmentState.STA);
_uiThread.Start();
var sw = Stopwatch.StartNew();
while (_hwnd == IntPtr.Zero && sw.ElapsedMilliseconds < 750) Thread.Sleep(10);
}
}
catch (Exception ex)
{ SafeReportInternalError("EnsureWindowSafe", ex); }
}
private static IntPtr _yellowBrush = IntPtr.Zero;
private static IntPtr _hwndEdit = IntPtr.Zero;
private static IntPtr _hwndCloseBtn = IntPtr.Zero;
private static WndProc? _wndProcKeepAlive;
private static void TodoWindowThread()
{ try
{ _yellowBrush = CreateSolidBrush(RGB(255, 255, 153));
_wndProcKeepAlive = WndProcImpl;
var cls = new WNDCLASSEX
{ cbSize = (uint)Marshal.SizeOf<WNDCLASSEX>(),
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcKeepAlive),
hbrBackground = _yellowBrush,
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)IDC_ARROW),
lpszClassName = "TodoStickyNoteClass",
lpszMenuName = null,
hInstance = GetModuleHandle(null)
};
if (RegisterClassEx(ref cls) == 0)
{ SafeWriteLine("[TODO] Failed to register window class."); return;
}
int width = 380, height = 260, margin = 24;
int screenW = GetSystemMetrics(SM_CXSCREEN);
int x = Math.Max(0, screenW - width - margin);
int y = Math.Max(0, margin);
IntPtr hwnd = CreateWindowEx(
WS_EX_TOOLWINDOW,
"TodoStickyNoteClass",
"TODO",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE,
x, y, width, height,
IntPtr.Zero, IntPtr.Zero, cls.hInstance, IntPtr.Zero);
if (hwnd == IntPtr.Zero)
{ SafeWriteLine("[TODO] Failed to create TODO window."); return;
}
_hwnd = hwnd;
int buttonId = 1;
_hwndCloseBtn = CreateWindowEx(
0, "BUTTON", "X", WS_CHILD | WS_VISIBLE,
width - 36, 8, 24, 24, hwnd, (IntPtr)buttonId, cls.hInstance, IntPtr.Zero);
_hwndEdit = CreateWindowEx(
0, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL | WS_VSCROLL,
12, 44, width - 24, height - 56,
hwnd, IntPtr.Zero, cls.hInstance, IntPtr.Zero);
MSG msg;
while (GetMessage(out msg, IntPtr.Zero, 0, 0) > 0)
{ TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
}
catch (Exception ex)
{ SafeReportInternalError("TodoWindowThread", ex); }
finally
{ try { if (_yellowBrush != IntPtr.Zero) { DeleteObject(_yellowBrush); _yellowBrush = IntPtr.Zero; } } catch { } _hwnd = IntPtr.Zero;
_hwndEdit = IntPtr.Zero;
_hwndCloseBtn = IntPtr.Zero;
_uiThread = null;
}
}
private static IntPtr WndProcImpl(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{ try
{ switch (msg)
{ case WM_CREATE:
return IntPtr.Zero;
case WM_CTLCOLORSTATIC:
case WM_CTLCOLOREDIT:
return _yellowBrush;
case WM_SIZE:
{ int width = LOWORD((uint)lParam);
int height = HIWORD((uint)lParam);
if (_hwndEdit != IntPtr.Zero) MoveWindow(_hwndEdit, 12, 44, Math.Max(50, width - 24), Math.Max(32, height - 56), true);
if (_hwndCloseBtn != IntPtr.Zero) MoveWindow(_hwndCloseBtn, Math.Max(8, width - 36), 8, 24, 24, true);
return IntPtr.Zero;
}
case WM_COMMAND:
{ int code = HIWORD((uint)wParam);
int id = LOWORD((uint)wParam);
if (id == 1 && code == BN_CLICKED)
{ PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
return IntPtr.Zero;
}
break;
}
case WM_NCHITTEST:
{ var hit = DefWindowProc(hWnd, msg, wParam, lParam);
if (hit == (IntPtr)HTCLIENT)
{ if (_hwndEdit == IntPtr.Zero || _hwndCloseBtn == IntPtr.Zero) return (IntPtr)HTCAPTION;
POINT pt = new POINT { X = GET_X_LPARAM(lParam), Y = GET_Y_LPARAM(lParam) }; ScreenToClient(hWnd, ref pt);
if (!TryGetClientRect(hWnd, _hwndCloseBtn, out var rBtn) ||
!TryGetClientRect(hWnd, _hwndEdit, out var rEdit))
return (IntPtr)HTCAPTION;
if (!PtInRect(ref rBtn, pt) && !PtInRect(ref rEdit, pt))
return (IntPtr)HTCAPTION;
}
return hit;
}
case WM_TODO_APPEND:
{ DrainPendingToEdit();
return IntPtr.Zero;
}
case WM_CLOSE:
DestroyWindow(hWnd);
return IntPtr.Zero;
case WM_DESTROY:
PostQuitMessage(0);
return IntPtr.Zero;
}
}
catch (Exception ex)
{ SafeReportInternalError("WndProcImpl", ex); }
return DefWindowProc(hWnd, msg, wParam, lParam);
}
private static void DrainPendingToEdit()
{ try
{ if (_hwndEdit == IntPtr.Zero) return;
var sb = new StringBuilder();
while (_pending.TryDequeue(out var entry))
sb.Append(entry.ToDisplayBlock());
if (sb.Length == 0) return;
int len = (int)SendMessage(_hwndEdit, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
SendMessage(_hwndEdit, EM_SETSEL, (IntPtr)len, (IntPtr)len);
SendMessageString(_hwndEdit, EM_REPLACESEL, IntPtr.Zero, sb.ToString());
const int MaxChars = 256 * 1024;
len = (int)SendMessage(_hwndEdit, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (len > MaxChars)
{ int target = (int)(MaxChars * 0.75);
SendMessage(_hwndEdit, EM_SETSEL, IntPtr.Zero, (IntPtr)(len - target));
SendMessageString(_hwndEdit, EM_REPLACESEL, (IntPtr)1, "");
}
}
catch (Exception ex)
{ SafeReportInternalError("DrainPendingToEdit", ex); }
}
private readonly struct TodoEntry
{ public readonly string TimeIso;
public readonly int Pid;
public readonly int Tid;
public readonly string ProcessName;
public readonly string FileName;
public readonly int Line;
public readonly string Member;
public readonly string Message;
private TodoEntry(string timeIso, int pid, int tid, string proc, string fileName, int line, string member, string message)
{ TimeIso = timeIso;
Pid = pid;
Tid = tid;
ProcessName = proc;
FileName = fileName;
Line = line;
Member = member;
Message = message;
}
public static TodoEntry Create(string message, string file, int line, string member)
{ string timeIso = DateTimeOffset.Now.ToString("O"); int pid = GetSafe(() => Environment.ProcessId, 0);
int tid = GetSafe(() => Environment.CurrentManagedThreadId, 0);
string proc = GetSafe(() => AppDomain.CurrentDomain.FriendlyName, "process");
string fileName = GetSafe(() => Path.GetFileName(file), "unknown");
return new TodoEntry(timeIso, pid, tid, proc, fileName, line, member, message);
}
public string ToConsoleLine()
=> $"[TODO] {TimeIso} pid={Pid} tid={Tid} {ProcessName} at {FileName}:{Line} in {Member} - {Message}";
public string ToDisplayBlock()
=> $"{TimeIso} pid={Pid} tid={Tid}\r\n{ProcessName} at {FileName}:{Line} in {Member}\r\n- {Message}\r\n\r\n";
private static T GetSafe<T>(Func<T> f, T fallback)
{ try { return f(); } catch { return fallback; } }
}
private static void SafeReportInternalError(string where, Exception ex)
{ try { SafeWriteLine($"[TODO internal error] {where}: {ex.GetType().Name}: {ex.Message}"); } catch { } }
private static void SafeWriteLine(string text)
{ try { Console.WriteLine(text); } catch { } }
private static bool TryGetClientRect(IntPtr hwndParent, IntPtr hwndChild, out RECT clientRect)
{ clientRect = default;
if (hwndChild == IntPtr.Zero) return false;
if (!GetWindowRect(hwndChild, out var rScreen)) return false;
clientRect = ScreenRectToClient(hwndParent, rScreen);
return true;
}
private const int SM_CXSCREEN = 0;
private const int WS_OVERLAPPED = 0x00000000;
private const int WS_CAPTION = 0x00C00000;
private const int WS_SYSMENU = 0x00080000;
private const int WS_MINIMIZEBOX = 0x00020000;
private const int WS_VISIBLE = 0x10000000;
private const int WS_CHILD = 0x40000000;
private const int WS_VSCROLL = 0x00200000;
private const int ES_MULTILINE = 0x0004;
private const int ES_READONLY = 0x0800;
private const int ES_AUTOVSCROLL = 0x0040;
private const int WS_EX_TOOLWINDOW = 0x00000080;
private const int WM_CREATE = 0x0001;
private const int WM_SIZE = 0x0005;
private const int WM_COMMAND = 0x0111;
private const int WM_CLOSE = 0x0010;
private const int WM_DESTROY = 0x0002;
private const int WM_CTLCOLORSTATIC = 0x0138;
private const int WM_CTLCOLOREDIT = 0x0133;
private const int WM_NCHITTEST = 0x0084;
private const int WM_GETTEXTLENGTH = 0x000E;
private const int WM_APP = 0x8000;
private const int WM_TODO_APPEND = WM_APP + 1;
private const int EM_SETSEL = 0x00B1;
private const int EM_REPLACESEL = 0x00C2;
private const int BN_CLICKED = 0;
private const int HTCLIENT = 1;
private const int HTCAPTION = 2;
private const int IDC_ARROW = 32512;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct WNDCLASSEX
{ public uint cbSize;
public uint style;
public IntPtr lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public string? lpszMenuName;
public string? lpszClassName;
public IntPtr hIconSm;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSG
{ public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public int pt_x;
public int pt_y;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT { public int Left, Top, Right, Bottom; }
[StructLayout(LayoutKind.Sequential)]
private struct POINT { public int X, Y; }
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CreateWindowEx(
int dwExStyle, string lpClassName, string? lpWindowName, int dwStyle,
int x, int y, int nWidth, int nHeight,
IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("user32.dll", SetLastError = true)] private static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll")] private static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")] private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")] private static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll")] private static extern IntPtr DispatchMessage(ref MSG lpMsg);
[DllImport("user32.dll")] private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);
private static IntPtr SendMessageString(IntPtr hWnd, int Msg, IntPtr wParam, string text)
=> SendMessage(hWnd, Msg, wParam, text);
[DllImport("gdi32.dll")] private static extern IntPtr CreateSolidBrush(int colorRef);
[DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr hObject);
[DllImport("user32.dll")] private static extern int GetSystemMetrics(int nIndex);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string? lpModuleName);
[DllImport("user32.dll")] private static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[DllImport("user32.dll")] private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll")] private static extern bool PtInRect(ref RECT lprc, POINT pt);
[DllImport("user32.dll")] private static extern void PostQuitMessage(int nExitCode);
[DllImport("user32.dll")] private static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
private static int LOWORD(uint v) => unchecked((short)(v & 0xFFFF));
private static int HIWORD(uint v) => unchecked((short)((v >> 16) & 0xFFFF));
private static int GET_X_LPARAM(IntPtr lp) => unchecked((short)((long)lp & 0xFFFF));
private static int GET_Y_LPARAM(IntPtr lp) => unchecked((short)(((long)lp >> 16) & 0xFFFF));
private static RECT ScreenRectToClient(IntPtr hwndParent, RECT rScreen)
{ var tl = new POINT { X = rScreen.Left, Y = rScreen.Top }; var br = new POINT { X = rScreen.Right, Y = rScreen.Bottom }; ScreenToClient(hwndParent, ref tl);
ScreenToClient(hwndParent, ref br);
return new RECT { Left = tl.X, Top = tl.Y, Right = br.X, Bottom = br.Y }; }
private static int RGB(int r, int g, int b) => (r & 0xFF) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16);
}