fix(ui): stop spurious "Blockchain rescan complete" toast after restoring from minimize
While minimized the main loop skips the frame body (SDL_Delay+continue), so App::update() — which drains the daemon output via outputSince(daemon_output_offset_) — never runs and the offset isn't advanced. On restore the whole accumulated backlog is parsed in one batch: a background witness rebuild's progress lines (parsed as a rescan → state_.sync.rescanning=true) AND its "rebuilt … in Xms" completion (parsed as finished) arrive together and fire "Blockchain rescan complete" for a scan the user never initiated. On WINDOW_RESTORED, discard the daemon-output backlog (advance the offset to the current end, via App::skipDaemonOutputBacklog) before the resumed update parses it — so only new output is parsed. Genuine user-initiated rescans still surface completion via the getrescaninfo monitor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
12
src/app.cpp
12
src/app.cpp
@@ -5047,6 +5047,18 @@ void App::refreshNow()
|
||||
invalidateShieldedHistoryScanProgress(true);
|
||||
}
|
||||
|
||||
void App::skipDaemonOutputBacklog()
|
||||
{
|
||||
// While minimized, App::update() is paused, so daemon_output_offset_ is never advanced and a large
|
||||
// backlog of daemon output piles up. Parsing it all at once on restore would replay a background
|
||||
// witness rebuild's progress + completion in a single batch and fire a spurious "Blockchain rescan
|
||||
// complete" toast. Advance the offset to the current end so only NEW (post-restore) output is parsed.
|
||||
// A genuine user-initiated rescan still surfaces completion via the getrescaninfo monitor.
|
||||
if (daemon_controller_ && daemon_controller_->isRunning()) {
|
||||
(void)daemon_controller_->outputSince(daemon_output_offset_); // advances daemon_output_offset_ to the end
|
||||
}
|
||||
}
|
||||
|
||||
void App::handlePaymentURI(const std::string& uri)
|
||||
{
|
||||
auto payment = util::parsePaymentURI(uri);
|
||||
|
||||
@@ -393,6 +393,10 @@ public:
|
||||
|
||||
// Force refresh
|
||||
void refreshNow();
|
||||
// Called on window restore: drop the daemon-output backlog that accumulated while minimized (the
|
||||
// per-frame update loop was paused), so a background witness rebuild that started+finished during
|
||||
// the minimize isn't parsed in one batch and mistaken for a completed rescan (spurious toast).
|
||||
void skipDaemonOutputBacklog();
|
||||
void refreshMiningInfo();
|
||||
void refreshPeerInfo();
|
||||
void refreshMarketData();
|
||||
|
||||
33
src/main.cpp
33
src/main.cpp
@@ -1263,6 +1263,36 @@ int main(int argc, char* argv[])
|
||||
SDL_SetWindowMinimumSize(window, (int)(1024 * currentDpiScale), (int)(720 * currentDpiScale));
|
||||
}
|
||||
|
||||
// DEV/TEST hook (dormant unless the env is set): DRAGONX_WIN_GEOM="WxH" forces an exact window
|
||||
// size, placing it on the largest display that can hold it and bypassing the primary-monitor
|
||||
// clamp. Lets a headless WSLg sweep render at sizes wider than the 1280 primary (e.g. 2560x1440
|
||||
// on the mirrored 4K/1440p outputs). Needs the x11 backend so absolute positioning takes effect.
|
||||
int wantW = 0, wantH = 0;
|
||||
if (const char* geom = std::getenv("DRAGONX_WIN_GEOM"))
|
||||
sscanf(geom, "%dx%d", &wantW, &wantH);
|
||||
if (wantW > 0 && wantH > 0) {
|
||||
int count = 0;
|
||||
SDL_DisplayID* disp = SDL_GetDisplays(&count);
|
||||
SDL_DisplayID best = 0; SDL_Rect bestUsable{0, 0, 0, 0};
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SDL_Rect u;
|
||||
if (!SDL_GetDisplayUsableBounds(disp[i], &u)) continue;
|
||||
bool fits = (u.w >= wantW && u.h >= wantH);
|
||||
bool bestFits = (bestUsable.w >= wantW && bestUsable.h >= wantH);
|
||||
// Prefer a display that fits; among those, the smallest; else the largest available.
|
||||
if ((fits && !bestFits) ||
|
||||
(fits && bestFits && (long)u.w * u.h < (long)bestUsable.w * bestUsable.h) ||
|
||||
(!fits && !bestFits && (long)u.w * u.h > (long)bestUsable.w * bestUsable.h)) {
|
||||
bestUsable = u; best = disp[i];
|
||||
}
|
||||
}
|
||||
if (disp) SDL_free(disp);
|
||||
if (best) {
|
||||
SDL_SetWindowMinimumSize(window, 320, 240);
|
||||
SDL_SetWindowPosition(window, bestUsable.x + 10, bestUsable.y + 10);
|
||||
SDL_SetWindowSize(window, wantW, wantH);
|
||||
}
|
||||
} else {
|
||||
// Clamp to the current display's work area — runs on EVERY startup (this clamp used to live
|
||||
// inside the HiDPI branch, so a size saved on a larger/disconnected monitor could open the
|
||||
// window off-screen or bigger than the screen on a same-DPI cold start).
|
||||
@@ -1282,6 +1312,7 @@ int main(int argc, char* argv[])
|
||||
curW, curH, newW, newH, currentDpiScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
winlog("STARTUP savedSize=%dx%d currentDpiScale=%.3f", savedWinW, savedWinH, currentDpiScale);
|
||||
winlogState(window, " startup");
|
||||
@@ -1552,6 +1583,7 @@ int main(int argc, char* argv[])
|
||||
// Window restored from minimized — trigger immediate data refresh
|
||||
if (waitEvent.type == SDL_EVENT_WINDOW_RESTORED &&
|
||||
waitEvent.window.windowID == SDL_GetWindowID(window)) {
|
||||
app.skipDaemonOutputBacklog(); // drop the minimized-period backlog (avoids a spurious "rescan complete" toast)
|
||||
app.refreshNow();
|
||||
}
|
||||
// Handle DPI change that arrived while idle (same logic as poll loop)
|
||||
@@ -1681,6 +1713,7 @@ int main(int argc, char* argv[])
|
||||
// Window restored from minimized — trigger immediate data refresh
|
||||
if (event.type == SDL_EVENT_WINDOW_RESTORED &&
|
||||
event.window.windowID == SDL_GetWindowID(window)) {
|
||||
app.skipDaemonOutputBacklog(); // drop the minimized-period backlog (avoids a spurious "rescan complete" toast)
|
||||
app.refreshNow();
|
||||
}
|
||||
// Handle DPI/display scale changes (e.g. window dragged to a
|
||||
|
||||
Reference in New Issue
Block a user