mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-07-20 03:35:46 +00:00
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>
This commit is contained in:
parent
6bf5ae700a
commit
8c33b0bb5d
11 changed files with 269 additions and 14 deletions
|
@ -1,9 +1,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
package org.yuzu.yuzu_emu
|
||||
|
||||
|
@ -489,4 +489,9 @@ object NativeLibrary {
|
|||
* Checks if all necessary keys are present for decryption
|
||||
*/
|
||||
external fun areKeysPresent(): Boolean
|
||||
|
||||
/**
|
||||
* Updates the device power state to global variables
|
||||
*/
|
||||
external fun updatePowerState(percentage: Int, isCharging: Boolean, hasBattery: Boolean)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
@ -13,6 +16,7 @@ import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
|||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.PowerStateUpdater
|
||||
|
||||
fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
|
||||
|
||||
|
@ -40,6 +44,7 @@ class YuzuApplication : Application() {
|
|||
GpuDriverHelper.initializeDriverParameters()
|
||||
NativeInput.reloadInputDevices()
|
||||
NativeLibrary.logDeviceInfo()
|
||||
PowerStateUpdater.start()
|
||||
Log.logDeviceInfo()
|
||||
|
||||
createNotificationChannels()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
package org.yuzu.yuzu_emu.activities
|
||||
|
||||
|
@ -58,6 +58,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
|||
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
import org.yuzu.yuzu_emu.utils.PowerStateUtils
|
||||
import java.text.NumberFormat
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
|
@ -79,6 +79,11 @@ static EmulationSession s_instance;
|
|||
std::unique_ptr<AndroidMultiplayer> multiplayer{nullptr};
|
||||
std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
|
||||
|
||||
//Power Status default values
|
||||
std::atomic<int> g_battery_percentage = {100};
|
||||
std::atomic<bool> g_is_charging = {false};
|
||||
std::atomic<bool> g_has_battery = {true};
|
||||
|
||||
EmulationSession::EmulationSession() {
|
||||
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
}
|
||||
|
@ -1014,4 +1019,16 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_network_NetPlayManager_netPlayUnb
|
|||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring username) {
|
||||
multiplayer->NetPlayUnbanUser(Common::Android::GetJString(env, username));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_updatePowerState(
|
||||
JNIEnv* env,
|
||||
jobject,
|
||||
jint percentage,
|
||||
jboolean isCharging,
|
||||
jboolean hasBattery) {
|
||||
|
||||
g_battery_percentage.store(percentage, std::memory_order_relaxed);
|
||||
g_is_charging.store(isCharging, std::memory_order_relaxed);
|
||||
g_has_battery.store(hasBattery, std::memory_order_relaxed);
|
||||
}
|
||||
} // extern "C"
|
||||
|
|
|
@ -42,6 +42,8 @@ add_library(common STATIC
|
|||
demangle.h
|
||||
detached_tasks.cpp
|
||||
detached_tasks.h
|
||||
device_power_state.cpp
|
||||
device_power_state.h
|
||||
div_ceil.h
|
||||
dynamic_library.cpp
|
||||
dynamic_library.h
|
||||
|
|
102
src/common/device_power_state.cpp
Normal file
102
src/common/device_power_state.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
14
src/common/device_power_state.h
Normal file
14
src/common/device_power_state.h
Normal file
|
@ -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
|
|
@ -1,8 +1,12 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/device_power_state.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
|
@ -148,17 +152,31 @@ PSM::~PSM() = default;
|
|||
void PSM::GetBatteryChargePercentage(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_PTM, "called");
|
||||
|
||||
u32 percentage = 100;
|
||||
|
||||
Common::PowerStatus power_status = Common::GetPowerStatus();
|
||||
|
||||
if (power_status.has_battery && power_status.percentage >= 0) {
|
||||
percentage = static_cast<u32>(power_status.percentage);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(battery_charge_percentage);
|
||||
rb.Push<u32>(percentage);
|
||||
}
|
||||
|
||||
void PSM::GetChargerType(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_PTM, "called");
|
||||
|
||||
ChargerType charger = ChargerType::Unplugged;
|
||||
Common::PowerStatus power_status = Common::GetPowerStatus();
|
||||
if (power_status.has_battery && power_status.charging) {
|
||||
charger = ChargerType::RegularCharger;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(charger_type);
|
||||
rb.PushEnum(charger);
|
||||
}
|
||||
|
||||
void PSM::OpenSession(HLERequestContext& ctx) {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
@ -23,9 +26,6 @@ private:
|
|||
void GetBatteryChargePercentage(HLERequestContext& ctx);
|
||||
void GetChargerType(HLERequestContext& ctx);
|
||||
void OpenSession(HLERequestContext& ctx);
|
||||
|
||||
u32 battery_charge_percentage{100};
|
||||
ChargerType charger_type{ChargerType::RegularCharger};
|
||||
};
|
||||
|
||||
} // namespace Service::PTM
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue