Skip to content

Commit 4272c8b

Browse files
authored
Merge pull request #510 from Freeesia/feature/fix_find_target
ウィンドウ識別機能の強化
2 parents c23eb3f + e541198 commit 4272c8b

4 files changed

Lines changed: 95 additions & 15 deletions

File tree

WindowTranslator/Modules/Startup/StartupViewModel.cs

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
using Kamishibai;
1111
using Microsoft.Extensions.DependencyInjection;
1212
using Windows.Graphics.Capture;
13-
using Windows.Win32.Foundation;
1413
using WindowTranslator.Extensions;
1514
using WindowTranslator.Modules.Main;
1615
using WindowTranslator.Properties;
@@ -24,16 +23,18 @@ public partial class StartupViewModel
2423
private readonly IPresentationService presentationService;
2524
private readonly IServiceProvider serviceProvider;
2625
private readonly IMainWindowModule mainWindowModule;
26+
private readonly IVirtualDesktopManager desktopManager;
2727
private readonly ObservableCollection<MenuItemViewModel> attachingWindows;
2828
private IWindow? logView;
2929

3030
public IEnumerable<MenuItemViewModel> TaskBarIconMenus { get; }
3131

32-
public StartupViewModel(IPresentationService presentationService, IServiceProvider serviceProvider, IMainWindowModule mainWindowModule)
32+
public StartupViewModel(IPresentationService presentationService, IServiceProvider serviceProvider, IMainWindowModule mainWindowModule, IVirtualDesktopManager desktopManager)
3333
{
3434
this.presentationService = presentationService;
3535
this.serviceProvider = serviceProvider;
3636
this.mainWindowModule = mainWindowModule;
37+
this.desktopManager = desktopManager;
3738
this.attachingWindows = new(this.mainWindowModule.OpenedWindows.Select(CreateMenu));
3839
this.mainWindowModule.OpenedWindows.CollectionChanged += OpenedWindows_CollectionChanged;
3940
this.TaskBarIconMenus =
@@ -100,7 +101,7 @@ public async Task RunAsync()
100101
}
101102
return;
102103
}
103-
p = FindProcessByWindowTitle(item.DisplayName);
104+
p = FindProcessByWindowTitle(item.DisplayName, item.Size);
104105
if (p is null)
105106
{
106107
this.presentationService.ShowMessage(string.Format(Resources.UnknownWindow, item.DisplayName), icon: Kamishibai.MessageBoxImage.Error, owner: window);
@@ -152,23 +153,56 @@ private async Task OpenLogWindowAsync()
152153
public static void Exit()
153154
=> Application.Current.Shutdown();
154155

155-
private static ProcessInfo? FindProcessByWindowTitle(string targetTitle)
156+
private ProcessInfo? FindProcessByWindowTitle(string targetTitle, Windows.Graphics.SizeInt32 targetSize)
156157
{
157-
var hWnd = FindWindowEx(HWND.Null, HWND.Null, null, null);
158+
ProcessInfo? result = null;
159+
ProcessInfo? candidate = null;
158160

159-
while (hWnd != IntPtr.Zero)
161+
EnumWindows((hWnd, _) =>
160162
{
163+
if (IsIgnoreWindow(hWnd) || !this.desktopManager.IsWindowOnCurrentVirtualDesktop(hWnd))
164+
{
165+
return true;
166+
}
167+
161168
var windowTitle = GetWindowText(hWnd);
162-
if (windowTitle == targetTitle)
169+
if (windowTitle != targetTitle)
163170
{
164-
_ = GetWindowThreadProcessId(hWnd, out var processId);
165-
var p = Process.GetProcessById(processId);
166-
return new ProcessInfo(windowTitle, processId, hWnd, p.ProcessName);
171+
return true;
167172
}
168173

169-
hWnd = FindWindowEx(HWND.Null, hWnd, null, null);
170-
}
171-
return null;
174+
if (GetWindowThreadProcessId(hWnd, out var processId) == 0)
175+
{
176+
return true;
177+
}
178+
179+
Process p;
180+
try
181+
{
182+
p = Process.GetProcessById(processId);
183+
}
184+
catch (ArgumentException)
185+
{
186+
return true;
187+
}
188+
189+
// ウィンドウサイズを取得
190+
var (width, height) = GetWindowSizeForWgcCompare(hWnd);
191+
// サイズが完全一致する場合は即座に結果を設定して終了
192+
if (width == targetSize.Width && height == targetSize.Height)
193+
{
194+
result = new ProcessInfo(windowTitle, processId, hWnd, p.ProcessName);
195+
return false; // 列挙を終了
196+
}
197+
198+
// タイトルは一致するが、サイズが異なる場合は候補として保持
199+
candidate ??= new ProcessInfo(windowTitle, processId, hWnd, p.ProcessName);
200+
201+
return true;
202+
}, IntPtr.Zero);
203+
204+
// 完全一致が見つかった場合はそれを返し、そうでなければ候補を返す
205+
return result ?? candidate;
172206
}
173207

174208
private record ProcessInfo(string Title, int PID, IntPtr WindowHandle, string Name);

WindowTranslator/NativeMethods.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Runtime.CompilerServices;
1+
using System.Diagnostics;
2+
using System.Runtime.CompilerServices;
23
using System.Runtime.InteropServices;
34
using System.Runtime.Versioning;
45

@@ -51,4 +52,47 @@ internal static int SetWindowLongPtr(Foundation.HWND hWnd, UI.WindowsAndMessagin
5152
[DllImport("USER32.dll", ExactSpelling = true, EntryPoint = "SetWindowLongPtrW", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
5253
[SupportedOSPlatform("windows5.0")]
5354
private static extern int _SetWindowLongPtr(Foundation.HWND hWnd, UI.WindowsAndMessaging.WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong);
55+
56+
/// <summary>
57+
/// ウィンドウがcloaked(非表示)状態かどうかを判定します。
58+
/// 注意: このメソッドは仮想デスクトップによる非表示を完全には検出できません。
59+
/// 仮想デスクトップの判定には IVirtualDesktopManager.IsWindowOnCurrentVirtualDesktop を併用してください。
60+
/// </summary>
61+
public static unsafe bool IsCloaked(Foundation.HWND hwnd)
62+
{
63+
var cloaked = 0;
64+
var hr = DwmGetWindowAttribute(hwnd, Graphics.Dwm.DWMWINDOWATTRIBUTE.DWMWA_CLOAKED, &cloaked, sizeof(int));
65+
if (hr.Failed)
66+
{
67+
return false;
68+
}
69+
return cloaked != 0;
70+
}
71+
72+
public static bool IsIgnoreWindow(Foundation.HWND hWnd)
73+
{
74+
// 非表示ウィンドウとcloakedウィンドウをスキップ
75+
if (!IsWindowVisible(hWnd) || IsCloaked(hWnd))
76+
{
77+
return true;
78+
}
79+
// ツールチップやコンテキストメニューは無視
80+
Span<char> className = stackalloc char[256];
81+
var l = GetClassName(hWnd, className);
82+
if (className[..l] is "tooltips_class32" or "#32768")
83+
{
84+
return true;
85+
}
86+
return false;
87+
}
88+
89+
public static unsafe (int w, int h) GetWindowSizeForWgcCompare(Foundation.HWND hwnd)
90+
{
91+
// 1) 影を含まない枠サイズを取得(WGCのキャプチャ領域に近い)
92+
Foundation.RECT rect;
93+
if (DwmGetWindowAttribute(hwnd, Graphics.Dwm.DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, &rect, (uint)Marshal.SizeOf<Foundation.RECT>()) != 0)
94+
throw new InvalidOperationException("DwmGetWindowAttribute failed.");
95+
96+
return (rect.Width, rect.Height);
97+
}
5498
}

WindowTranslator/NativeMethods.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ FindWindowEx
3333
GetWindowText
3434
GetWindowTextLength
3535
GetWindowThreadProcessId
36+
GetClassName
37+
DwmGetWindowAttribute
3638

3739

3840
// WindowMonitor

WindowTranslator/WindowMonitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private void CheckProcesses()
3232
var windows = new HashSet<IntPtr>();
3333
EnumWindows((hWnd, lParam) =>
3434
{
35-
if (!IsWindowVisible(hWnd) || !this.desktopManager.IsWindowOnCurrentVirtualDesktop(hWnd) || this.checkedWindows.Contains(hWnd))
35+
if (IsIgnoreWindow(hWnd) || !this.desktopManager.IsWindowOnCurrentVirtualDesktop(hWnd) || this.checkedWindows.Contains(hWnd))
3636
{
3737
return true;
3838
}

0 commit comments

Comments
 (0)