mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: WinDbg plugin dynamic dbgeng loading, editor two-tone bg, UI polish
WinDbg plugin: load dbgeng.dll dynamically from Debugging Tools directory instead of static linking (system dbgeng.dll lacks remote DebugConnect). Copy tools dbghelp.dll next to exe so it loads before System32 version. Add COM init on DbgEng thread, browse for tools dir, styled dialog. Editor: derive darker background via theme.background.darker(115) for visual depth between chrome and editor surfaces. UI: global scrollbar styling, workspace accent bar 1px, pane tab font from editor settings, workspace dock default width 128px.
This commit is contained in:
@@ -151,6 +151,26 @@ target_link_libraries(Reclass PRIVATE
|
|||||||
)
|
)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi raw_pdb)
|
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi raw_pdb)
|
||||||
|
|
||||||
|
# Copy Debugging Tools dbghelp.dll next to Reclass.exe so the Windows
|
||||||
|
# loader picks it up (app dir > System32). The system dbghelp.dll
|
||||||
|
# lacks StackWalk2 which the tools dbgeng.dll needs for remote debug.
|
||||||
|
set(_DBG_TOOLS_DIRS
|
||||||
|
"C:/Program Files (x86)/Windows Kits/10/Debuggers/x64"
|
||||||
|
"C:/Program Files/Windows Kits/10/Debuggers/x64")
|
||||||
|
foreach(_dir ${_DBG_TOOLS_DIRS})
|
||||||
|
if(EXISTS "${_dir}/dbghelp.dll")
|
||||||
|
foreach(_dll dbghelp.dll dbgcore.dll symsrv.dll)
|
||||||
|
if(EXISTS "${_dir}/${_dll}")
|
||||||
|
add_custom_command(TARGET Reclass POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${_dir}/${_dll}" $<TARGET_FILE_DIR:Reclass>
|
||||||
|
COMMENT "Copying ${_dll} from Debugging Tools")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(ReclassMcpBridge tools/rcx-mcp-stdio.cpp)
|
add_executable(ReclassMcpBridge tools/rcx-mcp-stdio.cpp)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ set(PLUGIN_SOURCES
|
|||||||
add_library(WinDbgMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
add_library(WinDbgMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||||
|
|
||||||
# Link Qt + DbgEng
|
# Link Qt + DbgEng
|
||||||
target_link_libraries(WinDbgMemoryPlugin PRIVATE ${QT}::Widgets dbgeng ole32)
|
target_link_libraries(WinDbgMemoryPlugin PRIVATE ${QT}::Widgets ole32)
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(WinDbgMemoryPlugin PRIVATE
|
target_include_directories(WinDbgMemoryPlugin PRIVATE
|
||||||
|
|||||||
@@ -12,12 +12,99 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <initguid.h>
|
#include <initguid.h>
|
||||||
#include <dbgeng.h>
|
#include <dbgeng.h>
|
||||||
#pragma comment(lib, "dbgeng.lib")
|
// dbgeng.dll is loaded dynamically — see loadDbgEngTools()
|
||||||
|
|
||||||
|
// The system dbgeng.dll (C:\Windows\System32) does not support remote
|
||||||
|
// connections (DebugConnect returns 0x8007053d). The full version lives
|
||||||
|
// in the Debugging Tools for Windows directory. We load it dynamically
|
||||||
|
// so the plugin works without requiring the debugger tools on PATH.
|
||||||
|
static const char* const kDbgToolsDirs[] = {
|
||||||
|
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64",
|
||||||
|
"C:\\Program Files\\Windows Kits\\10\\Debuggers\\x64",
|
||||||
|
};
|
||||||
|
static const char* const kSettingsKey = "WinDbgPlugin/DbgToolsDir";
|
||||||
|
|
||||||
|
typedef HRESULT (STDAPICALLTYPE *PFN_DebugConnect)(PCSTR, REFIID, PVOID*);
|
||||||
|
typedef HRESULT (STDAPICALLTYPE *PFN_DebugCreate)(REFIID, PVOID*);
|
||||||
|
|
||||||
|
static QString s_loadedDir;
|
||||||
|
static HMODULE s_hDbgEng = nullptr;
|
||||||
|
|
||||||
|
static HMODULE tryLoadFrom(const char* dir) {
|
||||||
|
SetDllDirectoryA(dir);
|
||||||
|
// Pre-load dependencies so the tools versions are used instead of
|
||||||
|
// the older System32 copies (e.g. dbghelp.dll without StackWalk2).
|
||||||
|
char path[MAX_PATH];
|
||||||
|
for (auto dep : {"dbghelp.dll", "dbgcore.dll", "symsrv.dll"}) {
|
||||||
|
snprintf(path, sizeof(path), "%s\\%s", dir, dep);
|
||||||
|
LoadLibraryA(path); // OK if missing
|
||||||
|
}
|
||||||
|
snprintf(path, sizeof(path), "%s\\dbgeng.dll", dir);
|
||||||
|
HMODULE h = LoadLibraryA(path);
|
||||||
|
if (h) {
|
||||||
|
s_loadedDir = QString::fromLocal8Bit(dir);
|
||||||
|
qDebug() << "[WinDbg] Loaded dbgeng.dll from" << dir;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HMODULE loadDbgEngTools() {
|
||||||
|
if (s_hDbgEng) return s_hDbgEng;
|
||||||
|
|
||||||
|
// 1. Try user-configured path from settings
|
||||||
|
QSettings settings;
|
||||||
|
QString userDir = settings.value(kSettingsKey).toString();
|
||||||
|
if (!userDir.isEmpty()) {
|
||||||
|
s_hDbgEng = tryLoadFrom(userDir.toLocal8Bit().constData());
|
||||||
|
if (s_hDbgEng) return s_hDbgEng;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Try well-known install paths
|
||||||
|
for (auto dir : kDbgToolsDirs) {
|
||||||
|
s_hDbgEng = tryLoadFrom(dir);
|
||||||
|
if (s_hDbgEng) return s_hDbgEng;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDllDirectoryA(nullptr);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dbgToolsFound() {
|
||||||
|
loadDbgEngTools();
|
||||||
|
return s_hDbgEng != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PFN_DebugConnect getDebugConnect() {
|
||||||
|
static PFN_DebugConnect pfn = nullptr;
|
||||||
|
static bool tried = false;
|
||||||
|
if (!tried) {
|
||||||
|
tried = true;
|
||||||
|
HMODULE h = loadDbgEngTools();
|
||||||
|
if (h) pfn = (PFN_DebugConnect)GetProcAddress(h, "DebugConnect");
|
||||||
|
if (!pfn) qWarning() << "[WinDbg] DebugConnect not available — Debugging Tools not found";
|
||||||
|
}
|
||||||
|
return pfn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PFN_DebugCreate getDebugCreate() {
|
||||||
|
static PFN_DebugCreate pfn = nullptr;
|
||||||
|
static bool tried = false;
|
||||||
|
if (!tried) {
|
||||||
|
tried = true;
|
||||||
|
HMODULE h = loadDbgEngTools();
|
||||||
|
if (h) pfn = (PFN_DebugCreate)GetProcAddress(h, "DebugCreate");
|
||||||
|
if (!pfn) qWarning() << "[WinDbg] DebugCreate not available — Debugging Tools not found";
|
||||||
|
}
|
||||||
|
return pfn;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
@@ -65,6 +152,9 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|
|||||||
dispatchToOwner([this, &target]() {
|
dispatchToOwner([this, &target]() {
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
|
// COM must be initialized on this thread for DbgEng
|
||||||
|
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
|
|
||||||
qDebug() << "[WinDbg] Opening target:" << target
|
qDebug() << "[WinDbg] Opening target:" << target
|
||||||
<< "on DbgEng thread" << QThread::currentThread();
|
<< "on DbgEng thread" << QThread::currentThread();
|
||||||
|
|
||||||
@@ -72,9 +162,11 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|
|||||||
|| target.startsWith("npipe:", Qt::CaseInsensitive))
|
|| target.startsWith("npipe:", Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
// ── Remote: connect to existing WinDbg debug server ──
|
// ── Remote: connect to existing WinDbg debug server ──
|
||||||
|
auto pfnConnect = getDebugConnect();
|
||||||
|
if (!pfnConnect) { qWarning() << "[WinDbg] Debugging Tools required for remote connections"; return; }
|
||||||
QByteArray connUtf8 = target.toUtf8();
|
QByteArray connUtf8 = target.toUtf8();
|
||||||
qDebug() << "[WinDbg] DebugConnect:" << target;
|
qDebug() << "[WinDbg] DebugConnect:" << target;
|
||||||
hr = DebugConnect(connUtf8.constData(), IID_IDebugClient, (void**)&m_client);
|
hr = pfnConnect(connUtf8.constData(), IID_IDebugClient, (void**)&m_client);
|
||||||
qDebug() << "[WinDbg] DebugConnect hr=" << Qt::hex << (unsigned long)hr
|
qDebug() << "[WinDbg] DebugConnect hr=" << Qt::hex << (unsigned long)hr
|
||||||
<< "client=" << (void*)m_client;
|
<< "client=" << (void*)m_client;
|
||||||
if (FAILED(hr) || !m_client) {
|
if (FAILED(hr) || !m_client) {
|
||||||
@@ -86,7 +178,9 @@ WinDbgMemoryProvider::WinDbgMemoryProvider(const QString& target)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// ── Local: create debug client for pid/dump ──
|
// ── Local: create debug client for pid/dump ──
|
||||||
hr = DebugCreate(IID_IDebugClient, (void**)&m_client);
|
auto pfnCreate = getDebugCreate();
|
||||||
|
if (!pfnCreate) { qWarning() << "[WinDbg] Debugging Tools required"; return; }
|
||||||
|
hr = pfnCreate(IID_IDebugClient, (void**)&m_client);
|
||||||
qDebug() << "[WinDbg] DebugCreate hr=" << Qt::hex << (unsigned long)hr
|
qDebug() << "[WinDbg] DebugCreate hr=" << Qt::hex << (unsigned long)hr
|
||||||
<< "client=" << (void*)m_client;
|
<< "client=" << (void*)m_client;
|
||||||
if (FAILED(hr) || !m_client) {
|
if (FAILED(hr) || !m_client) {
|
||||||
@@ -239,6 +333,7 @@ WinDbgMemoryProvider::~WinDbgMemoryProvider()
|
|||||||
m_client->DetachProcesses();
|
m_client->DetachProcesses();
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
|
CoUninitialize();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Thread not running — clean up directly (best-effort)
|
// Thread not running — clean up directly (best-effort)
|
||||||
@@ -503,7 +598,7 @@ std::unique_ptr<rcx::Provider> WinDbgMemoryPlugin::createProvider(const QString&
|
|||||||
*errorMsg = QString("Failed to connect to debug server.\n\n"
|
*errorMsg = QString("Failed to connect to debug server.\n\n"
|
||||||
"Target: %1\n\n"
|
"Target: %1\n\n"
|
||||||
"Make sure WinDbg is running with a matching .server command\n"
|
"Make sure WinDbg is running with a matching .server command\n"
|
||||||
"(e.g. .server tcp:port=5055) and the port/pipe is reachable.")
|
"(e.g. .server tcp:port=5056) and the port/pipe is reachable.")
|
||||||
.arg(target);
|
.arg(target);
|
||||||
else if (target.startsWith("pid:", Qt::CaseInsensitive))
|
else if (target.startsWith("pid:", Qt::CaseInsensitive))
|
||||||
*errorMsg = QString("Failed to attach to process.\n\n"
|
*errorMsg = QString("Failed to attach to process.\n\n"
|
||||||
@@ -532,7 +627,7 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
|||||||
{
|
{
|
||||||
QDialog dlg(parent);
|
QDialog dlg(parent);
|
||||||
dlg.setWindowTitle("WinDbg Settings");
|
dlg.setWindowTitle("WinDbg Settings");
|
||||||
dlg.resize(460, 300);
|
dlg.resize(480, 360);
|
||||||
|
|
||||||
QPalette dlgPal = qApp->palette();
|
QPalette dlgPal = qApp->palette();
|
||||||
dlg.setPalette(dlgPal);
|
dlg.setPalette(dlgPal);
|
||||||
@@ -540,17 +635,27 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
|||||||
|
|
||||||
auto* layout = new QVBoxLayout(&dlg);
|
auto* layout = new QVBoxLayout(&dlg);
|
||||||
|
|
||||||
|
QColor editBg = dlgPal.window().color().darker(115);
|
||||||
|
QString editSS = QStringLiteral(
|
||||||
|
"QLineEdit { background: %1; color: %2; border: 1px solid %3;"
|
||||||
|
" border-radius: 3px; padding: 4px 6px; }")
|
||||||
|
.arg(editBg.name(),
|
||||||
|
dlgPal.color(QPalette::Text).name(),
|
||||||
|
dlgPal.color(QPalette::Mid).name());
|
||||||
|
|
||||||
layout->addWidget(new QLabel(
|
layout->addWidget(new QLabel(
|
||||||
"Connect to a running WinDbg debug server.\n"
|
"Connect to a running WinDbg debug server.\n"
|
||||||
"In WinDbg, run: .server tcp:port=5055\n\n"
|
"In WinDbg, run: .server tcp:port=5056\n\n"
|
||||||
"Non-invasive debug and dump files only.\n"
|
"Non-invasive debug and dump files only.\n"
|
||||||
"Execution control (bp, g, t, p) is not supported."));
|
"Execution control (bp, g, t, p) is not supported.\n"
|
||||||
|
"WinDbg Classic is recommended."));
|
||||||
|
|
||||||
layout->addSpacing(8);
|
layout->addSpacing(8);
|
||||||
layout->addWidget(new QLabel("Connection string:"));
|
layout->addWidget(new QLabel("Connection string:"));
|
||||||
auto* connEdit = new QLineEdit;
|
auto* connEdit = new QLineEdit;
|
||||||
connEdit->setPlaceholderText("tcp:Port=5055,Server=localhost");
|
connEdit->setPlaceholderText("tcp:Port=5056,Server=127.0.0.1");
|
||||||
connEdit->setText("tcp:Port=5055,Server=localhost");
|
connEdit->setText("tcp:Port=5056,Server=127.0.0.1");
|
||||||
|
connEdit->setStyleSheet(editSS);
|
||||||
layout->addWidget(connEdit);
|
layout->addWidget(connEdit);
|
||||||
|
|
||||||
layout->addSpacing(4);
|
layout->addSpacing(4);
|
||||||
@@ -574,8 +679,72 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
|||||||
layout->addLayout(row);
|
layout->addLayout(row);
|
||||||
};
|
};
|
||||||
|
|
||||||
addExample(".server tcp:port=5055");
|
addExample(".server tcp:port=5056");
|
||||||
addExample(".server npipe:pipe=reclass");
|
addExample(".server npipe:pipe=reclass");
|
||||||
|
|
||||||
|
// ── Debugger Tools status ──
|
||||||
|
layout->addSpacing(8);
|
||||||
|
#ifdef _WIN32
|
||||||
|
bool found = dbgToolsFound();
|
||||||
|
auto* toolsRow = new QHBoxLayout;
|
||||||
|
auto* toolsLabel = new QLabel;
|
||||||
|
if (found) {
|
||||||
|
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
|
||||||
|
QPalette tp = dlgPal;
|
||||||
|
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
|
||||||
|
toolsLabel->setPalette(tp);
|
||||||
|
} else {
|
||||||
|
toolsLabel->setText("Debugging Tools: not found");
|
||||||
|
QPalette tp = dlgPal;
|
||||||
|
tp.setColor(QPalette::WindowText, QColor(220, 120, 80));
|
||||||
|
toolsLabel->setPalette(tp);
|
||||||
|
}
|
||||||
|
toolsLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
toolsRow->addWidget(toolsLabel, 1);
|
||||||
|
|
||||||
|
auto* browseBtn = new QPushButton("Browse...");
|
||||||
|
browseBtn->setFixedWidth(70);
|
||||||
|
browseBtn->setToolTip("Locate Debugging Tools for Windows directory (contains dbgeng.dll)");
|
||||||
|
QObject::connect(browseBtn, &QPushButton::clicked, [&dlg, toolsLabel, &dlgPal]() {
|
||||||
|
QString dir = QFileDialog::getExistingDirectory(&dlg,
|
||||||
|
"Locate Debugging Tools for Windows",
|
||||||
|
"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers");
|
||||||
|
if (dir.isEmpty()) return;
|
||||||
|
QString dllPath = dir + "/dbgeng.dll";
|
||||||
|
if (!QFileInfo::exists(dllPath)) {
|
||||||
|
QMessageBox::warning(&dlg, "Not Found",
|
||||||
|
"dbgeng.dll was not found in that directory.\n"
|
||||||
|
"Select the folder containing dbgeng.dll\n"
|
||||||
|
"(e.g. Debuggers\\x64).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue(kSettingsKey, dir);
|
||||||
|
// Force reload on next use
|
||||||
|
s_hDbgEng = nullptr;
|
||||||
|
s_loadedDir.clear();
|
||||||
|
if (dbgToolsFound()) {
|
||||||
|
toolsLabel->setText(QStringLiteral("Debugging Tools: %1").arg(s_loadedDir));
|
||||||
|
QPalette tp = dlgPal;
|
||||||
|
tp.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
|
||||||
|
toolsLabel->setPalette(tp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toolsRow->addWidget(browseBtn);
|
||||||
|
layout->addLayout(toolsRow);
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
auto* note = new QLabel(
|
||||||
|
"The system dbgeng.dll does not support remote connections.\n"
|
||||||
|
"Install Debugging Tools for Windows or use Browse to locate them.");
|
||||||
|
QPalette np = dlgPal;
|
||||||
|
np.setColor(QPalette::WindowText, dlgPal.color(QPalette::Disabled, QPalette::WindowText));
|
||||||
|
note->setPalette(np);
|
||||||
|
note->setWordWrap(true);
|
||||||
|
layout->addWidget(note);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
layout->addStretch();
|
layout->addStretch();
|
||||||
|
|
||||||
auto* btnLayout = new QHBoxLayout;
|
auto* btnLayout = new QHBoxLayout;
|
||||||
|
|||||||
@@ -837,8 +837,11 @@ void RcxEditor::allocateMarginStyles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RcxEditor::applyTheme(const Theme& theme) {
|
void RcxEditor::applyTheme(const Theme& theme) {
|
||||||
|
// Editor uses a slightly darker background than chrome for visual depth
|
||||||
|
const QColor editorBg = theme.background.darker(115);
|
||||||
|
|
||||||
// Paper and text
|
// Paper and text
|
||||||
m_sci->setPaper(theme.background);
|
m_sci->setPaper(editorBg);
|
||||||
m_sci->setColor(theme.text);
|
m_sci->setColor(theme.text);
|
||||||
m_sci->setCaretForegroundColor(theme.text);
|
m_sci->setCaretForegroundColor(theme.text);
|
||||||
|
|
||||||
@@ -882,25 +885,25 @@ void RcxEditor::applyTheme(const Theme& theme) {
|
|||||||
m_lexer->setColor(theme.text, QsciLexerCPP::Operator);
|
m_lexer->setColor(theme.text, QsciLexerCPP::Operator);
|
||||||
m_lexer->setColor(theme.syntaxType, QsciLexerCPP::GlobalClass);
|
m_lexer->setColor(theme.syntaxType, QsciLexerCPP::GlobalClass);
|
||||||
for (int i = 0; i <= 127; i++)
|
for (int i = 0; i <= 127; i++)
|
||||||
m_lexer->setPaper(theme.background, i);
|
m_lexer->setPaper(editorBg, i);
|
||||||
|
|
||||||
// Margins
|
// Margins
|
||||||
m_sci->setMarginsBackgroundColor(theme.background);
|
m_sci->setMarginsBackgroundColor(editorBg);
|
||||||
m_sci->setMarginsForegroundColor(theme.textFaint);
|
m_sci->setMarginsForegroundColor(theme.textFaint);
|
||||||
m_sci->setFoldMarginColors(theme.background, theme.background);
|
m_sci->setFoldMarginColors(editorBg, editorBg);
|
||||||
|
|
||||||
// Markers
|
// Markers
|
||||||
m_sci->setMarkerBackgroundColor(theme.markerPtr, M_PTR0);
|
m_sci->setMarkerBackgroundColor(theme.markerPtr, M_PTR0);
|
||||||
m_sci->setMarkerForegroundColor(theme.markerPtr, M_PTR0);
|
m_sci->setMarkerForegroundColor(theme.markerPtr, M_PTR0);
|
||||||
m_sci->setMarkerBackgroundColor(theme.background, M_CYCLE);
|
m_sci->setMarkerBackgroundColor(editorBg, M_CYCLE);
|
||||||
m_sci->setMarkerForegroundColor(theme.background, M_CYCLE);
|
m_sci->setMarkerForegroundColor(editorBg, M_CYCLE);
|
||||||
m_sci->setMarkerBackgroundColor(theme.markerError, M_ERR);
|
m_sci->setMarkerBackgroundColor(theme.markerError, M_ERR);
|
||||||
m_sci->setMarkerForegroundColor(theme.text, M_ERR);
|
m_sci->setMarkerForegroundColor(theme.text, M_ERR);
|
||||||
m_sci->setMarkerBackgroundColor(theme.background, M_STRUCT_BG);
|
m_sci->setMarkerBackgroundColor(editorBg, M_STRUCT_BG);
|
||||||
m_sci->setMarkerForegroundColor(theme.text, M_STRUCT_BG);
|
m_sci->setMarkerForegroundColor(theme.text, M_STRUCT_BG);
|
||||||
m_sci->setMarkerBackgroundColor(theme.hover, M_HOVER);
|
m_sci->setMarkerBackgroundColor(theme.hover, M_HOVER);
|
||||||
m_sci->setMarkerBackgroundColor(theme.selected, M_SELECTED);
|
m_sci->setMarkerBackgroundColor(theme.selected, M_SELECTED);
|
||||||
m_sci->setMarkerBackgroundColor(theme.background, M_CMD_ROW);
|
m_sci->setMarkerBackgroundColor(editorBg, M_CMD_ROW);
|
||||||
m_sci->setMarkerBackgroundColor(theme.indHoverSpan, M_ACCENT);
|
m_sci->setMarkerBackgroundColor(theme.indHoverSpan, M_ACCENT);
|
||||||
|
|
||||||
// Margin extended styles
|
// Margin extended styles
|
||||||
@@ -911,7 +914,7 @@ void RcxEditor::applyTheme(const Theme& theme) {
|
|||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE,
|
m_sci->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE,
|
||||||
abs, theme.textFaint);
|
abs, theme.textFaint);
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK,
|
m_sci->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK,
|
||||||
abs, theme.background);
|
abs, editorBg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
src/main.cpp
46
src/main.cpp
@@ -509,7 +509,19 @@ static void applyGlobalTheme(const rcx::Theme& theme) {
|
|||||||
|
|
||||||
qApp->setPalette(pal);
|
qApp->setPalette(pal);
|
||||||
|
|
||||||
qApp->setStyleSheet(QString());
|
// Global scrollbar styling — track matches control bg, handle is solid
|
||||||
|
qApp->setStyleSheet(QStringLiteral(
|
||||||
|
"QScrollBar:vertical { background: palette(window); width: 12px; margin: 0; border: none; }"
|
||||||
|
"QScrollBar::handle:vertical { background: %1; min-height: 20px; border: none; }"
|
||||||
|
"QScrollBar::handle:vertical:hover { background: %2; }"
|
||||||
|
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }"
|
||||||
|
"QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; }"
|
||||||
|
"QScrollBar:horizontal { background: palette(window); height: 12px; margin: 0; border: none; }"
|
||||||
|
"QScrollBar::handle:horizontal { background: %1; min-width: 20px; border: none; }"
|
||||||
|
"QScrollBar::handle:horizontal:hover { background: %2; }"
|
||||||
|
"QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; }"
|
||||||
|
"QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; }")
|
||||||
|
.arg(theme.textFaint.name(), theme.textDim.name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
class BorderOverlay : public QWidget {
|
class BorderOverlay : public QWidget {
|
||||||
@@ -1218,16 +1230,20 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) {
|
|||||||
// Style to match the top dock tab bar, with accent line on selected tab
|
// Style to match the top dock tab bar, with accent line on selected tab
|
||||||
{
|
{
|
||||||
const auto& t = ThemeManager::instance().current();
|
const auto& t = ThemeManager::instance().current();
|
||||||
|
QSettings s("Reclass", "Reclass");
|
||||||
|
QString editorFont = s.value("font", "JetBrains Mono").toString();
|
||||||
pane.tabWidget->setStyleSheet(QStringLiteral(
|
pane.tabWidget->setStyleSheet(QStringLiteral(
|
||||||
"QTabBar { border: none; }"
|
"QTabBar { border: none; }"
|
||||||
"QTabBar::tab {"
|
"QTabBar::tab {"
|
||||||
" background: %1; color: %2; padding: 0px 16px; border: none; border-radius: 0px; height: 24px;"
|
" background: %1; color: %2; padding: 0px 16px; border: none; border-radius: 0px; height: 24px;"
|
||||||
|
" font-family: '%7'; font-size: 10pt;"
|
||||||
"}"
|
"}"
|
||||||
"QTabBar::tab:selected { color: %3; background: %4;"
|
"QTabBar::tab:selected { color: %3; background: %4;"
|
||||||
" border-top: 3px solid %6; padding-top: -3px; }"
|
" border-top: 3px solid %6; padding-top: -3px; }"
|
||||||
"QTabBar::tab:hover { color: %3; background: %5; }")
|
"QTabBar::tab:hover { color: %3; background: %5; }")
|
||||||
.arg(t.background.name(), t.textMuted.name(), t.text.name(),
|
.arg(t.background.name(), t.textMuted.name(), t.text.name(),
|
||||||
t.backgroundAlt.name(), t.hover.name(), t.indHoverSpan.name()));
|
t.backgroundAlt.name(), t.hover.name(), t.indHoverSpan.name(),
|
||||||
|
editorFont));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create editor via controller (parent = tabWidget for ownership)
|
// Create editor via controller (parent = tabWidget for ownership)
|
||||||
@@ -2352,16 +2368,19 @@ void MainWindow::applyTheme(const Theme& theme) {
|
|||||||
|
|
||||||
// Restyle per-pane view tab bars (Reclass / C++)
|
// Restyle per-pane view tab bars (Reclass / C++)
|
||||||
{
|
{
|
||||||
|
QString editorFont = QSettings("Reclass", "Reclass").value("font", "JetBrains Mono").toString();
|
||||||
QString paneTabStyle = QStringLiteral(
|
QString paneTabStyle = QStringLiteral(
|
||||||
"QTabBar { border: none; }"
|
"QTabBar { border: none; }"
|
||||||
"QTabBar::tab {"
|
"QTabBar::tab {"
|
||||||
" background: %1; color: %2; padding: 0px 16px; border: none; border-radius: 0px; height: 24px;"
|
" background: %1; color: %2; padding: 0px 16px; border: none; border-radius: 0px; height: 24px;"
|
||||||
|
" font-family: '%7'; font-size: 10pt;"
|
||||||
"}"
|
"}"
|
||||||
"QTabBar::tab:selected { color: %3; background: %4;"
|
"QTabBar::tab:selected { color: %3; background: %4;"
|
||||||
" border-top: 3px solid %6; padding-top: -3px; }"
|
" border-top: 3px solid %6; padding-top: -3px; }"
|
||||||
"QTabBar::tab:hover { color: %3; background: %5; }")
|
"QTabBar::tab:hover { color: %3; background: %5; }")
|
||||||
.arg(theme.background.name(), theme.textMuted.name(), theme.text.name(),
|
.arg(theme.background.name(), theme.textMuted.name(), theme.text.name(),
|
||||||
theme.backgroundAlt.name(), theme.hover.name(), theme.indHoverSpan.name());
|
theme.backgroundAlt.name(), theme.hover.name(), theme.indHoverSpan.name(),
|
||||||
|
editorFont);
|
||||||
for (auto it = m_tabs.begin(); it != m_tabs.end(); ++it) {
|
for (auto it = m_tabs.begin(); it != m_tabs.end(); ++it) {
|
||||||
for (auto& pane : it->panes) {
|
for (auto& pane : it->panes) {
|
||||||
if (pane.tabWidget)
|
if (pane.tabWidget)
|
||||||
@@ -2498,10 +2517,11 @@ void MainWindow::applyTheme(const Theme& theme) {
|
|||||||
lexer->setColor(theme.text, QsciLexerCPP::Identifier);
|
lexer->setColor(theme.text, QsciLexerCPP::Identifier);
|
||||||
lexer->setColor(theme.syntaxPreproc, QsciLexerCPP::PreProcessor);
|
lexer->setColor(theme.syntaxPreproc, QsciLexerCPP::PreProcessor);
|
||||||
lexer->setColor(theme.text, QsciLexerCPP::Operator);
|
lexer->setColor(theme.text, QsciLexerCPP::Operator);
|
||||||
|
const QColor editorBg = theme.background.darker(115);
|
||||||
for (int i = 0; i <= 127; i++)
|
for (int i = 0; i <= 127; i++)
|
||||||
lexer->setPaper(theme.background, i);
|
lexer->setPaper(editorBg, i);
|
||||||
}
|
}
|
||||||
sci->setPaper(theme.background);
|
sci->setPaper(theme.background.darker(115));
|
||||||
sci->setColor(theme.text);
|
sci->setColor(theme.text);
|
||||||
sci->setCaretForegroundColor(theme.text);
|
sci->setCaretForegroundColor(theme.text);
|
||||||
sci->setCaretLineBackgroundColor(theme.hover);
|
sci->setCaretLineBackgroundColor(theme.hover);
|
||||||
@@ -2631,6 +2651,9 @@ void MainWindow::setEditorFont(const QString& fontName) {
|
|||||||
tabBar->update();
|
tabBar->update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Pane tab bars (Reclass / C++) — re-apply stylesheet with new font
|
||||||
|
// (stylesheet overrides setFont, so font must be in the CSS)
|
||||||
|
applyTheme(ThemeManager::instance().current());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2716,15 +2739,16 @@ void MainWindow::setupRenderedSci(QsciScintilla* sci) {
|
|||||||
lexer->setColor(theme.text, QsciLexerCPP::Identifier);
|
lexer->setColor(theme.text, QsciLexerCPP::Identifier);
|
||||||
lexer->setColor(theme.syntaxPreproc, QsciLexerCPP::PreProcessor);
|
lexer->setColor(theme.syntaxPreproc, QsciLexerCPP::PreProcessor);
|
||||||
lexer->setColor(theme.text, QsciLexerCPP::Operator);
|
lexer->setColor(theme.text, QsciLexerCPP::Operator);
|
||||||
|
const QColor editorBg = theme.background.darker(115);
|
||||||
for (int i = 0; i <= 127; i++) {
|
for (int i = 0; i <= 127; i++) {
|
||||||
lexer->setPaper(theme.background, i);
|
lexer->setPaper(editorBg, i);
|
||||||
lexer->setFont(f, i);
|
lexer->setFont(f, i);
|
||||||
}
|
}
|
||||||
sci->setLexer(lexer);
|
sci->setLexer(lexer);
|
||||||
sci->setBraceMatching(QsciScintilla::NoBraceMatch);
|
sci->setBraceMatching(QsciScintilla::NoBraceMatch);
|
||||||
|
|
||||||
// Colors applied AFTER setLexer() — the lexer resets these on attach
|
// Colors applied AFTER setLexer() — the lexer resets these on attach
|
||||||
sci->setPaper(theme.background);
|
sci->setPaper(editorBg);
|
||||||
sci->setColor(theme.text);
|
sci->setColor(theme.text);
|
||||||
sci->setCaretForegroundColor(theme.text);
|
sci->setCaretForegroundColor(theme.text);
|
||||||
sci->setCaretLineVisible(true);
|
sci->setCaretLineVisible(true);
|
||||||
@@ -2980,7 +3004,7 @@ void MainWindow::importFromSource() {
|
|||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
if (!m_docDocks.isEmpty()) {
|
if (!m_docDocks.isEmpty()) {
|
||||||
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
||||||
resizeDocks({m_workspaceDock}, {220}, Qt::Horizontal);
|
resizeDocks({m_workspaceDock}, {128}, Qt::Horizontal);
|
||||||
}
|
}
|
||||||
m_workspaceDock->show();
|
m_workspaceDock->show();
|
||||||
setAppStatus(QStringLiteral("Imported %1 classes from source").arg(classCount));
|
setAppStatus(QStringLiteral("Imported %1 classes from source").arg(classCount));
|
||||||
@@ -3034,7 +3058,7 @@ void MainWindow::importPdb() {
|
|||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
if (!m_docDocks.isEmpty()) {
|
if (!m_docDocks.isEmpty()) {
|
||||||
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
||||||
resizeDocks({m_workspaceDock}, {220}, Qt::Horizontal);
|
resizeDocks({m_workspaceDock}, {128}, Qt::Horizontal);
|
||||||
}
|
}
|
||||||
m_workspaceDock->show();
|
m_workspaceDock->show();
|
||||||
setAppStatus(QStringLiteral("Imported %1 classes from %2")
|
setAppStatus(QStringLiteral("Imported %1 classes from %2")
|
||||||
@@ -3225,7 +3249,7 @@ QDockWidget* MainWindow::project_open(const QString& path) {
|
|||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
if (!m_docDocks.isEmpty()) {
|
if (!m_docDocks.isEmpty()) {
|
||||||
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
||||||
resizeDocks({m_workspaceDock}, {220}, Qt::Horizontal);
|
resizeDocks({m_workspaceDock}, {128}, Qt::Horizontal);
|
||||||
}
|
}
|
||||||
m_workspaceDock->show();
|
m_workspaceDock->show();
|
||||||
int classCount = 0;
|
int classCount = 0;
|
||||||
@@ -3251,7 +3275,7 @@ QDockWidget* MainWindow::project_open(const QString& path) {
|
|||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
if (!m_docDocks.isEmpty()) {
|
if (!m_docDocks.isEmpty()) {
|
||||||
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
splitDockWidget(m_workspaceDock, m_docDocks.first(), Qt::Horizontal);
|
||||||
resizeDocks({m_workspaceDock}, {220}, Qt::Horizontal);
|
resizeDocks({m_workspaceDock}, {128}, Qt::Horizontal);
|
||||||
}
|
}
|
||||||
m_workspaceDock->show();
|
m_workspaceDock->show();
|
||||||
addRecentFile(filePath);
|
addRecentFile(filePath);
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ public:
|
|||||||
if (opt.state & QStyle::State_Selected) {
|
if (opt.state & QStyle::State_Selected) {
|
||||||
painter->fillRect(opt.rect, m_selected);
|
painter->fillRect(opt.rect, m_selected);
|
||||||
// Left accent bar
|
// Left accent bar
|
||||||
painter->fillRect(QRect(opt.rect.x(), opt.rect.y(), 2, opt.rect.height()), m_accent);
|
painter->fillRect(QRect(opt.rect.x(), opt.rect.y(), 1, opt.rect.height()), m_accent);
|
||||||
} else if (opt.state & QStyle::State_MouseOver) {
|
} else if (opt.state & QStyle::State_MouseOver) {
|
||||||
painter->fillRect(opt.rect, m_hover);
|
painter->fillRect(opt.rect, m_hover);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
using namespace rcx;
|
using namespace rcx;
|
||||||
|
|
||||||
static const char* CDB_PATH = "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\cdb.exe";
|
static const char* CDB_PATH = "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\cdb.exe";
|
||||||
static const int DBG_PORT = 5055;
|
static const int DBG_PORT = 5056;
|
||||||
|
|
||||||
class TestWinDbgProvider : public QObject {
|
class TestWinDbgProvider : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -132,7 +132,7 @@ private slots:
|
|||||||
|
|
||||||
void initTestCase()
|
void initTestCase()
|
||||||
{
|
{
|
||||||
m_connString = QString("tcp:Port=%1,Server=localhost").arg(DBG_PORT);
|
m_connString = QString("tcp:Port=%1,Server=127.0.0.1").arg(DBG_PORT);
|
||||||
|
|
||||||
// If a debug server is already listening (e.g. WinDbg with .server),
|
// If a debug server is already listening (e.g. WinDbg with .server),
|
||||||
// skip launching our own cdb.exe.
|
// skip launching our own cdb.exe.
|
||||||
@@ -207,7 +207,7 @@ private slots:
|
|||||||
void plugin_canHandle_tcp()
|
void plugin_canHandle_tcp()
|
||||||
{
|
{
|
||||||
WinDbgMemoryPlugin plugin;
|
WinDbgMemoryPlugin plugin;
|
||||||
QVERIFY(plugin.canHandle("tcp:Port=5055,Server=localhost"));
|
QVERIFY(plugin.canHandle("tcp:Port=5056,Server=localhost"));
|
||||||
QVERIFY(plugin.canHandle("TCP:Port=1234,Server=10.0.0.1"));
|
QVERIFY(plugin.canHandle("TCP:Port=1234,Server=10.0.0.1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +608,7 @@ private slots:
|
|||||||
// ── Kernel/dump session tests ──
|
// ── Kernel/dump session tests ──
|
||||||
// Set WINDBG_KERNEL_CONN to a target string:
|
// Set WINDBG_KERNEL_CONN to a target string:
|
||||||
// "dump:F:/path/to/file.dmp" — open dump directly
|
// "dump:F:/path/to/file.dmp" — open dump directly
|
||||||
// "tcp:Port=5055,Server=localhost" — connect to debug server
|
// "tcp:Port=5056,Server=localhost" — connect to debug server
|
||||||
// Set WINDBG_KERNEL_ADDR to a readable hex address (e.g. kernel base).
|
// Set WINDBG_KERNEL_ADDR to a readable hex address (e.g. kernel base).
|
||||||
|
|
||||||
static QString kernelTarget()
|
static QString kernelTarget()
|
||||||
|
|||||||
Reference in New Issue
Block a user