/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include #include "SystemInfo.h" #ifndef TARGET_POSIX #include #else #include #endif #include "CompileInfo.h" #include "ServiceBroker.h" #include "filesystem/CurlFile.h" #include "filesystem/File.h" #include "guilib/LocalizeStrings.h" #include "guilib/guiinfo/GUIInfoLabels.h" #include "network/Network.h" #include "platform/Filesystem.h" #include "rendering/RenderSystem.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/CPUInfo.h" #include "utils/log.h" #ifdef TARGET_WINDOWS #include #include "utils/CharsetConverter.h" #include #ifdef TARGET_WINDOWS_STORE #include #include using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::Security::ExchangeActiveSyncProvisioning; using namespace winrt::Windows::System; using namespace winrt::Windows::System::Profile; #endif #include #include "platform/win32/CharsetConverter.h" #endif #if defined(TARGET_DARWIN) #include "platform/darwin/DarwinUtils.h" #endif #include "powermanagement/PowerManager.h" #include "utils/StringUtils.h" #include "utils/XMLUtils.h" #if defined(TARGET_ANDROID) #include #include #include #endif /* Platform identification */ #if defined(TARGET_DARWIN) #include #include #include #elif defined(TARGET_ANDROID) #include #include #elif defined(TARGET_FREEBSD) #include #elif defined(TARGET_LINUX) #include "platform/linux/SysfsPath.h" #include #endif #include /* Expand macro before stringify */ #define STR_MACRO(x) #x #define XSTR_MACRO(x) STR_MACRO(x) namespace { auto startTime = std::chrono::steady_clock::now(); } using namespace XFILE; #ifdef TARGET_WINDOWS_DESKTOP static bool sysGetVersionExWByRef(OSVERSIONINFOEXW& osVerInfo) { osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo); typedef NTSTATUS(__stdcall *RtlGetVersionPtr)(RTL_OSVERSIONINFOEXW* pOsInfo); static HMODULE hNtDll = GetModuleHandleW(L"ntdll.dll"); if (hNtDll != NULL) { static RtlGetVersionPtr RtlGetVer = (RtlGetVersionPtr) GetProcAddress(hNtDll, "RtlGetVersion"); if (RtlGetVer && RtlGetVer(&osVerInfo) == 0) return true; } // failed to get OS information directly from ntdll.dll // use GetVersionExW() as fallback // note: starting from Windows 8.1 GetVersionExW() may return unfaithful information if (GetVersionExW((OSVERSIONINFOW*) &osVerInfo) != 0) return true; ZeroMemory(&osVerInfo, sizeof(osVerInfo)); return false; } static bool appendWindows10NameVersion(std::string& osNameVer) { wchar_t versionW[32] = {}; DWORD len = sizeof(versionW); bool obtained = false; if (ERROR_SUCCESS == RegGetValueW(HKEY_LOCAL_MACHINE, REG_CURRENT_VERSION, L"DisplayVersion", RRF_RT_REG_SZ, nullptr, &versionW, &len)) { obtained = true; } else if (ERROR_SUCCESS == RegGetValueW(HKEY_LOCAL_MACHINE, REG_CURRENT_VERSION, L"ReleaseId", RRF_RT_REG_SZ, nullptr, &versionW, &len)) { obtained = true; } if (obtained) osNameVer.append(StringUtils::Format(" {}", KODI::PLATFORM::WINDOWS::FromW(versionW))); return obtained; } #endif // TARGET_WINDOWS_DESKTOP #if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) static std::string getValueFromOs_release(std::string key) { FILE* os_rel = fopen("/etc/os-release", "r"); if (!os_rel) return ""; char* buf = new char[10 * 1024]; // more than enough size_t len = fread(buf, 1, 10 * 1024, os_rel); fclose(os_rel); if (len == 0) { delete[] buf; return ""; } std::string content(buf, len); delete[] buf; // find begin of value string size_t valStart = 0, seachPos; key += '='; if (content.compare(0, key.length(), key) == 0) valStart = key.length(); else { key = "\n" + key; seachPos = 0; do { seachPos = content.find(key, seachPos); if (seachPos == std::string::npos) return ""; if (seachPos == 0 || content[seachPos - 1] != '\\') valStart = seachPos + key.length(); else seachPos++; } while (valStart == 0); } if (content[valStart] == '\n') return ""; // find end of value string seachPos = valStart; do { seachPos = content.find('\n', seachPos + 1); } while (seachPos != std::string::npos && content[seachPos - 1] == '\\'); size_t const valEnd = seachPos; std::string value(content, valStart, valEnd - valStart); if (value.empty()) return value; // remove quotes if (value[0] == '\'' || value[0] == '"') { if (value.length() < 2) return value; size_t qEnd = value.rfind(value[0]); if (qEnd != std::string::npos) { value.erase(qEnd); value.erase(0, 1); } } // unescape characters for (size_t slashPos = value.find('\\'); slashPos < value.length() - 1; slashPos = value.find('\\', slashPos)) { if (value[slashPos + 1] == '\n') value.erase(slashPos, 2); else { value.erase(slashPos, 1); slashPos++; // skip unescaped character } } return value; } enum lsb_rel_info_type { lsb_rel_distributor, lsb_rel_description, lsb_rel_release, lsb_rel_codename }; static std::string getValueFromLsb_release(enum lsb_rel_info_type infoType) { std::string key, command("unset PYTHONHOME; unset PYTHONPATH; lsb_release "); switch (infoType) { case lsb_rel_distributor: command += "-i"; key = "Distributor ID:\t"; break; case lsb_rel_description: command += "-d"; key = "Description:\t"; break; case lsb_rel_release: command += "-r"; key = "Release:\t"; break; case lsb_rel_codename: command += "-c"; key = "Codename:\t"; break; default: return ""; } command += " 2>/dev/null"; FILE* lsb_rel = popen(command.c_str(), "r"); if (lsb_rel == NULL) return ""; char buf[300]; // more than enough if (fgets(buf, 300, lsb_rel) == NULL) { pclose(lsb_rel); return ""; } pclose(lsb_rel); std::string response(buf); if (response.compare(0, key.length(), key) != 0) return ""; return response.substr(key.length(), response.find('\n') - key.length()); } #endif // TARGET_LINUX && !TARGET_ANDROID CSysInfo g_sysinfo; CSysInfoJob::CSysInfoJob() = default; bool CSysInfoJob::DoWork() { m_info.systemUptime = GetSystemUpTime(false); m_info.systemTotalUptime = GetSystemUpTime(true); m_info.internetState = GetInternetState(); m_info.videoEncoder = GetVideoEncoder(); m_info.cpuFrequency = StringUtils::Format("{:4.0f} MHz", CServiceBroker::GetCPUInfo()->GetCPUFrequency()); m_info.osVersionInfo = CSysInfo::GetOsPrettyNameWithVersion() + " (kernel: " + CSysInfo::GetKernelName() + " " + CSysInfo::GetKernelVersionFull() + ")"; m_info.macAddress = GetMACAddress(); m_info.batteryLevel = GetBatteryLevel(); return true; } const CSysData &CSysInfoJob::GetData() const { return m_info; } CSysData::INTERNET_STATE CSysInfoJob::GetInternetState() { // Internet connection state! XFILE::CCurlFile http; if (http.IsInternet()) return CSysData::CONNECTED; return CSysData::DISCONNECTED; } std::string CSysInfoJob::GetMACAddress() { CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); if (iface) return iface->GetMacAddress(); return ""; } std::string CSysInfoJob::GetVideoEncoder() { return "GPU: " + CServiceBroker::GetRenderSystem()->GetRenderRenderer(); } std::string CSysInfoJob::GetBatteryLevel() { return StringUtils::Format("{}%", CServiceBroker::GetPowerManager().BatteryLevel()); } bool CSysInfoJob::SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays) { iHours = 0; iDays = 0; iMinutes = iInputMinutes; if (iMinutes >= 60) // Hour's { iHours = iMinutes / 60; iMinutes = iMinutes - (iHours *60); } if (iHours >= 24) // Days { iDays = iHours / 24; iHours = iHours - (iDays * 24); } return true; } std::string CSysInfoJob::GetSystemUpTime(bool bTotalUptime) { std::string strSystemUptime; int iInputMinutes, iMinutes,iHours,iDays; auto now = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(now - startTime); if(bTotalUptime) { //Total Uptime iInputMinutes = g_sysinfo.GetTotalUptime() + duration.count(); } else { //Current UpTime iInputMinutes = duration.count(); } SystemUpTime(iInputMinutes,iMinutes, iHours, iDays); if (iDays > 0) { strSystemUptime = StringUtils::Format("{} {}, {} {}, {} {}", iDays, g_localizeStrings.Get(12393), iHours, g_localizeStrings.Get(12392), iMinutes, g_localizeStrings.Get(12391)); } else if (iDays == 0 && iHours >= 1 ) { strSystemUptime = StringUtils::Format("{} {}, {} {}", iHours, g_localizeStrings.Get(12392), iMinutes, g_localizeStrings.Get(12391)); } else if (iDays == 0 && iHours == 0 && iMinutes >= 0) { strSystemUptime = StringUtils::Format("{} {}", iMinutes, g_localizeStrings.Get(12391)); } return strSystemUptime; } std::string CSysInfo::TranslateInfo(int info) const { switch(info) { case SYSTEM_VIDEO_ENCODER_INFO: return m_info.videoEncoder; case NETWORK_MAC_ADDRESS: return m_info.macAddress; case SYSTEM_OS_VERSION_INFO: return m_info.osVersionInfo; case SYSTEM_CPUFREQUENCY: return m_info.cpuFrequency; case SYSTEM_UPTIME: return m_info.systemUptime; case SYSTEM_TOTALUPTIME: return m_info.systemTotalUptime; case SYSTEM_INTERNET_STATE: if (m_info.internetState == CSysData::CONNECTED) return g_localizeStrings.Get(13296); else return g_localizeStrings.Get(13297); case SYSTEM_BATTERY_LEVEL: return m_info.batteryLevel; default: return ""; } } void CSysInfo::Reset() { m_info.Reset(); } CSysInfo::CSysInfo(void) : CInfoLoader(15 * 1000) { memset(MD5_Sign, 0, sizeof(MD5_Sign)); m_iSystemTimeTotalUp = 0; } CSysInfo::~CSysInfo() = default; bool CSysInfo::Load(const TiXmlNode *settings) { if (settings == NULL) return false; const TiXmlElement *pElement = settings->FirstChildElement("general"); if (pElement) XMLUtils::GetInt(pElement, "systemtotaluptime", m_iSystemTimeTotalUp, 0, INT_MAX); return true; } bool CSysInfo::Save(TiXmlNode *settings) const { if (settings == NULL) return false; TiXmlNode *generalNode = settings->FirstChild("general"); if (generalNode == NULL) { TiXmlElement generalNodeNew("general"); generalNode = settings->InsertEndChild(generalNodeNew); if (generalNode == NULL) return false; } XMLUtils::SetInt(generalNode, "systemtotaluptime", m_iSystemTimeTotalUp); return true; } const std::string& CSysInfo::GetAppName(void) { assert(CCompileInfo::GetAppName() != NULL); static const std::string appName(CCompileInfo::GetAppName()); return appName; } bool CSysInfo::GetDiskSpace(std::string drive,int& iTotal, int& iTotalFree, int& iTotalUsed, int& iPercentFree, int& iPercentUsed) { using namespace KODI::PLATFORM::FILESYSTEM; space_info total = {}; std::error_code ec; // None of this makes sense but the idea of total space // makes no sense on any system really. // Return space for / or for C: as it's correct in a sense // and not much worse than trying to count a total for different // drives/mounts if (drive.empty() || drive == "*") { #if defined(TARGET_WINDOWS) drive = "C"; #elif defined(TARGET_POSIX) drive = "/"; #endif } #ifdef TARGET_WINDOWS_DESKTOP using KODI::PLATFORM::WINDOWS::ToW; UINT uidriveType = GetDriveType(ToW(drive + ":\\").c_str()); if (uidriveType != DRIVE_UNKNOWN && uidriveType != DRIVE_NO_ROOT_DIR) total = space(drive + ":\\", ec); #elif defined(TARGET_POSIX) total = space(drive, ec); #endif if (ec.value() != 0) return false; iTotal = static_cast(total.capacity / MB); iTotalFree = static_cast(total.free / MB); iTotalUsed = iTotal - iTotalFree; if (total.capacity > 0) iPercentUsed = static_cast(100.0f * (total.capacity - total.free) / total.capacity + 0.5f); else iPercentUsed = 0; iPercentFree = 100 - iPercentUsed; return true; } std::string CSysInfo::GetKernelName(bool emptyIfUnknown /*= false*/) { static std::string kernelName; if (kernelName.empty()) { #if defined(TARGET_WINDOWS_DESKTOP) OSVERSIONINFOEXW osvi = {}; if (sysGetVersionExWByRef(osvi) && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) kernelName = "Windows NT"; #elif defined(TARGET_WINDOWS_STORE) auto e = EasClientDeviceInformation(); auto os = e.OperatingSystem(); g_charsetConverter.wToUTF8(std::wstring(os.c_str()), kernelName); #elif defined(TARGET_POSIX) struct utsname un; if (uname(&un) == 0) kernelName.assign(un.sysname); #endif // defined(TARGET_POSIX) if (kernelName.empty()) kernelName = "Unknown kernel"; // can't detect } if (emptyIfUnknown && kernelName == "Unknown kernel") return ""; return kernelName; } std::string CSysInfo::GetKernelVersionFull(void) { static std::string kernelVersionFull; if (!kernelVersionFull.empty()) return kernelVersionFull; #if defined(TARGET_WINDOWS_DESKTOP) OSVERSIONINFOEXW osvi = {}; DWORD dwBuildRevision = 0; DWORD len = sizeof(DWORD); if (sysGetVersionExWByRef(osvi)) kernelVersionFull = StringUtils::Format("{}.{}.{}", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber); // get UBR (updates build revision) if (ERROR_SUCCESS == RegGetValueW(HKEY_LOCAL_MACHINE, REG_CURRENT_VERSION, L"UBR", RRF_RT_REG_DWORD, nullptr, &dwBuildRevision, &len)) { kernelVersionFull += StringUtils::Format(".{}", dwBuildRevision); } #elif defined(TARGET_WINDOWS_STORE) // get the system version number auto sv = AnalyticsInfo::VersionInfo().DeviceFamilyVersion(); wchar_t* end; unsigned long long v = wcstoull(sv.c_str(), &end, 10); unsigned long long v1 = (v & 0xFFFF000000000000L) >> 48; unsigned long long v2 = (v & 0x0000FFFF00000000L) >> 32; unsigned long long v3 = (v & 0x00000000FFFF0000L) >> 16; unsigned long long v4 = (v & 0x000000000000FFFFL); kernelVersionFull = StringUtils::Format("{}.{}.{}", v1, v2, v3); if (v4) kernelVersionFull += StringUtils::Format(".{}", v4); #elif defined(TARGET_POSIX) struct utsname un; if (uname(&un) == 0) kernelVersionFull.assign(un.release); #endif // defined(TARGET_POSIX) if (kernelVersionFull.empty()) kernelVersionFull = "0.0.0"; // can't detect return kernelVersionFull; } std::string CSysInfo::GetKernelVersion(void) { static std::string kernelVersionClear; if (kernelVersionClear.empty()) { kernelVersionClear = GetKernelVersionFull(); const size_t erasePos = kernelVersionClear.find_first_not_of("0123456789."); if (erasePos != std::string::npos) kernelVersionClear.erase(erasePos); } return kernelVersionClear; } std::string CSysInfo::GetOsName(bool emptyIfUnknown /* = false*/) { static std::string osName; if (osName.empty()) { #if defined (TARGET_WINDOWS) osName = GetKernelName() + "-based OS"; #elif defined(TARGET_FREEBSD) osName = GetKernelName(true); // FIXME: for FreeBSD OS name is a kernel name #elif defined(TARGET_DARWIN_IOS) osName = "iOS"; #elif defined(TARGET_DARWIN_TVOS) osName = "tvOS"; #elif defined(TARGET_DARWIN_OSX) osName = "macOS"; #elif defined (TARGET_ANDROID) if (CJNIContext::GetPackageManager().hasSystemFeature("android.software.leanback")) osName = "Android TV"; else osName = "Android"; #elif defined(TARGET_LINUX) osName = getValueFromOs_release("NAME"); if (osName.empty()) osName = getValueFromLsb_release(lsb_rel_distributor); if (osName.empty()) osName = getValueFromOs_release("ID"); #endif // defined(TARGET_LINUX) if (osName.empty()) osName = "Unknown OS"; } if (emptyIfUnknown && osName == "Unknown OS") return ""; return osName; } std::string CSysInfo::GetOsVersion(void) { static std::string osVersion; if (!osVersion.empty()) return osVersion; #if defined(TARGET_WINDOWS) || defined(TARGET_FREEBSD) osVersion = GetKernelVersion(); // FIXME: for Win32 and FreeBSD OS version is a kernel version #elif defined(TARGET_DARWIN) osVersion = CDarwinUtils::GetVersionString(); #elif defined(TARGET_ANDROID) char versionCStr[PROP_VALUE_MAX]; int propLen = __system_property_get("ro.build.version.release", versionCStr); osVersion.assign(versionCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0); if (osVersion.empty() || std::string("0123456789").find(versionCStr[0]) == std::string::npos) osVersion.clear(); // can't correctly detect Android version else { size_t pointPos = osVersion.find('.'); if (pointPos == std::string::npos) osVersion += ".0.0"; else if (osVersion.find('.', pointPos + 1) == std::string::npos) osVersion += ".0"; } #elif defined(TARGET_LINUX) osVersion = getValueFromOs_release("VERSION_ID"); if (osVersion.empty()) osVersion = getValueFromLsb_release(lsb_rel_release); #endif // defined(TARGET_LINUX) if (osVersion.empty()) osVersion = "0.0"; return osVersion; } std::string CSysInfo::GetOsPrettyNameWithVersion(void) { static std::string osNameVer; if (!osNameVer.empty()) return osNameVer; #if defined (TARGET_WINDOWS_DESKTOP) OSVERSIONINFOEXW osvi = {}; osNameVer = "Windows "; if (sysGetVersionExWByRef(osvi)) { switch (GetWindowsVersion()) { case WindowsVersionWin7: if (osvi.wProductType == VER_NT_WORKSTATION) osNameVer.append("7"); else osNameVer.append("Server 2008 R2"); break; case WindowsVersionWin8: if (osvi.wProductType == VER_NT_WORKSTATION) osNameVer.append("8"); else osNameVer.append("Server 2012"); break; case WindowsVersionWin8_1: if (osvi.wProductType == VER_NT_WORKSTATION) osNameVer.append("8.1"); else osNameVer.append("Server 2012 R2"); break; case WindowsVersionWin10: case WindowsVersionWin10_1709: case WindowsVersionWin10_1803: case WindowsVersionWin10_1809: case WindowsVersionWin10_1903: case WindowsVersionWin10_1909: case WindowsVersionWin10_2004: case WindowsVersionWin10_Future: osNameVer.append("10"); appendWindows10NameVersion(osNameVer); break; case WindowsVersionWin11: osNameVer.append("11"); appendWindows10NameVersion(osNameVer); break; case WindowsVersionFuture: osNameVer.append("Unknown future version"); break; default: osNameVer.append("Unknown version"); break; } // Append Service Pack version if any if (osvi.wServicePackMajor > 0 || osvi.wServicePackMinor > 0) { osNameVer.append(StringUtils::Format(" SP{}", osvi.wServicePackMajor)); if (osvi.wServicePackMinor > 0) { osNameVer.append(StringUtils::Format(".{}", osvi.wServicePackMinor)); } } } else osNameVer.append(" unknown"); #elif defined(TARGET_WINDOWS_STORE) osNameVer = GetKernelName() + " " + GetOsVersion(); #elif defined(TARGET_FREEBSD) osNameVer = GetOsName() + " " + GetOsVersion(); #elif defined(TARGET_DARWIN) osNameVer = StringUtils::Format("{} {} ({})", GetOsName(), GetOsVersion(), CDarwinUtils::GetOSVersionString()); #elif defined(TARGET_ANDROID) osNameVer = GetOsName() + " " + GetOsVersion() + " API level " + std::to_string(CJNIBuild::SDK_INT); #elif defined(TARGET_LINUX) osNameVer = getValueFromOs_release("PRETTY_NAME"); if (osNameVer.empty()) { osNameVer = getValueFromLsb_release(lsb_rel_description); std::string osName(GetOsName(true)); if (!osName.empty() && osNameVer.find(osName) == std::string::npos) osNameVer = osName + osNameVer; if (osNameVer.empty()) osNameVer = "Unknown Linux Distribution"; } if (osNameVer.find(GetOsVersion()) == std::string::npos) osNameVer += " " + GetOsVersion(); #endif // defined(TARGET_LINUX) if (osNameVer.empty()) osNameVer = "Unknown OS Unknown version"; return osNameVer; } std::string CSysInfo::GetManufacturerName(void) { static std::string manufName; static bool inited = false; if (!inited) { #if defined(TARGET_ANDROID) char deviceCStr[PROP_VALUE_MAX]; int propLen = __system_property_get("ro.product.manufacturer", deviceCStr); manufName.assign(deviceCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0); #elif defined(TARGET_DARWIN) manufName = CDarwinUtils::GetManufacturer(); #elif defined(TARGET_WINDOWS_STORE) auto eas = EasClientDeviceInformation(); auto manufacturer = eas.SystemManufacturer(); g_charsetConverter.wToUTF8(std::wstring(manufacturer.c_str()), manufName); #elif defined(TARGET_LINUX) auto cpuInfo = CServiceBroker::GetCPUInfo(); manufName = cpuInfo->GetCPUSoC(); #elif defined(TARGET_WINDOWS) // We just don't care, might be useful on embedded #endif inited = true; } return manufName; } std::string CSysInfo::GetModelName(void) { static std::string modelName; static bool inited = false; if (!inited) { #if defined(TARGET_ANDROID) char deviceCStr[PROP_VALUE_MAX]; int propLen = __system_property_get("ro.product.model", deviceCStr); modelName.assign(deviceCStr, (propLen > 0 && propLen <= PROP_VALUE_MAX) ? propLen : 0); #elif defined(TARGET_DARWIN_EMBEDDED) modelName = CDarwinUtils::getIosPlatformString(); #elif defined(TARGET_DARWIN_OSX) size_t nameLen = 0; // 'nameLen' should include terminating null if (sysctlbyname("hw.model", NULL, &nameLen, NULL, 0) == 0 && nameLen > 1) { std::vector buf(nameLen); if (sysctlbyname("hw.model", buf.data(), &nameLen, NULL, 0) == 0 && nameLen == buf.size()) modelName.assign(buf.data(), nameLen - 1); // assign exactly 'nameLen-1' characters to 'modelName' } #elif defined(TARGET_WINDOWS_STORE) auto eas = EasClientDeviceInformation(); auto manufacturer = eas.SystemProductName(); g_charsetConverter.wToUTF8(std::wstring(manufacturer.c_str()), modelName); #elif defined(TARGET_LINUX) auto cpuInfo = CServiceBroker::GetCPUInfo(); modelName = cpuInfo->GetCPUHardware(); #elif defined(TARGET_WINDOWS) // We just don't care, might be useful on embedded #endif inited = true; } return modelName; } bool CSysInfo::IsAeroDisabled() { #ifdef TARGET_WINDOWS_STORE return true; // need to review https://msdn.microsoft.com/en-us/library/windows/desktop/aa969518(v=vs.85).aspx #elif defined(TARGET_WINDOWS) BOOL aeroEnabled = FALSE; HRESULT res = DwmIsCompositionEnabled(&aeroEnabled); if (SUCCEEDED(res)) return !aeroEnabled; #endif return false; } CSysInfo::WindowsVersion CSysInfo::m_WinVer = WindowsVersionUnknown; bool CSysInfo::IsWindowsVersion(WindowsVersion ver) { if (ver == WindowsVersionUnknown) return false; return GetWindowsVersion() == ver; } bool CSysInfo::IsWindowsVersionAtLeast(WindowsVersion ver) { if (ver == WindowsVersionUnknown) return false; return GetWindowsVersion() >= ver; } CSysInfo::WindowsVersion CSysInfo::GetWindowsVersion() { #ifdef TARGET_WINDOWS_DESKTOP if (m_WinVer == WindowsVersionUnknown) { OSVERSIONINFOEXW osvi = {}; if (sysGetVersionExWByRef(osvi)) { if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) m_WinVer = WindowsVersionWin7; else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) m_WinVer = WindowsVersionWin8; else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) m_WinVer = WindowsVersionWin8_1; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < 16299) m_WinVer = WindowsVersionWin10; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 16299) m_WinVer = WindowsVersionWin10_1709; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 17134) m_WinVer = WindowsVersionWin10_1803; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 17763) m_WinVer = WindowsVersionWin10_1809; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 18362) m_WinVer = WindowsVersionWin10_1903; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 18363) m_WinVer = WindowsVersionWin10_1909; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber == 19041) m_WinVer = WindowsVersionWin10_2004; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000) m_WinVer = WindowsVersionWin11; else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber > 19041) m_WinVer = WindowsVersionWin10_Future; /* Insert checks for new Windows versions here */ else if ( (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion > 3) || osvi.dwMajorVersion > 10) m_WinVer = WindowsVersionFuture; } } #elif defined(TARGET_WINDOWS_STORE) m_WinVer = WindowsVersionWin10; #endif // TARGET_WINDOWS return m_WinVer; } int CSysInfo::GetKernelBitness(void) { static int kernelBitness = -1; if (kernelBitness == -1) { #ifdef TARGET_WINDOWS_STORE Package package = Package::Current(); auto arch = package.Id().Architecture(); switch (arch) { case ProcessorArchitecture::X86: kernelBitness = 32; break; case ProcessorArchitecture::X64: kernelBitness = 64; break; case ProcessorArchitecture::Arm: kernelBitness = 32; break; case ProcessorArchitecture::Unknown: // not sure what to do here. guess 32 for now case ProcessorArchitecture::Neutral: kernelBitness = 32; break; } #elif defined(TARGET_WINDOWS_DESKTOP) SYSTEM_INFO si; GetNativeSystemInfo(&si); if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL || si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM) kernelBitness = 32; else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) kernelBitness = 64; else { BOOL isWow64 = FALSE; if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) // fallback kernelBitness = 64; } #elif defined(TARGET_DARWIN_EMBEDDED) // Note: OS X return x86 CPU type without CPU_ARCH_ABI64 flag const NXArchInfo* archInfo = NXGetLocalArchInfo(); if (archInfo) kernelBitness = ((archInfo->cputype & CPU_ARCH_ABI64) != 0) ? 64 : 32; #elif defined(TARGET_POSIX) struct utsname un; if (uname(&un) == 0) { std::string machine(un.machine); if (machine == "x86_64" || machine == "amd64" || machine == "arm64" || machine == "aarch64" || machine == "ppc64" || machine == "ppc64el" || machine == "ppc64le" || machine == "ia64" || machine == "mips64" || machine == "s390x" || machine == "riscv64") kernelBitness = 64; else kernelBitness = 32; } #endif if (kernelBitness == -1) kernelBitness = 0; // can't detect } return kernelBitness; } const std::string& CSysInfo::GetKernelCpuFamily(void) { static std::string kernelCpuFamily; if (kernelCpuFamily.empty()) { #ifdef TARGET_WINDOWS SYSTEM_INFO si; GetNativeSystemInfo(&si); if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL || si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) kernelCpuFamily = "x86"; else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM) kernelCpuFamily = "ARM"; #elif defined(TARGET_DARWIN) const NXArchInfo* archInfo = NXGetLocalArchInfo(); if (archInfo) { const cpu_type_t cpuType = (archInfo->cputype & ~CPU_ARCH_ABI64); // get CPU family without 64-bit ABI flag if (cpuType == CPU_TYPE_I386) kernelCpuFamily = "x86"; else if (cpuType == CPU_TYPE_ARM) kernelCpuFamily = "ARM"; #ifdef CPU_TYPE_MIPS else if (cpuType == CPU_TYPE_MIPS) kernelCpuFamily = "MIPS"; #endif // CPU_TYPE_MIPS } #elif defined(TARGET_POSIX) struct utsname un; if (uname(&un) == 0) { std::string machine(un.machine); if (machine.compare(0, 3, "arm", 3) == 0 || machine.compare(0, 7, "aarch64", 7) == 0) kernelCpuFamily = "ARM"; else if (machine.compare(0, 4, "mips", 4) == 0) kernelCpuFamily = "MIPS"; else if (machine.compare(0, 4, "i686", 4) == 0 || machine == "i386" || machine == "amd64" || machine.compare(0, 3, "x86", 3) == 0) kernelCpuFamily = "x86"; else if (machine.compare(0, 4, "s390", 4) == 0) kernelCpuFamily = "s390"; else if (machine.compare(0, 3, "ppc", 3) == 0 || machine.compare(0, 5, "power", 5) == 0) kernelCpuFamily = "PowerPC"; else if (machine.compare(0, 5, "riscv", 5) == 0) kernelCpuFamily = "RISC-V"; } #endif if (kernelCpuFamily.empty()) kernelCpuFamily = "unknown CPU family"; } return kernelCpuFamily; } int CSysInfo::GetXbmcBitness(void) { return static_cast(sizeof(void*) * 8); } bool CSysInfo::HasInternet() { if (m_info.internetState != CSysData::UNKNOWN) return m_info.internetState == CSysData::CONNECTED; return (m_info.internetState = CSysInfoJob::GetInternetState()) == CSysData::CONNECTED; } std::string CSysInfo::GetHddSpaceInfo(int drive, bool shortText) { int percent; return GetHddSpaceInfo( percent, drive, shortText); } std::string CSysInfo::GetHddSpaceInfo(int& percent, int drive, bool shortText) { int total, totalFree, totalUsed, percentFree, percentused; std::string strRet; percent = 0; if (g_sysinfo.GetDiskSpace("", total, totalFree, totalUsed, percentFree, percentused)) { if (shortText) { switch(drive) { case SYSTEM_FREE_SPACE: percent = percentFree; break; case SYSTEM_USED_SPACE: percent = percentused; break; } } else { switch(drive) { case SYSTEM_FREE_SPACE: strRet = StringUtils::Format("{} MB {}", totalFree, g_localizeStrings.Get(160)); break; case SYSTEM_USED_SPACE: strRet = StringUtils::Format("{} MB {}", totalUsed, g_localizeStrings.Get(20162)); break; case SYSTEM_TOTAL_SPACE: strRet = StringUtils::Format("{} MB {}", total, g_localizeStrings.Get(20161)); break; case SYSTEM_FREE_SPACE_PERCENT: strRet = StringUtils::Format("{} % {}", percentFree, g_localizeStrings.Get(160)); break; case SYSTEM_USED_SPACE_PERCENT: strRet = StringUtils::Format("{} % {}", percentused, g_localizeStrings.Get(20162)); break; } } } else { if (shortText) strRet = g_localizeStrings.Get(10006); // N/A else strRet = g_localizeStrings.Get(10005); // Not available } return strRet; } std::string CSysInfo::GetUserAgent() { static std::string result; if (!result.empty()) return result; result = GetAppName() + "/" + CSysInfo::GetVersionShort() + " ("; #if defined(TARGET_WINDOWS) result += GetKernelName() + " " + GetKernelVersion(); #ifndef TARGET_WINDOWS_STORE BOOL bIsWow = FALSE; if (IsWow64Process(GetCurrentProcess(), &bIsWow) && bIsWow) result.append("; WOW64"); else #endif { SYSTEM_INFO si = {}; GetSystemInfo(&si); if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) result.append("; Win64; x64"); else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) result.append("; Win64; IA64"); else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM) result.append("; ARM"); } #elif defined(TARGET_DARWIN) #if defined(TARGET_DARWIN_EMBEDDED) std::string iDevStr(GetModelName()); // device model name with number of model version size_t iDevStrDigit = iDevStr.find_first_of("0123456789"); std::string iDev(iDevStr, 0, iDevStrDigit); // device model name without number if (iDevStrDigit == 0) iDev = "unknown"; result += iDev + "; "; std::string iOSVersion(GetOsVersion()); size_t lastDotPos = iOSVersion.rfind('.'); if (lastDotPos != std::string::npos && iOSVersion.find('.') != lastDotPos && iOSVersion.find_first_not_of('0', lastDotPos + 1) == std::string::npos) iOSVersion.erase(lastDotPos); StringUtils::Replace(iOSVersion, '.', '_'); if (iDev == "AppleTV") { // check if it's ATV4 (AppleTV5,3) or later auto modelMajorNumberEndPos = iDevStr.find_first_of(',', iDevStrDigit); std::string s{iDevStr, iDevStrDigit, modelMajorNumberEndPos - iDevStrDigit}; if (stoi(s) >= 5) result += "CPU TVOS"; else result += "CPU OS"; } else if (iDev == "iPad") result += "CPU OS"; else result += "CPU iPhone OS "; result += iOSVersion + " like Mac OS X"; #else result += "Macintosh; "; std::string cpuFam(GetBuildTargetCpuFamily()); if (cpuFam == "x86") result += "Intel "; result += "Mac OS X "; std::string OSXVersion(GetOsVersion()); StringUtils::Replace(OSXVersion, '.', '_'); result += OSXVersion; #endif #elif defined(TARGET_ANDROID) result += "Linux; Android "; std::string versionStr(GetOsVersion()); const size_t verLen = versionStr.length(); if (verLen >= 2 && versionStr.compare(verLen - 2, 2, ".0", 2) == 0) versionStr.erase(verLen - 2); // remove last ".0" if any result += versionStr; std::string deviceInfo(GetModelName()); char buildId[PROP_VALUE_MAX]; int propLen = __system_property_get("ro.build.id", buildId); if (propLen > 0 && propLen <= PROP_VALUE_MAX) { if (!deviceInfo.empty()) deviceInfo += " "; deviceInfo += "Build/"; deviceInfo.append(buildId, propLen); } if (!deviceInfo.empty()) result += "; " + deviceInfo; #elif defined(TARGET_POSIX) result += "X11; "; struct utsname un; if (uname(&un) == 0) { std::string cpuStr(un.machine); if (cpuStr == "x86_64" && GetXbmcBitness() == 32) cpuStr = "i686 on x86_64"; result += un.sysname; result += " "; result += cpuStr; } else result += "Unknown"; #else result += "Unknown"; #endif result += ")"; if (GetAppName() != "Kodi") result += " Kodi_Fork_" + GetAppName() + "/1.0"; // default fork number is '1.0', replace it with actual number if necessary #ifdef TARGET_LINUX // Add distribution name std::string linuxOSName(GetOsName(true)); if (!linuxOSName.empty()) result += " " + linuxOSName + "/" + GetOsVersion(); #endif #if defined(TARGET_DARWIN_IOS) std::string iDevVer; if (iDevStrDigit == std::string::npos) iDevVer = "0.0"; else iDevVer.assign(iDevStr, iDevStrDigit, std::string::npos); StringUtils::Replace(iDevVer, ',', '.'); result += " HW_" + iDev + "/" + iDevVer; #endif // add more device IDs here if needed. // keep only one device ID in result! Form: // result += " HW_" + "deviceID" + "/" + "1.0"; // '1.0' if device has no version #if defined(TARGET_ANDROID) // Android has no CPU string by default, so add it as additional parameter struct utsname un1; if (uname(&un1) == 0) { std::string cpuStr(un1.machine); StringUtils::Replace(cpuStr, ' ', '_'); result += " Sys_CPU/" + cpuStr; } #endif result += " App_Bitness/" + std::to_string(GetXbmcBitness()); std::string fullVer(CSysInfo::GetVersion()); StringUtils::Replace(fullVer, ' ', '-'); result += " Version/" + fullVer; return result; } std::string CSysInfo::GetDeviceName() { std::string friendlyName = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SERVICES_DEVICENAME); if (StringUtils::EqualsNoCase(friendlyName, CCompileInfo::GetAppName())) { std::string hostname("[unknown]"); CServiceBroker::GetNetwork().GetHostName(hostname); return StringUtils::Format("{} ({})", friendlyName, hostname); } return friendlyName; } // Version string MUST NOT contain spaces. It is used // in the HTTP request user agent. std::string CSysInfo::GetVersionShort() { if (strlen(CCompileInfo::GetSuffix()) == 0) return StringUtils::Format("{}.{}", CCompileInfo::GetMajor(), CCompileInfo::GetMinor()); else return StringUtils::Format("{}.{}-{}", CCompileInfo::GetMajor(), CCompileInfo::GetMinor(), CCompileInfo::GetSuffix()); } std::string CSysInfo::GetVersion() { return GetVersionShort() + " (" + CCompileInfo::GetVersionCode() + ")" + " Git:" + CCompileInfo::GetSCMID(); } std::string CSysInfo::GetVersionCode() { return CCompileInfo::GetVersionCode(); } std::string CSysInfo::GetVersionGit() { return CCompileInfo::GetSCMID(); } std::string CSysInfo::GetBuildDate() { return CCompileInfo::GetBuildDate(); } std::string CSysInfo::GetBuildTargetPlatformName(void) { #if defined(TARGET_DARWIN_OSX) return "macOS"; #elif defined(TARGET_DARWIN_IOS) return "iOS"; #elif defined(TARGET_DARWIN_TVOS) return "tvOS"; #elif defined(TARGET_FREEBSD) return "FreeBSD"; #elif defined(TARGET_ANDROID) return "Android"; #elif defined(TARGET_LINUX) return "Linux"; #elif defined(TARGET_WINDOWS) #ifdef NTDDI_VERSION return "Windows NT"; #else // !NTDDI_VERSION return "unknown Win32 platform"; #endif // !NTDDI_VERSION #else return "unknown platform"; #endif } std::string CSysInfo::GetBuildTargetPlatformVersion(void) { #if defined(TARGET_DARWIN_OSX) return XSTR_MACRO(__MAC_OS_X_VERSION_MIN_REQUIRED); #elif defined(TARGET_DARWIN_IOS) return XSTR_MACRO(__IPHONE_OS_VERSION_MIN_REQUIRED); #elif defined(TARGET_DARWIN_TVOS) return XSTR_MACRO(__TV_OS_VERSION_MIN_REQUIRED); #elif defined(TARGET_FREEBSD) return XSTR_MACRO(__FreeBSD_version); #elif defined(TARGET_ANDROID) return "API level " XSTR_MACRO(__ANDROID_API__); #elif defined(TARGET_LINUX) return XSTR_MACRO(LINUX_VERSION_CODE); #elif defined(TARGET_WINDOWS) #ifdef NTDDI_VERSION return XSTR_MACRO(NTDDI_VERSION); #else // !NTDDI_VERSION return "(unknown Win32 platform)"; #endif // !NTDDI_VERSION #else return "(unknown platform)"; #endif } std::string CSysInfo::GetBuildTargetPlatformVersionDecoded(void) { #if defined(TARGET_DARWIN_OSX) if (__MAC_OS_X_VERSION_MIN_REQUIRED % 100) return StringUtils::Format("version {}.{}.{}", __MAC_OS_X_VERSION_MIN_REQUIRED / 10000, (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100, __MAC_OS_X_VERSION_MIN_REQUIRED % 100); else return StringUtils::Format("version {}.{}", __MAC_OS_X_VERSION_MIN_REQUIRED / 10000, (__MAC_OS_X_VERSION_MIN_REQUIRED / 100) % 100); #elif defined(TARGET_DARWIN_EMBEDDED) std::string versionStr = GetBuildTargetPlatformVersion(); static const int major = (std::stoi(versionStr) / 10000) % 100; static const int minor = (std::stoi(versionStr) / 100) % 100; static const int rev = std::stoi(versionStr) % 100; return StringUtils::Format("version {}.{}.{}", major, minor, rev); #elif defined(TARGET_FREEBSD) // FIXME: should works well starting from FreeBSD 8.1 static const int major = (__FreeBSD_version / 100000) % 100; static const int minor = (__FreeBSD_version / 1000) % 100; static const int Rxx = __FreeBSD_version % 1000; if ((major < 9 && Rxx == 0)) return StringUtils::Format("version {}.{}-RELEASE", major, minor); if (Rxx >= 500) return StringUtils::Format("version {}.{}-STABLE", major, minor); return StringUtils::Format("version {}.{}-CURRENT", major, minor); #elif defined(TARGET_ANDROID) return "API level " XSTR_MACRO(__ANDROID_API__); #elif defined(TARGET_LINUX) return StringUtils::Format("version {}.{}.{}", (LINUX_VERSION_CODE >> 16) & 0xFF, (LINUX_VERSION_CODE >> 8) & 0xFF, LINUX_VERSION_CODE & 0xFF); #elif defined(TARGET_WINDOWS) #ifdef NTDDI_VERSION std::string version(StringUtils::Format("version {}.{}", int(NTDDI_VERSION >> 24) & 0xFF, int(NTDDI_VERSION >> 16) & 0xFF)); if (SPVER(NTDDI_VERSION)) version += StringUtils::Format(" SP{}", int(SPVER(NTDDI_VERSION))); return version; #else // !NTDDI_VERSION return "(unknown Win32 platform)"; #endif // !NTDDI_VERSION #else return "(unknown platform)"; #endif } std::string CSysInfo::GetBuildTargetCpuFamily(void) { #if defined(__thumb__) || defined(_M_ARMT) return "ARM (Thumb)"; #elif defined(__arm__) || defined(_M_ARM) || defined (__aarch64__) return "ARM"; #elif defined(__mips__) || defined(mips) || defined(__mips) return "MIPS"; #elif defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) || \ defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(_X86_) return "x86"; #elif defined(__s390x__) return "s390"; #elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__ppc__) || defined(__ppc64__) || defined(_M_PPC) return "PowerPC"; #elif defined(__riscv) return "RISC-V"; #else return "unknown CPU family"; #endif } std::string CSysInfo::GetUsedCompilerNameAndVer(void) { #if defined(__clang__) #ifdef __clang_version__ return "Clang " __clang_version__; #else // ! __clang_version__ return "Clang " XSTR_MACRO(__clang_major__) "." XSTR_MACRO(__clang_minor__) "." XSTR_MACRO(__clang_patchlevel__); #endif //! __clang_version__ #elif defined (__INTEL_COMPILER) return "Intel Compiler " XSTR_MACRO(__INTEL_COMPILER); #elif defined (__GNUC__) std::string compilerStr; #ifdef __llvm__ /* Note: this will not detect GCC + DragonEgg */ compilerStr = "llvm-gcc "; #else // __llvm__ compilerStr = "GCC "; #endif // !__llvm__ compilerStr += XSTR_MACRO(__GNUC__) "." XSTR_MACRO(__GNUC_MINOR__) "." XSTR_MACRO(__GNUC_PATCHLEVEL__); return compilerStr; #elif defined (_MSC_VER) return "MSVC " XSTR_MACRO(_MSC_FULL_VER); #else return "unknown compiler"; #endif } std::string CSysInfo::GetPrivacyPolicy() { if (m_privacyPolicy.empty()) { CFile file; std::vector buf; if (file.LoadFile("special://xbmc/privacy-policy.txt", buf) > 0) { m_privacyPolicy = std::string(reinterpret_cast(buf.data()), buf.size()); } else m_privacyPolicy = g_localizeStrings.Get(19055); } return m_privacyPolicy; } CSysInfo::WindowsDeviceFamily CSysInfo::GetWindowsDeviceFamily() { #ifdef TARGET_WINDOWS_STORE auto familyName = AnalyticsInfo::VersionInfo().DeviceFamily(); if (familyName == L"Windows.Desktop") return WindowsDeviceFamily::Desktop; else if (familyName == L"Windows.Mobile") return WindowsDeviceFamily::Mobile; else if (familyName == L"Windows.Universal") return WindowsDeviceFamily::IoT; else if (familyName == L"Windows.Team") return WindowsDeviceFamily::Surface; else if (familyName == L"Windows.Xbox") return WindowsDeviceFamily::Xbox; else return WindowsDeviceFamily::Other; #endif // TARGET_WINDOWS_STORE return WindowsDeviceFamily::Desktop; } CJob *CSysInfo::GetJob() const { return new CSysInfoJob(); } void CSysInfo::OnJobComplete(unsigned int jobID, bool success, CJob *job) { m_info = static_cast(job)->GetData(); CInfoLoader::OnJobComplete(jobID, success, job); }