diff --git a/WindowTranslator.Abstractions/Stores/IProcessInfoStore.cs b/WindowTranslator.Abstractions/Stores/IProcessInfoStore.cs index d2307be2..7c44932a 100644 --- a/WindowTranslator.Abstractions/Stores/IProcessInfoStore.cs +++ b/WindowTranslator.Abstractions/Stores/IProcessInfoStore.cs @@ -14,4 +14,9 @@ public interface IProcessInfoStore /// 対象のプロセスの名前 /// string Name { get; } + + /// + /// 対象がモニター(ディスプレイ)かどうか + /// + bool IsMonitor { get; } } diff --git a/WindowTranslator/Modules/Capture/WindowsGraphicsCapture.cs b/WindowTranslator/Modules/Capture/WindowsGraphicsCapture.cs index ceb4f9c8..e4a62bb2 100644 --- a/WindowTranslator/Modules/Capture/WindowsGraphicsCapture.cs +++ b/WindowTranslator/Modules/Capture/WindowsGraphicsCapture.cs @@ -8,19 +8,22 @@ using Windows.Graphics.DirectX; using Windows.Graphics.DirectX.Direct3D11; using WindowTranslator.ComponentModel; +using WindowTranslator.Stores; using static Windows.Win32.PInvoke; namespace WindowTranslator.Modules.Capture; [DefaultModule] [DisplayName("Windows標準キャプチャー")] -public sealed class WindowsGraphicsCapture(ILogger logger) : ICaptureModule, IDisposable +public sealed class WindowsGraphicsCapture(ILogger logger, IProcessInfoStore processInfo) : ICaptureModule, IDisposable { private readonly IDirect3DDevice device = Direct3D11Helper.GetOrCreateDevice()!; private readonly SemaphoreSlim processing = new(1, 1); private readonly ILogger logger = logger; + private readonly IProcessInfoStore processInfo = processInfo; private readonly CancellationTokenSource cts = new(); private nint targetWindow; + private bool isMonitor; private Direct3D11CaptureFramePool? framePool; private GraphicsCaptureSession? session; private SizeInt32 lastSize = new(1000, 1000); @@ -40,9 +43,29 @@ public void StartCapture(IntPtr targetWindow) { this.logger.LogDebug("StartCapture"); this.targetWindow = targetWindow; - var item = CaptureHelper.CreateItemForWindow(targetWindow)!; + + // ディスプレイかウィンドウかを判定 + this.isMonitor = this.processInfo.IsMonitor; + + GraphicsCaptureItem? item; + if (this.isMonitor) + { + this.logger.LogDebug("Creating capture item for monitor"); + item = CaptureHelper.CreateItemForMonitor(targetWindow); + } + else + { + this.logger.LogDebug("Creating capture item for window"); + item = CaptureHelper.CreateItemForWindow(targetWindow); + } + + if (item is null) + { + throw new InvalidOperationException("Failed to create capture item"); + } + this.lastSize = item.Size; - this.lastMaximized = IsZoomed(new(targetWindow)); + this.lastMaximized = this.isMonitor ? false : IsZoomed(new(targetWindow)); this.framePool = Direct3D11CaptureFramePool.Create(device, DirectXPixelFormat.B8G8R8A8UIntNormalized, 1, lastSize); this.framePool.FrameArrived += FramePool_FrameArrived; this.session = this.framePool.CreateCaptureSession(item); @@ -78,7 +101,8 @@ private async void FramePool_FrameArrived(Direct3D11CaptureFramePool sender, obj { return; } - if (this.lastMaximized != IsZoomed(new(targetWindow))) + // モニターの場合は最大化チェックをスキップ + if (!this.isMonitor && this.lastMaximized != IsZoomed(new(targetWindow))) { this.lastMaximized = !this.lastMaximized; this.logger.LogDebug("セッション再作成"); diff --git a/WindowTranslator/Modules/Main/CaptureMainWindow.xaml.cs b/WindowTranslator/Modules/Main/CaptureMainWindow.xaml.cs index 56f30811..76650f43 100644 --- a/WindowTranslator/Modules/Main/CaptureMainWindow.xaml.cs +++ b/WindowTranslator/Modules/Main/CaptureMainWindow.xaml.cs @@ -33,6 +33,12 @@ private void Window_Loaded(object sender, RoutedEventArgs e) private void CheckTargetWindow() { + // ディスプレイの場合はウィンドウチェックをスキップ + if (this.processInfo.IsMonitor) + { + return; + } + var windowInfo = new WINDOWINFO() { cbSize = (uint)Marshal.SizeOf() }; if (!GetWindowInfo((HWND)this.processInfo.MainWindowHandle, ref windowInfo)) { diff --git a/WindowTranslator/Modules/Main/OverlayMainWindow.xaml.cs b/WindowTranslator/Modules/Main/OverlayMainWindow.xaml.cs index e9e1f108..1eb5e6e1 100644 --- a/WindowTranslator/Modules/Main/OverlayMainWindow.xaml.cs +++ b/WindowTranslator/Modules/Main/OverlayMainWindow.xaml.cs @@ -94,10 +94,14 @@ private void Window_Loaded(object sender, RoutedEventArgs e) { this.windowHandle = new WindowInteropHelper(this).Handle; - if (!this.desktopManager.IsWindowOnCurrentVirtualDesktop(this.processInfo.MainWindowHandle)) + // ディスプレイの場合は仮想デスクトップチェックをスキップ + if (!this.processInfo.IsMonitor) { - var targetDesktop = this.desktopManager.GetWindowDesktopId(this.processInfo.MainWindowHandle); - this.desktopManager.MoveWindowToDesktop(this.windowHandle, ref targetDesktop); + if (!this.desktopManager.IsWindowOnCurrentVirtualDesktop(this.processInfo.MainWindowHandle)) + { + var targetDesktop = this.desktopManager.GetWindowDesktopId(this.processInfo.MainWindowHandle); + this.desktopManager.MoveWindowToDesktop(this.windowHandle, ref targetDesktop); + } } var extendedStyle = (WINDOW_EX_STYLE)GetWindowLong(new(windowHandle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE) | WINDOW_EX_STYLE.WS_EX_TRANSPARENT; @@ -146,6 +150,14 @@ protected override void OnClosed(EventArgs e) private unsafe void UpdateWindowPositionAndSize() { var sw = Stopwatch.StartNew(); + + // ディスプレイの場合は専用の処理 + if (this.processInfo.IsMonitor) + { + UpdateDisplayPositionAndSize(); + return; + } + var windowInfo = new WINDOWINFO() { cbSize = (uint)Marshal.SizeOf() }; if (!GetWindowInfo(new(this.processInfo.MainWindowHandle), ref windowInfo)) { @@ -213,6 +225,63 @@ private unsafe void UpdateWindowPositionAndSize() this.SetCurrentValue(HeightProperty, height / eDpiScale); } + private unsafe void UpdateDisplayPositionAndSize() + { + // モニターハンドルを取得(IntPtrとして既に持っている) + var monitorHandle = this.processInfo.MainWindowHandle; + var monitorInfo = default(MONITORINFOEXW); + monitorInfo.monitorInfo.cbSize = (uint)Marshal.SizeOf(); + + if (!GetMonitorInfo(monitorHandle, ref monitorInfo.monitorInfo)) + { + this.logger.LogWarning("Failed to get monitor info"); + return; + } + + var left = monitorInfo.monitorInfo.rcMonitor.left; + var top = monitorInfo.monitorInfo.rcMonitor.top; + var width = monitorInfo.monitorInfo.rcMonitor.right - left; + var height = monitorInfo.monitorInfo.rcMonitor.bottom - top; + + // モニター座標の検証 + if (width <= 0 || height <= 0) + { + this.logger.LogWarning($"Invalid monitor dimensions: {width}x{height}"); + return; + } + + // モニターの解像度情報を取得 + var mode = default(DEVMODEW); + var eDpiScale = GetDpiForSystem() / 96.0; + var rDpiScale = eDpiScale; + + if (EnumDisplaySettings(monitorInfo.szDevice.ToString(), ENUM_DISPLAY_SETTINGS_MODE.ENUM_CURRENT_SETTINGS, ref mode)) + { + // EnumDisplaySettings が成功した場合のみ rDpiScale を計算 + if (mode.dmPelsWidth > 0) + { + rDpiScale = eDpiScale * mode.dmPelsWidth / width; + } + } + else + { + this.logger.LogWarning("Failed to get display settings, using default DPI scale"); + } + + GetCursorPos(out var nativePos); + var x = (nativePos.X - left) / eDpiScale; + var y = (nativePos.Y - top) / eDpiScale; + + this.logger.LogDebug($"Display: (x:{left:f2}, y:{top:f2}, w:{width:f2}, h:{height:f2}), マウス位置:({x:f2}, {y:f2})"); + this.SetCurrentValue(MousePosProperty, new Point(x, y)); + this.SetCurrentValue(VisibilityProperty, Visibility.Visible); + this.SetCurrentValue(ScaleProperty, 1 / rDpiScale); + this.SetCurrentValue(LeftProperty, left / eDpiScale); + this.SetCurrentValue(TopProperty, top / eDpiScale); + this.SetCurrentValue(WidthProperty, width / eDpiScale); + this.SetCurrentValue(HeightProperty, height / eDpiScale); + } + private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) { if (msg != WM_HOTKEY) diff --git a/WindowTranslator/Modules/Startup/StartupViewModel.cs b/WindowTranslator/Modules/Startup/StartupViewModel.cs index ae1a57e7..ae9770fd 100644 --- a/WindowTranslator/Modules/Startup/StartupViewModel.cs +++ b/WindowTranslator/Modules/Startup/StartupViewModel.cs @@ -1,6 +1,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Input; using System.Windows.Interop; @@ -10,6 +11,7 @@ using Kamishibai; using Microsoft.Extensions.DependencyInjection; using Windows.Graphics.Capture; +using Windows.Win32.Graphics.Gdi; using WindowTranslator.Extensions; using WindowTranslator.Modules.Main; using WindowTranslator.Properties; @@ -101,7 +103,20 @@ public async Task RunAsync() } return; } + + // まずウィンドウとして検索 p = FindProcessByWindowTitle(item.DisplayName, item.Size); + + // ウィンドウが見つからない場合、ディスプレイとして処理 + if (p is null) + { + var displayInfo = FindDisplayBySize(item.Size); + if (displayInfo is not null) + { + p = new ProcessInfo(item.DisplayName, -1, displayInfo.Value.MonitorHandle, $"DISPLAY__{displayInfo.Value.Index}"); + } + } + if (p is null) { this.presentationService.ShowMessage(string.Format(Resources.UnknownWindow, item.DisplayName), icon: Kamishibai.MessageBoxImage.Error, owner: window); @@ -205,6 +220,39 @@ public static void Exit() return result ?? candidate; } + private (IntPtr MonitorHandle, int Index)? FindDisplayBySize(Windows.Graphics.SizeInt32 targetSize) + { + var monitors = new List<(IntPtr Handle, int Width, int Height)>(); + + // モニターを列挙 + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMonitor, hdcMonitor, lprcMonitor, dwData) => + { + var monitorInfo = new MONITORINFOEXW(); + monitorInfo.monitorInfo.cbSize = (uint)Marshal.SizeOf(); + + if (GetMonitorInfo(hMonitor, ref monitorInfo.monitorInfo)) + { + var width = monitorInfo.monitorInfo.rcMonitor.right - monitorInfo.monitorInfo.rcMonitor.left; + var height = monitorInfo.monitorInfo.rcMonitor.bottom - monitorInfo.monitorInfo.rcMonitor.top; + monitors.Add((hMonitor, width, height)); + } + + return true; + }, IntPtr.Zero); + + // サイズが一致するモニターを探す + for (int i = 0; i < monitors.Count; i++) + { + var (handle, width, height) = monitors[i]; + if (width == targetSize.Width && height == targetSize.Height) + { + return (handle, i); + } + } + + return null; + } + private record ProcessInfo(string Title, int PID, IntPtr WindowHandle, string Name); } diff --git a/WindowTranslator/NativeMethods.txt b/WindowTranslator/NativeMethods.txt index 7f4af304..2c646062 100644 --- a/WindowTranslator/NativeMethods.txt +++ b/WindowTranslator/NativeMethods.txt @@ -35,6 +35,9 @@ GetWindowTextLength GetWindowThreadProcessId GetClassName DwmGetWindowAttribute +EnumDisplayMonitors +GetMonitorInfo +MonitorFromPoint // WindowMonitor diff --git a/WindowTranslator/Properties/Resources.de.resx b/WindowTranslator/Properties/Resources.de.resx index 37ec61bb..7e200d35 100644 --- a/WindowTranslator/Properties/Resources.de.resx +++ b/WindowTranslator/Properties/Resources.de.resx @@ -386,9 +386,7 @@ Bitte wählen Sie ein anderes Fenster als WindowTranslator aus - Das ausgewählte Fenster "{0}" kann den Prozess nicht bestimmen und kann nicht erfasst werden. -Monitore werden nicht unterstützt. - + Das ausgewählte Fenster "{0}" kann den Prozess nicht bestimmen und kann nicht erfasst werden. Einstellungsvalidierungsfehler diff --git a/WindowTranslator/Properties/Resources.en.resx b/WindowTranslator/Properties/Resources.en.resx index 6cfe85f0..36d48916 100644 --- a/WindowTranslator/Properties/Resources.en.resx +++ b/WindowTranslator/Properties/Resources.en.resx @@ -386,9 +386,7 @@ Please select a window other than WindowTranslator - The selected window "{0}" cannot determine the process and cannot be captured. -Monitors are not supported. - + The selected window "{0}" cannot determine the process and cannot be captured. Settings Validation Error diff --git a/WindowTranslator/Properties/Resources.hi.resx b/WindowTranslator/Properties/Resources.hi.resx index a2a38cde..4f621733 100644 --- a/WindowTranslator/Properties/Resources.hi.resx +++ b/WindowTranslator/Properties/Resources.hi.resx @@ -328,8 +328,7 @@ कृपया WindowTranslator के अलावा कोई अन्य विंडो चुनें - चयनित विंडो "{0}" प्रक्रिया निर्धारित नहीं कर सकती और कैप्चर नहीं की जा सकती। -मॉनिटर समर्थित नहीं हैं। + चयनित विंडो "{0}" प्रक्रिया निर्धारित नहीं कर सकती और कैप्चर नहीं की जा सकती। सेटिंग्स सत्यापन त्रुटि diff --git a/WindowTranslator/Properties/Resources.id.resx b/WindowTranslator/Properties/Resources.id.resx index 152157fa..ca9fbc34 100644 --- a/WindowTranslator/Properties/Resources.id.resx +++ b/WindowTranslator/Properties/Resources.id.resx @@ -328,8 +328,7 @@ Please select a window other than WindowTranslator - Jendela yang dipilih "{0}" tidak dapat menentukan proses dan tidak dapat ditangkap. -Monitor tidak didukung. + Jendela yang dipilih "{0}" tidak dapat menentukan proses dan tidak dapat ditangkap. Settings Validation Error diff --git a/WindowTranslator/Properties/Resources.ko.resx b/WindowTranslator/Properties/Resources.ko.resx index 0601b332..ec384314 100644 --- a/WindowTranslator/Properties/Resources.ko.resx +++ b/WindowTranslator/Properties/Resources.ko.resx @@ -386,9 +386,7 @@ WindowTranslator 이외의 윈도우를 선택해 주세요 - 선택한 윈도우 "{0}"는 프로세스를 특정할 수 없어 캡처할 수 없습니다. -모니터는 지원 대상 외입니다. - + 선택한 윈도우 "{0}"는 프로세스를 특정할 수 없어 캡처할 수 없습니다. 설정 검증 오류 diff --git a/WindowTranslator/Properties/Resources.ms.resx b/WindowTranslator/Properties/Resources.ms.resx index ad7beea7..3b226c65 100644 --- a/WindowTranslator/Properties/Resources.ms.resx +++ b/WindowTranslator/Properties/Resources.ms.resx @@ -328,8 +328,7 @@ Please select a window other than WindowTranslator - Tetingkap yang dipilih "{0}" tidak dapat menentukan proses dan tidak dapat ditangkap. -Monitor tidak disokong. + Tetingkap yang dipilih "{0}" tidak dapat menentukan proses dan tidak dapat ditangkap. Settings Validation Error diff --git a/WindowTranslator/Properties/Resources.pt-BR.resx b/WindowTranslator/Properties/Resources.pt-BR.resx index 7f4c96ea..d6298190 100644 --- a/WindowTranslator/Properties/Resources.pt-BR.resx +++ b/WindowTranslator/Properties/Resources.pt-BR.resx @@ -328,8 +328,7 @@ Please select a window other than WindowTranslator - Jendela yang dipilih "{0}" tidak dapat menentukan proses dan tidak dapat ditangkap. -Monitor tidak didukung. + A janela selecionada "{0}" não pode determinar o processo e não pode ser capturada. Settings Validation Error diff --git a/WindowTranslator/Properties/Resources.resx b/WindowTranslator/Properties/Resources.resx index d2821e8c..594e6ec5 100644 --- a/WindowTranslator/Properties/Resources.resx +++ b/WindowTranslator/Properties/Resources.resx @@ -386,9 +386,7 @@ WindowTranslator以外のウィンドウを選択してください - 選択したウィンドウ「{0}」はプロセスを特定できないため、キャプチャー出来ません。 -モニターはサポート対象外です。 - + 選択したウィンドウ「{0}」はプロセスを特定できないため、キャプチャー出来ません。 設定検証エラー diff --git a/WindowTranslator/Properties/Resources.vi.resx b/WindowTranslator/Properties/Resources.vi.resx index ab80f219..a90c467e 100644 --- a/WindowTranslator/Properties/Resources.vi.resx +++ b/WindowTranslator/Properties/Resources.vi.resx @@ -386,9 +386,7 @@ Vui lòng chọn cửa sổ khác ngoài WindowTranslator - Cửa sổ đã chọn "{0}" không thể xác định tiến trình và không thể chụp. -Màn hình không được hỗ trợ. - + Cửa sổ đã chọn "{0}" không thể xác định tiến trình và không thể chụp. Lỗi xác thực cài đặt diff --git a/WindowTranslator/Properties/Resources.zh-CN.resx b/WindowTranslator/Properties/Resources.zh-CN.resx index 86580c98..82a763b8 100644 --- a/WindowTranslator/Properties/Resources.zh-CN.resx +++ b/WindowTranslator/Properties/Resources.zh-CN.resx @@ -386,9 +386,7 @@ 请选择WindowTranslator以外的窗口 - 选择的窗口"{0}"无法确定进程,无法捕获。 -不支持显示器。 - + 选择的窗口"{0}"无法确定进程,无法捕获。 设置验证错误 diff --git a/WindowTranslator/Properties/Resources.zh-TW.resx b/WindowTranslator/Properties/Resources.zh-TW.resx index af9f1f9f..996c933f 100644 --- a/WindowTranslator/Properties/Resources.zh-TW.resx +++ b/WindowTranslator/Properties/Resources.zh-TW.resx @@ -386,9 +386,7 @@ 請選擇WindowTranslator以外的視窗 - 選擇的視窗「{0}」無法確定程序,無法擷取。 -不支援顯示器。 - + 選擇的視窗「{0}」無法確定程序,無法擷取。 設定驗證錯誤 diff --git a/WindowTranslator/Stores/ProcessInfoStore.cs b/WindowTranslator/Stores/ProcessInfoStore.cs index a0ee548e..6b8c32b5 100644 --- a/WindowTranslator/Stores/ProcessInfoStore.cs +++ b/WindowTranslator/Stores/ProcessInfoStore.cs @@ -6,11 +6,13 @@ public sealed class ProcessInfoStore(IAutoTargetStore targetStore) : IProcessInf public IntPtr MainWindowHandle { get; private set; } public string Name { get; private set; } = string.Empty; + public bool IsMonitor { get; private set; } public void SetTargetProcess(IntPtr mainWindowHandle, string name) { this.MainWindowHandle = mainWindowHandle; this.Name = name; + this.IsMonitor = name.StartsWith("DISPLAY__", StringComparison.OrdinalIgnoreCase); this.targetStore.AddTarget(mainWindowHandle, name); }