Files
wine-access/CMakeLists.txt
deedra 7d3ac09ea1 Keep Wine editor feedback responsive during reader reply stalls
PresentAccessibleEvent previously waited synchronously for the reader on
Wine Access's message-loop thread. A slow D-Bus acknowledgment prevented
that thread from receiving subsequent WM_COPYDATA editor notifications.
The hooks time out after 50 ms, so feedback disappeared even though the
application accepted the input. With a 350 ms delayed focus reply, the
original helper inserted all of abbacdef but forwarded only e and f.

Move menu, editor and standard-control event delivery to one reader worker.
Keep a FIFO of at most 128 pending events, retain each variant and the bus
connection until delivery completes, and preserve focus-before-text order.
Keep the 500 ms reply deadline and cancel outstanding calls during normal
shutdown without making the event thread wait for the reader. Event-thread
success now means queued; controller RPC calls retain their synchronous
reader status behavior.

Retain the most recent focus dependency on the worker. After a transport
failure, re-establish that focus before sending dependent editor actions.
Treat a successful false reply as a completed exchange because filtering
can suppress presentation while still establishing focus. Never replay a
timed-out text action. Report transport failures, recovery and queue
overflow without including event contents in diagnostics.

Add a native editor fixture and a private-session D-Bus integration runner
for delayed replies, reply timeouts, failed initial focus, repeated typing,
event order, unchanged native text/caret, and shutdown with a withheld
reply. Wait for editor-hook attachment before triggering typing so the
32-bit companion is exercised reliably. Document queue behavior, limits,
regression commands and remaining live acceptance checks.

Validation performed sequentially on a private Xorg dummy display with
disposable Wine prefixes and recording reader services:

- cmake --build build --parallel
- python3 tests/reader_stall_integration.py --build-dir build
- The reader-stall check with --32-bit, --timeout, --focus-error, and
  --32-bit --focus-error
- ctest --test-dir build --output-on-failure: all four checks passed
- python3 tests/menu_integration.py --build-dir build with --fixture menu,
  edit, edit --32-bit, edit --restart-helper, notepad, controls, and
  list --32-bit
- python3 tests/event_stall_integration.py --build-dir build
- python3 tests/controller_integration.py --build-dir build
- git diff --check

The delayed-reply regression failed against the original synchronous
helper and passed after the worker change. The failed-focus scenario also
failed before dependency recovery was added and passed afterward.

These recordings establish the shared failure paths, not the cause of
every live MUSHclient pause or audible speech and physical braille
acceptance. Live checks remain for command typing, incoming plugin speech,
output-buffer arrows, menus, switching between Wine and Linux, reader
restart, and protected fields. An unavailable reader can still cause
feedback loss or bounded queue overflow.
2026-10-13 05:26:11 -07:00

124 lines
7.1 KiB
CMake

cmake_minimum_required(VERSION 3.20)
project(wine_access LANGUAGES C CXX)
include(GNUInstallDirs)
set(WINE_ACCESS_INSTALL_DIR "${CMAKE_INSTALL_LIBEXECDIR}/cthulhu/wine" CACHE PATH
"Installation directory for the Wine helper and its Winelib sidecar")
find_package(PkgConfig REQUIRED)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb)
find_program(WIDL_EXECUTABLE widl REQUIRED)
find_program(MINGW_C_COMPILER x86_64-w64-mingw32-gcc REQUIRED)
find_program(MINGW32_C_COMPILER i686-w64-mingw32-gcc REQUIRED)
# A PE hook DLL can be loaded into ordinary Wine applications without any
# GLib dependencies. Keep it alongside the Winelib helper.
set(menu_hook "${CMAKE_CURRENT_BINARY_DIR}/wine-access-menu.dll")
add_custom_command(OUTPUT "${menu_hook}"
COMMAND "${MINGW_C_COMPILER}" -std=c11 -O2 -Wall -Wextra -shared
-static-libgcc -o "${menu_hook}" "${CMAKE_CURRENT_SOURCE_DIR}/menu_hook.c"
"${CMAKE_CURRENT_SOURCE_DIR}/edit_hook.c"
"${CMAKE_CURRENT_SOURCE_DIR}/scintilla_hook.c" -luser32 -lcomctl32 -lgdi32
DEPENDS menu_hook.c menu_bridge.h edit_hook.c edit_hook.h control_class.h scintilla_hook.c scintilla_messages.h
VERBATIM)
add_custom_target(wine-access-menu ALL DEPENDS "${menu_hook}")
set(menu_hook32 "${CMAKE_CURRENT_BINARY_DIR}/wine-access-menu32.dll")
set(menu_host32 "${CMAKE_CURRENT_BINARY_DIR}/wine-access-menu-host32.exe")
add_custom_command(OUTPUT "${menu_hook32}" "${menu_host32}"
COMMAND "${MINGW32_C_COMPILER}" -std=c11 -O2 -Wall -Wextra -shared
-static-libgcc -Wl,--kill-at -o "${menu_hook32}"
"${CMAKE_CURRENT_SOURCE_DIR}/menu_hook.c"
"${CMAKE_CURRENT_SOURCE_DIR}/edit_hook.c" -luser32 -lcomctl32
COMMAND "${MINGW32_C_COMPILER}" -std=c11 -O2 -Wall -Wextra -mwindows
-static-libgcc -o "${menu_host32}"
"${CMAKE_CURRENT_SOURCE_DIR}/menu_host32.c" -luser32
DEPENDS menu_hook.c menu_bridge.h menu_host32.c edit_hook.c edit_hook.h control_class.h
VERBATIM)
add_custom_target(wine-access-menu32 ALL DEPENDS "${menu_hook32}" "${menu_host32}")
set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(generated_header "${generated_dir}/nvdaController.h")
set(generated_server "${generated_dir}/nvdaController_s.c")
file(MAKE_DIRECTORY "${generated_dir}")
add_custom_command(
OUTPUT "${generated_header}" "${generated_server}"
COMMAND "${WIDL_EXECUTABLE}" --win64 -h -H "${generated_header}"
-s -S "${generated_server}" "${CMAKE_CURRENT_SOURCE_DIR}/nvdaController.idl"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/nvdaController.idl"
VERBATIM)
add_executable(cthulhu-wine-access reader_events.cpp msaa_events.cpp main.cpp foreground_focus.cpp list_view.cpp native_items.cpp review_capture.cpp review_x11.cpp review_service.cpp "${generated_server}")
add_dependencies(cthulhu-wine-access wine-access-menu wine-access-menu32)
target_compile_features(cthulhu-wine-access PRIVATE cxx_std_20)
target_include_directories(cthulhu-wine-access PRIVATE "${generated_dir}")
target_link_libraries(cthulhu-wine-access PRIVATE
PkgConfig::GIO PkgConfig::XCB oleacc ole32 oleaut32 rpcrt4 user32 gdi32 shell32)
target_link_options(cthulhu-wine-access PRIVATE -mwindows)
target_compile_options(cthulhu-wine-access PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wall -Wextra>)
include(CTest)
if(BUILD_TESTING)
set(foreground_fixture "${CMAKE_CURRENT_BINARY_DIR}/foreground-fixture.exe")
add_custom_command(OUTPUT "${foreground_fixture}"
COMMAND "${MINGW_C_COMPILER}" -std=c11 -Wall -Wextra -mwindows
-o "${foreground_fixture}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/foreground_fixture.c" -luser32
DEPENDS tests/foreground_fixture.c VERBATIM)
add_custom_target(foreground-fixture ALL DEPENDS "${foreground_fixture}")
add_executable(foreground-focus-test tests/foreground_focus_test.cpp foreground_focus.cpp review_x11.cpp)
add_dependencies(foreground-focus-test foreground-fixture)
target_compile_features(foreground-focus-test PRIVATE cxx_std_20)
target_compile_options(foreground-focus-test PRIVATE -Wall -Wextra)
target_link_libraries(foreground-focus-test PRIVATE user32 PkgConfig::XCB)
add_test(NAME foreground-focus COMMAND python3
"${CMAKE_CURRENT_SOURCE_DIR}/tests/foreground_focus_test.py" "${CMAKE_CURRENT_BINARY_DIR}")
set_tests_properties(foreground-focus PROPERTIES TIMEOUT 90 ENVIRONMENT
"WINEPREFIX=${CMAKE_CURRENT_BINARY_DIR}/focus-prefix;CTHULHU_WINE_ACCESS_MANAGED=1;WINEDEBUG=-all;WINEDLLOVERRIDES=mscoree,mshtml=")
add_executable(msaa-states-test tests/msaa_states_test.cpp)
target_compile_features(msaa-states-test PRIVATE cxx_std_20)
target_compile_options(msaa-states-test PRIVATE -Wall -Wextra)
add_test(NAME msaa-states COMMAND "${CMAKE_CURRENT_BINARY_DIR}/msaa-states-test.exe")
set_tests_properties(msaa-states PROPERTIES ENVIRONMENT
"WINEPREFIX=${CMAKE_CURRENT_BINARY_DIR}/test-prefix;CTHULHU_WINE_ACCESS_MANAGED=1;WINEDEBUG=-all;WINEDLLOVERRIDES=mscoree,mshtml=")
add_executable(review-capture-test tests/review_capture_test.cpp)
target_compile_features(review-capture-test PRIVATE cxx_std_20)
target_compile_options(review-capture-test PRIVATE -Wall -Wextra)
target_link_libraries(review-capture-test PRIVATE ole32 oleacc oleaut32 user32 gdi32 PkgConfig::GIO PkgConfig::XCB)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/review_capture.cpp")
target_sources(review-capture-test PRIVATE review_capture.cpp review_x11.cpp list_view.cpp native_items.cpp)
endif()
foreach(arch IN ITEMS 64 32)
if(arch EQUAL 32)
set(fixture_compiler "${MINGW32_C_COMPILER}")
else()
set(fixture_compiler "${MINGW_C_COMPILER}")
endif()
set(review_fixture "${CMAKE_CURRENT_BINARY_DIR}/review-fixture${arch}.exe")
add_custom_command(OUTPUT "${review_fixture}"
COMMAND "${fixture_compiler}" -std=c11 -Wall -Wextra -mwindows
-o "${review_fixture}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/review_fixture.c" -luser32 -lcomctl32
DEPENDS tests/review_fixture.c VERBATIM)
add_custom_target(review-fixture${arch} ALL DEPENDS "${review_fixture}")
add_dependencies(review-capture-test review-fixture${arch})
add_test(NAME review-capture${arch}
COMMAND "${CMAKE_CURRENT_BINARY_DIR}/review-capture-test.exe" --fixture "${review_fixture}")
set_tests_properties(review-capture${arch} PROPERTIES TIMEOUT 35 ENVIRONMENT
"WINEPREFIX=${CMAKE_CURRENT_BINARY_DIR}/review-prefix${arch};CTHULHU_WINE_ACCESS_MANAGED=1;WINEDEBUG=-all;WINEDLLOVERRIDES=mscoree,mshtml=")
endforeach()
endif()
# Winegcc emits a launcher and a Winelib sidecar. CMake's normal target
# installation looks for the Linux target name, so install both emitted
# artifacts explicitly and keep them together for Wine to resolve.
install(PROGRAMS
"${CMAKE_CURRENT_BINARY_DIR}/cthulhu-wine-access.exe"
DESTINATION "${WINE_ACCESS_INSTALL_DIR}")
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/cthulhu-wine-access.exe.so"
"${menu_hook}"
"${menu_hook32}"
"${menu_host32}"
DESTINATION "${WINE_ACCESS_INSTALL_DIR}")