diff --git a/windows/window.c b/windows/window.c index eab0aff9..d147b980 100644 --- a/windows/window.c +++ b/windows/window.c @@ -2172,6 +2172,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, static bool ignore_clip = false; static bool fullscr_on_max = false; static bool processed_resize = false; + static bool in_scrollbar_loop = false; static UINT last_mousemove = 0; int resize_action; @@ -2226,6 +2227,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_COMMAND: case WM_SYSCOMMAND: switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ + case SC_VSCROLL: + case SC_HSCROLL: + if (message == WM_SYSCOMMAND) { + /* As per the long comment in WM_VSCROLL handler: give + * this message the default handling, which starts a + * subsidiary message loop, but set a flag so that + * when we're re-entered from that loop, scroll events + * within an interactive scrollbar-drag can be handled + * differently. */ + in_scrollbar_loop = true; + LRESULT result = DefWindowProcW(hwnd, message, wParam, lParam); + in_scrollbar_loop = false; + return result; + } + break; case IDM_SHOWLOG: showeventlog(hwnd); break; @@ -3138,6 +3154,54 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (GetScrollInfo(hwnd, SB_VERT, &si) == 0) si.nTrackPos = HIWORD(wParam); term_scroll(term, 1, si.nTrackPos); + + if (in_scrollbar_loop) { + /* + * Allow window updates to happen during interactive + * scroll. + * + * When the user takes hold of our window's scrollbar + * and wobbles it interactively back and forth, the + * first thing that happens is that this window + * procedure receives WM_SYSCOMMAND / SC_VSCROLL. [1] + * The default handler for that window message starts + * a subsidiary message loop, which continues to run + * until the user lets go of the scrollbar again. All + * WM_VSCROLL / SB_THUMBTRACK messages are generated + * by the handlers within that subsidiary message + * loop. + * + * So, during that time, _our_ message loop is not + * running, which means toplevel callbacks and timers + * and so forth are not happening, which means that + * when we redraw the window and set a timer to clear + * the cooldown flag 20ms later, that timer never + * fires, and we aren't able to keep redrawing the + * window. + * + * The 'obvious' answer would be to seize that + * SYSCOMMAND ourselves and inhibit the default + * handler, so that our message loop carries on + * running. But that would mean we'd have to + * reimplement the whole of the scrollbar handler! + * + * So instead we apply a bodge: set a static variable + * that indicates that we're _in_ that sub-loop, and + * if so, decide it's OK to manually call + * term_update() proper, bypassing the timer and + * cooldown and rate-limiting systems completely, + * whenever we see an SB_THUMBTRACK. This shouldn't + * cause a rate overload, because we're only doing it + * once per UI event! + * + * [1] Actually, there's an extra oddity where + * SC_HSCROLL and SC_VSCROLL have their documented + * values the wrong way round. Many people on the + * Internet have noticed this, e.g. + * https://stackoverflow.com/q/55528397 + */ + term_update(term); + } break; } }