Last active
October 3, 2024 21:31
-
-
Save santisq/c0db788b71bca670c40e2702382c8cdb 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.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using System.Timers; | |
public sealed class TitleChange : EventArgs | |
{ | |
public DateTime TimeGenerated { get; } = DateTime.Now; | |
public string ProcessName { get; } | |
public int Id { get; } | |
public IntPtr Handle { get; } | |
public string OldTitle { get; } | |
public string NewTitle { get; } | |
internal TitleChange( | |
Process process, | |
IntPtr handle, | |
string oldTitle, | |
string newTitle) | |
{ | |
ProcessName = process.ProcessName; | |
Id = process.Id; | |
Handle = handle; | |
OldTitle = oldTitle; | |
NewTitle = newTitle; | |
} | |
} | |
public sealed class TitleWatcher : IDisposable | |
{ | |
private delegate bool EnumWindowsProc(IntPtr hWnd, int lParam); | |
[DllImport("user32.dll")] | |
private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam); | |
[DllImport("user32.dll")] | |
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); | |
[DllImport("user32.dll")] | |
private static extern bool IsWindowVisible(IntPtr hWnd); | |
[DllImport("user32.dll", CharSet = CharSet.Unicode)] | |
private static extern int GetWindowTextLength(IntPtr hWnd); | |
[DllImport("user32.dll", CharSet = CharSet.Unicode)] | |
private static extern int GetWindowText(IntPtr hWnd, char[] lpString, int nMaxCount); | |
private readonly Dictionary<uint, Process> _procesess = []; | |
private readonly Dictionary<IntPtr, string> _titles = []; | |
private readonly Timer _timer; | |
public event EventHandler<TitleChange>? TitleChanged; | |
public TitleWatcher(Process[] processes, double interval = 200) | |
{ | |
_procesess = processes.ToDictionary(e => (uint)e.Id); | |
_timer = new(interval); | |
_timer.Elapsed += WindowCallback; | |
_timer.Start(); | |
} | |
private void WindowCallback(object? sender, ElapsedEventArgs e) | |
{ | |
EnumWindows((IntPtr handle, int lParam) => | |
{ | |
if (!IsWindowVisible(handle)) | |
{ | |
return true; | |
} | |
if (GetWindowThreadProcessId(handle, out uint processId) == 0) | |
{ | |
return true; | |
} | |
if (!_procesess.TryGetValue(processId, out Process? process)) | |
{ | |
return true; | |
} | |
int len; | |
if ((len = GetWindowTextLength(handle)) == 0) | |
{ | |
return true; | |
} | |
char[] buffer = new char[len]; | |
if (GetWindowText(handle, buffer, len + 1) == 0) | |
{ | |
return true; | |
} | |
string newtitle = new(buffer); | |
if (_titles.TryGetValue(handle, out string? oldtitle) && newtitle != oldtitle) | |
{ | |
TitleChange change = new(process, handle, oldtitle, newtitle); | |
TitleChanged?.Invoke(this, change); | |
} | |
_titles[handle] = newtitle; | |
return true; | |
}, 0); | |
} | |
public void Dispose() | |
{ | |
_timer.Elapsed -= WindowCallback; | |
_timer.Dispose(); | |
} | |
} |
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.Collections.Generic; | |
using System.Diagnostics; | |
using System.Management.Automation; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
public record TitleChange( | |
string ProcessName, | |
uint Id, | |
IntPtr Handle, | |
string OldTitle, | |
string NewTitle); | |
[Cmdlet(VerbsCommon.Watch, "TitleChanges")] | |
public sealed class WatchTitleChangesCommand : PSCmdlet, IDisposable | |
{ | |
[Parameter(Mandatory = true, Position = 0)] | |
public string[] ProcessName { get; set; } = []; | |
private readonly ManualResetEventSlim _handle = new(); | |
private delegate bool EnumWindowsProc(IntPtr hWnd, int lParam); | |
[DllImport("user32.dll")] | |
private static extern bool EnumWindows( | |
EnumWindowsProc enumFunc, int lParam); | |
[DllImport("user32.dll")] | |
private static extern uint GetWindowThreadProcessId( | |
IntPtr hWnd, out uint lpdwProcessId); | |
[DllImport("user32.dll")] | |
private static extern bool IsWindowVisible(IntPtr hWnd); | |
[DllImport("user32.dll", CharSet = CharSet.Unicode)] | |
private static extern int GetWindowTextLength(IntPtr hWnd); | |
[DllImport("user32.dll", CharSet = CharSet.Unicode)] | |
private static extern int GetWindowText( | |
IntPtr hWnd, char[] lpString, int nMaxCount); | |
protected override void EndProcessing() | |
{ | |
Dictionary<uint, string> pids = []; | |
foreach (string name in ProcessName) | |
{ | |
foreach (Process process in Process.GetProcessesByName(name)) | |
{ | |
pids[(uint)process.Id] = process.ProcessName; | |
} | |
} | |
if (pids.Count == 0) | |
{ | |
WriteWarning("No process found..."); | |
return; | |
} | |
Dictionary<IntPtr, string> titles = []; | |
while (!_handle.WaitHandle.WaitOne(200)) | |
{ | |
WriteObject( | |
GetChangesByProcessId(pids, titles), | |
enumerateCollection: true); | |
} | |
} | |
private static TitleChange[] GetChangesByProcessId( | |
Dictionary<uint, string> pids, | |
Dictionary<IntPtr, string> titles) | |
{ | |
List<TitleChange> result = []; | |
EnumWindows((IntPtr handle, int lParam) => | |
{ | |
if (!IsWindowVisible(handle)) | |
{ | |
return true; | |
} | |
if (GetWindowThreadProcessId(handle, out uint processId) == 0) | |
{ | |
return true; | |
} | |
if (!pids.TryGetValue(processId, out string? processName)) | |
{ | |
return true; | |
} | |
int len; | |
if ((len = GetWindowTextLength(handle)) == 0) | |
{ | |
return true; | |
} | |
char[] buffer = new char[len]; | |
if (GetWindowText(handle, buffer, len + 1) == 0) | |
{ | |
return true; | |
} | |
string newtitle = new(buffer); | |
if (titles.TryGetValue(handle, out string? oldtitle) && newtitle != oldtitle) | |
{ | |
TitleChange change = new(processName, processId, handle, oldtitle, newtitle); | |
result.Add(change); | |
} | |
titles[handle] = newtitle; | |
return true; | |
}, 0); | |
return [.. result]; | |
} | |
protected override void StopProcessing() => _handle.Set(); | |
public void Dispose() => _handle.Dispose(); | |
} |
Author
santisq
commented
Oct 3, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment