Browse Source
Add Device Power State (Windows, Linux, Mac and Android) (#197)
Add Device Power State (Windows, Linux, Mac and Android) (#197)
Uses native power state methods to display battery percentage and charging state correctly. Mainly for qlaunch. Tested on Windows, Linux. Mac and Android Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/197 Co-authored-by: Maufeat <sahyno1996@gmail.com> Co-committed-by: Maufeat <sahyno1996@gmail.com>pull/21/head
committed by
Maufeat
11 changed files with 269 additions and 14 deletions
-
11src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
-
5src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
-
7src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
-
46src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PowerStateUpdater.kt
-
45src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PowerStateUtils.kt
-
23src/android/app/src/main/jni/native.cpp
-
2src/common/CMakeLists.txt
-
102src/common/device_power_state.cpp
-
14src/common/device_power_state.h
-
22src/core/hle/service/ptm/psm.cpp
-
6src/core/hle/service/ptm/psm.h
@ -0,0 +1,46 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
|
|||
package org.yuzu.yuzu_emu.utils |
|||
|
|||
import android.os.Handler |
|||
import android.os.Looper |
|||
import org.yuzu.yuzu_emu.NativeLibrary |
|||
import org.yuzu.yuzu_emu.YuzuApplication |
|||
|
|||
object PowerStateUpdater { |
|||
|
|||
private lateinit var handler: Handler |
|||
private lateinit var runnable: Runnable |
|||
private const val UPDATE_INTERVAL_MS = 1000L |
|||
private var isStarted = false |
|||
|
|||
fun start() { |
|||
|
|||
if (isStarted) { |
|||
return |
|||
} |
|||
|
|||
val context = YuzuApplication.appContext |
|||
|
|||
handler = Handler(Looper.getMainLooper()) |
|||
runnable = Runnable { |
|||
val info = PowerStateUtils.getBatteryInfo(context) |
|||
NativeLibrary.updatePowerState(info[0], info[1] == 1, info[2] == 1) |
|||
handler.postDelayed(runnable, UPDATE_INTERVAL_MS) |
|||
} |
|||
handler.post(runnable) |
|||
isStarted = true |
|||
} |
|||
|
|||
fun stop() { |
|||
if (!isStarted) { |
|||
return |
|||
} |
|||
if (::handler.isInitialized) { |
|||
handler.removeCallbacks(runnable) |
|||
} |
|||
isStarted = false |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu.utils |
|||
|
|||
import android.content.Context |
|||
import android.content.Intent |
|||
import android.content.IntentFilter |
|||
import android.os.BatteryManager |
|||
import android.os.Build |
|||
|
|||
object PowerStateUtils { |
|||
|
|||
@JvmStatic |
|||
fun getBatteryInfo(context: Context?): IntArray { |
|||
|
|||
if (context == null) { |
|||
return intArrayOf(0, 0, 0) // Percentage, IsCharging, HasBattery |
|||
} |
|||
|
|||
val results = intArrayOf(100, 0, 1) |
|||
val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) |
|||
val batteryStatusIntent: Intent? = context.registerReceiver(null, iFilter) |
|||
|
|||
if (batteryStatusIntent != null) { |
|||
val present = batteryStatusIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true) |
|||
if (!present) { |
|||
results[2] = 0; results[0] = 0; results[1] = 0; return results |
|||
} |
|||
results[2] = 1 |
|||
val level = batteryStatusIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) |
|||
val scale = batteryStatusIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1) |
|||
if (level != -1 && scale != -1 && scale != 0) { |
|||
results[0] = (level.toFloat() / scale.toFloat() * 100.0f).toInt() |
|||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
val bm = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager? |
|||
results[0] = bm?.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) ?: 100 |
|||
} |
|||
val status = batteryStatusIntent.getIntExtra(BatteryManager.EXTRA_STATUS, -1) |
|||
results[1] = if (status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL) 1 else 0 |
|||
} |
|||
|
|||
return results |
|||
} |
|||
} |
|||
@ -0,0 +1,102 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include "device_power_state.h"
|
|||
|
|||
#if defined(_WIN32)
|
|||
#include <windows.h>
|
|||
|
|||
#elif defined(__ANDROID__)
|
|||
#include <atomic>
|
|||
extern std::atomic<int> g_battery_percentage; |
|||
extern std::atomic<bool> g_is_charging; |
|||
extern std::atomic<bool> g_has_battery; |
|||
|
|||
#elif defined(__APPLE__)
|
|||
#include <TargetConditionals.h>
|
|||
#if TARGET_OS_MAC
|
|||
#include <IOKit/ps/IOPSKeys.h>
|
|||
#include <IOKit/ps/IOPowerSources.h>
|
|||
#endif
|
|||
|
|||
#elif defined(__linux__)
|
|||
#include <fstream>
|
|||
#include <string>
|
|||
#include <dirent.h>
|
|||
#endif
|
|||
|
|||
namespace Common { |
|||
|
|||
PowerStatus GetPowerStatus() |
|||
{ |
|||
PowerStatus info; |
|||
|
|||
#if defined(_WIN32)
|
|||
SYSTEM_POWER_STATUS status; |
|||
if (GetSystemPowerStatus(&status)) { |
|||
if (status.BatteryFlag == 128) { |
|||
info.has_battery = false; |
|||
} else { |
|||
info.percentage = status.BatteryLifePercent; |
|||
info.charging = (status.BatteryFlag & 8) != 0; |
|||
} |
|||
} else { |
|||
info.has_battery = false; |
|||
} |
|||
|
|||
#elif defined(__ANDROID__)
|
|||
info.percentage = g_battery_percentage.load(std::memory_order_relaxed); |
|||
info.charging = g_is_charging.load(std::memory_order_relaxed); |
|||
info.has_battery = g_has_battery.load(std::memory_order_relaxed); |
|||
|
|||
#elif defined(__APPLE__) && TARGET_OS_MAC
|
|||
CFTypeRef info_ref = IOPSCopyPowerSourcesInfo(); |
|||
CFArrayRef sources = IOPSCopyPowerSourcesList(info_ref); |
|||
if (CFArrayGetCount(sources) > 0) { |
|||
CFDictionaryRef battery = |
|||
IOPSGetPowerSourceDescription(info_ref, CFArrayGetValueAtIndex(sources, 0)); |
|||
|
|||
CFNumberRef curNum = |
|||
(CFNumberRef)CFDictionaryGetValue(battery, CFSTR(kIOPSCurrentCapacityKey)); |
|||
CFNumberRef maxNum = |
|||
(CFNumberRef)CFDictionaryGetValue(battery, CFSTR(kIOPSMaxCapacityKey)); |
|||
int cur = 0, max = 0; |
|||
CFNumberGetValue(curNum, kCFNumberIntType, &cur); |
|||
CFNumberGetValue(maxNum, kCFNumberIntType, &max); |
|||
|
|||
if (max > 0) |
|||
info.percentage = (cur * 100) / max; |
|||
|
|||
CFBooleanRef isCharging = |
|||
(CFBooleanRef)CFDictionaryGetValue(battery, CFSTR(kIOPSIsChargingKey)); |
|||
info.charging = CFBooleanGetValue(isCharging); |
|||
} else { |
|||
info.has_battery = false; |
|||
} |
|||
CFRelease(sources); |
|||
CFRelease(info_ref); |
|||
|
|||
#elif defined(__linux__)
|
|||
const char* battery_path = "/sys/class/power_supply/BAT0/"; |
|||
|
|||
std::ifstream capFile(std::string(battery_path) + "capacity"); |
|||
if (capFile) { |
|||
capFile >> info.percentage; |
|||
} |
|||
else { |
|||
info.has_battery = false; |
|||
} |
|||
|
|||
std::ifstream statFile(std::string(battery_path) + "status"); |
|||
if (statFile) { |
|||
std::string status; |
|||
std::getline(statFile, status); |
|||
info.charging = (status == "Charging"); |
|||
} |
|||
#else
|
|||
info.has_battery = false; |
|||
#endif
|
|||
|
|||
return info; |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
namespace Common { |
|||
struct PowerStatus { |
|||
int percentage = -1; |
|||
bool charging = false; |
|||
bool has_battery = true; |
|||
}; |
|||
|
|||
PowerStatus GetPowerStatus(); |
|||
} // namespace Common |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue