// 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 = NULL; PFN_GETWINDOW GetWindow = NULL; PFN_ISWINDOWVISIBLE IsWindowVisible = NULL; PFN_GETWINDOWTEXTLENGTHW GetWindowTextLengthW = NULL; PFN_GETWINDOWTEXTW GetWindowTextW = NULL; PFN_ENUMWINDOWS EnumWindows = NULL; }; }; 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(bool includeWindowTitles) { 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); if(!includeWindowTitles) return ret; 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 #include QProcessList QProcessInfo::enumerate(bool includeWindowTitles) { QProcessList ret; QDir proc(QStringLiteral("/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(QStringLiteral("/proc/") + f); // default to the exe symlink if valid QFileInfo exe(processDir.absoluteFilePath(QStringLiteral("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().isEmpty()) { QFile status(processDir.absoluteFilePath(QStringLiteral("status"))); if(status.open(QIODevice::ReadOnly)) { QByteArray contents = status.readAll(); QTextStream in(&contents); while(!in.atEnd()) { QString line = in.readLine(); if(line.startsWith(QStringLiteral("Name:"))) { line.remove(0, 5); // if we're using this name, surround with []s to indicate it's not a file info.setName(QStringLiteral("[%1]").arg(line.trimmed())); break; } } status.close(); } } // get the command line QFile cmdline(processDir.absoluteFilePath(QStringLiteral("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().isEmpty()) info.setName(QStringLiteral("[%1]").arg(firstparam)); contents.replace('\0', ' '); } info.setCommandLine(QString::fromUtf8(contents).trimmed()); cmdline.close(); } ret.push_back(info); } } if(includeWindowTitles) { // 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 << QStringLiteral("search") << QStringLiteral("--onlyvisible") << QStringLiteral(".*"); QList windowlist; QString inPath = QStandardPaths::findExecutable(QStringLiteral("xdotool")); if(inPath.isEmpty()) { // add a fake window title to the first process to indicate that xdotool is missing if(!ret.isEmpty()) ret[0].setWindowTitle(QStringLiteral("Window titles not available - install `xdotool`")); } else { QProcess process; process.start(QStringLiteral("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 << QStringLiteral("getwindowpid") << QString::fromLatin1(win); uint32_t pid = 0; { QProcess process; process.start(QStringLiteral("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 << QStringLiteral("getwindowgeometry") << QString::fromLatin1(win); QList winGeometry; { QProcess process; process.start(QStringLiteral("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(QStringLiteral("Position: (-?\\d+),(-?\\d+)")); QRegExp geometry(QStringLiteral("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 << QStringLiteral("getwindowname") << QString::fromLatin1(win); QProcess process; process.start(QStringLiteral("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(bool includeWindowTitles) { QProcessList ret; qWarning() << "Process enumeration not supported on this platform"; return ret; } #endif QProcessInfo::QProcessInfo() { m_pid = 0; } 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; }