Created
November 15, 2025 17:53
-
-
Save pjmagee/1af86931ce99c5260ce4a968380371af to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Diagnostics; | |
| using System.Runtime.InteropServices; | |
| using System.Threading; | |
| using System.Windows; | |
| using System.Windows.Interop; | |
| using System.Windows.Media; | |
| using System.Windows.Threading; | |
| namespace TransparentOverlay | |
| { | |
| public partial class MainWindow : Window | |
| { | |
| private IntPtr _notepadHwnd = IntPtr.Zero; | |
| private DispatcherTimer _clockTimer; | |
| private Timer _trackTimer; | |
| private const int TrackIntervalMs = 5; | |
| private RECT _lastRect; | |
| private bool _initializedStyles; | |
| public MainWindow() | |
| { | |
| InitializeComponent(); | |
| // Enable hardware acceleration | |
| RenderOptions.ProcessRenderMode = RenderMode.Default; | |
| Loaded += OnLoaded; | |
| } | |
| private void OnLoaded(object? sender, RoutedEventArgs e) | |
| { | |
| EnsureNotepadWindow(); | |
| SetupClock(); | |
| SetupTrackingTimer(); | |
| // Initial positioning attempt | |
| if (_notepadHwnd != IntPtr.Zero && GetWindowRect(_notepadHwnd, out var r)) | |
| { | |
| UpdateOverlayBounds(r); | |
| } | |
| else | |
| { | |
| // Fallback visible size so clock is seen until Notepad is found | |
| Width = 200; | |
| Height = 80; | |
| Left = (SystemParameters.PrimaryScreenWidth - Width) / 2; | |
| Top = 40; | |
| } | |
| ApplyTransparentClickThrough(); | |
| } | |
| private void SetupClock() | |
| { | |
| // Update at ~60fps for smooth millisecond rendering | |
| _clockTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) }; | |
| _clockTimer.Tick += (_, __) => ClockText.Text = DateTime.Now.ToString("HH:mm:ss.fff"); | |
| ClockText.Text = DateTime.Now.ToString("HH:mm:ss.fff"); | |
| _clockTimer.Start(); | |
| } | |
| private void SetupTrackingTimer() | |
| { | |
| _trackTimer = new Timer(_ => TrackNotepad(), null, TrackIntervalMs, TrackIntervalMs); | |
| } | |
| private void EnsureNotepadWindow() | |
| { | |
| var processes = Process.GetProcessesByName("notepad"); | |
| if (processes.Length == 0) | |
| { | |
| try | |
| { | |
| Process.Start(new ProcessStartInfo("notepad.exe") { UseShellExecute = true }); | |
| Thread.Sleep(300); | |
| processes = Process.GetProcessesByName("notepad"); | |
| } | |
| catch { } | |
| } | |
| foreach (var proc in processes) | |
| { | |
| if (proc.MainWindowHandle != IntPtr.Zero) | |
| { | |
| _notepadHwnd = proc.MainWindowHandle; | |
| break; | |
| } | |
| } | |
| } | |
| private void TrackNotepad() | |
| { | |
| if (_notepadHwnd == IntPtr.Zero) | |
| { | |
| EnsureNotepadWindow(); | |
| if (_notepadHwnd == IntPtr.Zero) return; | |
| } | |
| if (!GetWindowRect(_notepadHwnd, out RECT rect)) return; | |
| if (rect.Equals(_lastRect)) return; | |
| _lastRect = rect; | |
| // Use Render priority for smoother tracking during rapid moves | |
| Dispatcher.BeginInvoke(() => UpdateOverlayBounds(rect), DispatcherPriority.Send); | |
| } | |
| private void UpdateOverlayBounds(RECT rect) | |
| { | |
| int width = rect.Right - rect.Left; | |
| int height = rect.Bottom - rect.Top; | |
| // Update WPF properties | |
| Left = rect.Left; | |
| Top = rect.Top; | |
| Width = width; | |
| Height = height; | |
| var hwnd = new WindowInteropHelper(this).Handle; | |
| if (hwnd != IntPtr.Zero) | |
| { | |
| // Use SWP_ASYNCWINDOWPOS for async positioning to reduce lag | |
| SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, width, height, | |
| SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS); | |
| } | |
| } | |
| protected override void OnSourceInitialized(EventArgs e) | |
| { | |
| base.OnSourceInitialized(e); | |
| // Enable hardware rendering on composition target | |
| var hwndSource = PresentationSource.FromVisual(this) as HwndSource; | |
| if (hwndSource?.CompositionTarget != null) | |
| { | |
| hwndSource.CompositionTarget.RenderMode = RenderMode.Default; | |
| } | |
| ApplyTransparentClickThrough(); | |
| } | |
| private void ApplyTransparentClickThrough() | |
| { | |
| if (_initializedStyles) return; | |
| var hwnd = new WindowInteropHelper(this).Handle; | |
| if (hwnd == IntPtr.Zero) return; | |
| int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); | |
| exStyle |= WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW; | |
| SetWindowLong(hwnd, GWL_EXSTYLE, exStyle); | |
| _initializedStyles = true; | |
| } | |
| protected override void OnClosed(EventArgs e) | |
| { | |
| base.OnClosed(e); | |
| _trackTimer?.Dispose(); | |
| _clockTimer?.Stop(); | |
| } | |
| #region Win32 | |
| private const int GWL_EXSTYLE = -20; | |
| private const int WS_EX_TRANSPARENT = 0x00000020; | |
| private const int WS_EX_TOOLWINDOW = 0x00000080; | |
| private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); | |
| private const uint SWP_NOACTIVATE = 0x0010; | |
| private const uint SWP_SHOWWINDOW = 0x0040; | |
| private const uint SWP_ASYNCWINDOWPOS = 0x4000; | |
| [DllImport("user32.dll")] | |
| private static extern int GetWindowLong(IntPtr hWnd, int nIndex); | |
| [DllImport("user32.dll")] | |
| private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); | |
| [DllImport("user32.dll")] | |
| private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); | |
| [DllImport("user32.dll")] | |
| private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); | |
| [StructLayout(LayoutKind.Sequential)] | |
| private struct RECT : IEquatable<RECT> | |
| { | |
| public int Left; | |
| public int Top; | |
| public int Right; | |
| public int Bottom; | |
| public bool Equals(RECT other) => Left == other.Left && Top == other.Top && Right == other.Right && Bottom == other.Bottom; | |
| } | |
| #endregion | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment