From 9fd1e6bae200bd550fa82d4b1e35dd526ea962b7 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 11 Nov 2016 13:15:00 +0100 Subject: [PATCH] Add qprocessinfo from https://github.com/baldurk/qprocessinfo * This lets us enumerate processes and fill the inject list on windows and linux at least. --- qrenderdoc/Code/qprocessinfo.cpp | 378 +++++++++++++++++++ qrenderdoc/Code/qprocessinfo.h | 36 ++ qrenderdoc/Windows/Dialogs/CaptureDialog.cpp | 23 +- qrenderdoc/qrenderdoc.pro | 2 + qrenderdoc/qrenderdoc_local.vcxproj | 2 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 6 + 6 files changed, 436 insertions(+), 11 deletions(-) create mode 100644 qrenderdoc/Code/qprocessinfo.cpp create mode 100644 qrenderdoc/Code/qprocessinfo.h diff --git a/qrenderdoc/Code/qprocessinfo.cpp b/qrenderdoc/Code/qprocessinfo.cpp new file mode 100644 index 000000000..c72dbe8b7 --- /dev/null +++ b/qrenderdoc/Code/qprocessinfo.cpp @@ -0,0 +1,378 @@ +// Copyright (c) 2016, Baldur Karlsson +// +// Licensed under BSD 2-Clause License, see LICENSE file. +// +// Obtained from https://github.com/baldurk/qprocessinfo + +#include "qprocessinfo.h" + +#if defined(Q_OS_WIN32) + +#include + +#include + +typedef DWORD(WINAPI *PFN_GETWINDOWTHREADPROCESSID)(HWND hWnd, LPDWORD lpdwProcessId); +typedef HWND(WINAPI *PFN_GETWINDOW)(HWND hWnd, UINT uCmd); +typedef BOOL(WINAPI *PFN_ISWINDOWVISIBLE)(HWND hWnd); +typedef int(WINAPI *PFN_GETWINDOWTEXTLENGTHW)(HWND hWnd); +typedef int(WINAPI *PFN_GETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString, int nMaxCount); +typedef BOOL(WINAPI *PFN_ENUMWINDOWS)(WNDENUMPROC lpEnumFunc, LPARAM lParam); + +namespace +{ +struct callbackContext +{ + callbackContext(QProcessList &l) : list(l) {} + QProcessList &list; + + PFN_GETWINDOWTHREADPROCESSID GetWindowThreadProcessId; + PFN_GETWINDOW GetWindow; + PFN_ISWINDOWVISIBLE IsWindowVisible; + PFN_GETWINDOWTEXTLENGTHW GetWindowTextLengthW; + PFN_GETWINDOWTEXTW GetWindowTextW; + PFN_ENUMWINDOWS EnumWindows; +}; +}; + +static BOOL CALLBACK fillWindowTitles(HWND hwnd, LPARAM lp) +{ + callbackContext *ctx = (callbackContext *)lp; + + DWORD pid = 0; + ctx->GetWindowThreadProcessId(hwnd, &pid); + + HWND parent = ctx->GetWindow(hwnd, GW_OWNER); + + if(parent != 0) + return TRUE; + + if(!ctx->IsWindowVisible(hwnd)) + return TRUE; + + for(QProcessInfo &info : ctx->list) + { + if(info.pid() == (uint32_t)pid) + { + int len = ctx->GetWindowTextLengthW(hwnd); + wchar_t *buf = new wchar_t[len + 1]; + ctx->GetWindowTextW(hwnd, buf, len + 1); + buf[len] = 0; + info.setWindowTitle(QString::fromStdWString(std::wstring(buf))); + delete[] buf; + return TRUE; + } + } + + return TRUE; +} + +QProcessList QProcessInfo::enumerate() +{ + QProcessList ret; + + HANDLE h = NULL; + PROCESSENTRY32 pe = {0}; + pe.dwSize = sizeof(PROCESSENTRY32); + h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(Process32First(h, &pe)) + { + do + { + QProcessInfo info; + info.setPid((uint32_t)pe.th32ProcessID); + info.setName(QString::fromStdWString(std::wstring(pe.szExeFile))); + + ret.push_back(info); + } while(Process32Next(h, &pe)); + } + CloseHandle(h); + + HMODULE user32 = LoadLibraryA("user32.dll"); + + if(user32) + { + callbackContext ctx(ret); + + ctx.GetWindowThreadProcessId = + (PFN_GETWINDOWTHREADPROCESSID)GetProcAddress(user32, "GetWindowThreadProcessId"); + ctx.GetWindow = (PFN_GETWINDOW)GetProcAddress(user32, "GetWindow"); + ctx.IsWindowVisible = (PFN_ISWINDOWVISIBLE)GetProcAddress(user32, "IsWindowVisible"); + ctx.GetWindowTextLengthW = + (PFN_GETWINDOWTEXTLENGTHW)GetProcAddress(user32, "GetWindowTextLengthW"); + ctx.GetWindowTextW = (PFN_GETWINDOWTEXTW)GetProcAddress(user32, "GetWindowTextW"); + ctx.EnumWindows = (PFN_ENUMWINDOWS)GetProcAddress(user32, "EnumWindows"); + + if(ctx.GetWindowThreadProcessId && ctx.GetWindow && ctx.IsWindowVisible && + ctx.GetWindowTextLengthW && ctx.GetWindowTextW && ctx.EnumWindows) + { + ctx.EnumWindows(fillWindowTitles, (LPARAM)&ctx); + } + + FreeLibrary(user32); + } + + return ret; +} + +#elif defined(Q_OS_UNIX) + +#include +#include +#include +#include + +QProcessList QProcessInfo::enumerate() +{ + QProcessList ret; + + QDir proc("/proc"); + + QStringList files = proc.entryList(); + + for(const QString &f : files) + { + bool ok = false; + uint32_t pid = f.toUInt(&ok); + + if(ok) + { + QProcessInfo info; + info.setPid(pid); + + QDir processDir("/proc/" + f); + + // default to the exe symlink if valid + QFileInfo exe(processDir.absoluteFilePath("exe")); + exe = QFileInfo(exe.symLinkTarget()); + info.setName(exe.completeBaseName()); + + // if we didn't get a name from the symlink, check in the status file + if(info.name() == "") + { + QFile status(processDir.absoluteFilePath("status")); + if(status.open(QIODevice::ReadOnly)) + { + QByteArray contents = status.readAll(); + + QTextStream in(&contents); + while(!in.atEnd()) + { + QString line = in.readLine(); + + if(line.startsWith("Name:")) + { + line.remove(0, 5); + // if we're using this name, surround with []s to indicate it's not a file + info.setName(QString("[%1]").arg(line.trimmed())); + break; + } + } + status.close(); + } + } + + // get the command line + QFile cmdline(processDir.absoluteFilePath("cmdline")); + if(cmdline.open(QIODevice::ReadOnly)) + { + QByteArray contents = cmdline.readAll(); + + int nullIdx = contents.indexOf('\0'); + + if(nullIdx > 0) + { + QString firstparam = QString::fromUtf8(contents.data(), nullIdx); + + // if name is a truncated form of a filename, replace it + if(firstparam.endsWith(info.name()) && QFileInfo::exists(firstparam)) + info.setName(QFileInfo(firstparam).completeBaseName()); + + // if we don't have a name, replace it but with []s + if(info.name() == "") + info.setName(QString("[%1]").arg(firstparam)); + + contents.replace('\0', " "); + } + + info.setCommandLine(QString::fromUtf8(contents).trimmed()); + + cmdline.close(); + } + + ret.push_back(info); + } + } + + { + // get a list of all windows. This is faster than searching with --pid + // for every PID, and usually there will be fewer windows than PIDs. + QStringList params; + params << "search" + << "--onlyvisible" + << ".*"; + + QList windowlist; + + { + QProcess process; + process.start("xdotool", params); + process.waitForFinished(100); + + windowlist = process.readAll().split('\n'); + } + + // if xdotool isn't installed or failed to run, we'll have an empty + // list or else entries that aren't numbers, so we'll skip them + for(const QByteArray &win : windowlist) + { + // empty result, no window matches + if(win.size() == 0) + continue; + + bool isUInt = false; + win.toUInt(&isUInt); + + // skip invalid lines (maybe because xdotool failed) + if(!isUInt) + continue; + + // get the PID of the window first. If one isn't available we won't + // be able to match it up to our entries so don't proceed further + params.clear(); + params << "getwindowpid" << win; + + uint32_t pid = 0; + + { + QProcess process; + process.start("xdotool", params); + process.waitForFinished(100); + + pid = process.readAll().trimmed().toUInt(&isUInt); + } + + // can't find a PID, skip this window + if(!isUInt || pid == 0) + continue; + + // check to see if the geometry is somewhere offscreen + params.clear(); + params << "getwindowgeometry" << win; + + QList winGeometry; + + { + QProcess process; + process.start("xdotool", params); + process.waitForFinished(100); + + winGeometry = process.readAll().split('\n'); + } + + // should be three lines: Window \n Position: ... \n Geometry: ... + if(winGeometry.size() >= 3) + { + QRegExp pos("Position: (-?\\d+),(-?\\d+)"); + QRegExp geometry("Geometry: (\\d+)x(\\d+)"); + + QString posString = QString::fromUtf8(winGeometry[1]); + QString geometryString = QString::fromUtf8(winGeometry[2]); + + int x = 0, y = 0, w = 1000, h = 1000; + + if(pos.indexIn(posString) >= 0) + { + x = pos.cap(1).toInt(); + y = pos.cap(2).toInt(); + } + + if(geometry.indexIn(geometryString) >= 0) + { + w = geometry.cap(1).toInt(); + h = geometry.cap(2).toInt(); + } + + // some invisible windows are placed off screen, if we detect that skip it + if(x + w < 0 && y + h < 0) + continue; + } + + // take the first window name + { + params.clear(); + params << "getwindowname" << win; + + QProcess process; + process.start("xdotool", params); + process.waitForFinished(100); + + QString windowTitle = QString::fromUtf8(process.readAll().split('\n')[0]); + + for(QProcessInfo &info : ret) + { + if(info.pid() == pid) + { + info.setWindowTitle(windowTitle); + break; + } + } + } + } + } + + return ret; +} + +#else + +QProcessList QProcessInfo::enumerate() +{ + QProcessList ret; + + qWarning() << "Process enumeration not supported on this platform"; + + return ret; +} + +#endif + +uint32_t QProcessInfo::pid() const +{ + return m_pid; +} + +void QProcessInfo::setPid(uint32_t pid) +{ + m_pid = pid; +} + +const QString &QProcessInfo::name() const +{ + return m_name; +} + +void QProcessInfo::setName(const QString &name) +{ + m_name = name; +} + +const QString &QProcessInfo::windowTitle() const +{ + return m_title; +} + +void QProcessInfo::setWindowTitle(const QString &title) +{ + m_title = title; +} + +const QString &QProcessInfo::commandLine() const +{ + return m_cmdLine; +} + +void QProcessInfo::setCommandLine(const QString &cmd) +{ + m_cmdLine = cmd; +} diff --git a/qrenderdoc/Code/qprocessinfo.h b/qrenderdoc/Code/qprocessinfo.h new file mode 100644 index 000000000..af2b1341c --- /dev/null +++ b/qrenderdoc/Code/qprocessinfo.h @@ -0,0 +1,36 @@ +// Copyright (c) 2016, Baldur Karlsson +// +// Licensed under BSD 2-Clause License, see LICENSE file. +// +// Obtained from https://github.com/baldurk/qprocessinfo + +#pragma once + +#include + +class QProcessInfo; +typedef QList QProcessList; + +class QProcessInfo +{ +public: + static QProcessList enumerate(); + + uint32_t pid() const; + void setPid(uint32_t pid); + + const QString &name() const; + void setName(const QString &name); + + const QString &windowTitle() const; + void setWindowTitle(const QString &title); + + const QString &commandLine() const; + void setCommandLine(const QString &cmd); + +private: + uint32_t m_pid; + QString m_name; + QString m_title; + QString m_cmdLine; +}; diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index 6f6220179..147a4a5e0 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -26,6 +26,7 @@ #include #include #include "Code/QRDUtils.h" +#include "Code/qprocessinfo.h" #include "FlowLayout.h" #include "ui_CaptureDialog.h" @@ -452,19 +453,19 @@ void CaptureDialog::fillProcessList() { m_ProcessModel->removeRows(0, m_ProcessModel->rowCount()); + QProcessList processes = QProcessInfo::enumerate(); + // no way of listing processes in Qt, fill with dummy data - m_ProcessModel->insertRows(0, 5); + m_ProcessModel->insertRows(0, processes.size()); -#define ROW(n, name, pid, title) \ - m_ProcessModel->setData(m_ProcessModel->index(n, 0), name); \ - m_ProcessModel->setData(m_ProcessModel->index(n, 1), pid); \ - m_ProcessModel->setData(m_ProcessModel->index(n, 2), title); - - ROW(0, "foo.exe", 123, "Foo Window"); - ROW(1, "magic.exe", 456, "Magic Window"); - ROW(2, "system", 999, "Scary System process"); - ROW(3, "chrome.exe", 4539, "Chrome - renderdoc.org"); - ROW(4, "firefox.exe", 8483, "Firefox - renderdoc.org"); + int n = 0; + for(const QProcessInfo &process : processes) + { + m_ProcessModel->setData(m_ProcessModel->index(n, 0), process.name()); + m_ProcessModel->setData(m_ProcessModel->index(n, 1), process.pid()); + m_ProcessModel->setData(m_ProcessModel->index(n, 2), process.windowTitle()); + n++; + } } void CaptureDialog::setExecutableFilename(QString filename) diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 89636748c..91e4db609 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -80,6 +80,7 @@ SOURCES += 3rdparty/toolwindowmanager/ToolWindowManager.cpp \ 3rdparty/toolwindowmanager/ToolWindowManagerWrapper.cpp \ 3rdparty/flowlayout/FlowLayout.cpp \ Code/qrenderdoc.cpp \ + Code/qprocessinfo.cpp \ Code/RenderManager.cpp \ Code/CommonPipelineState.cpp \ Code/PersistantConfig.cpp \ @@ -106,6 +107,7 @@ HEADERS += 3rdparty/toolwindowmanager/ToolWindowManager.h \ 3rdparty/toolwindowmanager/ToolWindowManagerWrapper.h \ 3rdparty/flowlayout/FlowLayout.h \ Code/CaptureContext.h \ + Code/qprocessinfo.h \ Code/RenderManager.h \ Code/PersistantConfig.h \ Code/CommonPipelineState.h \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 69303a94a..ededc7511 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -278,6 +278,7 @@ + @@ -334,6 +335,7 @@ + diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index cafea34f0..8582dbec1 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -181,6 +181,9 @@ Generated Files + + Code + @@ -270,6 +273,9 @@ Generated Files + + Code +