368 lines
10 KiB
C++
368 lines
10 KiB
C++
// Copyright 2009-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include <QtCore/QtGlobal>
|
|
|
|
#if defined(Q_OS_WIN)
|
|
# include "win.h"
|
|
# include "versionhelpers.h"
|
|
#endif
|
|
|
|
#include "OSInfo.h"
|
|
|
|
#include "Version.h"
|
|
|
|
#include <QtCore/QCryptographicHash>
|
|
#include <QtCore/QSysInfo>
|
|
#include <QtNetwork/QNetworkInterface>
|
|
#include <QtXml/QDomDocument>
|
|
|
|
#if defined(Q_OS_WIN)
|
|
# include <intrin.h>
|
|
#endif
|
|
|
|
#if defined(Q_OS_MACOS)
|
|
# include <Carbon/Carbon.h>
|
|
# include <mach-o/arch.h>
|
|
# include <sys/sysctl.h>
|
|
# include <sys/types.h>
|
|
|
|
// Ignore deprecation warnings for Gestalt.
|
|
// See mumble-voip/mumble#3290 for more information.
|
|
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
#endif
|
|
|
|
#if defined(Q_OS_WIN)
|
|
// regString converts a wchar_t string of size to a
|
|
// QString. If the string contains a NUL value, that
|
|
// NUL value will terminate the string.
|
|
static QString regString(wchar_t *string, int size) {
|
|
if (size <= 0) {
|
|
return QString();
|
|
}
|
|
// If string contains a NUL, adjust the size such
|
|
// that the NUL is not included in the returned
|
|
// string.
|
|
const size_t adjustedSize = wcsnlen(string, static_cast< size_t >(size));
|
|
// The return value of wcsnlen is <= size which is
|
|
// an int, so casting adjustedSize to int is safe.
|
|
return QString::fromWCharArray(string, static_cast< int >(adjustedSize));
|
|
}
|
|
|
|
/// Query for a Windows 10-style displayable version.
|
|
///
|
|
/// This returns a string of the kind:
|
|
///
|
|
/// Windows 10 Enterprise 2009 19042.804
|
|
///
|
|
/// which is:
|
|
///
|
|
/// $ProductName $Version $Build.$Ubr
|
|
///
|
|
/// Of note, $Version is formatted as YYMM.
|
|
/// So, 2009 is a Windows 10 update released in September 2020.
|
|
///
|
|
/// This function can be called on non-Windows 10 OSes.
|
|
/// On those, this functions fails to query some of the
|
|
/// registry keys used for building the version string
|
|
/// to be returned. Because of that, it is safe to call
|
|
/// this function, and if it returns an empty/null string,
|
|
/// a legacy version string can be displayed.
|
|
static QString win10DisplayableVersion() {
|
|
HKEY key = 0;
|
|
auto err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &key);
|
|
if (err != ERROR_SUCCESS) {
|
|
RegCloseKey(key);
|
|
return QString();
|
|
}
|
|
|
|
wchar_t buf[64];
|
|
DWORD len = sizeof(buf);
|
|
err = RegQueryValueEx(key, L"ProductName", nullptr, nullptr, reinterpret_cast< LPBYTE >(&buf[0]), &len);
|
|
if (err != ERROR_SUCCESS) {
|
|
RegCloseKey(key);
|
|
return QString();
|
|
}
|
|
const auto productName = regString(buf, static_cast< int >(len / sizeof(buf[0])));
|
|
|
|
len = sizeof(buf);
|
|
err = RegQueryValueEx(key, L"ReleaseId", nullptr, nullptr, reinterpret_cast< LPBYTE >(&buf[0]), &len);
|
|
if (err != ERROR_SUCCESS) {
|
|
RegCloseKey(key);
|
|
return QString();
|
|
}
|
|
const auto releaseId = regString(buf, static_cast< int >(len / sizeof(buf[0])));
|
|
|
|
len = sizeof(buf);
|
|
err = RegQueryValueEx(key, L"CurrentBuild", nullptr, nullptr, reinterpret_cast< LPBYTE >(&buf[0]), &len);
|
|
if (err != ERROR_SUCCESS) {
|
|
RegCloseKey(key);
|
|
return QString();
|
|
}
|
|
const auto currentBuild = regString(buf, static_cast< int >(len / sizeof(buf[0])));
|
|
|
|
DWORD dw = 0;
|
|
len = sizeof(dw);
|
|
err = RegQueryValueEx(key, L"UBR", nullptr, nullptr, reinterpret_cast< LPBYTE >(&dw), &len);
|
|
if (err != ERROR_SUCCESS) {
|
|
RegCloseKey(key);
|
|
return QString();
|
|
}
|
|
const auto ubr = QString::number(static_cast< ulong >(dw), 10);
|
|
|
|
RegCloseKey(key);
|
|
|
|
return QString::fromLatin1("%1 %2 %3.%4").arg(productName, releaseId, currentBuild, ubr);
|
|
}
|
|
#endif
|
|
|
|
QString OSInfo::getArchitecture(const bool build) {
|
|
QString architecture = build ? QSysInfo::buildCpuArchitecture() : QSysInfo::currentCpuArchitecture();
|
|
if (architecture == QLatin1String("x86_64")) {
|
|
architecture = QLatin1String("x64");
|
|
} else if (architecture == QLatin1String("i386")) {
|
|
architecture = QLatin1String("x86");
|
|
}
|
|
|
|
return architecture;
|
|
}
|
|
|
|
QString OSInfo::getMacHash(const QList< QHostAddress > &qlBind) {
|
|
QString first, second, third;
|
|
for (const QNetworkInterface &qni : QNetworkInterface::allInterfaces()) {
|
|
if (!qni.isValid())
|
|
continue;
|
|
if (qni.flags() & QNetworkInterface::IsLoopBack)
|
|
continue;
|
|
if (qni.hardwareAddress().isEmpty())
|
|
continue;
|
|
|
|
QString hash = QString::fromUtf8(
|
|
QCryptographicHash::hash(qni.hardwareAddress().toUtf8(), QCryptographicHash::Sha1).toHex());
|
|
|
|
if (third.isEmpty() || third > hash)
|
|
third = hash;
|
|
|
|
if (!(qni.flags() & (QNetworkInterface::IsUp | QNetworkInterface::IsRunning)))
|
|
continue;
|
|
|
|
if (second.isEmpty() || second > hash)
|
|
second = hash;
|
|
|
|
for (const QNetworkAddressEntry &qnae : qni.addressEntries()) {
|
|
const QHostAddress &qha = qnae.ip();
|
|
if (qlBind.isEmpty() || qlBind.contains(qha)) {
|
|
if (first.isEmpty() || first > hash)
|
|
first = hash;
|
|
}
|
|
}
|
|
}
|
|
if (!first.isEmpty())
|
|
return first;
|
|
if (!second.isEmpty())
|
|
return second;
|
|
if (!third.isEmpty())
|
|
return third;
|
|
return QString();
|
|
}
|
|
|
|
QString OSInfo::getOS() {
|
|
#if defined(Q_OS_WIN)
|
|
return QLatin1String("Windows");
|
|
#elif defined(Q_OS_LINUX)
|
|
return QLatin1String("Linux");
|
|
#elif defined(Q_OS_MACOS)
|
|
return QLatin1String("macOS");
|
|
#elif defined(Q_OS_FREEBSD)
|
|
return QLatin1String("FreeBSD");
|
|
#elif defined(Q_OS_NETBSD)
|
|
return QLatin1String("NetBSD");
|
|
#elif defined(Q_OS_OPENBSD)
|
|
return QLatin1String("OpenBSD");
|
|
#elif defined(Q_OS_BSD4)
|
|
return QLatin1String("BSD");
|
|
#elif defined(Q_OS_UNIX)
|
|
return QLatin1String("UNIX");
|
|
#else
|
|
return QLatin1String("Unknown");
|
|
#endif
|
|
}
|
|
|
|
QString OSInfo::getOSDisplayableVersion(const bool appendArch) {
|
|
#if defined(Q_OS_WIN)
|
|
QString os = win10DisplayableVersion();
|
|
if (os.isEmpty()) {
|
|
if (IsWindows10OrGreater()) {
|
|
if (!IsWindowsServer()) {
|
|
os = QLatin1String("Windows 10");
|
|
} else {
|
|
os = QLatin1String("Windows 10 Server");
|
|
}
|
|
} else if (IsWindows8Point1OrGreater()) {
|
|
if (!IsWindowsServer()) {
|
|
os = QLatin1String("Windows 8.1");
|
|
} else {
|
|
os = QLatin1String("Windows 8.1 Server");
|
|
}
|
|
} else if (IsWindows8OrGreater()) {
|
|
if (!IsWindowsServer()) {
|
|
os = QLatin1String("Windows 8");
|
|
} else {
|
|
os = QLatin1String("Windows 8 Server");
|
|
}
|
|
} else if (IsWindows7OrGreater()) {
|
|
if (!IsWindowsServer()) {
|
|
os = QLatin1String("Windows 7");
|
|
} else {
|
|
os = QLatin1String("Windows 7 Server");
|
|
}
|
|
} else if (IsWindowsVistaOrGreater()) {
|
|
if (!IsWindowsServer()) {
|
|
os = QLatin1String("Windows Vista");
|
|
} else {
|
|
os = QLatin1String("Windows Vista Server");
|
|
}
|
|
} else if (IsWindowsXPOrGreater()) {
|
|
if (!IsWindowsServer()) {
|
|
os = QLatin1String("Windows XP");
|
|
} else {
|
|
os = QLatin1String("Windows XP Server");
|
|
}
|
|
} else {
|
|
os = QLatin1String("Ancient Windows version");
|
|
}
|
|
|
|
return os;
|
|
}
|
|
#elif defined(Q_OS_MACOS)
|
|
const QString os = QLatin1String("macOS ") + getOSVersion();
|
|
#else
|
|
const QString os = QSysInfo::prettyProductName();
|
|
#endif
|
|
if (!appendArch) {
|
|
return os;
|
|
}
|
|
|
|
return os + QString(" [%1]").arg(getArchitecture(false));
|
|
}
|
|
|
|
QString OSInfo::getOSVersion() {
|
|
#if defined(Q_OS_MACOS)
|
|
SInt32 major, minor, bugfix;
|
|
OSErr err = Gestalt(gestaltSystemVersionMajor, &major);
|
|
if (err == noErr)
|
|
err = Gestalt(gestaltSystemVersionMinor, &minor);
|
|
if (err == noErr)
|
|
err = Gestalt(gestaltSystemVersionBugFix, &bugfix);
|
|
if (err != noErr)
|
|
return QString::number(QSysInfo::MacintoshVersion, 16);
|
|
|
|
char *buildno = nullptr;
|
|
char buildno_buf[32];
|
|
size_t sz_buildno_buf = sizeof(buildno);
|
|
int ret = sysctlbyname("kern.osversion", buildno_buf, &sz_buildno_buf, nullptr, 0);
|
|
if (ret == 0) {
|
|
buildno = &buildno_buf[0];
|
|
}
|
|
|
|
return QString::asprintf("%lu.%lu.%lu %s", static_cast< unsigned long >(major), static_cast< unsigned long >(minor),
|
|
static_cast< unsigned long >(bugfix), buildno ? buildno : "unknown");
|
|
#else
|
|
return QSysInfo::productVersion();
|
|
#endif
|
|
}
|
|
|
|
void OSInfo::fillXml(QDomDocument &doc, QDomElement &root, const QList< QHostAddress > &qlBind) {
|
|
QDomElement tag = doc.createElement(QLatin1String("machash"));
|
|
root.appendChild(tag);
|
|
QDomText t = doc.createTextNode(getMacHash(qlBind));
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("arch"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(getArchitecture(true));
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("version"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(Version::getRelease());
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("release"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(Version::getRelease());
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("os"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(getOS());
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("osarch"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(getArchitecture(false));
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("osver"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(getOSVersion());
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("osverbose"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(getOSDisplayableVersion(false));
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("qt"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(QString::fromLatin1(qVersion()));
|
|
tag.appendChild(t);
|
|
|
|
QString cpu_id, cpu_extid;
|
|
bool bSSE2 = false;
|
|
#if defined(Q_OS_WIN)
|
|
# define regstr(x) QString::fromLatin1(reinterpret_cast< const char * >(&x), 4)
|
|
int chop;
|
|
int cpuinfo[4];
|
|
|
|
__cpuid(cpuinfo, 1);
|
|
bSSE2 = (cpuinfo[3] & 0x04000000);
|
|
|
|
__cpuid(cpuinfo, 0);
|
|
|
|
cpu_id = regstr(cpuinfo[1]) + regstr(cpuinfo[3]) + regstr(cpuinfo[2]);
|
|
|
|
for (unsigned int j = 2; j <= 4; ++j) {
|
|
__cpuid(cpuinfo, 0x80000000 + j);
|
|
cpu_extid += regstr(cpuinfo[0]) + regstr(cpuinfo[1]) + regstr(cpuinfo[2]) + regstr(cpuinfo[3]);
|
|
}
|
|
|
|
cpu_id = cpu_id.trimmed();
|
|
chop = cpu_id.indexOf(QLatin1Char('\0'));
|
|
if (chop != -1)
|
|
cpu_id.truncate(chop);
|
|
|
|
cpu_extid = cpu_extid.trimmed();
|
|
chop = cpu_extid.indexOf(QLatin1Char('\0'));
|
|
if (chop != -1)
|
|
cpu_extid.truncate(chop);
|
|
#endif
|
|
|
|
tag = doc.createElement(QLatin1String("cpu_id"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(cpu_id);
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("cpu_extid"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(cpu_extid);
|
|
tag.appendChild(t);
|
|
|
|
tag = doc.createElement(QLatin1String("cpu_sse2"));
|
|
root.appendChild(tag);
|
|
t = doc.createTextNode(QString::number(bSSE2 ? 1 : 0));
|
|
tag.appendChild(t);
|
|
}
|