120 lines
4.2 KiB
C++
120 lines
4.2 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
#include "list_view.h"
|
|
#include "control_class.h"
|
|
#include "native_query.h"
|
|
#include <commctrl.h>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <iterator>
|
|
|
|
namespace {
|
|
using native_control::query;
|
|
|
|
// LVM_GETITEMTEXT is not marshalled by USER32. The structure and text buffer
|
|
// must live in the target process, with pointers matching its architecture.
|
|
struct Item32 {
|
|
uint32_t mask;
|
|
int32_t item, subItem;
|
|
uint32_t state, stateMask, text;
|
|
int32_t capacity;
|
|
};
|
|
struct Item64 {
|
|
uint32_t mask;
|
|
int32_t item, subItem;
|
|
uint32_t state, stateMask, padding;
|
|
uint64_t text;
|
|
int32_t capacity;
|
|
uint32_t padding2;
|
|
};
|
|
static_assert(offsetof(Item32, text) == 20);
|
|
static_assert(offsetof(Item64, text) == 24);
|
|
constexpr size_t textOffset = 128;
|
|
constexpr size_t textCapacity = 1024;
|
|
constexpr size_t bufferSize = textOffset + textCapacity * sizeof(wchar_t);
|
|
}
|
|
|
|
bool is_list_view(HWND window) {
|
|
wchar_t className[64] = {};
|
|
return GetClassNameW(window, className, std::size(className)) &&
|
|
wine_access_class_matches(className, L"SysListView32");
|
|
}
|
|
|
|
int list_view_index(HWND window, int *count) {
|
|
DWORD_PTR result = 0;
|
|
*count = 0;
|
|
if (!query(window, LVM_GETITEMCOUNT, 0, 0, &result) ||
|
|
static_cast<LONG_PTR>(result) <= 0)
|
|
return -1;
|
|
*count = static_cast<int>(result);
|
|
if (query(window, LVM_GETNEXTITEM, static_cast<WPARAM>(-1), LVNI_FOCUSED,
|
|
&result) && static_cast<LONG_PTR>(result) >= 0)
|
|
return static_cast<int>(result);
|
|
if (query(window, LVM_GETNEXTITEM, static_cast<WPARAM>(-1), LVNI_SELECTED,
|
|
&result) && static_cast<LONG_PTR>(result) >= 0)
|
|
return static_cast<int>(result);
|
|
// An installer can focus a populated list without selecting any row.
|
|
return 0;
|
|
}
|
|
|
|
ListViewRow read_list_view(HWND window) {
|
|
ListViewRow row;
|
|
row.index = list_view_index(window, &row.count);
|
|
if (row.index < 0 || row.index >= row.count)
|
|
return row;
|
|
DWORD_PTR state = 0;
|
|
if (query(window, LVM_GETITEMSTATE, row.index, LVIS_SELECTED, &state))
|
|
row.selected = state & LVIS_SELECTED;
|
|
row.checked = list_view_checked(window, row.index);
|
|
native_control::RemoteBuffer remote(window, bufferSize);
|
|
if (!remote) return row;
|
|
const bool wow64 = remote.is32Bit();
|
|
DWORD_PTR header = 0, columnCount = 1;
|
|
if (query(window, LVM_GETHEADER, 0, 0, &header) && header != 0) {
|
|
DWORD_PTR columns = 0;
|
|
if (query(reinterpret_cast<HWND>(header), HDM_GETITEMCOUNT, 0, 0, &columns) &&
|
|
columns > 0) columnCount = columns;
|
|
}
|
|
// Keep provider work and the protocol payload bounded even for huge tables.
|
|
if (columnCount > 8) columnCount = 8;
|
|
for (DWORD_PTR column = 0; column < columnCount; ++column) {
|
|
BYTE buffer[bufferSize] = {};
|
|
const auto textAddress = remote.address(textOffset);
|
|
if (wow64) {
|
|
Item32 item{LVIF_TEXT, row.index, static_cast<int32_t>(column),
|
|
0, 0, static_cast<uint32_t>(textAddress), textCapacity};
|
|
memcpy(buffer, &item, sizeof(item));
|
|
} else {
|
|
Item64 item{LVIF_TEXT, row.index, static_cast<int32_t>(column),
|
|
0, 0, 0, textAddress, textCapacity, 0};
|
|
memcpy(buffer, &item, sizeof(item));
|
|
}
|
|
DWORD_PTR result = 0;
|
|
wchar_t text[textCapacity] = {};
|
|
if (!remote.write(buffer, sizeof(buffer)) ||
|
|
!query(window, LVM_GETITEMTEXTW, row.index, static_cast<LPARAM>(remote.address()), &result) ||
|
|
!remote.read(text, sizeof(text), textOffset))
|
|
break;
|
|
text[textCapacity - 1] = L'\0';
|
|
if (!text[0]) continue;
|
|
char utf8[textCapacity * 4] = {};
|
|
if (!WideCharToMultiByte(CP_UTF8, 0, text, -1, utf8, sizeof(utf8), nullptr, nullptr))
|
|
continue;
|
|
if (!row.text.empty()) row.text += ", ";
|
|
row.text += utf8;
|
|
}
|
|
return row;
|
|
}
|
|
|
|
int list_view_checked(HWND window, int index) {
|
|
DWORD_PTR style = 0, state = 0;
|
|
if (index < 0 ||
|
|
!query(window, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0, &style) ||
|
|
!(style & LVS_EX_CHECKBOXES) ||
|
|
!query(window, LVM_GETITEMSTATE, index, LVIS_STATEIMAGEMASK, &state))
|
|
return -1;
|
|
const DWORD_PTR image = (state & LVIS_STATEIMAGEMASK) >> 12;
|
|
// Other state images are application-defined; do not invent a check state.
|
|
return image == 1 ? 0 : image == 2 ? 1 : -1;
|
|
}
|