using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.Threading; namespace Flow.Launcher.Plugin.ProcessKiller { internal class ProcessHelper { [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); private readonly HashSet _systemProcessList = new HashSet() { "conhost", "svchost", "idle", "system", "rundll32", "csrss", "lsass", "lsm", "smss", "wininit", "winlogon", "services", "spoolsv", "explorer" }; private bool IsSystemProcess(Process p) => _systemProcessList.Contains(p.ProcessName.ToLower()); /// /// Returns a ProcessResult for evey running non-system process whose name matches the given searchTerm /// public List GetMatchingProcesses(string searchTerm) { var processlist = new List(); foreach (var p in Process.GetProcesses()) { if (IsSystemProcess(p)) continue; if (string.IsNullOrWhiteSpace(searchTerm)) { // show all non-system processes processlist.Add(new ProcessResult(p, 0)); } else { var score = StringMatcher.FuzzySearch(searchTerm, p.ProcessName + p.Id).Score; if (score > 0) { processlist.Add(new ProcessResult(p, score)); } } } return processlist; } /// /// Returns a dictionary of process IDs and their window titles for processes that have a visible main window with a non-empty title. /// public Dictionary GetProcessesWithNonEmptyWindowTitle() { var processDict = new Dictionary(); EnumWindows((hWnd, lParam) => { StringBuilder windowTitle = new StringBuilder(); GetWindowText(hWnd, windowTitle, windowTitle.Capacity); if (!string.IsNullOrWhiteSpace(windowTitle.ToString()) && IsWindowVisible(hWnd)) { GetWindowThreadProcessId(hWnd, out var processId); var process = Process.GetProcessById((int)processId); if (!processDict.ContainsKey((int)processId)) { processDict.Add((int)processId, windowTitle.ToString()); } } return true; }, IntPtr.Zero); return processDict; } /// /// Returns all non-system processes whose file path matches the given processPath /// public IEnumerable GetSimilarProcesses(string processPath) { return Process.GetProcesses().Where(p => !IsSystemProcess(p) && TryGetProcessFilename(p) == processPath); } public void TryKill(Process p) { try { if (!p.HasExited) { p.Kill(); p.WaitForExit(50); } } catch (Exception e) { Log.Exception($"{nameof(ProcessHelper)}", $"Failed to kill process {p.ProcessName}", e); } } public unsafe string TryGetProcessFilename(Process p) { try { var handle = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)p.Id); if (handle.Value == IntPtr.Zero) { return string.Empty; } using var safeHandle = new SafeProcessHandle(handle.Value, true); uint capacity = 2000; Span buffer = new char[capacity]; fixed (char* pBuffer = buffer) { if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, (PWSTR)pBuffer, ref capacity)) { return string.Empty; } return buffer[..(int)capacity].ToString(); } } catch { return string.Empty; } } } }