diff --git a/src/core/hle/service/am/applet_manager.cpp b/src/core/hle/service/am/applet_manager.cpp index de8ebf9..e36f4da 100644 --- a/src/core/hle/service/am/applet_manager.cpp +++ b/src/core/hle/service/am/applet_manager.cpp @@ -102,7 +102,7 @@ void PushInShowController(Core::System& system, AppletStorageChannel& channel) { // ShowControllerFirmwareUpdateForSystem/ShowControllerKeyRemappingForSystem, // which sets this to the input param .style_set = Core::HID::NpadStyleSet::None, - .joy_hold_type = 0, + .joy_hold_type = Frontend::NpadJoyDeviceHoldType::Vertical, }; std::vector common_args_data(sizeof(common_args)); std::vector private_args_data(sizeof(private_args)); diff --git a/src/core/hle/service/am/frontend/applet_controller.h b/src/core/hle/service/am/frontend/applet_controller.h index ae414c4..8c6b30f 100644 --- a/src/core/hle/service/am/frontend/applet_controller.h +++ b/src/core/hle/service/am/frontend/applet_controller.h @@ -53,6 +53,8 @@ enum class ControllerSupportResult : u32 { Cancel = 2, }; +enum class NpadJoyDeviceHoldType : u32 { Vertical = 0, Horizontal = 1 }; + struct ControllerSupportArgPrivate { u32 arg_private_size{}; u32 arg_size{}; @@ -61,7 +63,7 @@ struct ControllerSupportArgPrivate { ControllerSupportMode mode{}; ControllerSupportCaller caller{}; Core::HID::NpadStyleSet style_set{}; - u32 joy_hold_type{}; + NpadJoyDeviceHoldType joy_hold_type{}; }; static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, "ControllerSupportArgPrivate has incorrect size."); diff --git a/src/sudachi/configuration/configure_ui.cpp b/src/sudachi/configuration/configure_ui.cpp index f69c431..c86a3b3 100644 --- a/src/sudachi/configuration/configure_ui.cpp +++ b/src/sudachi/configuration/configure_ui.cpp @@ -125,6 +125,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); connect(ui->show_play_time, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); + connect(ui->show_total_times, &QCheckBox::stateChanged, this, + &ConfigureUi::RequestGameListUpdate); connect(ui->game_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); connect(ui->folder_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), @@ -170,6 +172,7 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.show_size = ui->show_size->isChecked(); UISettings::values.show_types = ui->show_types->isChecked(); UISettings::values.show_play_time = ui->show_play_time->isChecked(); + UISettings::values.show_total_times = ui->show_total_times->isChecked(); UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); @@ -200,6 +203,7 @@ void ConfigureUi::SetConfiguration() { ui->show_size->setChecked(UISettings::values.show_size.GetValue()); ui->show_types->setChecked(UISettings::values.show_types.GetValue()); ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue()); + ui->show_total_times->setChecked(UISettings::values.show_total_times.GetValue()); ui->game_icon_size_combobox->setCurrentIndex( ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); ui->folder_icon_size_combobox->setCurrentIndex( @@ -262,7 +266,7 @@ void ConfigureUi::InitializeLanguageComboBox() { #else const QString country = QLocale::countryToString(QLocale(locale).country()); ui->language_combobox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale); - #endif +#endif } // Unlike other configuration changes, interface language changes need to be reflected on the diff --git a/src/sudachi/configuration/configure_ui.ui b/src/sudachi/configuration/configure_ui.ui index b8e6483..7cb8384 100644 --- a/src/sudachi/configuration/configure_ui.ui +++ b/src/sudachi/configuration/configure_ui.ui @@ -111,6 +111,13 @@ + + + + Show Total Times Column + + + diff --git a/src/sudachi/game_list.cpp b/src/sudachi/game_list.cpp index eeaf6cd..7e94715 100644 --- a/src/sudachi/game_list.cpp +++ b/src/sudachi/game_list.cpp @@ -344,6 +344,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); + tree_view->setColumnHidden(COLUMN_TOTAL_TIMES, !UISettings::values.show_total_times); item_model->setSortRole(GameListItemPath::SortRole); connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); @@ -800,6 +801,7 @@ void GameList::RetranslateUI() { item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time")); + item_model->setHeaderData(COLUMN_TOTAL_TIMES, Qt::Horizontal, tr("Total times")); } void GameListSearchField::changeEvent(QEvent* event) { @@ -828,6 +830,7 @@ void GameList::PopulateAsync(QVector& game_dirs) { tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); + tree_view->setColumnHidden(COLUMN_TOTAL_TIMES, !UISettings::values.show_total_times); // Cancel any existing worker. current_worker.reset(); diff --git a/src/sudachi/game_list.h b/src/sudachi/game_list.h index 3135e91..6b92b14 100644 --- a/src/sudachi/game_list.h +++ b/src/sudachi/game_list.h @@ -16,9 +16,9 @@ #include "common/common_types.h" #include "core/core.h" -#include "uisettings.h" #include "sudachi/compatibility_list.h" #include "sudachi/play_time_manager.h" +#include "uisettings.h" namespace Core { class System; @@ -77,6 +77,7 @@ public: COLUMN_FILE_TYPE, COLUMN_SIZE, COLUMN_PLAY_TIME, + COLUMN_TOTAL_TIMES, COLUMN_COUNT, // Number of columns }; diff --git a/src/sudachi/game_list_p.h b/src/sudachi/game_list_p.h index e57539a..bc9e497 100644 --- a/src/sudachi/game_list_p.h +++ b/src/sudachi/game_list_p.h @@ -247,6 +247,31 @@ public: } }; +/** + * GameListItem for Play Time values. + * This object stores the play time of a game in seconds, and its readable + * representation in minutes/hours + */ +class GameListItemTotalTimes : public GameListItem { +public: + static constexpr int TotalTimesRole = SortRole; + + GameListItemTotalTimes() = default; + explicit GameListItemTotalTimes(const qulonglong times_count) { + setData(times_count, TotalTimesRole); + } + + void setData(const QVariant& value, int role) override { + qulonglong times_count = value.toULongLong(); + GameListItem::setData(PlayTime::ReadableTotalTimes(times_count), Qt::DisplayRole); + GameListItem::setData(value, TotalTimesRole); + } + + bool operator<(const QStandardItem& other) const override { + return data(TotalTimesRole).toULongLong() < other.data(TotalTimesRole).toULongLong(); + } +}; + class GameListDir : public GameListItem { public: static constexpr int GameDirRole = Qt::UserRole + 2; diff --git a/src/sudachi/game_list_worker.cpp b/src/sudachi/game_list_worker.cpp index e7355ae..3106a51 100644 --- a/src/sudachi/game_list_worker.cpp +++ b/src/sudachi/game_list_worker.cpp @@ -215,7 +215,7 @@ QList MakeGameListEntry(const std::string& path, const std::stri new GameListItem(file_type_string), new GameListItemSize(size), new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), - }; + new GameListItemTotalTimes(play_time_manager.GetTotalTimes(program_id))}; const auto patch_versions = GetGameListCachedObject( fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { diff --git a/src/sudachi/play_time.cpp b/src/sudachi/play_time.cpp index 1af5cb7..0929f9b 100644 --- a/src/sudachi/play_time.cpp +++ b/src/sudachi/play_time.cpp @@ -18,6 +18,7 @@ void PlayTimeManager::SetProgramId(u64 program_id) { inline void PlayTimeManager::UpdateTimestamp() { this->last_timestamp = std::chrono::steady_clock::now(); + this->last_total_times = GetTotalTimes(this->running_program_id); } void PlayTimeManager::Start() { @@ -50,12 +51,12 @@ void PlayTimeManager::Save() { std::chrono::steady_clock::duration(now - this->last_timestamp)) .count()); UpdateTimestamp(); - if (!UpdatePlayTime(running_program_id, duration)) { + if (!UpdatePlayTime(running_program_id, duration, 1)) { LOG_ERROR(Common, "Failed to update play time"); } } -bool UpdatePlayTime(u64 program_id, u64 add_play_time) { +bool UpdatePlayTime(u64 program_id, u64 add_play_time, u64 add_total_times) { std::vector play_time_elements; if (!ReadPlayTimeFile(play_time_elements)) { return false; @@ -63,9 +64,11 @@ bool UpdatePlayTime(u64 program_id, u64 add_play_time) { const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); if (it == play_time_elements.end()) { - play_time_elements.push_back({.program_id = program_id, .play_time = add_play_time}); + play_time_elements.push_back( + {.program_id = program_id, .play_time = add_play_time, .total_times = add_total_times}); } else { play_time_elements.at(it - play_time_elements.begin()).play_time += add_play_time; + play_time_elements.at(it - play_time_elements.begin()).total_times += add_total_times; } if (!WritePlayTimeFile(play_time_elements)) { return false; @@ -86,6 +89,19 @@ u64 GetPlayTime(u64 program_id) { return play_time_elements.at(it - play_time_elements.begin()).play_time; } +u64 GetTotalTimes(u64 program_id) { + std::vector play_time_elements; + + if (!ReadPlayTimeFile(play_time_elements)) { + return 0; + } + const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); + if (it == play_time_elements.end()) { + return 0; + } + return play_time_elements.at(it - play_time_elements.begin()).total_times; +} + bool PlayTimeManager::ResetProgramPlayTime(u64 program_id) { std::vector play_time_elements; @@ -174,4 +190,8 @@ QString ReadablePlayTime(qulonglong time_seconds) { .arg(QString::fromUtf8(units[unit])); } +QString ReadableTotalTimes(qulonglong times_count) { + return QStringLiteral("%L1").arg((double)times_count, 0, 'f', 0); +} + } // namespace PlayTime diff --git a/src/sudachi/play_time.h b/src/sudachi/play_time.h index c6bfaf5..5b7f6f5 100644 --- a/src/sudachi/play_time.h +++ b/src/sudachi/play_time.h @@ -20,6 +20,7 @@ namespace PlayTime { struct PlayTimeElement { u64 program_id; u64 play_time; + u64 total_times; inline bool operator==(const PlayTimeElement& other) const { return program_id == other.program_id; @@ -49,6 +50,7 @@ public: private: u64 running_program_id; std::chrono::steady_clock::time_point last_timestamp; + u64 last_total_times; std::jthread play_time_thread; void AutoTimestamp(std::stop_token stop_token); void Save(); @@ -56,13 +58,15 @@ private: std::optional GetCurrentUserPlayTimePath(); -bool UpdatePlayTime(u64 program_id, u64 add_play_time); +bool UpdatePlayTime(u64 program_id, u64 add_play_time, u64 add_total_times); [[nodiscard]] bool ReadPlayTimeFile(std::vector& out_play_time_elements); [[nodiscard]] bool WritePlayTimeFile(const std::vector& play_time_elements); u64 GetPlayTime(u64 program_id); +u64 GetTotalTimes(u64 program_id); QString ReadablePlayTime(qulonglong time_seconds); +QString ReadableTotalTimes(qulonglong times_count); } // namespace PlayTime diff --git a/src/sudachi/play_time_manager.cpp b/src/sudachi/play_time_manager.cpp index 691e26d..8a91083 100644 --- a/src/sudachi/play_time_manager.cpp +++ b/src/sudachi/play_time_manager.cpp @@ -18,6 +18,7 @@ namespace { struct PlayTimeElement { ProgramId program_id; PlayTime play_time; + TotalTimes total_times; }; std::optional GetCurrentUserPlayTimePath( @@ -57,9 +58,9 @@ std::optional GetCurrentUserPlayTimePath( return false; } - for (const auto& [program_id, play_time] : elements) { + for (const auto& [program_id, play_time, total_times] : elements) { if (program_id != 0) { - out_play_time_db[program_id] = play_time; + out_play_time_db[program_id] = {play_time, total_times}; } } } @@ -89,7 +90,7 @@ std::optional GetCurrentUserPlayTimePath( for (auto& [program_id, play_time] : play_time_db) { if (program_id != 0) { - elements.push_back(PlayTimeElement{program_id, play_time}); + elements.push_back(PlayTimeElement{program_id, play_time.first, play_time.second}); } } @@ -139,7 +140,8 @@ void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) { while (!stop_token.stop_requested()) { Common::StoppableTimedWait(stop_token, 30s); - database[running_program_id] += GetDuration(); + database[running_program_id].first += GetDuration(); + database[running_program_id].second += 1; Save(); } } @@ -153,7 +155,16 @@ void PlayTimeManager::Save() { u64 PlayTimeManager::GetPlayTime(u64 program_id) const { auto it = database.find(program_id); if (it != database.end()) { - return it->second; + return it->second.first; + } else { + return 0; + } +} + +u64 PlayTimeManager::GetTotalTimes(u64 program_id) const { + auto it = database.find(program_id); + if (it != database.end()) { + return it->second.second; } else { return 0; } @@ -179,4 +190,12 @@ QString ReadablePlayTime(qulonglong time_seconds) { .arg(QString::fromUtf8(unit)); } +QString ReadableTotalTimes(qulonglong times_count) { + if (times_count == 0) { + return {}; + } + + return QStringLiteral("%L1").arg((double)times_count, 0, 'f', 0); +} + } // namespace PlayTime diff --git a/src/sudachi/play_time_manager.h b/src/sudachi/play_time_manager.h index f5aaa82..bfb4a3d 100644 --- a/src/sudachi/play_time_manager.h +++ b/src/sudachi/play_time_manager.h @@ -19,7 +19,8 @@ namespace PlayTime { using ProgramId = u64; using PlayTime = u64; -using PlayTimeDatabase = std::map; +using TotalTimes = u64; +using PlayTimeDatabase = std::map>; class PlayTimeManager { public: @@ -30,6 +31,7 @@ public: SUDACHI_NON_MOVEABLE(PlayTimeManager); u64 GetPlayTime(u64 program_id) const; + u64 GetTotalTimes(u64 program_id) const; void ResetProgramPlayTime(u64 program_id); void SetProgramId(u64 program_id); void Start(); @@ -46,5 +48,6 @@ private: }; QString ReadablePlayTime(qulonglong time_seconds); +QString ReadableTotalTimes(qulonglong times_count); } // namespace PlayTime diff --git a/src/sudachi/uisettings.h b/src/sudachi/uisettings.h index fe87064..f803809 100644 --- a/src/sudachi/uisettings.h +++ b/src/sudachi/uisettings.h @@ -212,6 +212,9 @@ struct Values { // Play time Setting show_play_time{linkage, true, "show_play_time", Category::UiGameList}; + // Total times + Setting show_total_times{linkage, true, "show_total_times", Category::UiGameList}; + bool configuration_applied; bool reset_to_defaults; bool shortcut_already_warned{false};