mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Added linux support (tested on Ubuntu)
CMakeList: fixed for building on linux processpicker: linux process enumeration main.cpp: "_Exit()" works on linux & windows "ProcessMemory" plugin: added linux support
This commit is contained in:
@@ -43,9 +43,10 @@ target_link_libraries(ReclassX PRIVATE
|
||||
Qt6::Svg
|
||||
Qt6::Concurrent
|
||||
QScintilla::QScintilla
|
||||
dbghelp
|
||||
psapi
|
||||
)
|
||||
if(WIN32)
|
||||
target_link_libraries(ReclassX PRIVATE dbghelp psapi)
|
||||
endif()
|
||||
|
||||
add_custom_target(screenshot ALL
|
||||
COMMAND ReclassX --screenshot ${CMAKE_BINARY_DIR}/screenshot.png
|
||||
@@ -140,7 +141,10 @@ if(BUILD_TESTING)
|
||||
target_include_directories(test_controller PRIVATE src)
|
||||
target_link_libraries(test_controller PRIVATE
|
||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||
QScintilla::QScintilla dbghelp psapi)
|
||||
QScintilla::QScintilla)
|
||||
if(WIN32)
|
||||
target_link_libraries(test_controller PRIVATE dbghelp psapi)
|
||||
endif()
|
||||
add_test(NAME test_controller COMMAND test_controller)
|
||||
|
||||
add_executable(test_validation tests/test_validation.cpp
|
||||
@@ -149,7 +153,10 @@ if(BUILD_TESTING)
|
||||
target_include_directories(test_validation PRIVATE src)
|
||||
target_link_libraries(test_validation PRIVATE
|
||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||
QScintilla::QScintilla dbghelp psapi)
|
||||
QScintilla::QScintilla)
|
||||
if(WIN32)
|
||||
target_link_libraries(test_validation PRIVATE dbghelp psapi)
|
||||
endif()
|
||||
add_test(NAME test_validation COMMAND test_validation)
|
||||
|
||||
add_executable(test_generator tests/test_generator.cpp
|
||||
@@ -164,7 +171,10 @@ if(BUILD_TESTING)
|
||||
target_include_directories(test_context_menu PRIVATE src)
|
||||
target_link_libraries(test_context_menu PRIVATE
|
||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||
QScintilla::QScintilla dbghelp psapi)
|
||||
QScintilla::QScintilla)
|
||||
if(WIN32)
|
||||
target_link_libraries(test_context_menu PRIVATE dbghelp psapi)
|
||||
endif()
|
||||
add_test(NAME test_context_menu COMMAND test_context_menu)
|
||||
|
||||
add_executable(test_new_features tests/test_new_features.cpp
|
||||
@@ -173,7 +183,10 @@ if(BUILD_TESTING)
|
||||
target_include_directories(test_new_features PRIVATE src)
|
||||
target_link_libraries(test_new_features PRIVATE
|
||||
Qt6::Widgets Qt6::PrintSupport Qt6::Concurrent Qt6::Test
|
||||
QScintilla::QScintilla dbghelp psapi)
|
||||
QScintilla::QScintilla)
|
||||
if(WIN32)
|
||||
target_link_libraries(test_new_features PRIVATE dbghelp psapi)
|
||||
endif()
|
||||
add_test(NAME test_new_features COMMAND test_new_features)
|
||||
endif()
|
||||
add_subdirectory(plugins/ProcessMemory)
|
||||
|
||||
@@ -26,6 +26,16 @@ add_library(ProcessMemoryPlugin SHARED ${PLUGIN_SOURCES})
|
||||
# Link Qt
|
||||
target_link_libraries(ProcessMemoryPlugin PRIVATE Qt6::Widgets)
|
||||
|
||||
# Platform-specific linking
|
||||
if(WIN32)
|
||||
target_link_libraries(ProcessMemoryPlugin PRIVATE psapi shell32)
|
||||
endif()
|
||||
|
||||
# On Linux, hide all symbols by default so only RCX_PLUGIN_EXPORT-marked ones are exported
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_options(ProcessMemoryPlugin PRIVATE -fvisibility=hidden)
|
||||
endif()
|
||||
|
||||
# Include directories
|
||||
target_include_directories(ProcessMemoryPlugin PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
#include "ProcessMemoryPlugin.h"
|
||||
|
||||
#include "../../src/processpicker.h"
|
||||
|
||||
#include <QStyle>
|
||||
#include <QApplication>
|
||||
#include <QRegularExpression>
|
||||
#include <QMessageBox>
|
||||
#include <QPixmap>
|
||||
#include <QImage>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
#elif defined(__linux__)
|
||||
#include <climits>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/uio.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// ProcessMemoryProvider implementation
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processName)
|
||||
#ifdef _WIN32
|
||||
|
||||
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||
: m_handle(nullptr)
|
||||
, m_pid(pid)
|
||||
, m_processName(processName)
|
||||
@@ -31,16 +54,8 @@ ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processNa
|
||||
}
|
||||
|
||||
if (m_handle)
|
||||
{
|
||||
cacheModules();
|
||||
}
|
||||
}
|
||||
|
||||
ProcessMemoryProvider::~ProcessMemoryProvider()
|
||||
{
|
||||
if (m_handle)
|
||||
CloseHandle(m_handle);
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
@@ -64,9 +79,16 @@ bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||
|
||||
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||
{
|
||||
// TODO: Implement module enumeration with EnumProcessModules
|
||||
// For now, just return empty (no symbol resolution)
|
||||
Q_UNUSED(addr);
|
||||
for (const auto& mod : m_modules)
|
||||
{
|
||||
if (addr >= mod.base && addr < mod.base + mod.size)
|
||||
{
|
||||
uint64_t offset = addr - mod.base;
|
||||
return QStringLiteral("%1+0x%2")
|
||||
.arg(mod.name)
|
||||
.arg(offset, 0, 16, QChar('0'));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -98,6 +120,190 @@ void ProcessMemoryProvider::cacheModules()
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||
: m_fd(-1)
|
||||
, m_pid(pid)
|
||||
, m_processName(processName)
|
||||
, m_writable(false)
|
||||
, m_base(0)
|
||||
{
|
||||
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||
QByteArray pathUtf8 = memPath.toUtf8();
|
||||
|
||||
// Try read-write first
|
||||
m_fd = ::open(pathUtf8.constData(), O_RDWR);
|
||||
if (m_fd >= 0)
|
||||
m_writable = true;
|
||||
else
|
||||
{
|
||||
// Fall back to read-only
|
||||
m_fd = ::open(pathUtf8.constData(), O_RDONLY);
|
||||
m_writable = false;
|
||||
}
|
||||
|
||||
if (m_fd >= 0)
|
||||
cacheModules();
|
||||
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
||||
{
|
||||
if (m_fd < 0 || len <= 0) return false;
|
||||
|
||||
uint64_t absAddr = m_base + addr;
|
||||
|
||||
// Try process_vm_readv first (faster, no fd seek contention)
|
||||
struct iovec local;
|
||||
local.iov_base = buf;
|
||||
local.iov_len = static_cast<size_t>(len);
|
||||
|
||||
struct iovec remote;
|
||||
remote.iov_base = reinterpret_cast<void*>(absAddr);
|
||||
remote.iov_len = static_cast<size_t>(len);
|
||||
|
||||
ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0);
|
||||
if (nread == static_cast<ssize_t>(len))
|
||||
return true;
|
||||
|
||||
// Fallback: pread on /proc/<pid>/mem
|
||||
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
|
||||
return nread == static_cast<ssize_t>(len);
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||
{
|
||||
if (m_fd < 0 || !m_writable || len <= 0) return false;
|
||||
|
||||
uint64_t absAddr = m_base + addr;
|
||||
|
||||
// Try process_vm_writev first
|
||||
struct iovec local;
|
||||
local.iov_base = const_cast<void*>(buf);
|
||||
local.iov_len = static_cast<size_t>(len);
|
||||
|
||||
struct iovec remote;
|
||||
remote.iov_base = reinterpret_cast<void*>(absAddr);
|
||||
remote.iov_len = static_cast<size_t>(len);
|
||||
|
||||
ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0);
|
||||
if (nwritten == static_cast<ssize_t>(len))
|
||||
return true;
|
||||
|
||||
// Fallback: pwrite on /proc/<pid>/mem
|
||||
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(absAddr));
|
||||
return nwritten == static_cast<ssize_t>(len);
|
||||
}
|
||||
|
||||
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
||||
{
|
||||
for (const auto& mod : m_modules)
|
||||
{
|
||||
if (addr >= mod.base && addr < mod.base + mod.size)
|
||||
{
|
||||
uint64_t offset = addr - mod.base;
|
||||
return QStringLiteral("%1+0x%2")
|
||||
.arg(mod.name)
|
||||
.arg(offset, 0, 16, QChar('0'));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProcessMemoryProvider::cacheModules()
|
||||
{
|
||||
// Parse /proc/<pid>/maps to discover loaded modules
|
||||
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
||||
std::ifstream mapsFile(mapsPath.toStdString());
|
||||
if (!mapsFile.is_open()) return;
|
||||
|
||||
// Accumulate base/end per path, then convert to ModuleInfo
|
||||
struct Range { uint64_t base; uint64_t end; };
|
||||
QMap<QString, Range> moduleRanges;
|
||||
|
||||
std::string line;
|
||||
bool firstExec = true;
|
||||
while (std::getline(mapsFile, line))
|
||||
{
|
||||
// Format: addr_start-addr_end perms offset dev inode pathname
|
||||
// Example: 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/foo
|
||||
std::istringstream iss(line);
|
||||
std::string addrRange, perms, offset, dev, inode, pathname;
|
||||
iss >> addrRange >> perms >> offset >> dev >> inode;
|
||||
std::getline(iss, pathname);
|
||||
|
||||
// Trim leading whitespace from pathname
|
||||
size_t start = pathname.find_first_not_of(" \t");
|
||||
if (start == std::string::npos) continue;
|
||||
pathname = pathname.substr(start);
|
||||
|
||||
// Skip non-file mappings
|
||||
if (pathname.empty() || pathname[0] != '/') continue;
|
||||
// Skip special mappings
|
||||
if (pathname.find("/dev/") == 0 || pathname.find("/memfd:") == 0) continue;
|
||||
|
||||
// Parse address range
|
||||
auto dash = addrRange.find('-');
|
||||
if (dash == std::string::npos) continue;
|
||||
uint64_t addrStart = std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||
uint64_t addrEnd = std::stoull(addrRange.substr(dash + 1), nullptr, 16);
|
||||
|
||||
QString qpath = QString::fromStdString(pathname);
|
||||
|
||||
// Track first executable mapping as the base address
|
||||
if (firstExec && perms.size() >= 3 && perms[2] == 'x')
|
||||
{
|
||||
m_base = addrStart;
|
||||
firstExec = false;
|
||||
}
|
||||
|
||||
auto it = moduleRanges.find(qpath);
|
||||
if (it != moduleRanges.end())
|
||||
{
|
||||
if (addrStart < it->base) it->base = addrStart;
|
||||
if (addrEnd > it->end) it->end = addrEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
moduleRanges.insert(qpath, {addrStart, addrEnd});
|
||||
}
|
||||
}
|
||||
|
||||
m_modules.reserve(moduleRanges.size());
|
||||
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
||||
{
|
||||
QFileInfo fi(it.key());
|
||||
m_modules.append({
|
||||
fi.fileName(),
|
||||
it->base,
|
||||
it->end - it->base
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endif // platform
|
||||
|
||||
ProcessMemoryProvider::~ProcessMemoryProvider()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_handle)
|
||||
CloseHandle(m_handle);
|
||||
#elif defined(__linux__)
|
||||
if (m_fd >= 0)
|
||||
::close(m_fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
int ProcessMemoryProvider::size() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return m_handle ? INT_MAX : 0;
|
||||
#elif defined(__linux__)
|
||||
return m_fd ? INT_MAX : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// ProcessMemoryPlugin implementation
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -119,9 +325,10 @@ std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString
|
||||
// Parse target: "pid:name" or just "pid"
|
||||
QStringList parts = target.split(':');
|
||||
bool ok = false;
|
||||
DWORD pid = parts[0].toUInt(&ok);
|
||||
uint32_t pid = parts[0].toUInt(&ok);
|
||||
|
||||
if (!ok || pid == 0) {
|
||||
if (!ok || pid == 0)
|
||||
{
|
||||
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -132,11 +339,9 @@ std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString
|
||||
if (!provider->isValid())
|
||||
{
|
||||
if (errorMsg)
|
||||
{
|
||||
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
||||
"Ensure the process is running and you have sufficient permissions.")
|
||||
.arg(name).arg(pid);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -164,13 +369,36 @@ uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
||||
{
|
||||
MODULEINFO mi{};
|
||||
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
||||
{
|
||||
base = (uint64_t)mi.lpBaseOfDll;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hProc);
|
||||
return base;
|
||||
#elif defined(__linux__)
|
||||
// Parse PID from target
|
||||
QStringList parts = target.split(':');
|
||||
bool ok = false;
|
||||
uint32_t pid = parts[0].toUInt(&ok);
|
||||
if (!ok || pid == 0) return 0;
|
||||
|
||||
// Find first executable mapping from /proc/<pid>/maps
|
||||
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(pid);
|
||||
std::ifstream mapsFile(mapsPath.toStdString());
|
||||
if (!mapsFile.is_open()) return 0;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(mapsFile, line)) {
|
||||
std::istringstream iss(line);
|
||||
std::string addrRange, perms;
|
||||
iss >> addrRange >> perms;
|
||||
if (perms.size() >= 3 && perms[2] == 'x') {
|
||||
auto dash = addrRange.find('-');
|
||||
if (dash != std::string::npos) {
|
||||
return std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
Q_UNUSED(target);
|
||||
return 0;
|
||||
@@ -257,6 +485,45 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
#elif defined(__linux__)
|
||||
QDir procDir("/proc");
|
||||
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||
|
||||
for (const QString& entry : entries) {
|
||||
bool ok = false;
|
||||
uint32_t pid = entry.toUInt(&ok);
|
||||
if (!ok || pid == 0) continue;
|
||||
|
||||
// Read process name from /proc/<pid>/comm
|
||||
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
||||
QFile commFile(commPath);
|
||||
QString procName;
|
||||
if (commFile.open(QIODevice::ReadOnly)) {
|
||||
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
||||
commFile.close();
|
||||
}
|
||||
if (procName.isEmpty()) continue; // Skip kernel threads with no name
|
||||
|
||||
// Read exe path from /proc/<pid>/exe symlink
|
||||
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||
QFileInfo exeInfo(exePath);
|
||||
QString resolvedPath;
|
||||
if (exeInfo.exists())
|
||||
resolvedPath = exeInfo.symLinkTarget();
|
||||
|
||||
// Skip if we can't read the process memory (no access)
|
||||
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
||||
continue;
|
||||
|
||||
PluginProcessInfo info;
|
||||
info.pid = pid;
|
||||
info.name = procName;
|
||||
info.path = resolvedPath;
|
||||
info.icon = defaultIcon;
|
||||
processes.append(info);
|
||||
}
|
||||
#endif
|
||||
|
||||
return processes;
|
||||
@@ -266,7 +533,7 @@ QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
||||
// Plugin factory
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
extern "C" __declspec(dllexport) IPlugin* CreatePlugin()
|
||||
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
||||
{
|
||||
return new ProcessMemoryPlugin();
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
#pragma once
|
||||
#include "../../src/iplugin.h"
|
||||
#include "../../src/core.h"
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* Windows process memory provider
|
||||
* Reads/writes memory from a live process using Win32 API
|
||||
* Process memory provider
|
||||
* Reads/writes memory from a live process using platform APIs
|
||||
*/
|
||||
class ProcessMemoryProvider : public rcx::Provider {
|
||||
class ProcessMemoryProvider : public rcx::Provider
|
||||
{
|
||||
public:
|
||||
ProcessMemoryProvider(DWORD pid, const QString& processName);
|
||||
ProcessMemoryProvider(uint32_t pid, const QString& processName);
|
||||
~ProcessMemoryProvider() override;
|
||||
|
||||
// Required overrides
|
||||
bool read(uint64_t addr, void* buf, int len) const override;
|
||||
int size() const override { return m_handle ? INT_MAX : NULL; } // Process memory has no fixed size
|
||||
int size() const override;
|
||||
|
||||
// Optional overrides
|
||||
bool write(uint64_t addr, const void* buf, int len) override;
|
||||
@@ -27,7 +26,7 @@ public:
|
||||
QString getSymbol(uint64_t addr) const override;
|
||||
|
||||
// Process-specific helpers
|
||||
DWORD pid() const { return m_pid; }
|
||||
uint32_t pid() const { return m_pid; }
|
||||
uint64_t baseAddress() const { return m_base; }
|
||||
void refreshModules() { m_modules.clear(); cacheModules(); }
|
||||
|
||||
@@ -35,8 +34,12 @@ private:
|
||||
void cacheModules();
|
||||
|
||||
private:
|
||||
HANDLE m_handle;
|
||||
DWORD m_pid;
|
||||
#ifdef _WIN32
|
||||
void* m_handle;
|
||||
#elif defined(__linux__)
|
||||
int m_fd;
|
||||
#endif
|
||||
uint32_t m_pid;
|
||||
QString m_processName;
|
||||
bool m_writable;
|
||||
uint64_t m_base;
|
||||
@@ -52,12 +55,13 @@ private:
|
||||
/**
|
||||
* Plugin that provides ProcessMemoryProvider
|
||||
*/
|
||||
class ProcessMemoryPlugin : public IProviderPlugin {
|
||||
class ProcessMemoryPlugin : public IProviderPlugin
|
||||
{
|
||||
public:
|
||||
std::string Name() const override { return "Process Memory"; }
|
||||
std::string Version() const override { return "1.0.0"; }
|
||||
std::string Author() const override { return "ReclassX"; }
|
||||
std::string Description() const override { return "Read and write memory from local running Windows processes"; }
|
||||
std::string Description() const override { return "Read and write memory from local running processes"; }
|
||||
k_ELoadType LoadType() const override { return k_ELoadTypeAuto; }
|
||||
QIcon Icon() const override;
|
||||
|
||||
@@ -72,4 +76,4 @@ public:
|
||||
};
|
||||
|
||||
// Plugin export
|
||||
extern "C" __declspec(dllexport) IPlugin* CreatePlugin();
|
||||
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||
|
||||
@@ -4,14 +4,20 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define RCX_PLUGIN_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define RCX_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
// Forward declaration
|
||||
namespace rcx { class Provider; }
|
||||
|
||||
/**
|
||||
* Plugin interface for ReclassX
|
||||
*
|
||||
* Plugins are loaded from the "Plugins" folder as DLLs.
|
||||
* Each plugin must export a C function: extern "C" __declspec(dllexport) IPlugin* CreatePlugin();
|
||||
* Plugins are loaded from the "Plugins" folder as shared libraries.
|
||||
* Each plugin must export a C function: extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin();
|
||||
*/
|
||||
class IPlugin {
|
||||
public:
|
||||
|
||||
@@ -1188,7 +1188,7 @@ int main(int argc, char* argv[]) {
|
||||
QTimer::singleShot(1000, [&window, out]() {
|
||||
QDir().mkpath(QFileInfo(out).absolutePath());
|
||||
window.grab().save(out);
|
||||
::_exit(0); // immediate exit — no need for clean shutdown in screenshot mode
|
||||
::_Exit(0); // immediate exit — no need for clean shutdown in screenshot mode
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
#include <tlhelp32.h>
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
#elif defined(__linux__)
|
||||
#include <QDir>
|
||||
#include <QStyle>
|
||||
#include <QApplication>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
ProcessPicker::ProcessPicker(QWidget *parent)
|
||||
@@ -155,6 +160,45 @@ void ProcessPicker::enumerateProcesses()
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
#elif defined(__linux__)
|
||||
QDir procDir("/proc");
|
||||
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||
|
||||
for (const QString& entry : entries) {
|
||||
bool ok = false;
|
||||
uint32_t pid = entry.toUInt(&ok);
|
||||
if (!ok || pid == 0) continue;
|
||||
|
||||
// Read process name from /proc/<pid>/comm
|
||||
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
||||
QFile commFile(commPath);
|
||||
QString procName;
|
||||
if (commFile.open(QIODevice::ReadOnly)) {
|
||||
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
||||
commFile.close();
|
||||
}
|
||||
if (procName.isEmpty()) continue;
|
||||
|
||||
// Read exe path from /proc/<pid>/exe symlink
|
||||
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
||||
QFileInfo exeInfo(exePath);
|
||||
QString resolvedPath;
|
||||
if (exeInfo.exists())
|
||||
resolvedPath = exeInfo.symLinkTarget();
|
||||
|
||||
// Skip if we can't read the process memory
|
||||
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
||||
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
||||
continue;
|
||||
|
||||
ProcessInfo info;
|
||||
info.pid = pid;
|
||||
info.name = procName;
|
||||
info.path = resolvedPath;
|
||||
info.icon = defaultIcon;
|
||||
processes.append(info);
|
||||
}
|
||||
#else
|
||||
// Platform not supported
|
||||
QMessageBox::warning(this, "Error", "Process enumeration not supported on this platform.");
|
||||
|
||||
Reference in New Issue
Block a user