Skip to content

Commit 1f96838

Browse files
CopilotasklarCopilot
authored
Fix non-finite bounds producing UB via static_cast in XAML/WinUI3 elements (#7)
* Initial plan * Fix element bounds validation and safe double-to-int conversion - TAP DLL: validate _wtof/ParseOffset results are finite (reject NaN/Inf) - graft_json_node (xaml_diag_common, plugin_loader, wpf_inject): add safe_double_to_int helper that clamps to INT_MIN..INT_MAX range - screenshot.cpp: use long long arithmetic to prevent int overflow in bounds computations during annotation - Add unit tests for bounds edge cases (zero size, large offsets, normal) Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Extract safe_double_to_int to shared bounds_util.h and fix screenshot overflow - Move safe_double_to_int to src/bounds_util.h to eliminate code duplication - Cast winRect fields to long long before subtraction in screenshot annotation Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Extract safe_double_to_int to shared bounds_util.h and fix screenshot overflow Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Remove CodeQL artifact and add to gitignore Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Return std::optional from safe_double_to_int, add annotations test hook and integration tests - bounds_util.h: safe_double_to_int now returns std::optional<int>, nullopt for non-finite - Call sites updated to use if(sx && sy && sw && sh) pattern - Added --annotations-json CLI flag for structured annotation output - Added collect_annotations() for computing which elements get annotated - Integration tests: Win32 bounds validation, annotation verification, framework-conditional WinUI3/XAML bounds checks Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Use named constants for bounds thresholds, match annotate_pixels clipping in collect_annotations Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Guard --annotations-json flag with #ifndef NDEBUG for debug-only builds Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Add controlled Win32 window integration tests for tree structure, bounds, and annotations Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> * Fix integration test file locking: close ifstream before fs::remove On Windows, fs::remove throws when the file is still open by an ifstream in the same process. Close the ifstream explicitly before removing the annotations JSON temp files in the three affected tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix WinUI3 element bounds: layout offset computation and bridge matching WinUI3's XAML diagnostics API serializes Vector3 ActualOffset as '0' instead of proper vector components, causing all XAML elements to stack at the bridge origin with no position differentiation. TAP DLL (lvt_tap.cpp): - Collect Orientation, Spacing, Margin from property chain for layout - ComputeLayoutOffsets: compute child offsets by accumulating sibling widths (horizontal) or heights (vertical) along StackPanel stacking axis - Use first Orientation value in property chain (most specific override) Bridge-to-XAML matching (xaml_diag_common.cpp): - Replace order-based matching with size-based best-fit matching - Recursively search XAML root descendants for content dimensions - Skip contentless XAML roots (w=0, h=0) to prevent bridge consumption - Match each XAML island to the bridge with closest width+height Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unreliable layout offset heuristic, add DPI scaling The ComputeLayoutOffsets heuristic produced wrong positions for elements in Grid, Canvas, and other non-StackPanel containers, causing annotation rectangles to be misaligned with actual UI elements. Changes: - Remove ComputeLayoutOffsets and layout field collection from TAP DLL - Add ApplyDpiScaling: scale XAML logical pixel dimensions by the window DPI factor (dpi/96) so they match the physical pixel space used by GetWindowRect and the screenshot capture bitmap - Remove unused IVisualTreeService2 member (GetProperty approach didn't work for Vector3 value types) - Keep the improved size-based bridge-to-XAML matching Without per-element offsets from ActualOffset (WinUI3 serializes Vector3 as '0'), elements within a XAML island share the bridge origin position. This is a known WinUI3 diagnostics limitation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix annotation bounds, exclude own terminal from --title, add build script Annotation bounds fix: - TAP DLL: only include offsetX/offsetY in JSON when non-zero, since WinUI3 serializes ActualOffset (Vector3) as '0' making zero offsets indistinguishable from missing data - graft_json_node: only set positioned screen bounds on XAML elements when explicit offset data is present in the JSON; elements without offsets still appear in the tree (type/name/framework/width/height) but won't produce garbage overlapping annotation rectangles - Remove ApplyDpiScaling (no longer needed without positioned bounds) Terminal self-match fix: - Exclude windows belonging to our own process or console host from --name/--title enumeration, preventing the terminal window (whose title contains the lvt command line) from matching Build script: - Add build.cmd for building from a VS Developer Command Prompt - Builds .NET dependencies (WPF TAP, Avalonia walker) before CMake - Supports 'build.cmd clean' to reconfigure from scratch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix XAML element positions via TransformToVisual and DPI scaling TAP DLL — get real element positions: - Use IXamlDiagnostics::GetIInspectableFromHandle to get the IInspectable for each XAML element, QI for IUIElement (IID from WinUI3 winmd), and call TransformToVisual(nullptr).TransformPoint({0,0}) via raw vtable calls to get each element's position relative to the XAML island root - This bypasses the broken ActualOffset property serialization (WinUI3 serializes Vector3 as '0' for readonly computed properties) - CollectPositionsViaTransformToVisual runs on the UI thread via SendMessage dispatch, collecting positions for 284/289 elements TAP DLL — serialize offsets correctly: - Only include offsetX/offsetY in JSON when non-zero, so graft_json_node can distinguish 'no offset data' from 'offset is (0,0)' graft_json_node — use absolute offsets: - TransformToVisual offsets are absolute within the XAML island (not cumulative parent-relative), so pass bridge base to all children instead of accumulating offsets - Only set bounds when explicit offset data is present in JSON screenshot.cpp — fix DPI scaling: - Use GetWindowRect (logical pixels, same space as element bounds) instead of DwmGetWindowAttribute (physical pixels) for the annotation origin, then scale by bmpWidth/winW to convert to bitmap coordinates - This fixes annotation alignment on high-DPI displays (e.g. 150%) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Extract important XAML properties into tree dump TAP DLL: capture key properties from GetPropertyValuesChain during bounds collection. Properties extracted: - Text content: Text, Content, Header, PlaceholderText, Description, Title, Glyph (filtered: skip numeric handle references >10 digits) - Accessibility: AutomationProperties.Name, AutomationProperties.AutomationId, AutomationProperties.HelpText - State: IsEnabled, Visibility, IsChecked, IsSelected, IsOn, Orientation - Other: Source, Tag Properties are serialized as a JSON object in the TAP pipe output and copied into Element.properties by graft_json_node. Text/Content/Header values also populate Element.text for display. The Win32 provider already populates properties (hwnd, style, visible, enabled). XAML elements now get similar treatment, enabling AI consumers to identify controls by their accessible names (e.g. 'Bold (Ctrl+B)'). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use C++/WinRT projected types for XAML element inspection Replace raw vtable calls with proper C++/WinRT projected types from generated WinUI3 headers (Microsoft.UI.Xaml.winmd → cppwinrt.exe). TAP DLL changes: - #include winrt/Microsoft.UI.Xaml.h, .Media.h, .Controls.h for UIElement, GeneralTransform, TextBlock projected types - CollectPositionsAndText: use inspectable.try_as<UIElement>() and call TransformToVisual(nullptr).TransformPoint({0,0}) for position - Read TextBlock.Text() directly via C++/WinRT (no more numeric handle references from the property chain) - Add WINRT_LEAN_AND_MEAN to avoid windowsapp.lib dependency; provide stub for WINRT_IMPL_RoOriginateLanguageException graft_json_node (xaml_diag_common.cpp): - Map XAML x:Name to properties['name'] instead of Element.text - Set Element.text from Text > Content > Header > AutomationProperties.Name Build: - Generate C++/WinRT headers from WinUI3 winmd at build setup time - Add src/tap/winui3/ to TAP DLL include path; gitignored (generated) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix text property: don't use AutomationProperties.Name as text, remove duplicates - Only set Element.text from actual text-content properties (Text, Content, Header) — not AutomationProperties.Name - AutomationProperties.Name stays as a property attribute in the output - When Text/Content/Header populates Element.text, remove it from properties to avoid outputting both text='File' and Text='File' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reduce annotation clutter: skip layout containers, place labels inside boxes - Skip pure layout types (Grid, StackPanel, Border, ContentPresenter, ScrollViewer, Window, etc.) from screenshot annotations unless they have text content or an AutomationProperties.Name - Place labels inside the bounding box when they fit, falling back to above or below the box when they don't - Keeps interactive controls (Button, ToggleButton, TextBlock, etc.) and elements with meaningful text/a11y names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CI: generate WinUI3 C++/WinRT headers at configure time The TAP DLL's WinUI3 C++/WinRT projected headers are generated from Microsoft.UI.Xaml.winmd using cppwinrt.exe and are gitignored. CI needs to generate them at build time. CMakeLists.txt: - Add configure-time step to find cppwinrt.exe (from Windows SDK), locate the Windows App SDK winmd files (NuGet global cache or local packages/ dir), and run cppwinrt to generate headers - Search both NuGet global cache and CI packages/ directory - Skip generation if headers already exist (incremental builds) CI (ci.yml): - Add step to install Windows App SDK NuGet package before configure TAP DLL (lvt_tap.cpp): - Make WinUI3 C++/WinRT include optional via __has_include guard - All TransformToVisual/TextBlock.Text code is #if LVT_HAS_WINUI3_PROJECTION - Without the headers, TAP DLL compiles and works for property chain bounds but without position resolution or text reading Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Support UWP/system XAML (Windows.UI.Xaml) for position and text reading The TAP DLL now tries both WinUI3 (Microsoft.UI.Xaml) and system XAML (Windows.UI.Xaml) interfaces when reading element positions and text. This enables proper annotation of UWP apps like Clock, Calculator, etc. - System XAML headers (Windows.UI.Xaml) are always available from the Windows SDK — no generation needed - TransformToVisual: tries WinUI3 UIElement first, falls back to system XAML UIElement - TextBlock.Text: tries WinUI3 TextBlock first, falls back to system - Guard changed from LVT_HAS_WINUI3_PROJECTION to LVT_HAS_XAML_PROJECTION (always true since system headers are always present) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: asklar <22989529+asklar@users.noreply.github.com> Co-authored-by: Alexander Sklar <AskLar@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 25e3ef9 commit 1f96838

15 files changed

Lines changed: 1143 additions & 56 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ jobs:
2323
with:
2424
vcpkgGitCommitId: b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01
2525

26+
- name: Install Windows App SDK (for WinUI3 C++/WinRT headers)
27+
run: nuget install Microsoft.WindowsAppSDK -Version 1.5.240607001 -OutputDirectory packages
28+
2629
- name: Configure
2730
run: cmake --preset default
2831

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ Desktop.ini
1515
.DS_Store
1616
*.png
1717
!docs/*.png
18+
_codeql_detected_source_root
19+
20+
src/tap/winui3/

CMakeLists.txt

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,84 @@ endif()
5353

5454
# TAP DLL — injected into target process for XAML diagnostics
5555
# Uses static CRT (/MT) to avoid CRT version conflicts in the target process
56+
57+
# Generate C++/WinRT projected headers from the Windows App SDK winmd.
58+
# These are needed for TransformToVisual / TextBlock.Text in the TAP DLL.
59+
set(WINUI3_CPPWINRT_DIR "${CMAKE_SOURCE_DIR}/src/tap/winui3")
60+
set(WINUI3_CPPWINRT_STAMP "${WINUI3_CPPWINRT_DIR}/winrt/Microsoft.UI.Xaml.h")
61+
if(NOT EXISTS "${WINUI3_CPPWINRT_STAMP}")
62+
# Find cppwinrt.exe from Windows SDK
63+
find_program(CPPWINRT_EXE cppwinrt
64+
HINTS "$ENV{WindowsSdkDir}/bin/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/x64"
65+
"$ENV{WindowsSdkDir}/bin/10.0.22621.0/x64"
66+
"C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64"
67+
)
68+
if(NOT CPPWINRT_EXE)
69+
# Fallback: search PATH
70+
find_program(CPPWINRT_EXE cppwinrt)
71+
endif()
72+
73+
# Find Windows App SDK NuGet package (winmd files).
74+
# Search NuGet global cache and local packages/ dir (CI installs here).
75+
set(WASDK_WINMD_DIR "")
76+
file(GLOB _wasdk_candidates
77+
"$ENV{USERPROFILE}/.nuget/packages/microsoft.windowsappsdk/*/lib/uap10.0"
78+
"$ENV{NUGET_PACKAGES}/microsoft.windowsappsdk/*/lib/uap10.0"
79+
"${CMAKE_SOURCE_DIR}/packages/Microsoft.WindowsAppSDK.*/lib/uap10.0"
80+
)
81+
foreach(_candidate ${_wasdk_candidates})
82+
if(EXISTS "${_candidate}/Microsoft.UI.Xaml.winmd")
83+
set(WASDK_WINMD_DIR "${_candidate}")
84+
break()
85+
endif()
86+
endforeach()
87+
88+
# Find Windows.winmd for system type references
89+
set(WIN_UNION_WINMD "")
90+
if(DEFINED ENV{WindowsSdkDir})
91+
file(GLOB _union "$ENV{WindowsSdkDir}/UnionMetadata/*/Windows.winmd")
92+
if(_union)
93+
list(SORT _union ORDER DESCENDING)
94+
list(GET _union 0 WIN_UNION_WINMD)
95+
endif()
96+
endif()
97+
if(NOT WIN_UNION_WINMD)
98+
file(GLOB _union "C:/Program Files (x86)/Windows Kits/10/UnionMetadata/*/Windows.winmd")
99+
if(_union)
100+
list(SORT _union ORDER DESCENDING)
101+
list(GET _union 0 WIN_UNION_WINMD)
102+
endif()
103+
endif()
104+
105+
if(CPPWINRT_EXE AND WASDK_WINMD_DIR AND WIN_UNION_WINMD)
106+
message(STATUS "Generating WinUI3 C++/WinRT headers from ${WASDK_WINMD_DIR}")
107+
file(GLOB WASDK_ALL_WINMDS "${WASDK_WINMD_DIR}/*.winmd")
108+
# Also include other winmd directories in the package
109+
get_filename_component(_wasdk_root "${WASDK_WINMD_DIR}/../.." ABSOLUTE)
110+
file(GLOB_RECURSE _extra_winmds "${_wasdk_root}/*.winmd")
111+
list(APPEND WASDK_ALL_WINMDS ${_extra_winmds})
112+
list(REMOVE_DUPLICATES WASDK_ALL_WINMDS)
113+
execute_process(
114+
COMMAND "${CPPWINRT_EXE}" -in ${WASDK_ALL_WINMDS} -ref "${WIN_UNION_WINMD}" -out "${WINUI3_CPPWINRT_DIR}"
115+
RESULT_VARIABLE _cppwinrt_result
116+
)
117+
if(_cppwinrt_result EQUAL 0)
118+
message(STATUS "WinUI3 C++/WinRT headers generated in ${WINUI3_CPPWINRT_DIR}")
119+
else()
120+
message(WARNING "cppwinrt failed (${_cppwinrt_result}); TAP DLL WinUI3 features will be limited")
121+
endif()
122+
else()
123+
message(WARNING "Cannot generate WinUI3 C++/WinRT headers:"
124+
" cppwinrt=${CPPWINRT_EXE} winmd_dir=${WASDK_WINMD_DIR} union=${WIN_UNION_WINMD}")
125+
endif()
126+
endif()
127+
56128
add_library(lvt_tap SHARED
57129
src/tap/lvt_tap.cpp
58130
src/tap/lvt_tap.def
59131
)
60-
target_compile_definitions(lvt_tap PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
61-
target_include_directories(lvt_tap PRIVATE src)
132+
target_compile_definitions(lvt_tap PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX WINRT_LEAN_AND_MEAN)
133+
target_include_directories(lvt_tap PRIVATE src src/tap/winui3)
62134
target_link_libraries(lvt_tap PRIVATE ole32)
63135
set_property(TARGET lvt_tap PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
64136

build.cmd

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
@echo off
2+
setlocal enabledelayedexpansion
3+
4+
REM lvt build script — run from a VS Developer Command Prompt (x64)
5+
REM Requires: VCPKG_ROOT set, cl.exe and ninja.exe on PATH
6+
REM Usage: build.cmd [clean]
7+
8+
if "%VCPKG_ROOT%"=="" (
9+
echo ERROR: VCPKG_ROOT is not set.
10+
exit /b 1
11+
)
12+
13+
where cl.exe >nul 2>&1 || (echo ERROR: cl.exe not found. Run from a VS Developer Command Prompt. & exit /b 1)
14+
where ninja.exe >nul 2>&1 || (echo ERROR: ninja.exe not found. & exit /b 1)
15+
16+
if /i "%1"=="clean" (
17+
echo Cleaning build directory...
18+
if exist build rmdir /s /q build
19+
)
20+
21+
REM --- .NET projects (must build before CMake) ---
22+
23+
echo.
24+
echo === Building .NET projects ===
25+
26+
echo [1/2] LvtWpfTap (net48)...
27+
dotnet build src\tap_wpf\LvtWpfTap.csproj -c Release -v:q --nologo
28+
if errorlevel 1 (
29+
echo WARNING: LvtWpfTap build failed (WPF TAP will be unavailable)
30+
)
31+
32+
echo [2/2] LvtAvaloniaTreeWalker (net8.0)...
33+
dotnet restore src\plugin_avalonia\LvtAvaloniaTreeWalker\LvtAvaloniaTreeWalker.csproj -v:q --nologo
34+
dotnet publish src\plugin_avalonia\LvtAvaloniaTreeWalker\LvtAvaloniaTreeWalker.csproj -c Release -v:q --nologo
35+
if errorlevel 1 (
36+
echo WARNING: LvtAvaloniaTreeWalker build failed (Avalonia plugin will be unavailable)
37+
)
38+
39+
REM --- CMake configure + build ---
40+
41+
echo.
42+
echo === Configuring CMake (x64) ===
43+
44+
if not exist build (
45+
cmake --preset default
46+
if errorlevel 1 (
47+
echo ERROR: CMake configure failed.
48+
exit /b 1
49+
)
50+
)
51+
52+
echo.
53+
echo === Building C++ targets ===
54+
55+
cmake --build build
56+
if errorlevel 1 (
57+
echo.
58+
echo ERROR: C++ build failed.
59+
exit /b 1
60+
)
61+
62+
echo.
63+
echo === Build complete ===
64+
echo Output: build\lvt.exe
65+
echo build\lvt_tap_x64.dll
66+
echo Tests: build\lvt_unit_tests.exe
67+
echo build\lvt_integration_tests.exe

src/bounds_util.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
#include <cmath>
3+
#include <climits>
4+
#include <optional>
5+
6+
namespace lvt {
7+
8+
// Safe double-to-int conversion: clamp to int range and reject non-finite values.
9+
// Returns std::nullopt for NaN/Infinity so callers can skip bounds entirely.
10+
// Prevents undefined behavior from static_cast<int> when the double is NaN, Inf,
11+
// or outside the representable int range.
12+
inline std::optional<int> safe_double_to_int(double v) {
13+
if (!std::isfinite(v)) return std::nullopt;
14+
if (v >= static_cast<double>(INT_MAX)) return INT_MAX;
15+
if (v <= static_cast<double>(INT_MIN)) return INT_MIN;
16+
return static_cast<int>(v);
17+
}
18+
19+
} // namespace lvt

src/main.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <cstring>
1212
#include <string>
1313
#include <fstream>
14+
#include <nlohmann/json.hpp>
1415

1516
static void print_usage() {
1617
fprintf(stderr,
@@ -30,6 +31,9 @@ static void print_usage() {
3031
" --output <file> Write output to file instead of stdout\n"
3132
" --format <fmt> Output format: json (default) or xml\n"
3233
" --screenshot <file> Capture annotated screenshot to PNG\n"
34+
#ifndef NDEBUG
35+
" --annotations-json <file> Write annotation rectangles as JSON (test hook)\n"
36+
#endif
3337
" --dump Output the tree (default; implied unless --screenshot)\n"
3438
" --element <id> Scope to a specific element subtree\n"
3539
" --frameworks Just detect and list frameworks\n"
@@ -47,6 +51,9 @@ struct Args {
4751
std::string outputFile;
4852
std::string format = "json";
4953
std::string screenshotFile;
54+
#ifndef NDEBUG
55+
std::string annotationsFile;
56+
#endif
5057
std::string elementId;
5158
int depth = -1;
5259
bool frameworksOnly = false;
@@ -75,6 +82,10 @@ static Args parse_args(int argc, char* argv[]) {
7582
args.format = argv[++i];
7683
} else if (strcmp(argv[i], "--screenshot") == 0 && i + 1 < argc) {
7784
args.screenshotFile = argv[++i];
85+
#ifndef NDEBUG
86+
} else if (strcmp(argv[i], "--annotations-json") == 0 && i + 1 < argc) {
87+
args.annotationsFile = argv[++i];
88+
#endif
7889
} else if (strcmp(argv[i], "--element") == 0 && i + 1 < argc) {
7990
args.elementId = argv[++i];
8091
} else if (strcmp(argv[i], "--depth") == 0 && i + 1 < argc) {
@@ -265,6 +276,25 @@ int main(int argc, char* argv[]) {
265276
}
266277
}
267278

279+
#ifndef NDEBUG
280+
// Annotations JSON — test hook for verifying which elements are annotated
281+
if (!args.annotationsFile.empty()) {
282+
auto annotations = lvt::collect_annotations(target.hwnd, &tree);
283+
nlohmann::json aj = nlohmann::json::array();
284+
for (auto& a : annotations) {
285+
aj.push_back({{"id", a.id}, {"x", a.x}, {"y", a.y},
286+
{"width", a.width}, {"height", a.height}});
287+
}
288+
std::ofstream out(args.annotationsFile);
289+
if (out) {
290+
out << aj.dump(2) << "\n";
291+
if (lvt::g_debug)
292+
fprintf(stderr, "lvt: wrote %zu annotations to %s\n",
293+
annotations.size(), args.annotationsFile.c_str());
294+
}
295+
}
296+
#endif
297+
268298
lvt::unload_plugins();
269299
return 0;
270300
}

src/plugin_loader.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#include "plugin_loader.h"
22
#include "debug.h"
3+
#include "bounds_util.h"
34
#include <nlohmann/json.hpp>
45
#include <cstdio>
56
#include <cstdlib>
7+
#include <cmath>
68
#include <functional>
79
#include <userenv.h>
810

@@ -143,13 +145,19 @@ static void graft_json_node(const json& j, Element& parent, const std::string& f
143145
double oy = j.value("offsetY", 0.0);
144146
double w = j.value("width", 0.0);
145147
double h = j.value("height", 0.0);
146-
double absX = parentOffsetX + ox;
147-
double absY = parentOffsetY + oy;
148+
double absX = std::isfinite(ox) ? parentOffsetX + ox : parentOffsetX;
149+
double absY = std::isfinite(oy) ? parentOffsetY + oy : parentOffsetY;
148150
if (w > 0 && h > 0) {
149-
el.bounds.x = static_cast<int>(absX);
150-
el.bounds.y = static_cast<int>(absY);
151-
el.bounds.width = static_cast<int>(w);
152-
el.bounds.height = static_cast<int>(h);
151+
auto sx = safe_double_to_int(absX);
152+
auto sy = safe_double_to_int(absY);
153+
auto sw = safe_double_to_int(w);
154+
auto sh = safe_double_to_int(h);
155+
if (sx && sy && sw && sh) {
156+
el.bounds.x = *sx;
157+
el.bounds.y = *sy;
158+
el.bounds.width = *sw;
159+
el.bounds.height = *sh;
160+
}
153161
}
154162

155163
// Copy additional properties if present

src/providers/wpf_inject.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "wpf_inject.h"
77
#include "../debug.h"
88
#include "../target.h"
9+
#include "../bounds_util.h"
910

1011
#include <Windows.h>
1112
#include <objbase.h>
@@ -15,6 +16,7 @@
1516
#include <wil/resource.h>
1617
#include <nlohmann/json.hpp>
1718
#include <cstdio>
19+
#include <cmath>
1820
#include <string>
1921
#include <fstream>
2022

@@ -71,10 +73,16 @@ static void graft_json_node(const json& j, Element& parent, const std::string& f
7173
double ox = j.value("offsetX", 0.0);
7274
double oy = j.value("offsetY", 0.0);
7375
if (w > 0 && h > 0) {
74-
el.bounds.x = static_cast<int>(ox);
75-
el.bounds.y = static_cast<int>(oy);
76-
el.bounds.width = static_cast<int>(w);
77-
el.bounds.height = static_cast<int>(h);
76+
auto sx = safe_double_to_int(ox);
77+
auto sy = safe_double_to_int(oy);
78+
auto sw = safe_double_to_int(w);
79+
auto sh = safe_double_to_int(h);
80+
if (sx && sy && sw && sh) {
81+
el.bounds.x = *sx;
82+
el.bounds.y = *sy;
83+
el.bounds.width = *sw;
84+
el.bounds.height = *sh;
85+
}
7886
}
7987

8088
// Visibility/enabled as properties

0 commit comments

Comments
 (0)