diff options
| author | 2023-10-08 17:11:34 -0400 | |
|---|---|---|
| committer | 2023-10-08 17:11:34 -0400 | |
| commit | 85d99f873fefe9c66b44482b24c2139dd6cec247 (patch) | |
| tree | c6ef0151ae64518fc0b5a558a47ec8ca28540e14 /src | |
| parent | Merge pull request #11656 from liamwhite/recreate-surface-automatically (diff) | |
| parent | Address review comments (diff) | |
| download | yuzu-85d99f873fefe9c66b44482b24c2139dd6cec247.tar.gz yuzu-85d99f873fefe9c66b44482b24c2139dd6cec247.tar.xz yuzu-85d99f873fefe9c66b44482b24c2139dd6cec247.zip | |
Merge pull request #10519 from mdmrk/master
yuzu-qt: Track play time
Diffstat (limited to 'src')
| -rw-r--r-- | src/common/fs/fs_paths.h | 1 | ||||
| -rw-r--r-- | src/common/fs/path_util.cpp | 1 | ||||
| -rw-r--r-- | src/common/fs/path_util.h | 1 | ||||
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_ui.cpp | 5 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_ui.ui | 7 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 14 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 7 | ||||
| -rw-r--r-- | src/yuzu/game_list_p.h | 26 | ||||
| -rw-r--r-- | src/yuzu/game_list_worker.cpp | 23 | ||||
| -rw-r--r-- | src/yuzu/game_list_worker.h | 6 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 25 | ||||
| -rw-r--r-- | src/yuzu/main.h | 7 | ||||
| -rw-r--r-- | src/yuzu/play_time_manager.cpp | 179 | ||||
| -rw-r--r-- | src/yuzu/play_time_manager.h | 44 | ||||
| -rw-r--r-- | src/yuzu/uisettings.h | 3 |
16 files changed, 337 insertions, 14 deletions
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index 61bac9eba..59d66f71e 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #define LOAD_DIR "load" | 18 | #define LOAD_DIR "load" |
| 19 | #define LOG_DIR "log" | 19 | #define LOG_DIR "log" |
| 20 | #define NAND_DIR "nand" | 20 | #define NAND_DIR "nand" |
| 21 | #define PLAY_TIME_DIR "play_time" | ||
| 21 | #define SCREENSHOTS_DIR "screenshots" | 22 | #define SCREENSHOTS_DIR "screenshots" |
| 22 | #define SDMC_DIR "sdmc" | 23 | #define SDMC_DIR "sdmc" |
| 23 | #define SHADER_DIR "shader" | 24 | #define SHADER_DIR "shader" |
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index dce219fcf..84ed0ad10 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp | |||
| @@ -124,6 +124,7 @@ public: | |||
| 124 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); | 124 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); |
| 125 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); | 125 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); |
| 126 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); | 126 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); |
| 127 | GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR); | ||
| 127 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); | 128 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); |
| 128 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); | 129 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); |
| 129 | GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); | 130 | GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); |
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index ba28964d0..289974e32 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h | |||
| @@ -20,6 +20,7 @@ enum class YuzuPath { | |||
| 20 | LoadDir, // Where cheat/mod files are stored. | 20 | LoadDir, // Where cheat/mod files are stored. |
| 21 | LogDir, // Where log files are stored. | 21 | LogDir, // Where log files are stored. |
| 22 | NANDDir, // Where the emulated NAND is stored. | 22 | NANDDir, // Where the emulated NAND is stored. |
| 23 | PlayTimeDir, // Where play time data is stored. | ||
| 23 | ScreenshotsDir, // Where yuzu screenshots are stored. | 24 | ScreenshotsDir, // Where yuzu screenshots are stored. |
| 24 | SDMCDir, // Where the emulated SDMC is stored. | 25 | SDMCDir, // Where the emulated SDMC is stored. |
| 25 | ShaderDir, // Where shaders are stored. | 26 | ShaderDir, // Where shaders are stored. |
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 8f86a1553..9ebece907 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -195,6 +195,8 @@ add_executable(yuzu | |||
| 195 | multiplayer/state.cpp | 195 | multiplayer/state.cpp |
| 196 | multiplayer/state.h | 196 | multiplayer/state.h |
| 197 | multiplayer/validation.h | 197 | multiplayer/validation.h |
| 198 | play_time_manager.cpp | ||
| 199 | play_time_manager.h | ||
| 198 | precompiled_headers.h | 200 | precompiled_headers.h |
| 199 | qt_common.cpp | 201 | qt_common.cpp |
| 200 | qt_common.h | 202 | qt_common.h |
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index a9fde9f4f..82f3b6e78 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp | |||
| @@ -123,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) | |||
| 123 | connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 123 | connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 124 | connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 124 | connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 125 | connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 125 | connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 126 | connect(ui->show_play_time, &QCheckBox::stateChanged, this, | ||
| 127 | &ConfigureUi::RequestGameListUpdate); | ||
| 126 | connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, | 128 | connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, |
| 127 | &ConfigureUi::RequestGameListUpdate); | 129 | &ConfigureUi::RequestGameListUpdate); |
| 128 | connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), | 130 | connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), |
| @@ -167,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 167 | UISettings::values.show_compat = ui->show_compat->isChecked(); | 169 | UISettings::values.show_compat = ui->show_compat->isChecked(); |
| 168 | UISettings::values.show_size = ui->show_size->isChecked(); | 170 | UISettings::values.show_size = ui->show_size->isChecked(); |
| 169 | UISettings::values.show_types = ui->show_types->isChecked(); | 171 | UISettings::values.show_types = ui->show_types->isChecked(); |
| 172 | UISettings::values.show_play_time = ui->show_play_time->isChecked(); | ||
| 170 | UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); | 173 | UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); |
| 171 | UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); | 174 | UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); |
| 172 | UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); | 175 | UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); |
| @@ -179,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 179 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); | 182 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); |
| 180 | UISettings::values.screenshot_height.SetValue(height); | 183 | UISettings::values.screenshot_height.SetValue(height); |
| 181 | 184 | ||
| 185 | RequestGameListUpdate(); | ||
| 182 | system.ApplySettings(); | 186 | system.ApplySettings(); |
| 183 | } | 187 | } |
| 184 | 188 | ||
| @@ -194,6 +198,7 @@ void ConfigureUi::SetConfiguration() { | |||
| 194 | ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); | 198 | ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); |
| 195 | ui->show_size->setChecked(UISettings::values.show_size.GetValue()); | 199 | ui->show_size->setChecked(UISettings::values.show_size.GetValue()); |
| 196 | ui->show_types->setChecked(UISettings::values.show_types.GetValue()); | 200 | ui->show_types->setChecked(UISettings::values.show_types.GetValue()); |
| 201 | ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue()); | ||
| 197 | ui->game_icon_size_combobox->setCurrentIndex( | 202 | ui->game_icon_size_combobox->setCurrentIndex( |
| 198 | ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); | 203 | ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); |
| 199 | ui->folder_icon_size_combobox->setCurrentIndex( | 204 | ui->folder_icon_size_combobox->setCurrentIndex( |
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index cb66ef104..b8e648381 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui | |||
| @@ -105,6 +105,13 @@ | |||
| 105 | </widget> | 105 | </widget> |
| 106 | </item> | 106 | </item> |
| 107 | <item> | 107 | <item> |
| 108 | <widget class="QCheckBox" name="show_play_time"> | ||
| 109 | <property name="text"> | ||
| 110 | <string>Show Play Time Column</string> | ||
| 111 | </property> | ||
| 112 | </widget> | ||
| 113 | </item> | ||
| 114 | <item> | ||
| 108 | <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> | 115 | <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> |
| 109 | <item> | 116 | <item> |
| 110 | <widget class="QLabel" name="game_icon_size_label"> | 117 | <widget class="QLabel" name="game_icon_size_label"> |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index f254c1e1c..3f04fcb66 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() { | |||
| 312 | } | 312 | } |
| 313 | 313 | ||
| 314 | GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, | 314 | GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, |
| 315 | Core::System& system_, GMainWindow* parent) | 315 | PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, |
| 316 | : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { | 316 | GMainWindow* parent) |
| 317 | : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, | ||
| 318 | play_time_manager{play_time_manager_}, system{system_} { | ||
| 317 | watcher = new QFileSystemWatcher(this); | 319 | watcher = new QFileSystemWatcher(this); |
| 318 | connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); | 320 | connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); |
| 319 | 321 | ||
| @@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid | |||
| 340 | 342 | ||
| 341 | tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); | 343 | tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); |
| 342 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); | 344 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); |
| 345 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | ||
| 343 | item_model->setSortRole(GameListItemPath::SortRole); | 346 | item_model->setSortRole(GameListItemPath::SortRole); |
| 344 | 347 | ||
| 345 | connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); | 348 | connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); |
| @@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 548 | QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); | 551 | QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); |
| 549 | QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); | 552 | QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); |
| 550 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); | 553 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); |
| 554 | QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data")); | ||
| 551 | QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); | 555 | QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); |
| 552 | QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); | 556 | QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); |
| 553 | QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); | 557 | QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); |
| @@ -622,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 622 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { | 626 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { |
| 623 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); | 627 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); |
| 624 | }); | 628 | }); |
| 629 | connect(remove_play_time_data, &QAction::triggered, | ||
| 630 | [this, program_id]() { emit RemovePlayTimeRequested(program_id); }); | ||
| 625 | connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { | 631 | connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { |
| 626 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); | 632 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); |
| 627 | }); | 633 | }); |
| @@ -790,6 +796,7 @@ void GameList::RetranslateUI() { | |||
| 790 | item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); | 796 | item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); |
| 791 | item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); | 797 | item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); |
| 792 | item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); | 798 | item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); |
| 799 | item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time")); | ||
| 793 | } | 800 | } |
| 794 | 801 | ||
| 795 | void GameListSearchField::changeEvent(QEvent* event) { | 802 | void GameListSearchField::changeEvent(QEvent* event) { |
| @@ -817,6 +824,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 817 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); | 824 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); |
| 818 | tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); | 825 | tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); |
| 819 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); | 826 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); |
| 827 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | ||
| 820 | 828 | ||
| 821 | // Delete any rows that might already exist if we're repopulating | 829 | // Delete any rows that might already exist if we're repopulating |
| 822 | item_model->removeRows(0, item_model->rowCount()); | 830 | item_model->removeRows(0, item_model->rowCount()); |
| @@ -825,7 +833,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 825 | emit ShouldCancelWorker(); | 833 | emit ShouldCancelWorker(); |
| 826 | 834 | ||
| 827 | GameListWorker* worker = | 835 | GameListWorker* worker = |
| 828 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); | 836 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); |
| 829 | 837 | ||
| 830 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); | 838 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); |
| 831 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, | 839 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 1fcbbf0ba..712570cea 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #include "core/core.h" | 18 | #include "core/core.h" |
| 19 | #include "uisettings.h" | 19 | #include "uisettings.h" |
| 20 | #include "yuzu/compatibility_list.h" | 20 | #include "yuzu/compatibility_list.h" |
| 21 | #include "yuzu/play_time_manager.h" | ||
| 21 | 22 | ||
| 22 | namespace Core { | 23 | namespace Core { |
| 23 | class System; | 24 | class System; |
| @@ -75,11 +76,13 @@ public: | |||
| 75 | COLUMN_ADD_ONS, | 76 | COLUMN_ADD_ONS, |
| 76 | COLUMN_FILE_TYPE, | 77 | COLUMN_FILE_TYPE, |
| 77 | COLUMN_SIZE, | 78 | COLUMN_SIZE, |
| 79 | COLUMN_PLAY_TIME, | ||
| 78 | COLUMN_COUNT, // Number of columns | 80 | COLUMN_COUNT, // Number of columns |
| 79 | }; | 81 | }; |
| 80 | 82 | ||
| 81 | explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, | 83 | explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, |
| 82 | FileSys::ManualContentProvider* provider_, Core::System& system_, | 84 | FileSys::ManualContentProvider* provider_, |
| 85 | PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, | ||
| 83 | GMainWindow* parent = nullptr); | 86 | GMainWindow* parent = nullptr); |
| 84 | ~GameList() override; | 87 | ~GameList() override; |
| 85 | 88 | ||
| @@ -113,6 +116,7 @@ signals: | |||
| 113 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); | 116 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); |
| 114 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, | 117 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, |
| 115 | const std::string& game_path); | 118 | const std::string& game_path); |
| 119 | void RemovePlayTimeRequested(u64 program_id); | ||
| 116 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 120 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 117 | void VerifyIntegrityRequested(const std::string& game_path); | 121 | void VerifyIntegrityRequested(const std::string& game_path); |
| 118 | void CopyTIDRequested(u64 program_id); | 122 | void CopyTIDRequested(u64 program_id); |
| @@ -168,6 +172,7 @@ private: | |||
| 168 | 172 | ||
| 169 | friend class GameListSearchField; | 173 | friend class GameListSearchField; |
| 170 | 174 | ||
| 175 | const PlayTime::PlayTimeManager& play_time_manager; | ||
| 171 | Core::System& system; | 176 | Core::System& system; |
| 172 | }; | 177 | }; |
| 173 | 178 | ||
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 1800f090f..86a0c41d9 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #include "common/common_types.h" | 18 | #include "common/common_types.h" |
| 19 | #include "common/logging/log.h" | 19 | #include "common/logging/log.h" |
| 20 | #include "common/string_util.h" | 20 | #include "common/string_util.h" |
| 21 | #include "yuzu/play_time_manager.h" | ||
| 21 | #include "yuzu/uisettings.h" | 22 | #include "yuzu/uisettings.h" |
| 22 | #include "yuzu/util/util.h" | 23 | #include "yuzu/util/util.h" |
| 23 | 24 | ||
| @@ -221,6 +222,31 @@ public: | |||
| 221 | } | 222 | } |
| 222 | }; | 223 | }; |
| 223 | 224 | ||
| 225 | /** | ||
| 226 | * GameListItem for Play Time values. | ||
| 227 | * This object stores the play time of a game in seconds, and its readable | ||
| 228 | * representation in minutes/hours | ||
| 229 | */ | ||
| 230 | class GameListItemPlayTime : public GameListItem { | ||
| 231 | public: | ||
| 232 | static constexpr int PlayTimeRole = SortRole; | ||
| 233 | |||
| 234 | GameListItemPlayTime() = default; | ||
| 235 | explicit GameListItemPlayTime(const qulonglong time_seconds) { | ||
| 236 | setData(time_seconds, PlayTimeRole); | ||
| 237 | } | ||
| 238 | |||
| 239 | void setData(const QVariant& value, int role) override { | ||
| 240 | qulonglong time_seconds = value.toULongLong(); | ||
| 241 | GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole); | ||
| 242 | GameListItem::setData(value, PlayTimeRole); | ||
| 243 | } | ||
| 244 | |||
| 245 | bool operator<(const QStandardItem& other) const override { | ||
| 246 | return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong(); | ||
| 247 | } | ||
| 248 | }; | ||
| 249 | |||
| 224 | class GameListDir : public GameListItem { | 250 | class GameListDir : public GameListItem { |
| 225 | public: | 251 | public: |
| 226 | static constexpr int GameDirRole = Qt::UserRole + 2; | 252 | static constexpr int GameDirRole = Qt::UserRole + 2; |
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index e7fb8a282..588f1dd6e 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -194,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 194 | const std::size_t size, const std::vector<u8>& icon, | 194 | const std::size_t size, const std::vector<u8>& icon, |
| 195 | Loader::AppLoader& loader, u64 program_id, | 195 | Loader::AppLoader& loader, u64 program_id, |
| 196 | const CompatibilityList& compatibility_list, | 196 | const CompatibilityList& compatibility_list, |
| 197 | const PlayTime::PlayTimeManager& play_time_manager, | ||
| 197 | const FileSys::PatchManager& patch) { | 198 | const FileSys::PatchManager& patch) { |
| 198 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | 199 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); |
| 199 | 200 | ||
| @@ -212,6 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 212 | new GameListItemCompat(compatibility), | 213 | new GameListItemCompat(compatibility), |
| 213 | new GameListItem(file_type_string), | 214 | new GameListItem(file_type_string), |
| 214 | new GameListItemSize(size), | 215 | new GameListItemSize(size), |
| 216 | new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), | ||
| 215 | }; | 217 | }; |
| 216 | 218 | ||
| 217 | const auto patch_versions = GetGameListCachedObject( | 219 | const auto patch_versions = GetGameListCachedObject( |
| @@ -227,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 227 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, | 229 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, |
| 228 | FileSys::ManualContentProvider* provider_, | 230 | FileSys::ManualContentProvider* provider_, |
| 229 | QVector<UISettings::GameDir>& game_dirs_, | 231 | QVector<UISettings::GameDir>& game_dirs_, |
| 230 | const CompatibilityList& compatibility_list_, Core::System& system_) | 232 | const CompatibilityList& compatibility_list_, |
| 233 | const PlayTime::PlayTimeManager& play_time_manager_, | ||
| 234 | Core::System& system_) | ||
| 231 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, | 235 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, |
| 232 | compatibility_list{compatibility_list_}, system{system_} {} | 236 | compatibility_list{compatibility_list_}, |
| 237 | play_time_manager{play_time_manager_}, system{system_} {} | ||
| 233 | 238 | ||
| 234 | GameListWorker::~GameListWorker() = default; | 239 | GameListWorker::~GameListWorker() = default; |
| 235 | 240 | ||
| @@ -280,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 280 | } | 285 | } |
| 281 | 286 | ||
| 282 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, | 287 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, |
| 283 | program_id, compatibility_list, patch), | 288 | program_id, compatibility_list, play_time_manager, patch), |
| 284 | parent_dir); | 289 | parent_dir); |
| 285 | } | 290 | } |
| 286 | } | 291 | } |
| @@ -357,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 357 | 362 | ||
| 358 | emit EntryReady(MakeGameListEntry(physical_name, name, | 363 | emit EntryReady(MakeGameListEntry(physical_name, name, |
| 359 | Common::FS::GetSize(physical_name), icon, | 364 | Common::FS::GetSize(physical_name), icon, |
| 360 | *loader, id, compatibility_list, patch), | 365 | *loader, id, compatibility_list, |
| 366 | play_time_manager, patch), | ||
| 361 | parent_dir); | 367 | parent_dir); |
| 362 | } | 368 | } |
| 363 | } else { | 369 | } else { |
| @@ -370,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 370 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), | 376 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), |
| 371 | system.GetContentProvider()}; | 377 | system.GetContentProvider()}; |
| 372 | 378 | ||
| 373 | emit EntryReady( | 379 | emit EntryReady(MakeGameListEntry(physical_name, name, |
| 374 | MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), | 380 | Common::FS::GetSize(physical_name), icon, |
| 375 | icon, *loader, program_id, compatibility_list, patch), | 381 | *loader, program_id, compatibility_list, |
| 376 | parent_dir); | 382 | play_time_manager, patch), |
| 383 | parent_dir); | ||
| 377 | } | 384 | } |
| 378 | } | 385 | } |
| 379 | } else if (is_dir) { | 386 | } else if (is_dir) { |
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 24a4e92c3..2bb0a0cb6 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include <QString> | 13 | #include <QString> |
| 14 | 14 | ||
| 15 | #include "yuzu/compatibility_list.h" | 15 | #include "yuzu/compatibility_list.h" |
| 16 | #include "yuzu/play_time_manager.h" | ||
| 16 | 17 | ||
| 17 | namespace Core { | 18 | namespace Core { |
| 18 | class System; | 19 | class System; |
| @@ -36,7 +37,9 @@ public: | |||
| 36 | explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, | 37 | explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, |
| 37 | FileSys::ManualContentProvider* provider_, | 38 | FileSys::ManualContentProvider* provider_, |
| 38 | QVector<UISettings::GameDir>& game_dirs_, | 39 | QVector<UISettings::GameDir>& game_dirs_, |
| 39 | const CompatibilityList& compatibility_list_, Core::System& system_); | 40 | const CompatibilityList& compatibility_list_, |
| 41 | const PlayTime::PlayTimeManager& play_time_manager_, | ||
| 42 | Core::System& system_); | ||
| 40 | ~GameListWorker() override; | 43 | ~GameListWorker() override; |
| 41 | 44 | ||
| 42 | /// Starts the processing of directory tree information. | 45 | /// Starts the processing of directory tree information. |
| @@ -76,6 +79,7 @@ private: | |||
| 76 | FileSys::ManualContentProvider* provider; | 79 | FileSys::ManualContentProvider* provider; |
| 77 | QVector<UISettings::GameDir>& game_dirs; | 80 | QVector<UISettings::GameDir>& game_dirs; |
| 78 | const CompatibilityList& compatibility_list; | 81 | const CompatibilityList& compatibility_list; |
| 82 | const PlayTime::PlayTimeManager& play_time_manager; | ||
| 79 | 83 | ||
| 80 | QStringList watch_list; | 84 | QStringList watch_list; |
| 81 | std::atomic_bool stop_processing; | 85 | std::atomic_bool stop_processing; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 1753fec12..e0bfa7185 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -150,6 +150,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 150 | #include "yuzu/install_dialog.h" | 150 | #include "yuzu/install_dialog.h" |
| 151 | #include "yuzu/loading_screen.h" | 151 | #include "yuzu/loading_screen.h" |
| 152 | #include "yuzu/main.h" | 152 | #include "yuzu/main.h" |
| 153 | #include "yuzu/play_time_manager.h" | ||
| 153 | #include "yuzu/startup_checks.h" | 154 | #include "yuzu/startup_checks.h" |
| 154 | #include "yuzu/uisettings.h" | 155 | #include "yuzu/uisettings.h" |
| 155 | #include "yuzu/util/clickable_label.h" | 156 | #include "yuzu/util/clickable_label.h" |
| @@ -338,6 +339,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan | |||
| 338 | SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); | 339 | SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); |
| 339 | discord_rpc->Update(); | 340 | discord_rpc->Update(); |
| 340 | 341 | ||
| 342 | play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(); | ||
| 343 | |||
| 341 | system->GetRoomNetwork().Init(); | 344 | system->GetRoomNetwork().Init(); |
| 342 | 345 | ||
| 343 | RegisterMetaTypes(); | 346 | RegisterMetaTypes(); |
| @@ -986,7 +989,7 @@ void GMainWindow::InitializeWidgets() { | |||
| 986 | render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); | 989 | render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); |
| 987 | render_window->hide(); | 990 | render_window->hide(); |
| 988 | 991 | ||
| 989 | game_list = new GameList(vfs, provider.get(), *system, this); | 992 | game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this); |
| 990 | ui->horizontalLayout->addWidget(game_list); | 993 | ui->horizontalLayout->addWidget(game_list); |
| 991 | 994 | ||
| 992 | game_list_placeholder = new GameListPlaceholder(this); | 995 | game_list_placeholder = new GameListPlaceholder(this); |
| @@ -1461,6 +1464,8 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 1461 | connect(game_list, &GameList::RemoveInstalledEntryRequested, this, | 1464 | connect(game_list, &GameList::RemoveInstalledEntryRequested, this, |
| 1462 | &GMainWindow::OnGameListRemoveInstalledEntry); | 1465 | &GMainWindow::OnGameListRemoveInstalledEntry); |
| 1463 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); | 1466 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); |
| 1467 | connect(game_list, &GameList::RemovePlayTimeRequested, this, | ||
| 1468 | &GMainWindow::OnGameListRemovePlayTimeData); | ||
| 1464 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | 1469 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); |
| 1465 | connect(game_list, &GameList::VerifyIntegrityRequested, this, | 1470 | connect(game_list, &GameList::VerifyIntegrityRequested, this, |
| 1466 | &GMainWindow::OnGameListVerifyIntegrity); | 1471 | &GMainWindow::OnGameListVerifyIntegrity); |
| @@ -2535,6 +2540,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ | |||
| 2535 | } | 2540 | } |
| 2536 | } | 2541 | } |
| 2537 | 2542 | ||
| 2543 | void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) { | ||
| 2544 | if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"), | ||
| 2545 | QMessageBox::Yes | QMessageBox::No, | ||
| 2546 | QMessageBox::No) != QMessageBox::Yes) { | ||
| 2547 | return; | ||
| 2548 | } | ||
| 2549 | |||
| 2550 | play_time_manager->ResetProgramPlayTime(program_id); | ||
| 2551 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 2552 | } | ||
| 2553 | |||
| 2538 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { | 2554 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { |
| 2539 | const auto target_file_name = [target] { | 2555 | const auto target_file_name = [target] { |
| 2540 | switch (target) { | 2556 | switch (target) { |
| @@ -3358,6 +3374,9 @@ void GMainWindow::OnStartGame() { | |||
| 3358 | UpdateMenuState(); | 3374 | UpdateMenuState(); |
| 3359 | OnTasStateChanged(); | 3375 | OnTasStateChanged(); |
| 3360 | 3376 | ||
| 3377 | play_time_manager->SetProgramId(system->GetApplicationProcessProgramID()); | ||
| 3378 | play_time_manager->Start(); | ||
| 3379 | |||
| 3361 | discord_rpc->Update(); | 3380 | discord_rpc->Update(); |
| 3362 | } | 3381 | } |
| 3363 | 3382 | ||
| @@ -3373,6 +3392,7 @@ void GMainWindow::OnRestartGame() { | |||
| 3373 | 3392 | ||
| 3374 | void GMainWindow::OnPauseGame() { | 3393 | void GMainWindow::OnPauseGame() { |
| 3375 | emu_thread->SetRunning(false); | 3394 | emu_thread->SetRunning(false); |
| 3395 | play_time_manager->Stop(); | ||
| 3376 | UpdateMenuState(); | 3396 | UpdateMenuState(); |
| 3377 | AllowOSSleep(); | 3397 | AllowOSSleep(); |
| 3378 | } | 3398 | } |
| @@ -3393,6 +3413,9 @@ void GMainWindow::OnStopGame() { | |||
| 3393 | return; | 3413 | return; |
| 3394 | } | 3414 | } |
| 3395 | 3415 | ||
| 3416 | play_time_manager->Stop(); | ||
| 3417 | // Update game list to show new play time | ||
| 3418 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 3396 | if (OnShutdownBegin()) { | 3419 | if (OnShutdownBegin()) { |
| 3397 | OnShutdownBeginDialog(); | 3420 | OnShutdownBeginDialog(); |
| 3398 | } else { | 3421 | } else { |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 52028234c..c1872ecd4 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -81,6 +81,10 @@ namespace DiscordRPC { | |||
| 81 | class DiscordInterface; | 81 | class DiscordInterface; |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | namespace PlayTime { | ||
| 85 | class PlayTimeManager; | ||
| 86 | } | ||
| 87 | |||
| 84 | namespace FileSys { | 88 | namespace FileSys { |
| 85 | class ContentProvider; | 89 | class ContentProvider; |
| 86 | class ManualContentProvider; | 90 | class ManualContentProvider; |
| @@ -323,6 +327,7 @@ private slots: | |||
| 323 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); | 327 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); |
| 324 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 328 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 325 | const std::string& game_path); | 329 | const std::string& game_path); |
| 330 | void OnGameListRemovePlayTimeData(u64 program_id); | ||
| 326 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 331 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 327 | void OnGameListVerifyIntegrity(const std::string& game_path); | 332 | void OnGameListVerifyIntegrity(const std::string& game_path); |
| 328 | void OnGameListCopyTID(u64 program_id); | 333 | void OnGameListCopyTID(u64 program_id); |
| @@ -389,6 +394,7 @@ private: | |||
| 389 | void RemoveVulkanDriverPipelineCache(u64 program_id); | 394 | void RemoveVulkanDriverPipelineCache(u64 program_id); |
| 390 | void RemoveAllTransferableShaderCaches(u64 program_id); | 395 | void RemoveAllTransferableShaderCaches(u64 program_id); |
| 391 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); | 396 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); |
| 397 | void RemovePlayTimeData(u64 program_id); | ||
| 392 | void RemoveCacheStorage(u64 program_id); | 398 | void RemoveCacheStorage(u64 program_id); |
| 393 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, | 399 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, |
| 394 | u64* selected_title_id, u8* selected_content_record_type); | 400 | u64* selected_title_id, u8* selected_content_record_type); |
| @@ -428,6 +434,7 @@ private: | |||
| 428 | 434 | ||
| 429 | std::unique_ptr<Core::System> system; | 435 | std::unique_ptr<Core::System> system; |
| 430 | std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; | 436 | std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; |
| 437 | std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager; | ||
| 431 | std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; | 438 | std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; |
| 432 | 439 | ||
| 433 | MultiplayerState* multiplayer_state = nullptr; | 440 | MultiplayerState* multiplayer_state = nullptr; |
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp new file mode 100644 index 000000000..155c36b7d --- /dev/null +++ b/src/yuzu/play_time_manager.cpp | |||
| @@ -0,0 +1,179 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "common/thread.h" | ||
| 11 | #include "core/hle/service/acc/profile_manager.h" | ||
| 12 | #include "yuzu/play_time_manager.h" | ||
| 13 | |||
| 14 | namespace PlayTime { | ||
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | struct PlayTimeElement { | ||
| 19 | ProgramId program_id; | ||
| 20 | PlayTime play_time; | ||
| 21 | }; | ||
| 22 | |||
| 23 | std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() { | ||
| 24 | const Service::Account::ProfileManager manager; | ||
| 25 | const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user)); | ||
| 26 | if (!uuid.has_value()) { | ||
| 27 | return std::nullopt; | ||
| 28 | } | ||
| 29 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) / | ||
| 30 | uuid->RawString().append(".bin"); | ||
| 31 | } | ||
| 32 | |||
| 33 | [[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) { | ||
| 34 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 35 | |||
| 36 | if (!filename.has_value()) { | ||
| 37 | LOG_ERROR(Frontend, "Failed to get current user path"); | ||
| 38 | return false; | ||
| 39 | } | ||
| 40 | |||
| 41 | out_play_time_db.clear(); | ||
| 42 | |||
| 43 | if (Common::FS::Exists(filename.value())) { | ||
| 44 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read, | ||
| 45 | Common::FS::FileType::BinaryFile}; | ||
| 46 | if (!file.IsOpen()) { | ||
| 47 | LOG_ERROR(Frontend, "Failed to open play time file: {}", | ||
| 48 | Common::FS::PathToUTF8String(filename.value())); | ||
| 49 | return false; | ||
| 50 | } | ||
| 51 | |||
| 52 | const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement); | ||
| 53 | std::vector<PlayTimeElement> elements(num_elements); | ||
| 54 | |||
| 55 | if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) { | ||
| 56 | return false; | ||
| 57 | } | ||
| 58 | |||
| 59 | for (const auto& [program_id, play_time] : elements) { | ||
| 60 | if (program_id != 0) { | ||
| 61 | out_play_time_db[program_id] = play_time; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | return true; | ||
| 67 | } | ||
| 68 | |||
| 69 | [[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) { | ||
| 70 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 71 | |||
| 72 | if (!filename.has_value()) { | ||
| 73 | LOG_ERROR(Frontend, "Failed to get current user path"); | ||
| 74 | return false; | ||
| 75 | } | ||
| 76 | |||
| 77 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write, | ||
| 78 | Common::FS::FileType::BinaryFile}; | ||
| 79 | if (!file.IsOpen()) { | ||
| 80 | LOG_ERROR(Frontend, "Failed to open play time file: {}", | ||
| 81 | Common::FS::PathToUTF8String(filename.value())); | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | std::vector<PlayTimeElement> elements; | ||
| 86 | elements.reserve(play_time_db.size()); | ||
| 87 | |||
| 88 | for (auto& [program_id, play_time] : play_time_db) { | ||
| 89 | if (program_id != 0) { | ||
| 90 | elements.push_back(PlayTimeElement{program_id, play_time}); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | return file.WriteSpan<PlayTimeElement>(elements) == elements.size(); | ||
| 95 | } | ||
| 96 | |||
| 97 | } // namespace | ||
| 98 | |||
| 99 | PlayTimeManager::PlayTimeManager() { | ||
| 100 | if (!ReadPlayTimeFile(database)) { | ||
| 101 | LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default."); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | PlayTimeManager::~PlayTimeManager() { | ||
| 106 | Save(); | ||
| 107 | } | ||
| 108 | |||
| 109 | void PlayTimeManager::SetProgramId(u64 program_id) { | ||
| 110 | running_program_id = program_id; | ||
| 111 | } | ||
| 112 | |||
| 113 | void PlayTimeManager::Start() { | ||
| 114 | play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); }); | ||
| 115 | } | ||
| 116 | |||
| 117 | void PlayTimeManager::Stop() { | ||
| 118 | play_time_thread = {}; | ||
| 119 | } | ||
| 120 | |||
| 121 | void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) { | ||
| 122 | Common::SetCurrentThreadName("PlayTimeReport"); | ||
| 123 | |||
| 124 | using namespace std::literals::chrono_literals; | ||
| 125 | using std::chrono::seconds; | ||
| 126 | using std::chrono::steady_clock; | ||
| 127 | |||
| 128 | auto timestamp = steady_clock::now(); | ||
| 129 | |||
| 130 | const auto GetDuration = [&]() -> u64 { | ||
| 131 | const auto last_timestamp = std::exchange(timestamp, steady_clock::now()); | ||
| 132 | const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp); | ||
| 133 | return static_cast<u64>(duration.count()); | ||
| 134 | }; | ||
| 135 | |||
| 136 | while (!stop_token.stop_requested()) { | ||
| 137 | Common::StoppableTimedWait(stop_token, 30s); | ||
| 138 | |||
| 139 | database[running_program_id] += GetDuration(); | ||
| 140 | Save(); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | void PlayTimeManager::Save() { | ||
| 145 | if (!WritePlayTimeFile(database)) { | ||
| 146 | LOG_ERROR(Frontend, "Failed to update play time database!"); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | u64 PlayTimeManager::GetPlayTime(u64 program_id) const { | ||
| 151 | auto it = database.find(program_id); | ||
| 152 | if (it != database.end()) { | ||
| 153 | return it->second; | ||
| 154 | } else { | ||
| 155 | return 0; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | void PlayTimeManager::ResetProgramPlayTime(u64 program_id) { | ||
| 160 | database.erase(program_id); | ||
| 161 | Save(); | ||
| 162 | } | ||
| 163 | |||
| 164 | QString ReadablePlayTime(qulonglong time_seconds) { | ||
| 165 | if (time_seconds == 0) { | ||
| 166 | return {}; | ||
| 167 | } | ||
| 168 | const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0); | ||
| 169 | const auto time_hours = static_cast<double>(time_seconds) / 3600; | ||
| 170 | const bool is_minutes = time_minutes < 60; | ||
| 171 | const char* unit = is_minutes ? "m" : "h"; | ||
| 172 | const auto value = is_minutes ? time_minutes : time_hours; | ||
| 173 | |||
| 174 | return QStringLiteral("%L1 %2") | ||
| 175 | .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0) | ||
| 176 | .arg(QString::fromUtf8(unit)); | ||
| 177 | } | ||
| 178 | |||
| 179 | } // namespace PlayTime | ||
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h new file mode 100644 index 000000000..5f96f3447 --- /dev/null +++ b/src/yuzu/play_time_manager.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <QString> | ||
| 7 | |||
| 8 | #include <map> | ||
| 9 | |||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/polyfill_thread.h" | ||
| 13 | |||
| 14 | namespace PlayTime { | ||
| 15 | |||
| 16 | using ProgramId = u64; | ||
| 17 | using PlayTime = u64; | ||
| 18 | using PlayTimeDatabase = std::map<ProgramId, PlayTime>; | ||
| 19 | |||
| 20 | class PlayTimeManager { | ||
| 21 | public: | ||
| 22 | explicit PlayTimeManager(); | ||
| 23 | ~PlayTimeManager(); | ||
| 24 | |||
| 25 | YUZU_NON_COPYABLE(PlayTimeManager); | ||
| 26 | YUZU_NON_MOVEABLE(PlayTimeManager); | ||
| 27 | |||
| 28 | u64 GetPlayTime(u64 program_id) const; | ||
| 29 | void ResetProgramPlayTime(u64 program_id); | ||
| 30 | void SetProgramId(u64 program_id); | ||
| 31 | void Start(); | ||
| 32 | void Stop(); | ||
| 33 | |||
| 34 | private: | ||
| 35 | PlayTimeDatabase database; | ||
| 36 | u64 running_program_id; | ||
| 37 | std::jthread play_time_thread; | ||
| 38 | void AutoTimestamp(std::stop_token stop_token); | ||
| 39 | void Save(); | ||
| 40 | }; | ||
| 41 | |||
| 42 | QString ReadablePlayTime(qulonglong time_seconds); | ||
| 43 | |||
| 44 | } // namespace PlayTime | ||
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 8a2caa9dd..975008159 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -183,6 +183,9 @@ struct Values { | |||
| 183 | Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; | 183 | Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; |
| 184 | Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; | 184 | Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; |
| 185 | 185 | ||
| 186 | // Play time | ||
| 187 | Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList}; | ||
| 188 | |||
| 186 | bool configuration_applied; | 189 | bool configuration_applied; |
| 187 | bool reset_to_defaults; | 190 | bool reset_to_defaults; |
| 188 | bool shortcut_already_warned{false}; | 191 | bool shortcut_already_warned{false}; |