128 lines
4.5 KiB
C#
128 lines
4.5 KiB
C#
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);
|
|
}
|