diff options
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 | 6 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 2 | ||||
| -rw-r--r-- | src/yuzu/game_list_p.h | 26 | ||||
| -rw-r--r-- | src/yuzu/game_list_worker.cpp | 2 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 26 | ||||
| -rw-r--r-- | src/yuzu/main.h | 7 | ||||
| -rw-r--r-- | src/yuzu/play_time.cpp | 177 | ||||
| -rw-r--r-- | src/yuzu/play_time.h | 68 | ||||
| -rw-r--r-- | src/yuzu/uisettings.h | 3 |
15 files changed, 334 insertions, 0 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 d71cfacc6..3bbe9ff87 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp | |||
| @@ -122,6 +122,7 @@ public: | |||
| 122 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); | 122 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); |
| 123 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); | 123 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); |
| 124 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); | 124 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); |
| 125 | GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR); | ||
| 125 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); | 126 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); |
| 126 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); | 127 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); |
| 127 | GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); | 128 | 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..89763f64f 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.cpp | ||
| 199 | play_time.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 34ab01617..058bd9f4d 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp | |||
| @@ -126,6 +126,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) | |||
| 126 | connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 126 | connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 127 | connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 127 | connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 128 | connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 128 | connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 129 | connect(ui->show_play_time, &QCheckBox::stateChanged, this, | ||
| 130 | &ConfigureUi::RequestGameListUpdate); | ||
| 129 | connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, | 131 | connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, |
| 130 | &ConfigureUi::RequestGameListUpdate); | 132 | &ConfigureUi::RequestGameListUpdate); |
| 131 | connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), | 133 | connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), |
| @@ -170,6 +172,7 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 170 | UISettings::values.show_compat = ui->show_compat->isChecked(); | 172 | UISettings::values.show_compat = ui->show_compat->isChecked(); |
| 171 | UISettings::values.show_size = ui->show_size->isChecked(); | 173 | UISettings::values.show_size = ui->show_size->isChecked(); |
| 172 | UISettings::values.show_types = ui->show_types->isChecked(); | 174 | UISettings::values.show_types = ui->show_types->isChecked(); |
| 175 | UISettings::values.show_play_time = ui->show_play_time->isChecked(); | ||
| 173 | UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); | 176 | UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); |
| 174 | UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); | 177 | UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); |
| 175 | UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); | 178 | UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); |
| @@ -182,6 +185,7 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 182 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); | 185 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); |
| 183 | UISettings::values.screenshot_height.SetValue(height); | 186 | UISettings::values.screenshot_height.SetValue(height); |
| 184 | 187 | ||
| 188 | RequestGameListUpdate(); | ||
| 185 | system.ApplySettings(); | 189 | system.ApplySettings(); |
| 186 | } | 190 | } |
| 187 | 191 | ||
| @@ -197,6 +201,7 @@ void ConfigureUi::SetConfiguration() { | |||
| 197 | ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); | 201 | ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); |
| 198 | ui->show_size->setChecked(UISettings::values.show_size.GetValue()); | 202 | ui->show_size->setChecked(UISettings::values.show_size.GetValue()); |
| 199 | ui->show_types->setChecked(UISettings::values.show_types.GetValue()); | 203 | ui->show_types->setChecked(UISettings::values.show_types.GetValue()); |
| 204 | ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue()); | ||
| 200 | ui->game_icon_size_combobox->setCurrentIndex( | 205 | ui->game_icon_size_combobox->setCurrentIndex( |
| 201 | ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); | 206 | ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); |
| 202 | ui->folder_icon_size_combobox->setCurrentIndex( | 207 | 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 b5a02700d..98e410e0f 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -340,6 +340,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid | |||
| 340 | 340 | ||
| 341 | tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); | 341 | tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); |
| 342 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); | 342 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); |
| 343 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | ||
| 343 | item_model->setSortRole(GameListItemPath::SortRole); | 344 | item_model->setSortRole(GameListItemPath::SortRole); |
| 344 | 345 | ||
| 345 | connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); | 346 | connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); |
| @@ -548,6 +549,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 548 | QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); | 549 | QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); |
| 549 | QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); | 550 | QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); |
| 550 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); | 551 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); |
| 552 | 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")); | 553 | 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")); | 554 | 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")); | 555 | QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); |
| @@ -619,6 +621,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 619 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { | 621 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { |
| 620 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); | 622 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); |
| 621 | }); | 623 | }); |
| 624 | connect(remove_play_time_data, &QAction::triggered, | ||
| 625 | [this, program_id]() { emit RemovePlayTimeRequested(program_id); }); | ||
| 622 | connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { | 626 | connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { |
| 623 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); | 627 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); |
| 624 | }); | 628 | }); |
| @@ -785,6 +789,7 @@ void GameList::RetranslateUI() { | |||
| 785 | item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); | 789 | item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); |
| 786 | item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); | 790 | item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); |
| 787 | item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); | 791 | item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); |
| 792 | item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time")); | ||
| 788 | } | 793 | } |
| 789 | 794 | ||
| 790 | void GameListSearchField::changeEvent(QEvent* event) { | 795 | void GameListSearchField::changeEvent(QEvent* event) { |
| @@ -812,6 +817,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 812 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); | 817 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); |
| 813 | tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); | 818 | tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); |
| 814 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); | 819 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); |
| 820 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | ||
| 815 | 821 | ||
| 816 | // Delete any rows that might already exist if we're repopulating | 822 | // Delete any rows that might already exist if we're repopulating |
| 817 | item_model->removeRows(0, item_model->rowCount()); | 823 | item_model->removeRows(0, item_model->rowCount()); |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 6c2f75e53..cde6f1e1f 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -74,6 +74,7 @@ public: | |||
| 74 | COLUMN_ADD_ONS, | 74 | COLUMN_ADD_ONS, |
| 75 | COLUMN_FILE_TYPE, | 75 | COLUMN_FILE_TYPE, |
| 76 | COLUMN_SIZE, | 76 | COLUMN_SIZE, |
| 77 | COLUMN_PLAY_TIME, | ||
| 77 | COLUMN_COUNT, // Number of columns | 78 | COLUMN_COUNT, // Number of columns |
| 78 | }; | 79 | }; |
| 79 | 80 | ||
| @@ -112,6 +113,7 @@ signals: | |||
| 112 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); | 113 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); |
| 113 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, | 114 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, |
| 114 | const std::string& game_path); | 115 | const std::string& game_path); |
| 116 | void RemovePlayTimeRequested(u64 program_id); | ||
| 115 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 117 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 116 | void CopyTIDRequested(u64 program_id); | 118 | void CopyTIDRequested(u64 program_id); |
| 117 | void CreateShortcut(u64 program_id, const std::string& game_path, | 119 | void CreateShortcut(u64 program_id, const std::string& game_path, |
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 1800f090f..33a929aae 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.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..b15ed730e 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -27,6 +27,7 @@ | |||
| 27 | #include "yuzu/game_list.h" | 27 | #include "yuzu/game_list.h" |
| 28 | #include "yuzu/game_list_p.h" | 28 | #include "yuzu/game_list_p.h" |
| 29 | #include "yuzu/game_list_worker.h" | 29 | #include "yuzu/game_list_worker.h" |
| 30 | #include "yuzu/play_time.h" | ||
| 30 | #include "yuzu/uisettings.h" | 31 | #include "yuzu/uisettings.h" |
| 31 | 32 | ||
| 32 | namespace { | 33 | namespace { |
| @@ -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(PlayTime::GetPlayTime(program_id)), | ||
| 215 | }; | 217 | }; |
| 216 | 218 | ||
| 217 | const auto patch_versions = GetGameListCachedObject( | 219 | const auto patch_versions = GetGameListCachedObject( |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 33c9fd0af..53ab7ada9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -146,6 +146,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 146 | #include "yuzu/install_dialog.h" | 146 | #include "yuzu/install_dialog.h" |
| 147 | #include "yuzu/loading_screen.h" | 147 | #include "yuzu/loading_screen.h" |
| 148 | #include "yuzu/main.h" | 148 | #include "yuzu/main.h" |
| 149 | #include "yuzu/play_time.h" | ||
| 149 | #include "yuzu/startup_checks.h" | 150 | #include "yuzu/startup_checks.h" |
| 150 | #include "yuzu/uisettings.h" | 151 | #include "yuzu/uisettings.h" |
| 151 | #include "yuzu/util/clickable_label.h" | 152 | #include "yuzu/util/clickable_label.h" |
| @@ -334,6 +335,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan | |||
| 334 | SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); | 335 | SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); |
| 335 | discord_rpc->Update(); | 336 | discord_rpc->Update(); |
| 336 | 337 | ||
| 338 | play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(); | ||
| 339 | |||
| 337 | system->GetRoomNetwork().Init(); | 340 | system->GetRoomNetwork().Init(); |
| 338 | 341 | ||
| 339 | RegisterMetaTypes(); | 342 | RegisterMetaTypes(); |
| @@ -1446,6 +1449,8 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 1446 | connect(game_list, &GameList::RemoveInstalledEntryRequested, this, | 1449 | connect(game_list, &GameList::RemoveInstalledEntryRequested, this, |
| 1447 | &GMainWindow::OnGameListRemoveInstalledEntry); | 1450 | &GMainWindow::OnGameListRemoveInstalledEntry); |
| 1448 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); | 1451 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); |
| 1452 | connect(game_list, &GameList::RemovePlayTimeRequested, this, | ||
| 1453 | &GMainWindow::OnGameListRemovePlayTimeData); | ||
| 1449 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | 1454 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); |
| 1450 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 1455 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 1451 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 1456 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| @@ -2458,6 +2463,20 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ | |||
| 2458 | } | 2463 | } |
| 2459 | } | 2464 | } |
| 2460 | 2465 | ||
| 2466 | void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) { | ||
| 2467 | if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"), | ||
| 2468 | QMessageBox::Yes | QMessageBox::No, | ||
| 2469 | QMessageBox::No) != QMessageBox::Yes) { | ||
| 2470 | return; | ||
| 2471 | } | ||
| 2472 | if (!play_time_manager->ResetProgramPlayTime(program_id)) { | ||
| 2473 | QMessageBox::warning(this, tr("Error Resetting Play Time Data"), | ||
| 2474 | tr("Play time couldn't be cleared")); | ||
| 2475 | return; | ||
| 2476 | } | ||
| 2477 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 2478 | } | ||
| 2479 | |||
| 2461 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { | 2480 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { |
| 2462 | const auto target_file_name = [target] { | 2481 | const auto target_file_name = [target] { |
| 2463 | switch (target) { | 2482 | switch (target) { |
| @@ -3230,6 +3249,9 @@ void GMainWindow::OnStartGame() { | |||
| 3230 | UpdateMenuState(); | 3249 | UpdateMenuState(); |
| 3231 | OnTasStateChanged(); | 3250 | OnTasStateChanged(); |
| 3232 | 3251 | ||
| 3252 | play_time_manager->SetProgramId(system->GetApplicationProcessProgramID()); | ||
| 3253 | play_time_manager->Start(); | ||
| 3254 | |||
| 3233 | discord_rpc->Update(); | 3255 | discord_rpc->Update(); |
| 3234 | } | 3256 | } |
| 3235 | 3257 | ||
| @@ -3245,6 +3267,7 @@ void GMainWindow::OnRestartGame() { | |||
| 3245 | 3267 | ||
| 3246 | void GMainWindow::OnPauseGame() { | 3268 | void GMainWindow::OnPauseGame() { |
| 3247 | emu_thread->SetRunning(false); | 3269 | emu_thread->SetRunning(false); |
| 3270 | play_time_manager->Stop(); | ||
| 3248 | UpdateMenuState(); | 3271 | UpdateMenuState(); |
| 3249 | AllowOSSleep(); | 3272 | AllowOSSleep(); |
| 3250 | } | 3273 | } |
| @@ -3265,6 +3288,9 @@ void GMainWindow::OnStopGame() { | |||
| 3265 | return; | 3288 | return; |
| 3266 | } | 3289 | } |
| 3267 | 3290 | ||
| 3291 | play_time_manager->Stop(); | ||
| 3292 | // Update game list to show new play time | ||
| 3293 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 3268 | if (OnShutdownBegin()) { | 3294 | if (OnShutdownBegin()) { |
| 3269 | OnShutdownBeginDialog(); | 3295 | OnShutdownBeginDialog(); |
| 3270 | } else { | 3296 | } else { |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1b7055122..c3003f8d9 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -76,6 +76,10 @@ namespace DiscordRPC { | |||
| 76 | class DiscordInterface; | 76 | class DiscordInterface; |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | namespace PlayTime { | ||
| 80 | class PlayTimeManager; | ||
| 81 | } | ||
| 82 | |||
| 79 | namespace FileSys { | 83 | namespace FileSys { |
| 80 | class ContentProvider; | 84 | class ContentProvider; |
| 81 | class ManualContentProvider; | 85 | class ManualContentProvider; |
| @@ -312,6 +316,7 @@ private slots: | |||
| 312 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); | 316 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); |
| 313 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 317 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 314 | const std::string& game_path); | 318 | const std::string& game_path); |
| 319 | void OnGameListRemovePlayTimeData(u64 program_id); | ||
| 315 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 320 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 316 | void OnGameListCopyTID(u64 program_id); | 321 | void OnGameListCopyTID(u64 program_id); |
| 317 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 322 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| @@ -374,6 +379,7 @@ private: | |||
| 374 | void RemoveVulkanDriverPipelineCache(u64 program_id); | 379 | void RemoveVulkanDriverPipelineCache(u64 program_id); |
| 375 | void RemoveAllTransferableShaderCaches(u64 program_id); | 380 | void RemoveAllTransferableShaderCaches(u64 program_id); |
| 376 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); | 381 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); |
| 382 | void RemovePlayTimeData(u64 program_id); | ||
| 377 | void RemoveCacheStorage(u64 program_id); | 383 | void RemoveCacheStorage(u64 program_id); |
| 378 | std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); | 384 | std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); |
| 379 | InstallResult InstallNSPXCI(const QString& filename); | 385 | InstallResult InstallNSPXCI(const QString& filename); |
| @@ -411,6 +417,7 @@ private: | |||
| 411 | 417 | ||
| 412 | std::unique_ptr<Core::System> system; | 418 | std::unique_ptr<Core::System> system; |
| 413 | std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; | 419 | std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; |
| 420 | std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager; | ||
| 414 | std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; | 421 | std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; |
| 415 | 422 | ||
| 416 | MultiplayerState* multiplayer_state = nullptr; | 423 | MultiplayerState* multiplayer_state = nullptr; |
diff --git a/src/yuzu/play_time.cpp b/src/yuzu/play_time.cpp new file mode 100644 index 000000000..6be0327b2 --- /dev/null +++ b/src/yuzu/play_time.cpp | |||
| @@ -0,0 +1,177 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/fs/file.h" | ||
| 5 | #include "common/fs/path_util.h" | ||
| 6 | #include "common/logging/log.h" | ||
| 7 | #include "common/settings.h" | ||
| 8 | #include "common/thread.h" | ||
| 9 | #include "core/hle/service/acc/profile_manager.h" | ||
| 10 | |||
| 11 | #include "yuzu/play_time.h" | ||
| 12 | |||
| 13 | namespace PlayTime { | ||
| 14 | |||
| 15 | void PlayTimeManager::SetProgramId(u64 program_id) { | ||
| 16 | this->running_program_id = program_id; | ||
| 17 | } | ||
| 18 | |||
| 19 | inline void PlayTimeManager::UpdateTimestamp() { | ||
| 20 | this->last_timestamp = std::chrono::steady_clock::now(); | ||
| 21 | } | ||
| 22 | |||
| 23 | void PlayTimeManager::Start() { | ||
| 24 | UpdateTimestamp(); | ||
| 25 | play_time_thread = | ||
| 26 | std::jthread([&](std::stop_token stop_token) { this->AutoTimestamp(stop_token); }); | ||
| 27 | } | ||
| 28 | |||
| 29 | void PlayTimeManager::Stop() { | ||
| 30 | play_time_thread.request_stop(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) { | ||
| 34 | Common::SetCurrentThreadName("PlayTimeReport"); | ||
| 35 | |||
| 36 | using namespace std::literals::chrono_literals; | ||
| 37 | |||
| 38 | const auto duration = 30s; | ||
| 39 | while (Common::StoppableTimedWait(stop_token, duration)) { | ||
| 40 | Save(); | ||
| 41 | } | ||
| 42 | |||
| 43 | Save(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void PlayTimeManager::Save() { | ||
| 47 | const auto now = std::chrono::steady_clock::now(); | ||
| 48 | const auto duration = | ||
| 49 | static_cast<u64>(std::chrono::duration_cast<std::chrono::seconds>( | ||
| 50 | std::chrono::steady_clock::duration(now - this->last_timestamp)) | ||
| 51 | .count()); | ||
| 52 | UpdateTimestamp(); | ||
| 53 | if (!UpdatePlayTime(running_program_id, duration)) { | ||
| 54 | LOG_ERROR(Common, "Failed to update play time"); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | bool UpdatePlayTime(u64 program_id, u64 add_play_time) { | ||
| 59 | std::vector<PlayTimeElement> play_time_elements; | ||
| 60 | if (!ReadPlayTimeFile(play_time_elements)) { | ||
| 61 | return false; | ||
| 62 | } | ||
| 63 | const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); | ||
| 64 | |||
| 65 | if (it == play_time_elements.end()) { | ||
| 66 | play_time_elements.push_back({.program_id = program_id, .play_time = add_play_time}); | ||
| 67 | } else { | ||
| 68 | play_time_elements.at(it - play_time_elements.begin()).play_time += add_play_time; | ||
| 69 | } | ||
| 70 | if (!WritePlayTimeFile(play_time_elements)) { | ||
| 71 | return false; | ||
| 72 | } | ||
| 73 | return true; | ||
| 74 | } | ||
| 75 | |||
| 76 | u64 GetPlayTime(u64 program_id) { | ||
| 77 | std::vector<PlayTimeElement> play_time_elements; | ||
| 78 | |||
| 79 | if (!ReadPlayTimeFile(play_time_elements)) { | ||
| 80 | return 0; | ||
| 81 | } | ||
| 82 | const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); | ||
| 83 | if (it == play_time_elements.end()) { | ||
| 84 | return 0; | ||
| 85 | } | ||
| 86 | return play_time_elements.at(it - play_time_elements.begin()).play_time; | ||
| 87 | } | ||
| 88 | |||
| 89 | bool PlayTimeManager::ResetProgramPlayTime(u64 program_id) { | ||
| 90 | std::vector<PlayTimeElement> play_time_elements; | ||
| 91 | |||
| 92 | if (!ReadPlayTimeFile(play_time_elements)) { | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); | ||
| 96 | if (it == play_time_elements.end()) { | ||
| 97 | return false; | ||
| 98 | } | ||
| 99 | play_time_elements.erase(it); | ||
| 100 | if (!WritePlayTimeFile(play_time_elements)) { | ||
| 101 | return false; | ||
| 102 | } | ||
| 103 | return true; | ||
| 104 | } | ||
| 105 | |||
| 106 | std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() { | ||
| 107 | const Service::Account::ProfileManager manager; | ||
| 108 | const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user)); | ||
| 109 | if (!uuid.has_value()) { | ||
| 110 | return std::nullopt; | ||
| 111 | } | ||
| 112 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) / | ||
| 113 | uuid->RawString().append(".bin"); | ||
| 114 | } | ||
| 115 | |||
| 116 | [[nodiscard]] bool ReadPlayTimeFile(std::vector<PlayTimeElement>& out_play_time_elements) { | ||
| 117 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 118 | if (!filename.has_value()) { | ||
| 119 | LOG_ERROR(Common, "Failed to get current user path"); | ||
| 120 | return false; | ||
| 121 | } | ||
| 122 | |||
| 123 | if (Common::FS::Exists(filename.value())) { | ||
| 124 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read, | ||
| 125 | Common::FS::FileType::BinaryFile}; | ||
| 126 | if (!file.IsOpen()) { | ||
| 127 | LOG_ERROR(Common, "Failed to open play time file: {}", | ||
| 128 | Common::FS::PathToUTF8String(filename.value())); | ||
| 129 | return false; | ||
| 130 | } | ||
| 131 | const size_t elem_num = file.GetSize() / sizeof(PlayTimeElement); | ||
| 132 | out_play_time_elements.resize(elem_num); | ||
| 133 | const bool success = file.ReadSpan<PlayTimeElement>(out_play_time_elements) == elem_num; | ||
| 134 | file.Close(); | ||
| 135 | return success; | ||
| 136 | } else { | ||
| 137 | out_play_time_elements.clear(); | ||
| 138 | return true; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | [[nodiscard]] bool WritePlayTimeFile(const std::vector<PlayTimeElement>& play_time_elements) { | ||
| 143 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 144 | if (!filename.has_value()) { | ||
| 145 | LOG_ERROR(Common, "Failed to get current user path"); | ||
| 146 | return false; | ||
| 147 | } | ||
| 148 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write, | ||
| 149 | Common::FS::FileType::BinaryFile}; | ||
| 150 | |||
| 151 | if (!file.IsOpen()) { | ||
| 152 | LOG_ERROR(Common, "Failed to open play time file: {}", | ||
| 153 | Common::FS::PathToUTF8String(filename.value())); | ||
| 154 | return false; | ||
| 155 | } | ||
| 156 | const bool success = | ||
| 157 | file.WriteSpan<PlayTimeElement>(play_time_elements) == play_time_elements.size(); | ||
| 158 | file.Close(); | ||
| 159 | return success; | ||
| 160 | } | ||
| 161 | |||
| 162 | QString ReadablePlayTime(qulonglong time_seconds) { | ||
| 163 | static constexpr std::array units{"m", "h"}; | ||
| 164 | if (time_seconds == 0) { | ||
| 165 | return QLatin1String(""); | ||
| 166 | } | ||
| 167 | const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0); | ||
| 168 | const auto time_hours = static_cast<double>(time_seconds) / 3600; | ||
| 169 | const int unit = time_minutes < 60 ? 0 : 1; | ||
| 170 | const auto value = unit == 0 ? time_minutes : time_hours; | ||
| 171 | |||
| 172 | return QStringLiteral("%L1 %2") | ||
| 173 | .arg(value, 0, 'f', unit && time_seconds % 60 != 0) | ||
| 174 | .arg(QString::fromUtf8(units[unit])); | ||
| 175 | } | ||
| 176 | |||
| 177 | } // namespace PlayTime | ||
diff --git a/src/yuzu/play_time.h b/src/yuzu/play_time.h new file mode 100644 index 000000000..68e40955c --- /dev/null +++ b/src/yuzu/play_time.h | |||
| @@ -0,0 +1,68 @@ | |||
| 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 <atomic> | ||
| 9 | #include <condition_variable> | ||
| 10 | #include <mutex> | ||
| 11 | #include <optional> | ||
| 12 | #include <thread> | ||
| 13 | |||
| 14 | #include "common/common_types.h" | ||
| 15 | #include "common/fs/fs.h" | ||
| 16 | #include "common/polyfill_thread.h" | ||
| 17 | #include "core/core.h" | ||
| 18 | |||
| 19 | namespace PlayTime { | ||
| 20 | struct PlayTimeElement { | ||
| 21 | u64 program_id; | ||
| 22 | u64 play_time; | ||
| 23 | |||
| 24 | inline bool operator==(const PlayTimeElement& other) const { | ||
| 25 | return program_id == other.program_id; | ||
| 26 | } | ||
| 27 | |||
| 28 | inline bool operator==(const u64 _program_id) const { | ||
| 29 | return program_id == _program_id; | ||
| 30 | } | ||
| 31 | }; | ||
| 32 | |||
| 33 | class PlayTimeManager { | ||
| 34 | public: | ||
| 35 | explicit PlayTimeManager() = default; | ||
| 36 | ~PlayTimeManager() = default; | ||
| 37 | |||
| 38 | public: | ||
| 39 | YUZU_NON_COPYABLE(PlayTimeManager); | ||
| 40 | YUZU_NON_MOVEABLE(PlayTimeManager); | ||
| 41 | |||
| 42 | public: | ||
| 43 | bool ResetProgramPlayTime(u64 program_id); | ||
| 44 | void SetProgramId(u64 program_id); | ||
| 45 | inline void UpdateTimestamp(); | ||
| 46 | void Start(); | ||
| 47 | void Stop(); | ||
| 48 | |||
| 49 | private: | ||
| 50 | u64 running_program_id; | ||
| 51 | std::chrono::steady_clock::time_point last_timestamp; | ||
| 52 | std::jthread play_time_thread; | ||
| 53 | void AutoTimestamp(std::stop_token stop_token); | ||
| 54 | void Save(); | ||
| 55 | }; | ||
| 56 | |||
| 57 | std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(); | ||
| 58 | |||
| 59 | bool UpdatePlayTime(u64 program_id, u64 add_play_time); | ||
| 60 | |||
| 61 | [[nodiscard]] bool ReadPlayTimeFile(std::vector<PlayTimeElement>& out_play_time_elements); | ||
| 62 | [[nodiscard]] bool WritePlayTimeFile(const std::vector<PlayTimeElement>& play_time_elements); | ||
| 63 | |||
| 64 | u64 GetPlayTime(u64 program_id); | ||
| 65 | |||
| 66 | QString ReadablePlayTime(qulonglong time_seconds); | ||
| 67 | |||
| 68 | } // namespace PlayTime | ||
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 8efd63f31..848631a9e 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}; |