[core, android] Initial playtime implementation #2535
19 changed files with 178 additions and 47 deletions
|
@ -206,6 +206,16 @@ object NativeLibrary {
|
||||||
ErrorUnknown
|
ErrorUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* playtime tracking
|
||||||
|
*/
|
||||||
|
external fun playTimeManagerInit()
|
||||||
|
external fun playTimeManagerStart()
|
||||||
|
external fun playTimeManagerStop()
|
||||||
|
external fun playTimeManagerGetPlayTime(programId: String): Long
|
||||||
|
external fun playTimeManagerGetCurrentTitleId(): Long
|
||||||
|
external fun playTimeManagerResetProgramPlayTime(programId: String)
|
||||||
|
|
||||||
var coreErrorAlertResult = false
|
var coreErrorAlertResult = false
|
||||||
val coreErrorAlertLock = Object()
|
val coreErrorAlertLock = Object()
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ class YuzuApplication : Application() {
|
||||||
application = this
|
application = this
|
||||||
documentsTree = DocumentsTree()
|
documentsTree = DocumentsTree()
|
||||||
DirectoryInitialization.start()
|
DirectoryInitialization.start()
|
||||||
|
NativeLibrary.playTimeManagerInit()
|
||||||
GpuDriverHelper.initializeDriverParameters()
|
GpuDriverHelper.initializeDriverParameters()
|
||||||
NativeInput.reloadInputDevices()
|
NativeInput.reloadInputDevices()
|
||||||
NativeLibrary.logDeviceInfo()
|
NativeLibrary.logDeviceInfo()
|
||||||
|
|
|
@ -61,6 +61,7 @@ import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||||
|
import androidx.core.os.BundleCompat
|
||||||
|
|
||||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
private lateinit var binding: ActivityEmulationBinding
|
private lateinit var binding: ActivityEmulationBinding
|
||||||
|
@ -322,6 +323,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
|
|
||||||
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
|
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
NativeLibrary.playTimeManagerStop()
|
||||||
|
}
|
||||||
|
|
||||||
private fun enableFullscreenImmersive() {
|
private fun enableFullscreenImmersive() {
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
|
@ -526,6 +532,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
|
|
||||||
fun onEmulationStarted() {
|
fun onEmulationStarted() {
|
||||||
emulationViewModel.setEmulationStarted(true)
|
emulationViewModel.setEmulationStarted(true)
|
||||||
|
NativeLibrary.playTimeManagerStart()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEmulationStopped(status: Int) {
|
fun onEmulationStopped(status: Int) {
|
||||||
|
|
|
@ -1635,6 +1635,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
Log.debug("[EmulationFragment] Pausing emulation.")
|
Log.debug("[EmulationFragment] Pausing emulation.")
|
||||||
|
|
||||||
NativeLibrary.pauseEmulation()
|
NativeLibrary.pauseEmulation()
|
||||||
|
NativeLibrary.playTimeManagerStop()
|
||||||
|
|
||||||
state = State.PAUSED
|
state = State.PAUSED
|
||||||
} else {
|
} else {
|
||||||
|
@ -1725,6 +1726,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
State.PAUSED -> {
|
State.PAUSED -> {
|
||||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||||
NativeLibrary.unpauseEmulation()
|
NativeLibrary.unpauseEmulation()
|
||||||
|
NativeLibrary.playTimeManagerStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
||||||
|
|
|
@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter
|
import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter
|
||||||
|
@ -104,6 +105,8 @@ class GamePropertiesFragment : Fragment() {
|
||||||
binding.title.text = args.game.title
|
binding.title.text = args.game.title
|
||||||
binding.title.marquee()
|
binding.title.marquee()
|
||||||
|
|
||||||
|
getPlayTime()
|
||||||
|
|
||||||
binding.buttonStart.setOnClickListener {
|
binding.buttonStart.setOnClickListener {
|
||||||
LaunchGameDialogFragment.newInstance(args.game)
|
LaunchGameDialogFragment.newInstance(args.game)
|
||||||
.show(childFragmentManager, LaunchGameDialogFragment.TAG)
|
.show(childFragmentManager, LaunchGameDialogFragment.TAG)
|
||||||
|
@ -128,6 +131,25 @@ class GamePropertiesFragment : Fragment() {
|
||||||
gamesViewModel.reloadGames(true)
|
gamesViewModel.reloadGames(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getPlayTime() {
|
||||||
|
binding.playtime.text = buildString {
|
||||||
|
val playTimeSeconds = NativeLibrary.playTimeManagerGetPlayTime(args.game.programId)
|
||||||
|
|
||||||
|
val hours = playTimeSeconds / 3600
|
||||||
|
val minutes = (playTimeSeconds % 3600) / 60
|
||||||
|
val seconds = playTimeSeconds % 60
|
||||||
|
|
||||||
|
val readablePlayTime = when {
|
||||||
|
hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
|
||||||
|
minutes > 0 -> "${minutes}m ${seconds}s"
|
||||||
|
else -> "${seconds}s"
|
||||||
|
}
|
||||||
|
|
||||||
|
append("Playtime: ")
|
||||||
|
append(readablePlayTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun reloadList() {
|
private fun reloadList() {
|
||||||
_binding ?: return
|
_binding ?: return
|
||||||
|
|
||||||
|
@ -272,6 +294,31 @@ class GamePropertiesFragment : Fragment() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (NativeLibrary.playTimeManagerGetPlayTime(args.game.programId) > 0) {
|
||||||
|
add(
|
||||||
|
SubmenuProperty(
|
||||||
|
R.string.reset_playtime,
|
||||||
|
R.string.reset_playtime_description,
|
||||||
|
R.drawable.ic_delete
|
||||||
|
) {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
requireActivity(),
|
||||||
|
titleId = R.string.reset_playtime,
|
||||||
|
descriptionId = R.string.reset_playtime_warning_description,
|
||||||
|
positiveAction = {
|
||||||
|
NativeLibrary.playTimeManagerResetProgramPlayTime( args.game.programId)
|
||||||
|
Toast.makeText(
|
||||||
|
YuzuApplication.appContext,
|
||||||
|
R.string.playtime_reset_successfully,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
getPlayTime()
|
||||||
|
homeViewModel.reloadPropertiesList(true)
|
||||||
|
}
|
||||||
|
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.listProperties.apply {
|
binding.listProperties.apply {
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
#include "common/play_time_manager.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/cpu_manager.h"
|
#include "core/cpu_manager.h"
|
||||||
#include "core/crypto/key_manager.h"
|
#include "core/crypto/key_manager.h"
|
||||||
|
@ -85,6 +86,9 @@ std::atomic<int> g_battery_percentage = {100};
|
||||||
std::atomic<bool> g_is_charging = {false};
|
std::atomic<bool> g_is_charging = {false};
|
||||||
std::atomic<bool> g_has_battery = {true};
|
std::atomic<bool> g_has_battery = {true};
|
||||||
|
|
||||||
|
// playtime
|
||||||
|
std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
|
||||||
|
|
||||||
EmulationSession::EmulationSession() {
|
EmulationSession::EmulationSession() {
|
||||||
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||||
}
|
}
|
||||||
|
@ -733,6 +737,48 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerInit(JNIEnv* env, jobject obj) {
|
||||||
|
// for some reason the full user directory isnt initialized in Android, so we need to create it
|
||||||
|
const auto play_time_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::PlayTimeDir);
|
||||||
|
if (!Common::FS::IsDir(play_time_dir)) {
|
||||||
|
if (!Common::FS::CreateDir(play_time_dir)) {
|
||||||
|
LOG_WARNING(Frontend, "Failed to create play time directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerStart(JNIEnv* env, jobject obj) {
|
||||||
|
if (play_time_manager) {
|
||||||
|
play_time_manager->SetProgramId(EmulationSession::GetInstance().System().GetApplicationProcessProgramID());
|
||||||
|
play_time_manager->Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerStop(JNIEnv* env, jobject obj) {
|
||||||
|
play_time_manager->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
jlong Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerGetPlayTime(JNIEnv* env, jobject obj,
|
||||||
|
jstring jprogramId) {
|
||||||
|
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
|
return play_time_manager->GetPlayTime(program_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
jlong Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerGetCurrentTitleId(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
return EmulationSession::GetInstance().System().GetApplicationProcessProgramID();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_playTimeManagerResetProgramPlayTime(JNIEnv* env, jobject obj,
|
||||||
|
jstring jprogramId) {
|
||||||
|
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
|
if (play_time_manager) {
|
||||||
|
play_time_manager->ResetProgramPlayTime(program_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz,
|
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz,
|
||||||
jlong jid) {
|
jlong jid) {
|
||||||
auto bis_system =
|
auto bis_system =
|
||||||
|
|
|
@ -105,6 +105,16 @@
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
tools:text="deko_basic" />
|
tools:text="deko_basic" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/playtime"
|
||||||
|
style="?attr/textAppearanceBodyMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/title"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/about_game_filename"
|
||||||
|
android:ellipsize="none"
|
||||||
|
tools:text="Game Playtime" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
|
@ -74,12 +74,22 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:layout_marginBottom="12dp"
|
android:layout_marginBottom="2dp"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:requiresFadingEdge="horizontal"
|
android:requiresFadingEdge="horizontal"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
tools:text="deko_basic" />
|
tools:text="deko_basic" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/playtime"
|
||||||
|
style="?attr/textAppearanceBodyMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/title"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/about_game_filename"
|
||||||
|
tools:text="Game Playtime" />
|
||||||
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list_properties"
|
android:id="@+id/list_properties"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1632,4 +1632,9 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
</string>
|
</string>
|
||||||
|
<string name="playtime_format">Playtime: %1$d h, %2$d m</string>
|
||||||
|
<string name="reset_playtime">Clear Playtime</string>
|
||||||
|
<string name="reset_playtime_description">Reset the current game\'s playtime back to 0 seconds</string>
|
||||||
|
<string name="reset_playtime_warning_description">This will clear the current game\'s playtime data. Are you sure?</string>
|
||||||
|
<string name="playtime_reset_successfully">Playtime has been reset</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -104,6 +104,8 @@ add_library(
|
||||||
parent_of_member.h
|
parent_of_member.h
|
||||||
point.h
|
point.h
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
|
play_time_manager.cpp
|
||||||
|
play_time_manager.h
|
||||||
quaternion.h
|
quaternion.h
|
||||||
range_map.h
|
range_map.h
|
||||||
range_mutex.h
|
range_mutex.h
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/hle/service/acc/profile_manager.h"
|
#include "core/hle/service/acc/profile_manager.h"
|
||||||
#include "yuzu/play_time_manager.h"
|
#include "common/play_time_manager.h"
|
||||||
|
|
||||||
namespace PlayTime {
|
namespace PlayTime {
|
||||||
|
|
||||||
|
@ -22,19 +22,13 @@ struct PlayTimeElement {
|
||||||
PlayTime play_time;
|
PlayTime play_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(
|
std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
|
||||||
const Service::Account::ProfileManager& manager) {
|
|
||||||
const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
|
|
||||||
if (!uuid.has_value()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
return Common::FS::GetEdenPath(Common::FS::EdenPath::PlayTimeDir) /
|
return Common::FS::GetEdenPath(Common::FS::EdenPath::PlayTimeDir) /
|
||||||
uuid->RawString().append(".bin");
|
"playtime.bin";
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db,
|
[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
|
||||||
const Service::Account::ProfileManager& manager) {
|
const auto filename = GetCurrentUserPlayTimePath();
|
||||||
const auto filename = GetCurrentUserPlayTimePath(manager);
|
|
||||||
|
|
||||||
if (!filename.has_value()) {
|
if (!filename.has_value()) {
|
||||||
LOG_ERROR(Frontend, "Failed to get current user path");
|
LOG_ERROR(Frontend, "Failed to get current user path");
|
||||||
|
@ -69,9 +63,8 @@ std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db,
|
[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
|
||||||
const Service::Account::ProfileManager& manager) {
|
const auto filename = GetCurrentUserPlayTimePath();
|
||||||
const auto filename = GetCurrentUserPlayTimePath(manager);
|
|
||||||
|
|
||||||
if (!filename.has_value()) {
|
if (!filename.has_value()) {
|
||||||
LOG_ERROR(Frontend, "Failed to get current user path");
|
LOG_ERROR(Frontend, "Failed to get current user path");
|
||||||
|
@ -100,9 +93,9 @@ std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
PlayTimeManager::PlayTimeManager(Service::Account::ProfileManager& profile_manager)
|
PlayTimeManager::PlayTimeManager()
|
||||||
: manager{profile_manager} {
|
: running_program_id() {
|
||||||
if (!ReadPlayTimeFile(database, manager)) {
|
if (!ReadPlayTimeFile(database)) {
|
||||||
LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
|
LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +140,7 @@ void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayTimeManager::Save() {
|
void PlayTimeManager::Save() {
|
||||||
if (!WritePlayTimeFile(database, manager)) {
|
if (!WritePlayTimeFile(database)) {
|
||||||
LOG_ERROR(Frontend, "Failed to update play time database!");
|
LOG_ERROR(Frontend, "Failed to update play time database!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,19 +159,4 @@ void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ReadablePlayTime(qulonglong time_seconds) {
|
|
||||||
if (time_seconds == 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const auto time_minutes = (std::max)(static_cast<double>(time_seconds) / 60, 1.0);
|
|
||||||
const auto time_hours = static_cast<double>(time_seconds) / 3600;
|
|
||||||
const bool is_minutes = time_minutes < 60;
|
|
||||||
const char* unit = is_minutes ? "m" : "h";
|
|
||||||
const auto value = is_minutes ? time_minutes : time_hours;
|
|
||||||
|
|
||||||
return QStringLiteral("%L1 %2")
|
|
||||||
.arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
|
|
||||||
.arg(QString::fromUtf8(unit));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace PlayTime
|
} // namespace PlayTime
|
|
@ -6,8 +6,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
|
@ -27,7 +25,7 @@ using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
|
||||||
|
|
||||||
class PlayTimeManager {
|
class PlayTimeManager {
|
||||||
public:
|
public:
|
||||||
explicit PlayTimeManager(Service::Account::ProfileManager& profile_manager);
|
explicit PlayTimeManager();
|
||||||
~PlayTimeManager();
|
~PlayTimeManager();
|
||||||
|
|
||||||
YUZU_NON_COPYABLE(PlayTimeManager);
|
YUZU_NON_COPYABLE(PlayTimeManager);
|
||||||
|
@ -46,9 +44,7 @@ private:
|
||||||
PlayTimeDatabase database;
|
PlayTimeDatabase database;
|
||||||
u64 running_program_id;
|
u64 running_program_id;
|
||||||
std::jthread play_time_thread;
|
std::jthread play_time_thread;
|
||||||
Service::Account::ProfileManager& manager;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QString ReadablePlayTime(qulonglong time_seconds);
|
|
||||||
|
|
||||||
} // namespace PlayTime
|
} // namespace PlayTime
|
|
@ -198,8 +198,6 @@ add_executable(yuzu
|
||||||
multiplayer/state.cpp
|
multiplayer/state.cpp
|
||||||
multiplayer/state.h
|
multiplayer/state.h
|
||||||
multiplayer/validation.h
|
multiplayer/validation.h
|
||||||
play_time_manager.cpp
|
|
||||||
play_time_manager.h
|
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
startup_checks.cpp
|
startup_checks.cpp
|
||||||
startup_checks.h
|
startup_checks.h
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include "qt_common/uisettings.h"
|
#include "qt_common/uisettings.h"
|
||||||
#include "qt_common/qt_game_util.h"
|
#include "qt_common/qt_game_util.h"
|
||||||
#include "yuzu/compatibility_list.h"
|
#include "yuzu/compatibility_list.h"
|
||||||
#include "yuzu/play_time_manager.h"
|
#include "common/play_time_manager.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "yuzu/play_time_manager.h"
|
#include "common/play_time_manager.h""
|
||||||
#include "qt_common/uisettings.h"
|
#include "qt_common/uisettings.h"
|
||||||
#include "yuzu/util/util.h"
|
#include "yuzu/util/util.h"
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ public:
|
||||||
|
|
||||||
void setData(const QVariant& value, int role) override {
|
void setData(const QVariant& value, int role) override {
|
||||||
qulonglong time_seconds = value.toULongLong();
|
qulonglong time_seconds = value.toULongLong();
|
||||||
GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
|
GameListItem::setData(ReadablePlayTime(time_seconds), Qt::DisplayRole);
|
||||||
GameListItem::setData(value, PlayTimeRole);
|
GameListItem::setData(value, PlayTimeRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#include "core/file_sys/registered_cache.h"
|
#include "core/file_sys/registered_cache.h"
|
||||||
#include "qt_common/uisettings.h"
|
#include "qt_common/uisettings.h"
|
||||||
#include "yuzu/compatibility_list.h"
|
#include "yuzu/compatibility_list.h"
|
||||||
#include "yuzu/play_time_manager.h"
|
#include "common/play_time_manager.h""
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
|
|
|
@ -163,7 +163,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||||
#include "yuzu/install_dialog.h"
|
#include "yuzu/install_dialog.h"
|
||||||
#include "yuzu/loading_screen.h"
|
#include "yuzu/loading_screen.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
#include "yuzu/play_time_manager.h"
|
#include "common/play_time_manager.h"
|
||||||
#include "yuzu/startup_checks.h"
|
#include "yuzu/startup_checks.h"
|
||||||
#include "qt_common/uisettings.h"
|
#include "qt_common/uisettings.h"
|
||||||
#include "yuzu/util/clickable_label.h"
|
#include "yuzu/util/clickable_label.h"
|
||||||
|
@ -447,7 +447,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||||
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
|
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
|
||||||
discord_rpc->Update();
|
discord_rpc->Update();
|
||||||
|
|
||||||
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(QtCommon::system->GetProfileManager());
|
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
|
||||||
|
|
||||||
Network::Init();
|
Network::Init();
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,21 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
|
||||||
return circle_pixmap;
|
return circle_pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ReadableDuration(qulonglong time_seconds) {
|
||||||
|
if (time_seconds == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
|
||||||
|
const auto time_hours = static_cast<double>(time_seconds) / 3600;
|
||||||
|
const bool is_minutes = time_minutes < 60;
|
||||||
|
const char* unit = is_minutes ? "m" : "h";
|
||||||
|
const auto value = is_minutes ? time_minutes : time_hours;
|
||||||
|
|
||||||
|
return QStringLiteral("%L1 %2")
|
||||||
|
.arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
|
||||||
|
.arg(QString::fromUtf8(unit));
|
||||||
|
}
|
||||||
|
|
||||||
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
|
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
|
||||||
#if defined(WIN32)
|
#if defined(WIN32)
|
||||||
#pragma pack(push, 2)
|
#pragma pack(push, 2)
|
||||||
|
|
|
@ -27,3 +27,6 @@
|
||||||
* @return bool If the operation succeeded
|
* @return bool If the operation succeeded
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
|
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
|
||||||
|
|
||||||
|
// Converts a length of time in seconds into a readable format
|
||||||
|
QString ReadableDuration(qulonglong time_seconds);
|
Loading…
Add table
Add a link
Reference in a new issue