using System.Runtime.InteropServices; using System.Windows; using System.Windows.Input; using System.Windows.Interop; using AxCopilot.Services; namespace AxCopilot.Views; public partial class LauncherWindow { // ─── Shell32 휴지통 삭제 ──────────────────────────────────────────────── [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct SHFILEOPSTRUCT { public IntPtr hwnd; public uint wFunc; [MarshalAs(UnmanagedType.LPWStr)] public string pFrom; [MarshalAs(UnmanagedType.LPWStr)] public string? pTo; public ushort fFlags; [MarshalAs(UnmanagedType.Bool)] public bool fAnyOperationsAborted; public IntPtr hNameMappings; [MarshalAs(UnmanagedType.LPWStr)] public string? lpszProgressTitle; } [DllImport("shell32.dll", CharSet = CharSet.Unicode)] private static extern int SHFileOperation(ref SHFILEOPSTRUCT lpFileOp); private const uint FO_DELETE = 0x0003; private const ushort FOF_ALLOWUNDO = 0x0040; private const ushort FOF_NOCONFIRMATION = 0x0010; private const ushort FOF_SILENT = 0x0004; /// 파일·폴더를 Windows 휴지통으로 보냅니다. private void SendToRecycleBin(string path) { // pFrom은 null-terminated + 추가 null 필요 var op = new SHFILEOPSTRUCT { hwnd = new WindowInteropHelper(this).Handle, wFunc = FO_DELETE, pFrom = path + '\0', fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT, }; int result = SHFileOperation(ref op); if (result != 0) throw new System.ComponentModel.Win32Exception(result, $"SHFileOperation 실패 (코드 {result})"); } // ─── 대형 텍스트 / 클립보드 외부 뷰어 ────────────────────────────────── private void ShowLargeType() { // 클립보드 항목 → 시스템 클립보드에 자동 복사 + 외부 앱에서 열기 if (_vm.SelectedItem?.Data is Services.ClipboardEntry clipEntry) { try { // 자동 클립보드 복사 억제 (히스토리 중복 방지) CurrentApp?.ClipboardHistoryService?.SuppressNextCapture(); if (!clipEntry.IsText && clipEntry.Image != null) { // 원본 이미지가 있으면 원본 사용, 없으면 썸네일 사용 var originalImg = Services.ClipboardHistoryService.LoadOriginalImage(clipEntry.OriginalImagePath); var imgToUse = originalImg ?? clipEntry.Image; // 시스템 클립보드에 원본 복사 Clipboard.SetImage(imgToUse); // 이미지: PNG로 저장 → 기본 이미지 뷰어 string path; if (!string.IsNullOrEmpty(clipEntry.OriginalImagePath) && System.IO.File.Exists(clipEntry.OriginalImagePath)) { path = clipEntry.OriginalImagePath; // 원본 파일 직접 열기 } else { path = Services.TempFileService.CreateTempFile("clip_image", ".png"); var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder(); encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(imgToUse)); using var fs = new System.IO.FileStream(path, System.IO.FileMode.Create); encoder.Save(fs); } System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); } else if (!string.IsNullOrEmpty(clipEntry.Text)) { // 시스템 클립보드에 텍스트 복사 Clipboard.SetText(clipEntry.Text); // 텍스트: txt로 저장 → 메모장 var path = Services.TempFileService.CreateTempFile("clip_text", ".txt"); System.IO.File.WriteAllText(path, clipEntry.Text, System.Text.Encoding.UTF8); System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("notepad.exe", $"\"{path}\"") { UseShellExecute = true }); } } catch (Exception ex) { Services.LogService.Warn($"클립보드 외부 뷰어 실패: {ex.Message}"); } return; } var text = _vm.GetLargeTypeText(); if (string.IsNullOrWhiteSpace(text)) return; new LargeTypeWindow(text).Show(); } // ─── 마우스 클릭 처리 ─────────────────────────────────────────────────── /// 이미 선택된 아이템을 클릭하면 Execute, 아직 선택되지 않은 아이템 클릭은 선택만. private SDK.LauncherItem? _lastClickedItem; private DateTime _lastClickTime; private void ResultList_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { // 클릭한 ListViewItem 찾기 var dep = e.OriginalSource as DependencyObject; while (dep != null && dep is not System.Windows.Controls.ListViewItem) dep = System.Windows.Media.VisualTreeHelper.GetParent(dep); if (dep is not System.Windows.Controls.ListViewItem lvi) return; var clickedItem = lvi.Content as SDK.LauncherItem; if (clickedItem == null) return; // Phase L2-5: Ctrl+Click → 클립보드 히스토리 모드에서 다중 선택 토글 if ((Keyboard.Modifiers & ModifierKeys.Control) != 0 && _vm.IsClipboardMode) { _vm.ToggleMergeItem(clickedItem); e.Handled = true; return; } var now = DateTime.UtcNow; var timeSinceLastClick = (now - _lastClickTime).TotalMilliseconds; if (_lastClickedItem == clickedItem && timeSinceLastClick < 600) { // 같은 아이템을 짧은 간격으로 재클릭 → 액션 모드 또는 실행 if (!_vm.IsActionMode && _vm.CanEnterActionMode()) { _vm.EnterActionMode(clickedItem); e.Handled = true; } else { _ = _vm.ExecuteSelectedAsync(); e.Handled = true; } _lastClickedItem = null; return; } // 첫 번째 클릭 → 선택만 _lastClickedItem = clickedItem; _lastClickTime = now; } private void ResultList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { _ = _vm.ExecuteSelectedAsync(); } // ─── 창 이벤트 / 스크롤 / 알림 ───────────────────────────────────────── private void Window_Deactivated(object sender, EventArgs e) { // 설정 › 기능 › "포커스 잃으면 닫기"가 켜진 경우에만 자동 숨김 if (_vm.CloseOnFocusLost) Hide(); } private void ScrollToSelected() { if (_vm.SelectedItem != null) ResultList.ScrollIntoView(_vm.SelectedItem); } private void ShowNotification(string message) { // 시스템 트레이 토스트 알림 표시 // App.xaml.cs의 TrayIcon을 통해 처리 } }