From 4029b05298900017f22f066d553a81ad0cab5a39 Mon Sep 17 00:00:00 2001 From: Sen66 Date: Mon, 9 Feb 2026 15:09:42 +0100 Subject: [PATCH] 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 --- CMakeLists.txt | 25 +- plugins/ProcessMemory/CMakeLists.txt | 12 +- plugins/ProcessMemory/ProcessMemoryPlugin.cpp | 357 +++++++++++++++--- plugins/ProcessMemory/ProcessMemoryPlugin.h | 46 +-- src/iplugin.h | 12 +- src/main.cpp | 2 +- src/processpicker.cpp | 44 +++ 7 files changed, 421 insertions(+), 77 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a74ff00..44e5fa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/plugins/ProcessMemory/CMakeLists.txt b/plugins/ProcessMemory/CMakeLists.txt index b114ee8..31d0400 100644 --- a/plugins/ProcessMemory/CMakeLists.txt +++ b/plugins/ProcessMemory/CMakeLists.txt @@ -26,8 +26,18 @@ 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 +target_include_directories(ProcessMemoryPlugin PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../src ) diff --git a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp index 1f58a0f..2f04b4e 100644 --- a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp +++ b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp @@ -1,17 +1,40 @@ #include "ProcessMemoryPlugin.h" + #include "../../src/processpicker.h" + #include #include #include #include #include #include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#elif defined(__linux__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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) @@ -19,7 +42,7 @@ ProcessMemoryProvider::ProcessMemoryProvider(DWORD pid, const QString& processNa , m_base(0) { // Try to open with write access first - m_handle = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, + m_handle = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, FALSE, pid); if (m_handle) m_writable = true; @@ -31,21 +54,13 @@ 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 { if (!m_handle || len <= 0) return false; - + SIZE_T bytesRead = 0; if (ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead)) return bytesRead == (SIZE_T)len; @@ -55,7 +70,7 @@ bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len) { if (!m_handle || !m_writable || len <= 0) return false; - + SIZE_T bytesWritten = 0; if (WriteProcessMemory(m_handle, (LPVOID)(m_base + addr), buf, (SIZE_T)len, &bytesWritten)) return bytesWritten == (SIZE_T)len; @@ -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(len); + + struct iovec remote; + remote.iov_base = reinterpret_cast(absAddr); + remote.iov_len = static_cast(len); + + ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0); + if (nread == static_cast(len)) + return true; + + // Fallback: pread on /proc//mem + nread = ::pread(m_fd, buf, static_cast(len), static_cast(absAddr)); + return nread == static_cast(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(buf); + local.iov_len = static_cast(len); + + struct iovec remote; + remote.iov_base = reinterpret_cast(absAddr); + remote.iov_len = static_cast(len); + + ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0); + if (nwritten == static_cast(len)) + return true; + + // Fallback: pwrite on /proc//mem + nwritten = ::pwrite(m_fd, buf, static_cast(len), static_cast(absAddr)); + return nwritten == static_cast(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//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 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,27 +325,26 @@ std::unique_ptr ProcessMemoryPlugin::createProvider(const QString // Parse target: "pid:name" or just "pid" QStringList parts = target.split(':'); bool ok = false; - DWORD pid = parts[0].toUInt(&ok); - - if (!ok || pid == 0) { + uint32_t pid = parts[0].toUInt(&ok); + + if (!ok || pid == 0) + { if (errorMsg) *errorMsg = "Invalid PID: " + target; return nullptr; } - + QString name = parts.size() > 1 ? parts[1] : QString("PID %1").arg(pid); - + auto provider = std::make_unique(pid, name); 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; } - + return provider; } @@ -151,26 +356,49 @@ uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const bool ok = false; DWORD pid = parts[0].toUInt(&ok); if (!ok || pid == 0) return 0; - + // Open process to get main module base HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); if (!hProc) return 0; - + uint64_t base = 0; HMODULE hMod = nullptr; DWORD needed = 0; - + if (EnumProcessModulesEx(hProc, &hMod, sizeof(hMod), &needed, LIST_MODULES_ALL) && hMod) { 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//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; @@ -181,7 +409,7 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target) { // Use custom process enumeration from plugin QVector pluginProcesses = enumerateProcesses(); - + // Convert to ProcessInfo for ProcessPicker QList processes; for (const auto& pinfo : pluginProcesses) @@ -193,50 +421,50 @@ bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target) info.icon = pinfo.icon; processes.append(info); } - + // Show ProcessPicker with custom process list ProcessPicker picker(processes, parent); if (picker.exec() == QDialog::Accepted) { uint32_t pid = picker.selectedProcessId(); QString name = picker.selectedProcessName(); - + // Format target as "pid:name" *target = QString("%1:%2").arg(pid).arg(name); return true; } - + return false; } QVector ProcessMemoryPlugin::enumerateProcesses() { QVector processes; - + #ifdef _WIN32 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { return processes; } - + PROCESSENTRY32W entry; entry.dwSize = sizeof(entry); - + if (Process32FirstW(snapshot, &entry)) { do { PluginProcessInfo info; info.pid = entry.th32ProcessID; info.name = QString::fromWCharArray(entry.szExeFile); - + // Try to get full path and icon HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID); if (hProcess) { wchar_t path[MAX_PATH * 2]; DWORD pathLen = sizeof(path) / sizeof(wchar_t); - + // Try QueryFullProcessImageNameW first if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) { info.path = QString::fromWCharArray(path); - + // Extract icon SHFILEINFOW sfi = {}; if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) { @@ -247,18 +475,57 @@ QVector ProcessMemoryPlugin::enumerateProcesses() } } } - + CloseHandle(hProcess); } - + processes.append(info); - + } while (Process32NextW(snapshot, &entry)); } - + 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//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//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 ProcessMemoryPlugin::enumerateProcesses() // Plugin factory // ────────────────────────────────────────────────────────────────────────── -extern "C" __declspec(dllexport) IPlugin* CreatePlugin() +extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin() { return new ProcessMemoryPlugin(); } diff --git a/plugins/ProcessMemory/ProcessMemoryPlugin.h b/plugins/ProcessMemory/ProcessMemoryPlugin.h index 456ea7a..089d4fc 100644 --- a/plugins/ProcessMemory/ProcessMemoryPlugin.h +++ b/plugins/ProcessMemory/ProcessMemoryPlugin.h @@ -1,42 +1,45 @@ #pragma once #include "../../src/iplugin.h" #include "../../src/core.h" -#include -#include -#include -#include + +#include /** - * 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; bool isWritable() const override { return m_writable; } QString name() const override { return m_processName; } QString kind() const override { return QStringLiteral("LocalProcess"); } 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(); } 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,24 +55,25 @@ 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; - + bool canHandle(const QString& target) const override; std::unique_ptr createProvider(const QString& target, QString* errorMsg) override; uint64_t getInitialBaseAddress(const QString& target) const override; bool selectTarget(QWidget* parent, QString* target) override; - + // Optional: provide custom process list bool providesProcessList() const override { return true; } QVector enumerateProcesses() override; }; // Plugin export -extern "C" __declspec(dllexport) IPlugin* CreatePlugin(); +extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin(); diff --git a/src/iplugin.h b/src/iplugin.h index 2e2ed9f..fca3b20 100644 --- a/src/iplugin.h +++ b/src/iplugin.h @@ -4,14 +4,20 @@ #include #include +#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: diff --git a/src/main.cpp b/src/main.cpp index 76f8835..289334b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 }); } diff --git a/src/processpicker.cpp b/src/processpicker.cpp index 54704fd..bdd3b94 100644 --- a/src/processpicker.cpp +++ b/src/processpicker.cpp @@ -11,6 +11,11 @@ #include #include #include +#elif defined(__linux__) +#include +#include +#include +#include #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//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//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.");