Compare commits

...

5 Commits

Author SHA1 Message Date
Niels Laute
50b8c9befb Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-29 13:59:41 +02:00
Niels Laute
2f85273f3e Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-29 13:59:27 +02:00
Niels Laute
19c2ef2347 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-29 13:59:13 +02:00
Niels Laute
501642ec20 Make bug report dialog header wrap and widen window
The failed/done header could be a long localized sentence; give it a
two-line height and a slightly larger window so it wraps instead of being
clipped at the window edge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-28 13:25:37 +02:00
Niels Laute
e4f69b6bc4 Add progress/result window for Bug Report with GitHub issue shortcut
Replace the silent 30s wait + end-of-run message box with a small native
window that shows an animated 'Generating bug report...' state while
PowerToys.BugReportTool.exe runs. When it finishes, the window shows where
the .zip was saved and offers 'Open folder' and 'Report on GitHub' actions.

The GitHub action opens the prefilled bug_report.yml issue template and
reveals the .zip in Explorer so the user can drag it into the issue (GitHub
has no API to pre-attach files to an issue).

The window is shared by both triggers (tray menu + Settings) since it lives
in the runner, uses the canonical foreground-steal recipe so it surfaces
even when launched from Settings, and adds no WinUI/binary payload.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-28 12:48:00 +02:00
6 changed files with 567 additions and 15 deletions

View File

@@ -186,6 +186,34 @@
<data name="BUGREPORT_SUCCESS" xml:space="preserve">
<value>Bug report .zip file has been created on your Desktop.</value>
</data>
<data name="BUGREPORT_DIALOG_TITLE" xml:space="preserve">
<value>PowerToys Bug Report</value>
</data>
<data name="BUGREPORT_GENERATING" xml:space="preserve">
<value>Generating bug report</value>
<comment>Shown while the bug report is being created. A few animated dots are appended at runtime.</comment>
</data>
<data name="BUGREPORT_GENERATING_HINT" xml:space="preserve">
<value>Collecting logs and diagnostics. This can take up to a minute.</value>
</data>
<data name="BUGREPORT_DONE_HEADER" xml:space="preserve">
<value>Your bug report is ready.</value>
</data>
<data name="BUGREPORT_DONE_HINT" xml:space="preserve">
<value>After the GitHub issue page opens, drag the highlighted .zip file into the report to attach it.</value>
</data>
<data name="BUGREPORT_FAILED" xml:space="preserve">
<value>Could not create the bug report. Please try again.</value>
</data>
<data name="BUGREPORT_OPEN_FOLDER" xml:space="preserve">
<value>Open folder</value>
</data>
<data name="BUGREPORT_CREATE_ISSUE" xml:space="preserve">
<value>Report on GitHub</value>
</data>
<data name="BUGREPORT_CLOSE" xml:space="preserve">
<value>Close</value>
</data>
<data name="PT_VERSION_CHANGE_ASK_FOR_COMPUTER_RESTART" xml:space="preserve">
<value>A new PowerToys version has been installed. Please restart the computer, when possible, to fully reload File Explorer extensions.</value>
<comment>File Explorer refers to the Windows File Explorer application.</comment>

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "bug_report.h"
#include "bug_report_dialog.h"
#include "Generated files/resource.h"
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
@@ -50,21 +51,14 @@ void BugReportManager::launch_bug_report() noexcept
notify_observers(true);
std::thread([this, bug_report_path]() {
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE };
sei.lpFile = bug_report_path.c_str();
sei.nShow = SW_HIDE;
if (ShellExecuteExW(&sei))
{
WaitForSingleObject(sei.hProcess, INFINITE);
CloseHandle(sei.hProcess);
static const std::wstring bugreport_success = GET_RESOURCE_STRING(IDS_BUGREPORT_SUCCESS);
MessageBoxW(nullptr, bugreport_success.c_str(), L"PowerToys", MB_OK);
}
m_isBugReportRunning.store(false);
// Notify observers that bug report has finished
notify_observers(false);
// Shows the progress/result window and runs the tool. The running
// state is cleared as soon as the tool process exits, so the window
// with the result and "Report on GitHub" action can stay open
// afterwards without keeping the UI in a "running" state.
run_bug_report_dialog(bug_report_path, [this]() {
m_isBugReportRunning.store(false);
notify_observers(false);
});
}).detach();
}
else

View File

@@ -0,0 +1,507 @@
#include "pch.h"
#include "bug_report_dialog.h"
#include "Generated files/resource.h"
#include <common/utils/resources.h>
#include <common/version/version.h>
#include <ShlObj.h>
#include <shellapi.h>
#include <atomic>
#include <filesystem>
#include <mutex>
#include <string>
namespace
{
constexpr UINT WM_BUGREPORT_DONE = WM_APP + 1;
constexpr UINT WM_BUGREPORT_FAILED = WM_APP + 2;
constexpr int ID_BTN_OPEN_FOLDER = 1001;
constexpr int ID_BTN_CREATE_ISSUE = 1002;
constexpr int ID_BTN_CLOSE = 1003;
constexpr UINT_PTR ANIM_TIMER_ID = 1;
const wchar_t* kWindowClass = L"PowerToysBugReportDialog";
// Only one bug report dialog can be open at a time.
std::atomic<HWND> g_dialogWnd{ nullptr };
struct DialogState
{
UINT dpi = 96;
HWND hStatus = nullptr; // header text (generating / done / failed)
HWND hPath = nullptr; // read-only edit showing the .zip path
HWND hHint = nullptr; // secondary explanatory text
HWND hOpenFolder = nullptr;
HWND hCreateIssue = nullptr;
HWND hClose = nullptr;
HFONT hFont = nullptr;
HFONT hHeaderFont = nullptr;
std::wstring zipPath;
int animDots = 0;
bool done = false;
};
int Scale(int value, UINT dpi)
{
return MulDiv(value, static_cast<int>(dpi), 96);
}
HFONT MakeFont(UINT dpi, bool header)
{
NONCLIENTMETRICSW ncm{ sizeof(ncm) };
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
LOGFONTW lf = ncm.lfMessageFont;
const int pointSize = header ? 14 : 9;
lf.lfHeight = -MulDiv(pointSize, static_cast<int>(dpi), 72);
lf.lfWeight = header ? FW_SEMIBOLD : FW_NORMAL;
return CreateFontIndirectW(&lf);
}
void ShowCtl(HWND ctl, bool show)
{
ShowWindow(ctl, show ? SW_SHOW : SW_HIDE);
}
std::wstring GetDesktopPath()
{
PWSTR raw = nullptr;
std::wstring result;
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Desktop, KF_FLAG_DEFAULT, nullptr, &raw)))
{
result = raw;
}
if (raw)
{
CoTaskMemFree(raw);
}
return result;
}
// The tool names its output PowerToysReport_<timestamp>.zip, so pick the most
// recently written matching file in the destination folder.
std::wstring FindNewestReport(const std::wstring& folder)
{
namespace fs = std::filesystem;
if (folder.empty())
{
return {};
}
std::wstring best;
fs::file_time_type bestTime{};
std::error_code ec;
for (const auto& entry : fs::directory_iterator(folder, ec))
{
if (ec)
{
break;
}
std::error_code itemEc;
if (!entry.is_regular_file(itemEc))
{
continue;
}
const auto name = entry.path().filename().wstring();
if (name.rfind(L"PowerToysReport_", 0) != 0 || entry.path().extension() != L".zip")
{
continue;
}
const auto writeTime = fs::last_write_time(entry, itemEc);
if (itemEc)
{
continue;
}
if (best.empty() || writeTime > bestTime)
{
best = entry.path().wstring();
bestTime = writeTime;
}
}
return best;
}
// The dialog is created from the runner (a background process). When the
// request comes from Settings, Settings owns the foreground, so a plain
// SetForegroundWindow would leave the dialog hidden behind it. Use the
// canonical AttachThreadInput recipe to reliably bring it to the front.
void ForceForeground(HWND hwnd)
{
const HWND foreground = GetForegroundWindow();
const DWORD targetThread = GetWindowThreadProcessId(hwnd, nullptr);
const DWORD foregroundThread = foreground ? GetWindowThreadProcessId(foreground, nullptr) : 0;
const bool attach = foregroundThread != 0 && foregroundThread != targetThread;
if (attach)
{
AttachThreadInput(foregroundThread, targetThread, TRUE);
}
BringWindowToTop(hwnd);
SetForegroundWindow(hwnd);
SetActiveWindow(hwnd);
if (attach)
{
AttachThreadInput(foregroundThread, targetThread, FALSE);
}
}
void RevealZip(const std::wstring& path) {
if (path.empty())
{
return;
}
const std::wstring args = L"/select,\"" + path + L"\"";
ShellExecuteW(nullptr, L"open", L"explorer.exe", args.c_str(), nullptr, SW_SHOWNORMAL);
}
void OpenIssuePage(const std::wstring& zipPath)
{
const std::wstring url =
L"https://github.com/microsoft/PowerToys/issues/new?template=bug_report.yml&labels=Issue-Bug%2CTriage-Needed&version=" +
get_product_version();
ShellExecuteW(nullptr, L"open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
// Reveal the .zip so the user can drag it into the freshly opened issue page.
RevealZip(zipPath);
}
HWND CreateButton(HWND parent, int id, const std::wstring& text, int x, int y, int w, int h, HFONT font, HINSTANCE inst, bool visible)
{
HWND btn = CreateWindowExW(0,
L"BUTTON",
text.c_str(),
WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON | (visible ? WS_VISIBLE : 0),
x,
y,
w,
h,
parent,
reinterpret_cast<HMENU>(static_cast<INT_PTR>(id)),
inst,
nullptr);
SendMessageW(btn, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
return btn;
}
void CreateControls(HWND hwnd, DialogState* st)
{
const HINSTANCE inst = reinterpret_cast<HINSTANCE>(&__ImageBase);
const UINT dpi = st->dpi;
RECT cr{};
GetClientRect(hwnd, &cr);
const int margin = Scale(20, dpi);
const int contentW = cr.right - 2 * margin;
int y = margin;
st->hStatus = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX, margin, y, contentW, Scale(58, dpi), hwnd, nullptr, inst, nullptr);
SendMessageW(st->hStatus, WM_SETFONT, reinterpret_cast<WPARAM>(st->hHeaderFont), TRUE);
y += Scale(64, dpi);
st->hPath = CreateWindowExW(0, L"EDIT", L"", WS_CHILD | ES_READONLY | ES_AUTOHSCROLL | WS_TABSTOP, margin, y, contentW, Scale(24, dpi), hwnd, nullptr, inst, nullptr);
SendMessageW(st->hPath, WM_SETFONT, reinterpret_cast<WPARAM>(st->hFont), TRUE);
ShowCtl(st->hPath, false);
y += Scale(34, dpi);
st->hHint = CreateWindowExW(0, L"STATIC", GET_RESOURCE_STRING(IDS_BUGREPORT_GENERATING_HINT).c_str(), WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX, margin, y, contentW, Scale(48, dpi), hwnd, nullptr, inst, nullptr);
SendMessageW(st->hHint, WM_SETFONT, reinterpret_cast<WPARAM>(st->hFont), TRUE);
const int btnH = Scale(30, dpi);
const int btnY = cr.bottom - margin - btnH;
const int gap = Scale(10, dpi);
const int wOpen = Scale(110, dpi);
const int wIssue = Scale(150, dpi);
const int wClose = Scale(90, dpi);
st->hOpenFolder = CreateButton(hwnd, ID_BTN_OPEN_FOLDER, GET_RESOURCE_STRING(IDS_BUGREPORT_OPEN_FOLDER), margin, btnY, wOpen, btnH, st->hFont, inst, false);
st->hCreateIssue = CreateButton(hwnd, ID_BTN_CREATE_ISSUE, GET_RESOURCE_STRING(IDS_BUGREPORT_CREATE_ISSUE), margin + wOpen + gap, btnY, wIssue, btnH, st->hFont, inst, false);
st->hClose = CreateButton(hwnd, ID_BTN_CLOSE, GET_RESOURCE_STRING(IDS_BUGREPORT_CLOSE), cr.right - margin - wClose, btnY, wClose, btnH, st->hFont, inst, true);
}
void SetDoneState(DialogState* st)
{
st->done = true;
SetWindowTextW(st->hStatus, GET_RESOURCE_STRING(IDS_BUGREPORT_DONE_HEADER).c_str());
SetWindowTextW(st->hPath, st->zipPath.c_str());
SetWindowTextW(st->hHint, GET_RESOURCE_STRING(IDS_BUGREPORT_DONE_HINT).c_str());
ShowCtl(st->hPath, true);
ShowCtl(st->hOpenFolder, true);
ShowCtl(st->hCreateIssue, true);
SetFocus(st->hCreateIssue);
}
void SetFailedState(DialogState* st)
{
st->done = true;
SetWindowTextW(st->hStatus, GET_RESOURCE_STRING(IDS_BUGREPORT_FAILED).c_str());
ShowCtl(st->hPath, false);
ShowCtl(st->hHint, false);
ShowCtl(st->hOpenFolder, false);
ShowCtl(st->hCreateIssue, false);
SetFocus(st->hClose);
}
LRESULT CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
DialogState* st = reinterpret_cast<DialogState*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
switch (msg)
{
case WM_CREATE:
{
auto* cs = reinterpret_cast<CREATESTRUCTW*>(lParam);
st = static_cast<DialogState*>(cs->lpCreateParams);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(st));
CreateControls(hwnd, st);
SetWindowTextW(st->hStatus, GET_RESOURCE_STRING(IDS_BUGREPORT_GENERATING).c_str());
SetTimer(hwnd, ANIM_TIMER_ID, 400, nullptr);
return 0;
}
case WM_TIMER:
if (st && wParam == ANIM_TIMER_ID && !st->done)
{
st->animDots = (st->animDots + 1) % 4;
std::wstring text = GET_RESOURCE_STRING(IDS_BUGREPORT_GENERATING);
text.append(static_cast<size_t>(st->animDots), L'.');
SetWindowTextW(st->hStatus, text.c_str());
}
return 0;
case WM_BUGREPORT_DONE:
if (st)
{
KillTimer(hwnd, ANIM_TIMER_ID);
SetDoneState(st);
}
return 0;
case WM_BUGREPORT_FAILED:
if (st)
{
KillTimer(hwnd, ANIM_TIMER_ID);
SetFailedState(st);
}
return 0;
case WM_CTLCOLORSTATIC:
SetBkColor(reinterpret_cast<HDC>(wParam), GetSysColor(COLOR_WINDOW));
SetTextColor(reinterpret_cast<HDC>(wParam), GetSysColor(COLOR_WINDOWTEXT));
return reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_WINDOW));
case WM_COMMAND:
if (st)
{
switch (LOWORD(wParam))
{
case ID_BTN_OPEN_FOLDER:
RevealZip(st->zipPath);
return 0;
case ID_BTN_CREATE_ISSUE:
OpenIssuePage(st->zipPath);
return 0;
case ID_BTN_CLOSE:
DestroyWindow(hwnd);
return 0;
}
}
return 0;
case WM_CLOSE:
DestroyWindow(hwnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
void RegisterDialogClass()
{
static std::once_flag flag;
std::call_once(flag, []() {
WNDCLASSEXW wc{ sizeof(wc) };
wc.lpfnWndProc = DlgProc;
wc.hInstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
wc.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wc.lpszClassName = kWindowClass;
wc.hIcon = LoadIconW(reinterpret_cast<HINSTANCE>(&__ImageBase), MAKEINTRESOURCEW(APPICON));
RegisterClassExW(&wc);
});
}
}
void run_bug_report_dialog(const std::wstring& toolPath, const std::function<void()>& onProcessFinished)
{
// If a result window from a previous run is still open, just focus it
// instead of starting a second report.
if (HWND existing = g_dialogWnd.load())
{
if (IsWindow(existing))
{
ForceForeground(existing);
if (onProcessFinished)
{
onProcessFinished();
}
return;
}
g_dialogWnd.store(nullptr);
}
RegisterDialogClass();
DialogState st;
st.dpi = GetDpiForSystem();
st.hFont = MakeFont(st.dpi, false);
st.hHeaderFont = MakeFont(st.dpi, true);
const DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
const DWORD exStyle = WS_EX_DLGMODALFRAME | WS_EX_APPWINDOW;
RECT rc{ 0, 0, Scale(480, st.dpi), Scale(270, st.dpi) };
AdjustWindowRectExForDpi(&rc, style, FALSE, exStyle, st.dpi);
const int w = rc.right - rc.left;
const int h = rc.bottom - rc.top;
RECT work{};
SystemParametersInfoW(SPI_GETWORKAREA, 0, &work, 0);
const int x = work.left + ((work.right - work.left) - w) / 2;
const int y = work.top + ((work.bottom - work.top) - h) / 2;
HWND hwnd = CreateWindowExW(exStyle,
kWindowClass,
GET_RESOURCE_STRING(IDS_BUGREPORT_DIALOG_TITLE).c_str(),
style,
x,
y,
w,
h,
nullptr,
nullptr,
reinterpret_cast<HINSTANCE>(&__ImageBase),
&st);
bool finishedNotified = false;
auto notifyFinished = [&]() {
if (!finishedNotified)
{
finishedNotified = true;
if (onProcessFinished)
{
onProcessFinished();
}
}
};
if (!hwnd)
{
// Fall back to running the tool without UI so a report is still produced.
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
sei.lpFile = toolPath.c_str();
sei.nShow = SW_HIDE;
if (ShellExecuteExW(&sei) && sei.hProcess)
{
WaitForSingleObject(sei.hProcess, INFINITE);
CloseHandle(sei.hProcess);
}
notifyFinished();
if (st.hFont)
{
DeleteObject(st.hFont);
}
if (st.hHeaderFont)
{
DeleteObject(st.hHeaderFont);
}
return;
}
g_dialogWnd.store(hwnd);
ShowWindow(hwnd, SW_SHOW);
ForceForeground(hwnd);
const std::wstring desktop = GetDesktopPath();
STARTUPINFOW si{ sizeof(si) };
PROCESS_INFORMATION pi{};
std::wstring cmd = L"\"" + toolPath + L"\"";
const BOOL started = CreateProcessW(nullptr, cmd.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi);
bool processHandled = !started;
if (!started)
{
PostMessageW(hwnd, WM_BUGREPORT_FAILED, 0, 0);
notifyFinished();
}
MSG msg;
bool running = true;
while (running)
{
const DWORD count = processHandled ? 0 : 1;
HANDLE handles[1] = { pi.hProcess };
const DWORD result = MsgWaitForMultipleObjects(count, count ? handles : nullptr, FALSE, INFINITE, QS_ALLINPUT);
if (count == 1 && result == WAIT_OBJECT_0)
{
DWORD exitCode = 1;
GetExitCodeProcess(pi.hProcess, &exitCode);
processHandled = true;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
pi.hProcess = nullptr;
pi.hThread = nullptr;
const std::wstring zip = (exitCode == 0) ? FindNewestReport(desktop) : std::wstring{};
if (exitCode == 0 && !zip.empty())
{
st.zipPath = zip;
PostMessageW(hwnd, WM_BUGREPORT_DONE, 0, 0);
}
else
{
PostMessageW(hwnd, WM_BUGREPORT_FAILED, 0, 0);
}
notifyFinished();
continue;
}
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
running = false;
break;
}
if (!IsDialogMessageW(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
g_dialogWnd.store(nullptr);
// If the window was closed before the tool finished, keep waiting for the tool
// so we only clear the "running" state once the process actually exits.
if (pi.hProcess)
{
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
pi.hProcess = nullptr;
}
if (pi.hThread)
{
CloseHandle(pi.hThread);
pi.hThread = nullptr;
}
notifyFinished();
if (st.hFont)
{
DeleteObject(st.hFont);
}
if (st.hHeaderFont)
{
DeleteObject(st.hHeaderFont);
}
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <functional>
#include <string>
// Shows a small native progress/result window while the Bug Report tool runs.
// - Displays an animated "Generating bug report..." state while toolPath executes.
// - When the tool finishes, switches to a result state that shows where the
// .zip was saved and offers "Open folder" and "Report on GitHub" actions.
//
// This function blocks until the user closes the window, so it must be called
// from a dedicated worker thread. onProcessFinished is invoked exactly once,
// as soon as the tool process exits (before the window is closed), so callers
// can clear any "running" state independently of the result window lifetime.
void run_bug_report_dialog(const std::wstring& toolPath, const std::function<void()>& onProcessFinished);

View File

@@ -61,6 +61,7 @@
<ClCompile Include="..\common\interop\two_way_pipe_message_ipc.cpp" />
<ClCompile Include="auto_start_helper.cpp" />
<ClCompile Include="bug_report.cpp" />
<ClCompile Include="bug_report_dialog.cpp" />
<ClCompile Include="centralized_hotkeys.cpp" />
<ClCompile Include="general_settings.cpp" />
<ClCompile Include="hotkey_conflict_detector.cpp" />
@@ -84,6 +85,7 @@
<ClInclude Include="ai_detection.h" />
<ClInclude Include="auto_start_helper.h" />
<ClInclude Include="bug_report.h" />
<ClInclude Include="bug_report_dialog.h" />
<ClInclude Include="centralized_hotkeys.h" />
<ClInclude Include="quick_access_host.h" />
<ClInclude Include="general_settings.h" />

View File

@@ -45,6 +45,9 @@
<ClCompile Include="bug_report.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="bug_report_dialog.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="hotkey_conflict_detector.cpp">
<Filter>Utils</Filter>
</ClCompile>
@@ -102,6 +105,9 @@
<ClInclude Include="bug_report.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="bug_report_dialog.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="hotkey_conflict_detector.h">
<Filter>Utils</Filter>
</ClInclude>