Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: cmake --preset default

- name: Build managed WPF assembly
run: msbuild src/tap_wpf/LvtWpfTap.csproj /p:Configuration=Release /restore /v:minimal
run: msbuild src/tap_wpf/LvtWpfTap.csproj /p:Configuration=Release /p:Platform=AnyCPU /restore /v:minimal

- name: Build
run: cmake --build build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
run: cmake --preset ${{ matrix.preset }} -DCMAKE_BUILD_TYPE=Release

- name: Build managed WPF assembly
run: msbuild src/tap_wpf/LvtWpfTap.csproj /p:Configuration=Release /restore /v:minimal
run: msbuild src/tap_wpf/LvtWpfTap.csproj /p:Configuration=Release /p:Platform=AnyCPU /restore /v:minimal

- name: Build
run: cmake --build ${{ matrix.build_dir }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Build output
build/
build-x86/
build-arm64/

# IDE files
.vs/
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_executable(lvt
src/tree_builder.cpp
src/json_serializer.cpp
src/screenshot.cpp
src/plugin_loader.cpp
src/providers/win32_provider.cpp
src/providers/comctl_provider.cpp
src/providers/xaml_provider.cpp
Expand Down Expand Up @@ -104,6 +105,7 @@ add_executable(lvt_unit_tests
src/json_serializer.cpp
src/framework_detector.cpp
src/target.cpp
src/plugin_loader.cpp
src/providers/win32_provider.cpp
src/providers/comctl_provider.cpp
src/providers/xaml_provider.cpp
Expand Down
13 changes: 13 additions & 0 deletions src/framework_detector.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "framework_detector.h"
#include "plugin_loader.h"
#include <wil/resource.h>
#include <Psapi.h>

Expand All @@ -13,10 +14,16 @@ std::string framework_to_string(Framework f) {
case Framework::Xaml: return "xaml";
case Framework::WinUI3: return "winui3";
case Framework::Wpf: return "wpf";
case Framework::Plugin: return "plugin";
}
return "unknown";
}

std::string framework_display_name(const FrameworkInfo& fi) {
if (!fi.name.empty()) return fi.name;
return framework_to_string(fi.type);
}

static const wchar_t* comctl_classes[] = {
L"SysListView32", L"SysTreeView32", L"SysTabControl32",
L"msctls_statusbar32", L"ToolbarWindow32", L"msctls_trackbar32",
Expand Down Expand Up @@ -191,6 +198,12 @@ std::vector<FrameworkInfo> detect_frameworks(HWND hwnd, DWORD pid) {
if (!detectedWpf && data.hasWpf)
result.push_back({Framework::Wpf, {}});

// Plugin-provided framework detection
auto pluginFws = detect_plugin_frameworks(hwnd, pid);
for (auto& pf : pluginFws) {
result.push_back({Framework::Plugin, pf.version, pf.name});
}

return result;
}

Expand Down
5 changes: 5 additions & 0 deletions src/framework_detector.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ enum class Framework {
Xaml,
WinUI3,
Wpf,
Plugin, // Plugin-provided framework (name in FrameworkInfo::name)
};

struct FrameworkInfo {
Framework type;
std::string version; // e.g. "3.1.7.2602" for WinUI3, "6.10" for comctl
std::string name; // Plugin-provided name (empty for built-in frameworks)
};

std::string framework_to_string(Framework f);

// Returns the display name for a FrameworkInfo (uses name field if set).
std::string framework_display_name(const FrameworkInfo& fi);

// Detect which UI frameworks are in use for the given window/process.
std::vector<FrameworkInfo> detect_frameworks(HWND hwnd, DWORD pid);

Expand Down
17 changes: 12 additions & 5 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "tree_builder.h"
#include "json_serializer.h"
#include "screenshot.h"
#include "plugin_loader.h"
#include "debug.h"

#include <cstdio>
Expand Down Expand Up @@ -111,6 +112,9 @@ int main(int argc, char* argv[]) {

auto args = parse_args(argc, argv);

// Load plugins from %USERPROFILE%/.lvt/plugins/
lvt::load_plugins();

// --dump is default unless --screenshot is specified without --dump
if (!args.dumpSet)
args.dump = args.screenshotFile.empty();
Expand Down Expand Up @@ -190,12 +194,13 @@ int main(int argc, char* argv[]) {
if (args.frameworksOnly) {
// Just print detected frameworks
for (auto& fi : frameworks) {
auto name = lvt::framework_display_name(fi);
if (fi.version.empty())
printf("%s\n", lvt::framework_to_string(fi.type).c_str());
printf("%s\n", name.c_str());
else
printf("%s %s\n", lvt::framework_to_string(fi.type).c_str(),
fi.version.c_str());
printf("%s %s\n", name.c_str(), fi.version.c_str());
}
lvt::unload_plugins();
return 0;
}

Expand All @@ -221,10 +226,11 @@ int main(int argc, char* argv[]) {
if (args.dump) {
std::vector<std::string> frameworkNames;
for (auto& fi : frameworks) {
auto name = lvt::framework_display_name(fi);
if (fi.version.empty())
frameworkNames.push_back(lvt::framework_to_string(fi.type));
frameworkNames.push_back(name);
else
frameworkNames.push_back(lvt::framework_to_string(fi.type) + " " + fi.version);
frameworkNames.push_back(name + " " + fi.version);
}

std::string serialized;
Expand Down Expand Up @@ -259,5 +265,6 @@ int main(int argc, char* argv[]) {
}
}

lvt::unload_plugins();
return 0;
}
89 changes: 89 additions & 0 deletions src/plugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#pragma once

// lvt plugin interface — C ABI for runtime-loaded framework provider plugins.
// Plugins are DLLs placed in %USERPROFILE%/.lvt/plugins/ and discovered at startup.
// This header is the ONLY dependency between lvt core and any plugin.

#include <stdint.h>
#include <Windows.h>

#ifdef __cplusplus
extern "C" {
#endif

#define LVT_PLUGIN_API_VERSION 1

// ---------- Plugin metadata ----------

struct LvtPluginInfo {
uint32_t struct_size; // sizeof(LvtPluginInfo), for versioning
uint32_t api_version; // must be LVT_PLUGIN_API_VERSION
const char* name; // short identifier, e.g. "myframework"
const char* description; // human-readable, e.g. "Custom framework support"
};

// ---------- Framework detection ----------

struct LvtFrameworkDetection {
uint32_t struct_size;
const char* name; // framework name reported by plugin
const char* version; // version string or NULL
};

// ---------- Element data (C ABI mirror of lvt::Element) ----------

struct LvtBounds {
int32_t x, y, width, height;
};

struct LvtProperty {
const char* key;
const char* value;
};

struct LvtElementData {
uint32_t struct_size;
const char* type;
const char* framework;
const char* class_name;
const char* text;
LvtBounds bounds;
const LvtProperty* properties;
uint32_t property_count;
struct LvtElementData* children;
uint32_t child_count;
uintptr_t native_handle; // e.g. HWND
};

// ---------- Plugin entry points ----------
// Plugins must export these functions by name.

// Returns static plugin metadata. Called once at load time.
typedef LvtPluginInfo* (*LvtPluginInfoFn)(void);

// Detect if this plugin's framework is present in the target process.
// Returns nonzero if detected, fills `out` with framework info.
// `out` is caller-allocated. Plugin should set name and version fields.
typedef int (*LvtDetectFrameworkFn)(DWORD pid, HWND hwnd, LvtFrameworkDetection* out);

// Enrich the element tree with this plugin's framework data.
// `json_out` receives a malloc'd JSON string (caller frees with lvt_plugin_free).
// The JSON follows the same schema as the XAML TAP DLL output:
// [{"type":"...", "name":"...", "children":[...], "width":..., "height":..., "offsetX":..., "offsetY":...}]
// `hwnd_filter` is the HWND of a specific host window to scope enrichment to,
// or NULL for all.
// Returns nonzero on success.
typedef int (*LvtEnrichTreeFn)(HWND hwnd, DWORD pid, const char* element_class_filter, char** json_out);

// Free memory allocated by the plugin (e.g. json_out from LvtEnrichTreeFn).
typedef void (*LvtPluginFreeFn)(void* ptr);

// Exported function names (for GetProcAddress)
#define LVT_PLUGIN_INFO_FUNC "lvt_plugin_info"
#define LVT_PLUGIN_DETECT_FUNC "lvt_detect_framework"
#define LVT_PLUGIN_ENRICH_FUNC "lvt_enrich_tree"
#define LVT_PLUGIN_FREE_FUNC "lvt_plugin_free"

#ifdef __cplusplus
}
#endif
Loading