mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-07 02:49:14 +02:00
Compare commits
5 Commits
main
...
niels9001/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50b8c9befb | ||
|
|
2f85273f3e | ||
|
|
19c2ef2347 | ||
|
|
501642ec20 | ||
|
|
e4f69b6bc4 |
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
507
src/runner/bug_report_dialog.cpp
Normal file
507
src/runner/bug_report_dialog.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
15
src/runner/bug_report_dialog.h
Normal file
15
src/runner/bug_report_dialog.h
Normal 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);
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user