Flow.Launcher/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs

188 lines
6.2 KiB
C#
Raw Permalink Normal View History

2025-04-15 13:21:27 +00:00
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
2025-03-14 08:42:54 +00:00
using System.Text;
2025-04-15 13:21:27 +00:00
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
2024-12-10 05:29:21 +00:00
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Threading;
namespace Flow.Launcher.Plugin.ProcessKiller
{
internal class ProcessHelper
{
2025-04-13 09:59:39 +00:00
private static readonly string ClassName = nameof(ProcessHelper);
private readonly HashSet<string> _systemProcessList =
[
"conhost",
"svchost",
"idle",
"system",
"rundll32",
"csrss",
"lsass",
"lsm",
"smss",
"wininit",
"winlogon",
"services",
"spoolsv",
"explorer"
];
2025-03-14 03:21:07 +00:00
private const string FlowLauncherProcessName = "Flow.Launcher";
private bool IsSystemProcessOrFlowLauncher(Process p) =>
2025-03-14 08:42:54 +00:00
_systemProcessList.Contains(p.ProcessName.ToLower()) ||
string.Equals(p.ProcessName, FlowLauncherProcessName, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Get title based on process name and id
/// </summary>
public static string GetProcessNameIdTitle(Process p)
{
2025-03-14 08:42:54 +00:00
var sb = new StringBuilder();
sb.Append(p.ProcessName);
sb.Append(" - ");
sb.Append(p.Id);
return sb.ToString();
}
/// <summary>
/// Returns a Process for evey running non-system process
/// </summary>
public List<Process> GetMatchingProcesses()
{
var processlist = new List<Process>();
foreach (var p in Process.GetProcesses())
{
2025-03-14 08:42:54 +00:00
if (IsSystemProcessOrFlowLauncher(p)) continue;
processlist.Add(p);
}
return processlist;
}
2024-12-27 11:43:43 +00:00
/// <summary>
/// Returns a dictionary of process IDs and their window titles for processes that have a visible main window with a non-empty title.
/// </summary>
2025-03-13 06:01:02 +00:00
public static unsafe Dictionary<int, string> GetProcessesWithNonEmptyWindowTitle()
2024-12-27 11:43:43 +00:00
{
2025-04-15 13:21:27 +00:00
// Collect all window handles
var windowHandles = new List<HWND>();
2025-03-13 06:09:13 +00:00
PInvoke.EnumWindows((hWnd, _) =>
2025-04-15 13:21:27 +00:00
{
if (PInvoke.IsWindowVisible(hWnd))
{
windowHandles.Add(hWnd);
}
return true;
}, IntPtr.Zero);
// Concurrently process each window handle
var processDict = new ConcurrentDictionary<int, string>();
var processedProcessIds = new ConcurrentDictionary<int, byte>();
Parallel.ForEach(windowHandles, hWnd =>
2024-12-27 11:43:43 +00:00
{
2025-03-13 06:01:02 +00:00
var windowTitle = GetWindowTitle(hWnd);
if (!string.IsNullOrWhiteSpace(windowTitle) && PInvoke.IsWindowVisible(hWnd))
2024-12-27 11:43:43 +00:00
{
2025-03-13 06:01:02 +00:00
uint processId = 0;
var result = PInvoke.GetWindowThreadProcessId(hWnd, &processId);
if (result == 0u || processId == 0u)
{
2025-04-15 13:21:27 +00:00
return;
2025-03-13 06:01:02 +00:00
}
2024-12-27 11:43:43 +00:00
2025-04-15 13:21:27 +00:00
// Ensure each process ID is processed only once
if (processedProcessIds.TryAdd((int)processId, 0))
2024-12-27 11:43:43 +00:00
{
2025-04-15 13:21:27 +00:00
try
{
var process = Process.GetProcessById((int)processId);
processDict.TryAdd((int)processId, windowTitle);
}
catch
{
// Handle exceptions (e.g., process exited)
}
2024-12-27 11:43:43 +00:00
}
}
2025-04-15 13:21:27 +00:00
});
2024-12-27 11:43:43 +00:00
2025-04-15 13:21:27 +00:00
return new Dictionary<int, string>(processDict);
2024-12-27 11:43:43 +00:00
}
2025-03-13 06:01:02 +00:00
private static unsafe string GetWindowTitle(HWND hwnd)
{
var capacity = PInvoke.GetWindowTextLength(hwnd) + 1;
int length;
Span<char> buffer = capacity < 1024 ? stackalloc char[capacity] : new char[capacity];
fixed (char* pBuffer = buffer)
{
// If the window has no title bar or text, if the title bar is empty,
// or if the window or control handle is invalid, the return value is zero.
length = PInvoke.GetWindowText(hwnd, pBuffer, capacity);
}
return buffer[..length].ToString();
}
/// <summary>
/// Returns all non-system processes whose file path matches the given processPath
/// </summary>
public IEnumerable<Process> GetSimilarProcesses(string processPath)
{
2025-03-14 08:42:54 +00:00
return Process.GetProcesses().Where(p => !IsSystemProcessOrFlowLauncher(p) && TryGetProcessFilename(p) == processPath);
}
public static void TryKill(Process p)
{
try
{
if (!p.HasExited)
{
p.Kill();
p.WaitForExit(50);
}
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e);
}
}
public static unsafe string TryGetProcessFilename(Process p)
{
try
{
2024-12-10 05:29:21 +00:00
var handle = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)p.Id);
2025-07-19 12:26:33 +00:00
if (handle == HWND.Null)
{
2024-12-10 05:29:21 +00:00
return string.Empty;
}
2025-07-19 12:26:33 +00:00
using var safeHandle = new SafeProcessHandle((nint)handle.Value, true);
2024-12-10 05:29:21 +00:00
uint capacity = 2000;
Span<char> buffer = new char[capacity];
2025-07-19 12:26:33 +00:00
if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, buffer, ref capacity))
2024-12-10 05:29:21 +00:00
{
2025-07-19 12:26:33 +00:00
return string.Empty;
2024-12-10 07:12:35 +00:00
}
2025-07-19 12:26:33 +00:00
return buffer[..(int)capacity].ToString();
}
catch
{
2024-12-10 05:29:21 +00:00
return string.Empty;
}
}
}
}