Initial commit to new repository
This commit is contained in:
127
src/AxCopilot/Handlers/ProcessHandler.cs
Normal file
127
src/AxCopilot/Handlers/ProcessHandler.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.Diagnostics;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 실행 중인 프로세스를 검색하고 종료합니다. "kill " 프리픽스로 사용합니다.
|
||||
/// 예: kill chrome → Chrome 프로세스 목록 표시 후 선택하면 종료
|
||||
/// kill → 현재 실행 중인 모든 사용자 프로세스 표시
|
||||
/// </summary>
|
||||
public class ProcessHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "kill "; // 뒤에 공백 포함 — 오탐 방지
|
||||
|
||||
public PluginMetadata Metadata => new(
|
||||
"ProcessKiller",
|
||||
"프로세스 종료 — kill 뒤에 프로세스명 입력",
|
||||
"1.0",
|
||||
"AX");
|
||||
|
||||
// 시스템 핵심 프로세스 보호 목록 (종료 방지)
|
||||
private static readonly HashSet<string> ProtectedProcesses = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"system", "smss", "csrss", "wininit", "winlogon", "services", "lsass",
|
||||
"svchost", "explorer", "dwm", "fontdrvhost", "spoolsv", "registry",
|
||||
};
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
"종료할 프로세스명을 입력하세요",
|
||||
"예: kill chrome · kill notepad · kill explorer",
|
||||
null, null, Symbol: Symbols.Power)
|
||||
]);
|
||||
}
|
||||
|
||||
var q = query.Trim().ToLowerInvariant();
|
||||
|
||||
try
|
||||
{
|
||||
var processes = Process.GetProcesses()
|
||||
.Where(p =>
|
||||
!ProtectedProcesses.Contains(p.ProcessName) &&
|
||||
p.ProcessName.ToLowerInvariant().Contains(q))
|
||||
.OrderBy(p => p.ProcessName)
|
||||
.Take(12)
|
||||
.ToList();
|
||||
|
||||
if (processes.Count == 0)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
$"'{query}' 프로세스를 찾을 수 없습니다",
|
||||
"실행 중인 프로세스가 없거나 이름이 다릅니다",
|
||||
null, null, Symbol: Symbols.Warning)
|
||||
]);
|
||||
}
|
||||
|
||||
// 같은 이름의 프로세스 묶기
|
||||
var grouped = processes
|
||||
.GroupBy(p => p.ProcessName, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(g =>
|
||||
{
|
||||
var pids = g.Select(p => p.Id).ToList();
|
||||
var memMb = g.Sum(p =>
|
||||
{
|
||||
try { return p.WorkingSet64 / 1024 / 1024; }
|
||||
catch { return 0L; }
|
||||
});
|
||||
var title = g.Count() > 1
|
||||
? $"{g.Key} ({g.Count()}개 인스턴스)"
|
||||
: g.Key;
|
||||
var subtitle = $"PID: {string.Join(", ", pids)} · 메모리: {memMb} MB · Enter로 종료";
|
||||
|
||||
return new LauncherItem(
|
||||
title,
|
||||
subtitle,
|
||||
null,
|
||||
new ProcessKillData(g.Key, pids),
|
||||
Symbol: Symbols.Power);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(grouped);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"프로세스 목록 조회 실패: {ex.Message}");
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem("프로세스 목록 조회 실패", ex.Message, null, null, Symbol: Symbols.Error)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is not ProcessKillData data) return Task.CompletedTask;
|
||||
|
||||
int killed = 0, failed = 0;
|
||||
foreach (var pid in data.Pids)
|
||||
{
|
||||
try
|
||||
{
|
||||
var proc = Process.GetProcessById(pid);
|
||||
proc.Kill(entireProcessTree: false);
|
||||
killed++;
|
||||
}
|
||||
catch
|
||||
{
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
LogService.Info($"프로세스 종료: {data.Name} — {killed}개 성공, {failed}개 실패");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private record ProcessKillData(string Name, List<int> Pids);
|
||||
}
|
||||
Reference in New Issue
Block a user