Skip to content

Commit b2532fc

Browse files
committed
Order search result by window title
1 parent e7e825e commit b2532fc

File tree

2 files changed

+61
-13
lines changed

2 files changed

+61
-13
lines changed

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using System.Linq;
33
using Flow.Launcher.Infrastructure;
44

@@ -60,30 +60,33 @@ public List<Result> LoadContextMenus(Result result)
6060
return menuOptions;
6161
}
6262

63+
private record RunningProcessInfo(string ProcessName, string MainWindowTitle);
64+
6365
private List<Result> CreateResultsFromQuery(Query query)
6466
{
6567
string termToSearch = query.Search;
66-
var processlist = processHelper.GetMatchingProcesses(termToSearch);
67-
68-
if (!processlist.Any())
68+
var processList = processHelper.GetMatchingProcesses(termToSearch);
69+
var processWithNonEmptyMainWindowTitleList = processHelper.GetProcessesWithNonEmptyWindowTitle();
70+
71+
if (!processList.Any())
6972
{
7073
return null;
7174
}
7275

7376
var results = new List<Result>();
7477

75-
foreach (var pr in processlist)
78+
foreach (var pr in processList)
7679
{
7780
var p = pr.Process;
7881
var path = processHelper.TryGetProcessFilename(p);
7982
results.Add(new Result()
8083
{
8184
IcoPath = path,
82-
Title = p.ProcessName + " - " + p.Id,
85+
Title = processWithNonEmptyMainWindowTitleList.TryGetValue(p.Id, out var mainWindowTitle) ? mainWindowTitle : p.ProcessName + " - " + p.Id,
8386
SubTitle = path,
8487
TitleHighlightData = StringMatcher.FuzzySearch(termToSearch, p.ProcessName).MatchData,
8588
Score = pr.Score,
86-
ContextData = p.ProcessName,
89+
ContextData = new RunningProcessInfo(p.ProcessName, mainWindowTitle),
8790
AutoCompleteText = $"{_context.CurrentPluginMetadata.ActionKeyword}{Plugin.Query.TermSeparator}{p.ProcessName}",
8891
Action = (c) =>
8992
{
@@ -95,22 +98,25 @@ private List<Result> CreateResultsFromQuery(Query query)
9598
});
9699
}
97100

98-
var sortedResults = results.OrderBy(x => x.Title).ToList();
101+
var sortedResults = results
102+
.OrderBy(x => string.IsNullOrEmpty(((RunningProcessInfo)x.ContextData).MainWindowTitle))
103+
.ThenBy(x => x.Title)
104+
.ToList();
99105

100106
// When there are multiple results AND all of them are instances of the same executable
101107
// add a quick option to kill them all at the top of the results.
102108
var firstResult = sortedResults.FirstOrDefault(x => !string.IsNullOrEmpty(x.SubTitle));
103-
if (processlist.Count > 1 && !string.IsNullOrEmpty(termToSearch) && sortedResults.All(r => r.SubTitle == firstResult?.SubTitle))
109+
if (processList.Count > 1 && !string.IsNullOrEmpty(termToSearch) && sortedResults.All(r => r.SubTitle == firstResult?.SubTitle))
104110
{
105111
sortedResults.Insert(1, new Result()
106112
{
107113
IcoPath = firstResult?.IcoPath,
108-
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), firstResult?.ContextData),
109-
SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processlist.Count),
114+
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), ((RunningProcessInfo)firstResult?.ContextData).ProcessName),
115+
SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processList.Count),
110116
Score = 200,
111117
Action = (c) =>
112118
{
113-
foreach (var p in processlist)
119+
foreach (var p in processList)
114120
{
115121
processHelper.TryKill(p.Process);
116122
}

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Flow.Launcher.Infrastructure;
1+
using Flow.Launcher.Infrastructure;
22
using Flow.Launcher.Infrastructure.Logger;
33
using System;
44
using System.Collections.Generic;
@@ -11,6 +11,20 @@ namespace Flow.Launcher.Plugin.ProcessKiller
1111
{
1212
internal class ProcessHelper
1313
{
14+
[DllImport("user32.dll")]
15+
private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
16+
17+
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
18+
19+
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
20+
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
21+
22+
[DllImport("user32.dll")]
23+
private static extern bool IsWindowVisible(IntPtr hWnd);
24+
25+
[DllImport("user32.dll")]
26+
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
27+
1428
private readonly HashSet<string> _systemProcessList = new HashSet<string>()
1529
{
1630
"conhost",
@@ -60,6 +74,34 @@ public List<ProcessResult> GetMatchingProcesses(string searchTerm)
6074
return processlist;
6175
}
6276

77+
/// <summary>
78+
/// Returns a dictionary of process IDs and their window titles for processes that have a visible main window with a non-empty title.
79+
/// </summary>
80+
public Dictionary<int, string> GetProcessesWithNonEmptyWindowTitle()
81+
{
82+
var processDict = new Dictionary<int, string>();
83+
EnumWindows((hWnd, lParam) =>
84+
{
85+
StringBuilder windowTitle = new StringBuilder();
86+
GetWindowText(hWnd, windowTitle, windowTitle.Capacity);
87+
88+
if (!string.IsNullOrWhiteSpace(windowTitle.ToString()) && IsWindowVisible(hWnd))
89+
{
90+
GetWindowThreadProcessId(hWnd, out var processId);
91+
var process = Process.GetProcessById((int)processId);
92+
93+
if (!processDict.ContainsKey((int)processId))
94+
{
95+
processDict.Add((int)processId, windowTitle.ToString());
96+
}
97+
}
98+
99+
return true;
100+
}, IntPtr.Zero);
101+
102+
return processDict;
103+
}
104+
63105
/// <summary>
64106
/// Returns all non-system processes whose file path matches the given processPath
65107
/// </summary>

0 commit comments

Comments
 (0)