Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active October 3, 2024 21:31
Show Gist options
  • Save santisq/c0db788b71bca670c40e2702382c8cdb to your computer and use it in GitHub Desktop.
Save santisq/c0db788b71bca670c40e2702382c8cdb to your computer and use it in GitHub Desktop.
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();
}
}
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();
}
@santisq
Copy link
Author

santisq commented Oct 3, 2024

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment