Initial commit to new repository

This commit is contained in:
2026-04-03 18:23:52 +09:00
commit deffb33cf9
5248 changed files with 267762 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
using System.Runtime.InteropServices;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;
namespace AxCopilot.Handlers;
/// <summary>
/// 미디어 컨트롤 핸들러. "media" 프리픽스로 사용합니다.
/// 예: media play → 재생/일시정지
/// media next → 다음 트랙
/// media prev → 이전 트랙
/// media vol+ → 볼륨 올리기
/// media vol- → 볼륨 낮추기
/// media mute → 음소거 토글
/// </summary>
public class MediaHandler : IActionHandler
{
public string? Prefix => "media";
public PluginMetadata Metadata => new(
"MediaControl",
"미디어 컨트롤 — media 뒤에 명령어 입력",
"1.0",
"AX");
// Windows 미디어/볼륨 가상 키 코드
private const byte VK_MEDIA_PLAY_PAUSE = 0xB3;
private const byte VK_MEDIA_NEXT_TRACK = 0xB0;
private const byte VK_MEDIA_PREV_TRACK = 0xB1;
private const byte VK_VOLUME_UP = 0xAF;
private const byte VK_VOLUME_DOWN = 0xAE;
private const byte VK_VOLUME_MUTE = 0xAD;
// KEYEVENTF flags
private const uint KEYEVENTF_EXTENDEDKEY = 0x0001;
private const uint KEYEVENTF_KEYUP = 0x0002;
[DllImport("user32.dll", SetLastError = false)]
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, nuint dwExtraInfo);
// 명령어 → (제목, 설명, VK코드, 심볼)
private static readonly List<(string[] Keys, string Title, string Subtitle, byte Vk, string Symbol)> _commands =
[
(["play", "pause", "pp"], "재생 / 일시정지", "현재 미디어 재생 또는 일시정지", VK_MEDIA_PLAY_PAUSE, Symbols.MediaPlay),
(["next", ">>"], "다음 트랙", "다음 곡으로 이동", VK_MEDIA_NEXT_TRACK, Symbols.MediaNext),
(["prev", "previous", "<<"], "이전 트랙", "이전 곡으로 이동", VK_MEDIA_PREV_TRACK, Symbols.MediaPrev),
(["vol+", "volup", "up"], "볼륨 올리기", "시스템 볼륨 증가", VK_VOLUME_UP, Symbols.VolumeUp),
(["vol-", "voldown", "down"], "볼륨 낮추기", "시스템 볼륨 감소", VK_VOLUME_DOWN, Symbols.VolumeDown),
(["mute", "음소거"], "음소거 토글", "볼륨 음소거 / 해제", VK_VOLUME_MUTE, Symbols.VolumeMute),
];
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
{
var q = query.Trim().ToLowerInvariant();
IEnumerable<(string[] Keys, string Title, string Subtitle, byte Vk, string Symbol)> matches;
if (string.IsNullOrEmpty(q))
{
// 쿼리 없으면 전체 목록 표시
matches = _commands;
}
else
{
// 입력어와 일치하는 명령 필터
matches = _commands.Where(c =>
c.Keys.Any(k => k.StartsWith(q, StringComparison.OrdinalIgnoreCase)) ||
c.Title.Contains(q, StringComparison.OrdinalIgnoreCase));
}
var items = matches
.Select(c => new LauncherItem(c.Title, c.Subtitle, null, new MediaKeyData(c.Vk), Symbol: c.Symbol))
.ToList();
if (items.Count == 0)
{
items.Add(new LauncherItem(
"알 수 없는 명령어",
"play · next · prev · vol+ · vol- · mute 중 하나를 입력하세요",
null, null, Symbol: Symbols.Warning));
}
return Task.FromResult<IEnumerable<LauncherItem>>(items);
}
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
{
if (item.Data is not MediaKeyData data) return Task.CompletedTask;
try
{
// 키 누름 → 키 뗌
keybd_event(data.Vk, 0, KEYEVENTF_EXTENDEDKEY, 0);
keybd_event(data.Vk, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
LogService.Info($"미디어 키 전송: VK=0x{data.Vk:X2}");
}
catch (Exception ex)
{
LogService.Warn($"미디어 키 전송 실패: {ex.Message}");
}
return Task.CompletedTask;
}
private record MediaKeyData(byte Vk);
}