From 0d2cbd32332f4af5ee175fcbd68c5445e34db0f4 Mon Sep 17 00:00:00 2001 From: mirkobrombin Date: Thu, 5 Nov 2026 09:04:46 +0000 Subject: [PATCH] fix: end a paint when its window changes size inside it --- native/CMakeLists.txt | 11 ++++- native/desktop/objects.cpp | 8 +++ native/desktop/objects.h | 2 + native/desktop/windows.cpp | 8 ++- testdata/native_loader/paint_resize_client.c | 52 ++++++++++++++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 testdata/native_loader/paint_resize_client.c diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 02240dc..376dd65 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -661,7 +661,16 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") -Wl,--entry,raven_entry,--image-base,0x1d0000000,--no-insert-timestamp -o ${CLIP_CLIENT} ${PROJECT_SOURCE_DIR}/testdata/native_loader/clip_client.c -luser32 -lgdi32 -lkernel32 DEPENDS ${PROJECT_SOURCE_DIR}/testdata/native_loader/clip_client.c VERBATIM) - add_custom_target(clip_pe_fixtures ALL DEPENDS ${CLIP_CLIENT} ${CLIP_PAINT_CLIENT}) + # A window that resizes itself while it paints ends that paint and paints again. + set(PAINT_RESIZE_CLIENT ${CMAKE_CURRENT_BINARY_DIR}/paint-resize-client.exe) + add_custom_command(OUTPUT ${PAINT_RESIZE_CLIENT} + COMMAND ${RAVEN_MINGW64} -O2 -nostdlib -fno-stack-protector -fno-builtin + -Wl,--entry,raven_entry,--image-base,0x1d0000000,--no-insert-timestamp + -o ${PAINT_RESIZE_CLIENT} ${PROJECT_SOURCE_DIR}/testdata/native_loader/paint_resize_client.c -luser32 -lgdi32 -lkernel32 + DEPENDS ${PROJECT_SOURCE_DIR}/testdata/native_loader/paint_resize_client.c VERBATIM) + add_custom_target(clip_pe_fixtures ALL DEPENDS ${CLIP_CLIENT} ${CLIP_PAINT_CLIENT} ${PAINT_RESIZE_CLIENT}) + add_test(NAME native_paint_resize COMMAND raven --run-process --execution=native --timeout-ms=30000 ${PAINT_RESIZE_CLIENT}) + set_tests_properties(native_paint_resize PROPERTIES TIMEOUT 60 PASS_REGULAR_EXPRESSION "Native entry returned 0 ") add_executable(clip_test desktop/clip_test.cpp) target_link_libraries(clip_test PRIVATE raven_cpu) add_dependencies(clip_test clip_pe_fixtures raven-bootstrap) diff --git a/native/desktop/objects.cpp b/native/desktop/objects.cpp index c7197d7..7713b09 100644 --- a/native/desktop/objects.cpp +++ b/native/desktop/objects.cpp @@ -497,6 +497,14 @@ void DesktopObjects::finish_paint(uint64_t handle) device->text_color=0; device->background_color=0xffffff; device->background_mode=2; } +void DesktopObjects::abandon_paint(uint64_t handle) +{ + auto *record=window(handle); + if (!record || !record->painting) return; + if (auto *device=dc(record->dc); device && device->clip) { used-=device->clip->bytes(); device->clip.reset(); } + record->painting=false; +} + static bool same_name(std::string_view a,std::string_view b) { if (a.size()!=b.size()) return false; diff --git a/native/desktop/objects.h b/native/desktop/objects.h index c63f6a1..f6eb2bb 100644 --- a/native/desktop/objects.h +++ b/native/desktop/objects.h @@ -368,6 +368,8 @@ public: bool validate(uint64_t window,DesktopRect rect); bool start_paint(uint64_t window); void finish_paint(uint64_t window); + // A paint whose surface went away while it ran, as a window resized inside its own paint: the window is no longer painting. + void abandon_paint(uint64_t window); uint16_t register_class(DesktopClass definition); uint32_t unregister_class(uint16_t atom); uint16_t find_class(uint64_t instance, std::string_view name) const; diff --git a/native/desktop/windows.cpp b/native/desktop/windows.cpp index 3800ae4..b6e1120 100644 --- a/native/desktop/windows.cpp +++ b/native/desktop/windows.cpp @@ -652,12 +652,14 @@ void NativeWindows::release_surface(uint64_t handle) if (!window->parent && dirty_roots.contains(handle) && presentation.present && window->bitmap) { try { present(handle, true); } catch (...) {} } + objects.abandon_paint(handle); objects.detach_window_dcs(handle); if (!window->parent) { // Children draw on the root's bitmap through their own surface contexts; those follow it out. const auto drop=[&](auto &&self,uint64_t node) -> void { for (const auto child:objects.child_windows(node)) { self(self,child); + objects.abandon_paint(child); objects.detach_window_dcs(child); if (auto *record=objects.window(child)) { if (record->dc) objects.erase(record->dc); @@ -2910,8 +2912,10 @@ bool NativeWindows::dispatch(std::string_view name) auto *window=objects.window(args[0]); if (!window) throw uint32_t(1400); const bool narrow=process.mode()==NativeMode::x86; const auto paint=process.read_memory(args[1],narrow ? 64 : 72); - if (!window->painting || read(paint,0,narrow ? 4 : 8)!=window->dc) throw uint32_t(87); - finish_paint(args[0]); + // A paint the window's surface ended early, when the window changed size inside it, has nothing left to finish; EndPaint + // still succeeds, as it always does on Windows. + if (window->painting && read(paint,0,narrow ? 4 : 8)!=window->dc) throw uint32_t(87); + if (window->painting) finish_paint(args[0]); if (objects.window_visible(args[0])) present(args[0]); result=1; } diff --git a/testdata/native_loader/paint_resize_client.c b/testdata/native_loader/paint_resize_client.c new file mode 100644 index 0000000..36e02ae --- /dev/null +++ b/testdata/native_loader/paint_resize_client.c @@ -0,0 +1,52 @@ +#include + +/* A window that changes its own size while it paints, as a dialog laying itself out does: the paint still ends, and the window paints + again afterwards on its new surface. */ + +static unsigned paints, errors; + +static LRESULT CALLBACK proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + if (message == WM_PAINT) { + PAINTSTRUCT ps; + HDC dc = BeginPaint(window, &ps); + if (!dc) { + ++errors; + return 0; + } + if (++paints == 1 && !SetWindowPos(window, NULL, 0, 0, 64, 48, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE)) + ++errors; + RECT all = {0, 0, 64, 48}; + FillRect(dc, &all, (HBRUSH)GetStockObject(BLACK_BRUSH)); + if (!EndPaint(window, &ps)) + ++errors; + return 0; + } + return DefWindowProcW(window, message, wparam, lparam); +} + +int raven_entry(void) +{ + HINSTANCE instance = GetModuleHandleW(NULL); + WNDCLASSW wc = {0}; + wc.lpfnWndProc = proc; + wc.hInstance = instance; + wc.lpszClassName = L"RavenPaintResizeFixture"; + if (!RegisterClassW(&wc)) + return 1; + HWND window = CreateWindowExW(0, wc.lpszClassName, L"paint resize", WS_POPUP, 100, 100, 32, 24, NULL, NULL, instance, NULL); + if (!window) + return 2; + ShowWindow(window, SW_SHOW); + if (!UpdateWindow(window) || paints < 1 || errors) + return 3; + RECT size; + if (!GetWindowRect(window, &size) || size.right - size.left != 64 || size.bottom - size.top != 48) + return 4; + /* The window is not left inside a paint: it paints again when asked. */ + const unsigned before = paints; + if (!InvalidateRect(window, NULL, FALSE) || !UpdateWindow(window) || paints == before || errors) + return 5; + DestroyWindow(window); + return 0; +}