613 lines
17 KiB
C#
613 lines
17 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Drawing;
|
||
using System.Drawing.Imaging;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Runtime.InteropServices;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Forms;
|
||
using System.Windows.Interop;
|
||
using System.Windows.Media.Imaging;
|
||
using System.Windows.Threading;
|
||
using AxCopilot.SDK;
|
||
using AxCopilot.Services;
|
||
using AxCopilot.Views;
|
||
|
||
namespace AxCopilot.Handlers;
|
||
|
||
public class ScreenCaptureHandler : IActionHandler
|
||
{
|
||
private struct RECT
|
||
{
|
||
public int left;
|
||
|
||
public int top;
|
||
|
||
public int right;
|
||
|
||
public int bottom;
|
||
}
|
||
|
||
private readonly SettingsService _settings;
|
||
|
||
private const int SW_RESTORE = 9;
|
||
|
||
private const byte VK_NEXT = 34;
|
||
|
||
private const uint WM_VSCROLL = 277u;
|
||
|
||
private const uint WM_KEYDOWN = 256u;
|
||
|
||
private const uint WM_KEYUP = 257u;
|
||
|
||
private const int SB_PAGEDOWN = 3;
|
||
|
||
private static readonly (string Key, string Label, string Desc)[] _options = new(string, string, string)[4]
|
||
{
|
||
("region", "영역 선택 캡처", "마우스로 드래그하여 원하는 영역만 캡처 · Shift+Enter: 타이머 캡처"),
|
||
("window", "활성 창 캡처", "런처 호출 전 활성 창만 캡처 · Shift+Enter: 타이머 캡처"),
|
||
("scroll", "스크롤 캡처", "활성 창을 끝까지 스크롤하며 페이지 전체 캡처 · Shift+Enter: 타이머 캡처"),
|
||
("screen", "전체 화면 캡처", "모든 모니터를 포함한 전체 화면 · Shift+Enter: 타이머 캡처")
|
||
};
|
||
|
||
public string? Prefix => string.IsNullOrWhiteSpace(_settings.Settings.ScreenCapture.Prefix) ? "cap" : _settings.Settings.ScreenCapture.Prefix.Trim();
|
||
|
||
public PluginMetadata Metadata => new PluginMetadata("ScreenCapture", "화면 캡처 — cap screen/window/scroll/region", "1.0", "AX");
|
||
|
||
internal int ScrollDelayMs => Math.Max(50, _settings.Settings.ScreenCapture.ScrollDelayMs);
|
||
|
||
public ScreenCaptureHandler(SettingsService settings)
|
||
{
|
||
_settings = settings;
|
||
}
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern bool GetWindowRect(nint hWnd, out RECT lpRect);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern bool IsWindow(nint hWnd);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern bool SetForegroundWindow(nint hWnd);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern bool PrintWindow(nint hwnd, nint hdcBlt, uint nFlags);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern bool IsWindowVisible(nint hWnd);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern nint FindWindowEx(nint parent, nint child, string? className, string? windowText);
|
||
|
||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||
{
|
||
string q = query.Trim().ToLowerInvariant();
|
||
string saveHint = "클립보드에 복사";
|
||
IEnumerable<(string, string, string)> enumerable;
|
||
if (!string.IsNullOrWhiteSpace(q))
|
||
{
|
||
enumerable = _options.Where(((string Key, string Label, string Desc) o) => o.Key.StartsWith(q) || o.Label.Contains(q));
|
||
}
|
||
else
|
||
{
|
||
IEnumerable<(string, string, string)> options = _options;
|
||
enumerable = options;
|
||
}
|
||
IEnumerable<(string, string, string)> source = enumerable;
|
||
List<LauncherItem> list = source.Select<(string, string, string), LauncherItem>(((string Key, string Label, string Desc) o) => new LauncherItem(o.Label, o.Desc + " · " + saveHint, null, o.Key, null, "\ue722")).ToList();
|
||
if (!list.Any())
|
||
{
|
||
list.Add(new LauncherItem("알 수 없는 캡처 모드: " + q, "screen / window / scroll / region", null, null, null, "\ue7ba"));
|
||
}
|
||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||
}
|
||
|
||
public IEnumerable<LauncherItem> GetDelayItems(string mode)
|
||
{
|
||
string text = _options.FirstOrDefault(((string Key, string Label, string Desc) o) => o.Key == mode).Label ?? mode;
|
||
return new LauncherItem[3]
|
||
{
|
||
new LauncherItem("3초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":3", null, "\ue916"),
|
||
new LauncherItem("5초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":5", null, "\ue916"),
|
||
new LauncherItem("10초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":10", null, "\ue916")
|
||
};
|
||
}
|
||
|
||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||
{
|
||
object data = item.Data;
|
||
if (!(data is string data2))
|
||
{
|
||
return;
|
||
}
|
||
if (data2.StartsWith("delay:"))
|
||
{
|
||
string[] parts = data2.Split(':');
|
||
if (parts.Length == 3 && int.TryParse(parts[2], out var delaySec))
|
||
{
|
||
await ExecuteDelayedCaptureAsync(parts[1], delaySec, ct);
|
||
return;
|
||
}
|
||
}
|
||
await Task.Delay(150, ct);
|
||
await CaptureDirectAsync(data2, ct);
|
||
}
|
||
|
||
private async Task ExecuteDelayedCaptureAsync(string mode, int delaySec, CancellationToken ct)
|
||
{
|
||
await Task.Delay(200, ct);
|
||
for (int i = delaySec; i > 0; i--)
|
||
{
|
||
ct.ThrowIfCancellationRequested();
|
||
await Task.Delay(1000, ct);
|
||
}
|
||
await CaptureDirectAsync(mode, ct);
|
||
}
|
||
|
||
public async Task CaptureDirectAsync(string mode, CancellationToken ct = default(CancellationToken))
|
||
{
|
||
try
|
||
{
|
||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||
switch (mode)
|
||
{
|
||
case "screen":
|
||
await CaptureScreenAsync(timestamp);
|
||
break;
|
||
case "window":
|
||
await CaptureWindowAsync(timestamp);
|
||
break;
|
||
case "scroll":
|
||
await CaptureScrollAsync(timestamp, ct);
|
||
break;
|
||
case "region":
|
||
await CaptureRegionAsync(timestamp, ct);
|
||
break;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Exception ex2 = ex;
|
||
LogService.Error("캡처 실패: " + ex2.Message);
|
||
NotificationService.Notify("AX Copilot", "캡처 실패: " + ex2.Message);
|
||
}
|
||
}
|
||
|
||
private async Task CaptureScreenAsync(string timestamp)
|
||
{
|
||
Rectangle bounds = GetAllScreenBounds();
|
||
using Bitmap bmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
|
||
using Graphics g = Graphics.FromImage(bmp);
|
||
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
|
||
CopyToClipboard(bmp);
|
||
await Task.Delay(10);
|
||
NotificationService.Notify("화면 캡처 완료", "클립보드에 복사되었습니다");
|
||
}
|
||
|
||
private async Task CaptureWindowAsync(string timestamp)
|
||
{
|
||
nint hwnd = WindowTracker.PreviousWindow;
|
||
if (hwnd == IntPtr.Zero || !IsWindow(hwnd))
|
||
{
|
||
NotificationService.Notify("AX Copilot", "캡처할 창이 없습니다. 런처 호출 전 창을 확인하세요.");
|
||
return;
|
||
}
|
||
nint launcherHwnd = GetLauncherHwnd();
|
||
if (launcherHwnd != IntPtr.Zero)
|
||
{
|
||
for (int i = 0; i < 10; i++)
|
||
{
|
||
if (!IsWindowVisible(launcherHwnd))
|
||
{
|
||
break;
|
||
}
|
||
await Task.Delay(50);
|
||
}
|
||
}
|
||
ShowWindow(hwnd, 9);
|
||
SetForegroundWindow(hwnd);
|
||
await Task.Delay(150);
|
||
if (!GetWindowRect(hwnd, out var rect))
|
||
{
|
||
return;
|
||
}
|
||
int w = rect.right - rect.left;
|
||
int h = rect.bottom - rect.top;
|
||
if (w <= 0 || h <= 0)
|
||
{
|
||
return;
|
||
}
|
||
using Bitmap bmp = CaptureWindow(hwnd, w, h, rect);
|
||
CopyToClipboard(bmp);
|
||
NotificationService.Notify("창 캡처 완료", "클립보드에 복사되었습니다");
|
||
}
|
||
|
||
private async Task CaptureScrollAsync(string timestamp, CancellationToken ct)
|
||
{
|
||
nint hwnd = WindowTracker.PreviousWindow;
|
||
if (hwnd == IntPtr.Zero || !IsWindow(hwnd))
|
||
{
|
||
NotificationService.Notify("AX Copilot", "캡처할 창이 없습니다.");
|
||
return;
|
||
}
|
||
nint launcherHwnd = GetLauncherHwnd();
|
||
if (launcherHwnd != IntPtr.Zero)
|
||
{
|
||
for (int i = 0; i < 10; i++)
|
||
{
|
||
if (!IsWindowVisible(launcherHwnd))
|
||
{
|
||
break;
|
||
}
|
||
await Task.Delay(50, ct);
|
||
}
|
||
}
|
||
ShowWindow(hwnd, 9);
|
||
SetForegroundWindow(hwnd);
|
||
await Task.Delay(200, ct);
|
||
if (!GetWindowRect(hwnd, out var rect))
|
||
{
|
||
return;
|
||
}
|
||
int w = rect.right - rect.left;
|
||
int h = rect.bottom - rect.top;
|
||
if (w <= 0 || h <= 0)
|
||
{
|
||
return;
|
||
}
|
||
nint scrollTarget = FindScrollableChild(hwnd);
|
||
List<Bitmap> frames = new List<Bitmap> { CaptureWindow(hwnd, w, h, rect) };
|
||
for (int j = 0; j < 14; j++)
|
||
{
|
||
ct.ThrowIfCancellationRequested();
|
||
if (scrollTarget != IntPtr.Zero)
|
||
{
|
||
SendMessage(scrollTarget, 277u, new IntPtr(3), IntPtr.Zero);
|
||
}
|
||
else
|
||
{
|
||
SendPageDown(hwnd);
|
||
}
|
||
await Task.Delay(ScrollDelayMs, ct);
|
||
if (!GetWindowRect(hwnd, out var newRect))
|
||
{
|
||
break;
|
||
}
|
||
Bitmap frame = CaptureWindow(hwnd, w, h, newRect);
|
||
int num;
|
||
if (frames.Count > 0)
|
||
{
|
||
num = (AreSimilar(frames[frames.Count - 1], frame) ? 1 : 0);
|
||
}
|
||
else
|
||
{
|
||
num = 0;
|
||
}
|
||
if (num != 0)
|
||
{
|
||
frame.Dispose();
|
||
break;
|
||
}
|
||
frames.Add(frame);
|
||
}
|
||
using Bitmap stitched = StitchFrames(frames, h);
|
||
foreach (Bitmap f in frames)
|
||
{
|
||
f.Dispose();
|
||
}
|
||
CopyToClipboard(stitched);
|
||
NotificationService.Notify("스크롤 캡처 완료", $"{stitched.Height}px · 클립보드에 복사되었습니다");
|
||
}
|
||
|
||
private static Bitmap CaptureWindow(nint hwnd, int w, int h, RECT rect)
|
||
{
|
||
Bitmap bitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb);
|
||
using Graphics graphics = Graphics.FromImage(bitmap);
|
||
nint hdc = graphics.GetHdc();
|
||
bool flag = PrintWindow(hwnd, hdc, 2u);
|
||
graphics.ReleaseHdc(hdc);
|
||
if (!flag)
|
||
{
|
||
graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(w, h), CopyPixelOperation.SourceCopy);
|
||
}
|
||
return bitmap;
|
||
}
|
||
|
||
private static Rectangle GetAllScreenBounds()
|
||
{
|
||
Screen[] allScreens = Screen.AllScreens;
|
||
int num = allScreens.Min((Screen s) => s.Bounds.X);
|
||
int num2 = allScreens.Min((Screen s) => s.Bounds.Y);
|
||
int num3 = allScreens.Max((Screen s) => s.Bounds.Right);
|
||
int num4 = allScreens.Max((Screen s) => s.Bounds.Bottom);
|
||
return new Rectangle(num, num2, num3 - num, num4 - num2);
|
||
}
|
||
|
||
private static nint FindScrollableChild(nint hwnd)
|
||
{
|
||
string[] array = new string[7] { "Internet Explorer_Server", "Chrome_RenderWidgetHostHWND", "MozillaWindowClass", "RichEdit20W", "RICHEDIT50W", "TextBox", "EDIT" };
|
||
foreach (string className in array)
|
||
{
|
||
nint num = FindWindowEx(hwnd, IntPtr.Zero, className, null);
|
||
if (num != IntPtr.Zero)
|
||
{
|
||
return num;
|
||
}
|
||
}
|
||
return IntPtr.Zero;
|
||
}
|
||
|
||
private static void SendPageDown(nint hwnd)
|
||
{
|
||
SendMessage(hwnd, 256u, new IntPtr(34), IntPtr.Zero);
|
||
SendMessage(hwnd, 257u, new IntPtr(34), IntPtr.Zero);
|
||
}
|
||
|
||
private unsafe static bool AreSimilar(Bitmap a, Bitmap b)
|
||
{
|
||
if (a.Width != b.Width || a.Height != b.Height)
|
||
{
|
||
return false;
|
||
}
|
||
int num = (int)((double)a.Height * 0.8);
|
||
int width = a.Width;
|
||
int height = a.Height;
|
||
Rectangle rect = new Rectangle(0, num, width, height - num);
|
||
Rectangle rect2 = new Rectangle(0, num, width, height - num);
|
||
BitmapData bitmapData = a.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||
BitmapData bitmapData2 = b.LockBits(rect2, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||
try
|
||
{
|
||
int num2 = 0;
|
||
int num3 = 0;
|
||
int stride = bitmapData.Stride;
|
||
int num4 = width / 16 + 1;
|
||
int num5 = (height - num) / 8 + 1;
|
||
byte* ptr = (byte*)((IntPtr)bitmapData.Scan0).ToPointer();
|
||
byte* ptr2 = (byte*)((IntPtr)bitmapData2.Scan0).ToPointer();
|
||
for (int i = 0; i < num5; i++)
|
||
{
|
||
int num6 = i * 8;
|
||
if (num6 >= height - num)
|
||
{
|
||
break;
|
||
}
|
||
for (int j = 0; j < num4; j++)
|
||
{
|
||
int num7 = j * 16;
|
||
if (num7 >= width)
|
||
{
|
||
break;
|
||
}
|
||
int num8 = num6 * stride + num7 * 4;
|
||
if (Math.Abs(ptr[num8] - ptr2[num8]) < 5 && Math.Abs(ptr[num8 + 1] - ptr2[num8 + 1]) < 5 && Math.Abs(ptr[num8 + 2] - ptr2[num8 + 2]) < 5)
|
||
{
|
||
num2++;
|
||
}
|
||
num3++;
|
||
}
|
||
}
|
||
return num3 > 0 && (double)num2 / (double)num3 > 0.97;
|
||
}
|
||
finally
|
||
{
|
||
a.UnlockBits(bitmapData);
|
||
b.UnlockBits(bitmapData2);
|
||
}
|
||
}
|
||
|
||
private static Bitmap StitchFrames(List<Bitmap> frames, int windowHeight)
|
||
{
|
||
if (frames.Count == 0)
|
||
{
|
||
return new Bitmap(1, 1);
|
||
}
|
||
if (frames.Count == 1)
|
||
{
|
||
return new Bitmap(frames[0]);
|
||
}
|
||
int width = frames[0].Width;
|
||
List<int> list = new List<int>();
|
||
List<int> list2 = new List<int>();
|
||
int num = windowHeight;
|
||
for (int i = 1; i < frames.Count; i++)
|
||
{
|
||
int num2 = FindOverlap(frames[i - 1], frames[i]);
|
||
int num3 = ((num2 > 0) ? num2 : (windowHeight / 5));
|
||
int num4 = windowHeight - num3;
|
||
if (num4 <= 0)
|
||
{
|
||
num4 = windowHeight / 4;
|
||
num3 = windowHeight - num4;
|
||
}
|
||
list.Add(num3);
|
||
list2.Add(num4);
|
||
num += num4;
|
||
}
|
||
Bitmap bitmap = new Bitmap(width, num, PixelFormat.Format32bppArgb);
|
||
using Graphics graphics = Graphics.FromImage(bitmap);
|
||
graphics.DrawImage(frames[0], 0, 0, width, windowHeight);
|
||
int num5 = windowHeight;
|
||
for (int j = 1; j < frames.Count; j++)
|
||
{
|
||
int y = list[j - 1];
|
||
int num6 = list2[j - 1];
|
||
graphics.DrawImage(srcRect: new Rectangle(0, y, width, num6), destRect: new Rectangle(0, num5, width, num6), image: frames[j], srcUnit: GraphicsUnit.Pixel);
|
||
num5 += num6;
|
||
}
|
||
return bitmap;
|
||
}
|
||
|
||
private unsafe static int FindOverlap(Bitmap prev, Bitmap next)
|
||
{
|
||
int num = Math.Min(prev.Width, next.Width);
|
||
int height = prev.Height;
|
||
if (height < 16 || num < 16)
|
||
{
|
||
return 0;
|
||
}
|
||
int num2 = (int)((double)height * 0.7);
|
||
BitmapData bitmapData = prev.LockBits(new Rectangle(0, 0, prev.Width, prev.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||
BitmapData bitmapData2 = next.LockBits(new Rectangle(0, 0, next.Width, next.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||
try
|
||
{
|
||
int stride = bitmapData.Stride;
|
||
int stride2 = bitmapData2.Stride;
|
||
int result = 0;
|
||
byte* ptr = (byte*)((IntPtr)bitmapData.Scan0).ToPointer();
|
||
byte* ptr2 = (byte*)((IntPtr)bitmapData2.Scan0).ToPointer();
|
||
for (int num3 = num2; num3 > 8; num3 -= 2)
|
||
{
|
||
int num4 = height - num3;
|
||
if (num4 >= 0)
|
||
{
|
||
int num5 = 0;
|
||
int num6 = 0;
|
||
for (int i = 0; i < 8; i++)
|
||
{
|
||
int num7 = i * (num3 / 8);
|
||
int num8 = num4 + num7;
|
||
int num9 = num7;
|
||
if (num8 >= height || num9 >= next.Height)
|
||
{
|
||
continue;
|
||
}
|
||
for (int j = 4; j < num - 4; j += 12)
|
||
{
|
||
int num10 = num8 * stride + j * 4;
|
||
int num11 = num9 * stride2 + j * 4;
|
||
if (num10 + 2 < bitmapData.Height * stride && num11 + 2 < bitmapData2.Height * stride2)
|
||
{
|
||
if (Math.Abs(ptr[num10] - ptr2[num11]) < 10 && Math.Abs(ptr[num10 + 1] - ptr2[num11 + 1]) < 10 && Math.Abs(ptr[num10 + 2] - ptr2[num11 + 2]) < 10)
|
||
{
|
||
num5++;
|
||
}
|
||
num6++;
|
||
}
|
||
}
|
||
}
|
||
if (num6 > 0 && (double)num5 / (double)num6 > 0.8)
|
||
{
|
||
result = num3;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
finally
|
||
{
|
||
prev.UnlockBits(bitmapData);
|
||
next.UnlockBits(bitmapData2);
|
||
}
|
||
}
|
||
|
||
private async Task CaptureRegionAsync(string timestamp, CancellationToken ct)
|
||
{
|
||
Rectangle bounds = GetAllScreenBounds();
|
||
Bitmap fullBmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
|
||
try
|
||
{
|
||
using (Graphics g = Graphics.FromImage(fullBmp))
|
||
{
|
||
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
|
||
}
|
||
Rectangle? selected = null;
|
||
System.Windows.Application current = System.Windows.Application.Current;
|
||
if (current != null)
|
||
{
|
||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||
{
|
||
RegionSelectWindow regionSelectWindow = new RegionSelectWindow(fullBmp, bounds);
|
||
regionSelectWindow.ShowDialog();
|
||
selected = regionSelectWindow.SelectedRect;
|
||
});
|
||
}
|
||
if (!selected.HasValue || selected.Value.Width < 4 || selected.Value.Height < 4)
|
||
{
|
||
NotificationService.Notify("AX Copilot", "영역 선택이 취소되었습니다.");
|
||
return;
|
||
}
|
||
Rectangle r = selected.Value;
|
||
using Bitmap crop = new Bitmap(r.Width, r.Height, PixelFormat.Format32bppArgb);
|
||
using (Graphics g2 = Graphics.FromImage(crop))
|
||
{
|
||
g2.DrawImage(fullBmp, new Rectangle(0, 0, r.Width, r.Height), r, GraphicsUnit.Pixel);
|
||
}
|
||
CopyToClipboard(crop);
|
||
NotificationService.Notify("영역 캡처 완료", $"{r.Width}×{r.Height} · 클립보드에 복사되었습니다");
|
||
await Task.CompletedTask;
|
||
}
|
||
finally
|
||
{
|
||
if (fullBmp != null)
|
||
{
|
||
((IDisposable)fullBmp).Dispose();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void CopyToClipboard(Bitmap bmp)
|
||
{
|
||
try
|
||
{
|
||
System.Windows.Application current = System.Windows.Application.Current;
|
||
if (current == null)
|
||
{
|
||
return;
|
||
}
|
||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||
{
|
||
using MemoryStream memoryStream = new MemoryStream();
|
||
bmp.Save(memoryStream, ImageFormat.Bmp);
|
||
memoryStream.Position = 0L;
|
||
BitmapImage bitmapImage = new BitmapImage();
|
||
bitmapImage.BeginInit();
|
||
bitmapImage.StreamSource = memoryStream;
|
||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||
bitmapImage.EndInit();
|
||
((Freezable)bitmapImage).Freeze();
|
||
System.Windows.Clipboard.SetImage(bitmapImage);
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogService.Warn("클립보드 이미지 복사 실패: " + ex.Message);
|
||
}
|
||
}
|
||
|
||
private static nint GetLauncherHwnd()
|
||
{
|
||
try
|
||
{
|
||
nint hwnd = IntPtr.Zero;
|
||
System.Windows.Application current = System.Windows.Application.Current;
|
||
if (current != null)
|
||
{
|
||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||
{
|
||
Window window = System.Windows.Application.Current.Windows.OfType<Window>().FirstOrDefault((Window w) => ((object)w).GetType().Name == "LauncherWindow");
|
||
if (window != null)
|
||
{
|
||
hwnd = new WindowInteropHelper(window).Handle;
|
||
}
|
||
});
|
||
}
|
||
return hwnd;
|
||
}
|
||
catch
|
||
{
|
||
return IntPtr.Zero;
|
||
}
|
||
}
|
||
}
|