// SPDX-License-Identifier: MIT // // ES-DE Frontend // SystemStatus.cpp // // Queries system status information from the operating system. // This includes Bluetooth, Wi-Fi, cellular and battery. // #include "SystemStatus.h" #include "Log.h" #include "Settings.h" #include "utils/FileSystemUtil.h" #include "utils/StringUtil.h" #include #include #if defined(__APPLE__) && !defined(__IOS__) #include "BluetoothStatusApple.h" #include #include #include #endif #if defined(_WIN64) // clang-format off // Because of course building fails if the files are included in the "wrong" order. #include #include #include // clang-format on #endif #if defined(__ANDROID__) #include "utils/PlatformUtilAndroid.h" #endif #define DEBUG_SYSTEM_STATUS false SystemStatus::SystemStatus() noexcept : mExitPolling {false} , mPollImmediately {false} , mHasBluetooth {false} , mHasWifi {false} , mHasCellular {false} , mHasBattery {false} , mBatteryCharging {false} , mBatteryCapacity {0} { setCheckFlags(); #if defined(__ANDROID__) // Polling the device status is very fast on Android and it's quite problematic to run // these calls in a separate thread anyway. getStatusBluetooth(); getStatusWifi(); getStatusCellular(); getStatusBattery(); #else mPollThread = std::make_unique(&SystemStatus::pollStatus, this); #endif } SystemStatus::~SystemStatus() { #if !defined(__ANDROID__) mExitPolling = true; if (mPollThread != nullptr && mPollThread->joinable()) { mPollThread->join(); mPollThread.reset(); } #endif } SystemStatus& SystemStatus::getInstance() { static SystemStatus instance; return instance; } void SystemStatus::setCheckFlags() { std::unique_lock statusLock {mStatusMutex}; mCheckBluetooth = Settings::getInstance()->getBool("SystemStatusBluetooth"); mCheckWifi = Settings::getInstance()->getBool("SystemStatusWifi"); mCheckCellular = Settings::getInstance()->getBool("SystemStatusCellular"); mCheckBattery = Settings::getInstance()->getBool("SystemStatusBattery"); } void SystemStatus::setPolling(const bool state) { #if defined(__ANDROID__) return; #endif if (state == false) { mExitPolling = true; if (mPollThread != nullptr && mPollThread->joinable()) { mPollThread->join(); mPollThread.reset(); } } else if (mPollThread == nullptr) { mExitPolling = false; mPollThread = std::make_unique(&SystemStatus::pollStatus, this); } } SystemStatus::Status SystemStatus::getStatus() { #if defined(__ANDROID__) getStatusBluetooth(); getStatusWifi(); getStatusCellular(); getStatusBattery(); #endif mStatus.hasBluetooth = mHasBluetooth; mStatus.hasWifi = mHasWifi; mStatus.hasCellular = mHasCellular; mStatus.hasBattery = mHasBattery; mStatus.batteryCharging = mBatteryCharging; mStatus.batteryCapacity = mBatteryCapacity; return mStatus; } void SystemStatus::pollStatus() { while (!mExitPolling) { std::unique_lock statusLock {mStatusMutex}; getStatusBluetooth(); getStatusWifi(); getStatusCellular(); getStatusBattery(); statusLock.unlock(); #if (DEBUG_SYSTEM_STATUS) std::string status {"Bluetooth "}; status.append(mHasBluetooth ? "enabled" : "disabled") .append(", Wi-Fi ") .append(mHasWifi ? "enabled" : "disabled") .append(", cellular ") .append(mHasCellular ? "enabled" : "disabled") .append(", battery ") .append(mHasBattery ? "enabled" : "disabled"); if (mHasBattery) { status.append(" (") .append(mBatteryCharging ? "charging" : "not charging") .append(" and at ") .append(std::to_string(mBatteryCapacity)) .append("% capacity)"); } LOG(LogDebug) << "SystemStatus::pollStatus(): " << status; #endif int delayValue {0}; while (!mPollImmediately && !mExitPolling && delayValue < 3000) { delayValue += 100; SDL_Delay(100); } mPollImmediately = false; } } void SystemStatus::getStatusBluetooth() { if (!mCheckBluetooth) return; bool hasBluetooth {false}; #if defined(__APPLE__) && !defined(__IOS__) if (getBluetoothStatus() == 1) hasBluetooth = true; #elif defined(_WIN64) BLUETOOTH_FIND_RADIO_PARAMS btFindRadio {sizeof(BLUETOOTH_FIND_RADIO_PARAMS)}; HANDLE btRadio {nullptr}; BLUETOOTH_RADIO_INFO btInfo {sizeof(BLUETOOTH_RADIO_INFO), 0}; if (BluetoothFindFirstRadio(&btFindRadio, &btRadio) != nullptr) { if (BluetoothGetRadioInfo(btRadio, &btInfo) == ERROR_SUCCESS) hasBluetooth = true; } #elif defined(__ANDROID__) if (Utils::Platform::Android::getBluetoothStatus()) hasBluetooth = true; #elif defined(__linux__) const std::string sysEntry {"/sys/class/rfkill"}; auto entries {Utils::FileSystem::getDirContent(sysEntry, false)}; for (auto& entry : entries) { if (Utils::FileSystem::exists(entry + "/type")) { std::string type; std::ifstream fileStream; fileStream.open(entry + "/type"); getline(fileStream, type); fileStream.close(); if (Utils::String::toLower(type) == "bluetooth") { std::string state; fileStream.open(entry + "/state"); getline(fileStream, state); fileStream.close(); if (std::stoi(state) == 1) hasBluetooth = true; break; } } } #endif mHasBluetooth = hasBluetooth; } void SystemStatus::getStatusWifi() { if (!mCheckWifi) return; bool hasWifi {false}; #if defined(__APPLE__) && !defined(__IOS__) const CFArrayRef interfaces {SCNetworkInterfaceCopyAll()}; if (interfaces != nullptr) { for (CFIndex i {0}; i < CFArrayGetCount(interfaces); ++i) { SCNetworkInterfaceRef interface { static_cast(CFArrayGetValueAtIndex(interfaces, i))}; if (SCNetworkInterfaceGetInterfaceType(interface) == kSCNetworkInterfaceTypeIEEE80211) { const CFStringRef bsdName {SCNetworkInterfaceGetBSDName(interface)}; const SCDynamicStoreRef session { SCDynamicStoreCreate(nullptr, CFSTR("Custom"), nullptr, nullptr)}; const CFStringRef resolvedQuery {CFStringCreateWithFormat( nullptr, nullptr, CFSTR("State:/Network/Interface/%@/IPv4"), bsdName)}; const CFDictionaryRef dict { static_cast(SCDynamicStoreCopyValue(session, resolvedQuery))}; if (dict != nullptr) { hasWifi = true; CFRelease(dict); CFRelease(resolvedQuery); CFRelease(session); break; } else { CFRelease(resolvedQuery); CFRelease(session); } } } CFRelease(interfaces); } #elif defined(_WIN64) PIP_ADAPTER_INFO pAdapterInfo {nullptr}; PIP_ADAPTER_INFO pAdapter {nullptr}; ULONG ulOutBufLen {sizeof(IP_ADAPTER_INFO)}; pAdapterInfo = reinterpret_cast(malloc(sizeof(IP_ADAPTER_INFO))); if (pAdapterInfo != nullptr) { // Make an initial call to GetAdaptersInfo to get the necessary size into the // ulOutBufLen variable, which may or may not be big enough. if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { free(pAdapterInfo); pAdapterInfo = reinterpret_cast(malloc(ulOutBufLen)); } if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == NO_ERROR) { pAdapter = pAdapterInfo; while (pAdapter) { if (pAdapter->Type == IF_TYPE_IEEE80211) { // Checking whether the interface has an IP address is crude but // it seems to get the job done. And there is no other obvious // way to query the interface status without using additional // convoluted API calls. if (const std::string {pAdapter->IpAddressList.IpAddress.String} != "0.0.0.0") { hasWifi = true; break; } } pAdapter = pAdapter->Next; } } if (pAdapterInfo) free(pAdapterInfo); } #elif defined(__ANDROID__) if (Utils::Platform::Android::getWifiStatus() == 1) hasWifi = true; #elif defined(__linux__) const std::string sysEntry {"/sys/class/net"}; auto entries {Utils::FileSystem::getDirContent(sysEntry, false)}; for (auto& entry : entries) { if (Utils::FileSystem::exists(entry + "/wireless") && Utils::FileSystem::exists(entry + "/operstate")) { std::string wifiState; std::ifstream fileStream; fileStream.open(entry + "/operstate"); getline(fileStream, wifiState); fileStream.close(); if (Utils::String::toLower(wifiState) == "up") hasWifi = true; } } #endif mHasWifi = hasWifi; } void SystemStatus::getStatusCellular() { if (!mCheckCellular) return; bool hasCellular {false}; #if defined(__ANDROID__) if (Utils::Platform::Android::getCellularStatus() >= 1) hasCellular = true; #endif mHasCellular = hasCellular; } void SystemStatus::getStatusBattery() { if (!mCheckBattery) return; bool hasBattery {false}; bool batteryCharging {false}; int batteryCapacity {0}; #if defined(__APPLE__) && !defined(__IOS__) CFTypeRef sourceInfo {IOPSCopyPowerSourcesInfo()}; CFArrayRef sourceList {IOPSCopyPowerSourcesList(sourceInfo)}; if (sourceList != nullptr && CFArrayGetCount(sourceList) > 0) { CFDictionaryRef source {nullptr}; for (CFIndex i {0}; i < CFArrayGetCount(sourceList); ++i) { source = IOPSGetPowerSourceDescription(sourceInfo, CFArrayGetValueAtIndex(sourceList, i)); // Check if this is a battery. const CFStringRef type {static_cast( CFDictionaryGetValue(source, CFSTR(kIOPSTransportTypeKey)))}; if (kCFCompareEqualTo == CFStringCompare(type, CFSTR(kIOPSInternalType), 0)) break; else source = nullptr; } if (source != nullptr) { hasBattery = true; if (CFDictionaryGetValue(source, CFSTR(kIOPSIsChargingKey)) != nullptr) { batteryCharging = CFBooleanGetValue(static_cast( CFDictionaryGetValue(source, CFSTR(kIOPSIsChargingKey)))); } int curCapacity {0}; const CFNumberRef curCapacityNum {static_cast( CFDictionaryGetValue(source, CFSTR(kIOPSCurrentCapacityKey)))}; CFNumberGetValue(curCapacityNum, kCFNumberIntType, &curCapacity); int maxCapacity {0}; const CFNumberRef maxCapacityNum { static_cast(CFDictionaryGetValue(source, CFSTR(kIOPSMaxCapacityKey)))}; CFNumberGetValue(maxCapacityNum, kCFNumberIntType, &maxCapacity); if (maxCapacity > 0) batteryCapacity = curCapacity / maxCapacity * 100; } } if (sourceInfo != nullptr) CFRelease(sourceInfo); if (sourceList != nullptr) CFRelease(sourceList); #elif defined(_WIN64) SYSTEM_POWER_STATUS powerStatus; if (GetSystemPowerStatus(&powerStatus)) { if (powerStatus.BatteryFlag != 128 && powerStatus.BatteryFlag != 255) { hasBattery = true; if (powerStatus.ACLineStatus == 1) atteryCharging = true; batteryCapacity = powerStatus.BatteryLifePercent; } else { hasBattery = false; } } #elif defined(__ANDROID__) std::pair batteryStatus {Utils::Platform::Android::getBatteryStatus()}; hasBattery = static_cast(batteryStatus.first); if (batteryStatus.first == -1 && batteryStatus.second == -1) { hasBattery = false; } else { hasBattery = true; if (batteryStatus.first == 1) batteryCharging = true; } batteryCapacity = batteryStatus.second; #elif defined(__linux__) const std::string sysEntry {"/sys/class/power_supply"}; std::string batteryDir; auto entries {Utils::FileSystem::getDirContent(sysEntry, false)}; if (std::find(entries.cbegin(), entries.cend(), sysEntry + "/BAT0") != entries.cend()) batteryDir = sysEntry + "/BAT0"; else if (std::find(entries.cbegin(), entries.cend(), sysEntry + "/BAT1") != entries.cend()) batteryDir = sysEntry + "/BAT1"; else if (std::find(entries.cbegin(), entries.cend(), sysEntry + "/battery") != entries.cend()) batteryDir = sysEntry + "/battery"; if (!Utils::FileSystem::exists(batteryDir + "/status")) hasBattery = false; if (!Utils::FileSystem::exists(batteryDir + "/capacity")) hasBattery = false; if (hasBattery) { std::string batteryStatusValue; std::string batteryCapacityValue; std::ifstream fileStream; fileStream.open(batteryDir + "/status"); getline(fileStream, batteryStatusValue); batteryStatusValue = Utils::String::toLower(batteryStatusValue); fileStream.close(); if (batteryStatusValue != "discharging") batteryCharging = true; fileStream.open(batteryDir + "/capacity"); getline(fileStream, batteryCapacityValue); fileStream.close(); batteryCapacity = std::stoi(batteryCapacityValue); } #endif mHasBattery = hasBattery; mBatteryCharging = batteryCharging; mBatteryCapacity = batteryCapacity; }