* This lets us enumerate processes and fill the inject list on windows
  and linux at least.
This commit is contained in:
baldurk
2016-11-11 13:15:00 +01:00
parent 7c4f0fd2e4
commit 9fd1e6bae2
6 changed files with 436 additions and 11 deletions
+378
View File
@@ -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 <windows.h>
#include <tlhelp32.h>
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 <QDir>
#include <QProcess>
#include <QRegExp>
#include <QTextStream>
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<QByteArray> 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<QByteArray> winGeometry;
{
QProcess process;
process.start("xdotool", params);
process.waitForFinished(100);
winGeometry = process.readAll().split('\n');
}
// should be three lines: Window <id> \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;
}
+36
View File
@@ -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 <QList>
class QProcessInfo;
typedef QList<QProcessInfo> 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;
};
+12 -11
View File
@@ -26,6 +26,7 @@
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#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)
+2
View File
@@ -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 \
+2
View File
@@ -278,6 +278,7 @@
<ItemGroup>
<ClCompile Include="Code\CommonPipelineState.cpp" />
<ClCompile Include="Code\PersistantConfig.cpp" />
<ClCompile Include="Code\qprocessinfo.cpp" />
<ClCompile Include="Code\QRDUtils.cpp" />
<ClCompile Include="generated\moc_AboutDialog.cpp" />
<ClCompile Include="generated\moc_CaptureContext.cpp" />
@@ -334,6 +335,7 @@
<ItemGroup>
<ClInclude Include="Code\CommonPipelineState.h" />
<ClInclude Include="Code\PersistantConfig.h" />
<ClInclude Include="Code\qprocessinfo.h" />
<ClInclude Include="Code\QRDUtils.h" />
<ClInclude Include="generated\ui_AboutDialog.h" />
<ClInclude Include="generated\ui_CaptureDialog.h" />
@@ -181,6 +181,9 @@
<ClCompile Include="generated\moc_QRDUtils.cpp">
<Filter>Generated Files</Filter>
</ClCompile>
<ClCompile Include="Code\qprocessinfo.cpp">
<Filter>Code</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="3rdparty\flowlayout\FlowLayout.h">
@@ -270,6 +273,9 @@
<ClInclude Include="Resources\resource.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="Code\qprocessinfo.h">
<Filter>Code</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="resources.qrc">