From 28e89a8a4d6911a4fb9f031c25426a6b31373dce Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:31:13 +0900 Subject: [PATCH] [UI] Implement achievement notification sound using Qt media player --- .github/workflows/CI.yml | 4 +-- docs/building.md | 4 +-- premake5.lua | 5 ++++ src/xenia/ui/config_dialog_qt.cc | 22 ++++++++++++---- src/xenia/ui/imgui_guest_notification.cc | 22 ---------------- src/xenia/ui/notification_widget_qt.cc | 33 +++++++++++++++++++++++- src/xenia/ui/notification_widget_qt.h | 4 +++ 7 files changed, 62 insertions(+), 32 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c995c1e92..394dbdaae 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -85,7 +85,7 @@ jobs: pip install aqtinstall echo "Available architectures for Qt 6.9.2:" aqt list-qt linux desktop --arch 6.9.2 || true - aqt install-qt linux desktop 6.9.2 linux_gcc_64 -O /opt/Qt + aqt install-qt linux desktop 6.9.2 linux_gcc_64 -m qtmultimedia -O /opt/Qt fi export PATH="/opt/Qt/6.9.2/gcc_64/bin:$PATH" export CMAKE_PREFIX_PATH="/opt/Qt/6.9.2/gcc_64" @@ -257,7 +257,7 @@ jobs: $qtCacheHit = '${{ steps.cache-qt.outputs.cache-hit }}' if ($qtCacheHit -ne 'true') { pip install aqtinstall - aqt install-qt windows desktop 6.9.2 win64_msvc2022_64 -O C:\Qt + aqt install-qt windows desktop 6.9.2 win64_msvc2022_64 -m qtmultimedia -O C:\Qt } $cacheHit = '${{ steps.cache-submodules.outputs.cache-hit }}' diff --git a/docs/building.md b/docs/building.md index dc30542f7..65342cf3d 100644 --- a/docs/building.md +++ b/docs/building.md @@ -20,7 +20,7 @@ drivers. * Install using aqtinstall: ``` pip install aqtinstall - python -m aqt install-qt windows desktop 6.9.2 win64_msvc2022_64 -O C:\Qt + python -m aqt install-qt windows desktop 6.9.2 win64_msvc2022_64 -m qtmultimedia -O C:\Qt # Note: msvc2022_64 is compatible with with VS2026 ``` * The build script will automatically detect it if installed in `C:\Qt` @@ -127,7 +127,7 @@ You can install Qt from your distribution's package manager, or use aqtinstall: ```sh pip install aqtinstall -aqt install-qt linux desktop 6.9.2 linux_gcc_64 -O /opt/Qt +aqt install-qt linux desktop 6.9.2 linux_gcc_64 -m qtmultimedia -O /opt/Qt ``` If using aqtinstall, add Qt to your environment: diff --git a/premake5.lua b/premake5.lua index 8f2ca3ff7..b0d1659fb 100644 --- a/premake5.lua +++ b/premake5.lua @@ -176,6 +176,7 @@ filter("platforms:Linux") path.join(qt_dir, "include/QtCore"), path.join(qt_dir, "include/QtGui"), path.join(qt_dir, "include/QtWidgets"), + path.join(qt_dir, "include/QtMultimedia"), }) libdirs({ path.join(qt_dir, "lib"), @@ -191,6 +192,7 @@ filter("platforms:Linux") "Qt6Core", "Qt6Gui", "Qt6Widgets", + "Qt6Multimedia", }) end @@ -332,6 +334,7 @@ filter("platforms:Windows") path.join(qt_dir, "include/QtCore"), path.join(qt_dir, "include/QtGui"), path.join(qt_dir, "include/QtWidgets"), + path.join(qt_dir, "include/QtMultimedia"), }) libdirs({ path.join(qt_dir, "lib"), @@ -344,6 +347,7 @@ filter({"platforms:Windows", "configurations:Release"}) "Qt6Core", "Qt6Gui", "Qt6Widgets", + "Qt6Multimedia", }) end @@ -353,6 +357,7 @@ filter({"platforms:Windows", "configurations:Debug or Checked"}) "Qt6Cored", "Qt6Guid", "Qt6Widgetsd", + "Qt6Multimediad", }) end diff --git a/src/xenia/ui/config_dialog_qt.cc b/src/xenia/ui/config_dialog_qt.cc index 5bddbfe7c..4b32d7f9a 100644 --- a/src/xenia/ui/config_dialog_qt.cc +++ b/src/xenia/ui/config_dialog_qt.cc @@ -312,8 +312,9 @@ QWidget* ConfigDialogQt::CreateEditorWidget(ConfigVarInfo* var_info) { connect(combo, QOverload::of(&QComboBox::currentIndexChanged), this, &ConfigDialogQt::OnValueChanged); return combo; - } else if (dynamic_cast*>( - var_info->var)) { + } else if (auto* path_var = + dynamic_cast*>( + var_info->var)) { // Path input with browse button auto* container = new QWidget(); auto* layout = new QHBoxLayout(container); @@ -328,9 +329,20 @@ QWidget* ConfigDialogQt::CreateEditorWidget(ConfigVarInfo* var_info) { auto* browse_button = new QPushButton("Browse..."); connect(browse_button, &QPushButton::clicked, [this, line_edit, var_info]() { - QString path = QFileDialog::getExistingDirectory( - this, SafeQString("Select Directory for " + var_info->name), - line_edit->text()); + QString path; + // Special case for notification_sound_path - file picker with + // audio filters + if (var_info->name == "notification_sound_path") { + path = QFileDialog::getOpenFileName( + this, "Select Notification Sound", line_edit->text(), + "Audio Files (*.wav *.mp3 *.ogg *.flac *.m4a *.aac *.wma " + "*.opus);;All Files (*)"); + } else { + // Default: directory picker + path = QFileDialog::getExistingDirectory( + this, SafeQString("Select Directory for " + var_info->name), + line_edit->text()); + } if (!path.isEmpty()) { line_edit->setText(path); } diff --git a/src/xenia/ui/imgui_guest_notification.cc b/src/xenia/ui/imgui_guest_notification.cc index ccef72f3c..33bebbcc7 100644 --- a/src/xenia/ui/imgui_guest_notification.cc +++ b/src/xenia/ui/imgui_guest_notification.cc @@ -14,15 +14,6 @@ #include "xenia/ui/imgui_guest_notification.h" #include "xenia/ui/imgui_notification.h" -#if XE_PLATFORM_WIN32 -#include -#endif - -DEFINE_string(notification_sound_path, "", - "Path (including filename) to selected notification sound. Sound " - "MUST be in wav format!", - "General"); - namespace xe { namespace ui { @@ -46,19 +37,6 @@ void ImGuiGuestNotification::UpdateNotificationState() { // TODO(Gliniak): Implement delayed notifications. current_stage_ = NotificationStage::FazeIn; notification_draw_progress_ = 0.2f; -#if XE_PLATFORM_WIN32 - if (!cvars::notification_sound_path.empty()) { - auto notification_sound_path = cvars::notification_sound_path; - if (std::filesystem::exists(notification_sound_path)) { - PlaySound(std::wstring(notification_sound_path.begin(), - notification_sound_path.end()) - .c_str(), - NULL, - SND_FILENAME | SND_NODEFAULT | SND_NOSTOP | SND_ASYNC); - } - } -#endif - break; case NotificationStage::FazeIn: { SetCreationTime(Clock::QueryHostUptimeMillis()); diff --git a/src/xenia/ui/notification_widget_qt.cc b/src/xenia/ui/notification_widget_qt.cc index 3dd4afc62..adfbf1838 100644 --- a/src/xenia/ui/notification_widget_qt.cc +++ b/src/xenia/ui/notification_widget_qt.cc @@ -9,16 +9,29 @@ #include "xenia/ui/notification_widget_qt.h" +#include #include +#include + +#include "xenia/base/cvar.h" +#include "xenia/base/string.h" +#include "xenia/ui/qt_util.h" + +DEFINE_path(notification_sound_path, "", + "Path (including filename) to selected notification sound. " + "Supports WAV, MP3, OGG, FLAC, and other common formats.", + "UI"); namespace xe { namespace app { +using xe::ui::SafeQString; + NotificationWidgetQt::NotificationWidgetQt(QWidget* parent, const QString& title, const QString& message, int duration_ms) - : QWidget(parent) { + : QWidget(parent), media_player_(nullptr), audio_output_(nullptr) { setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_TransparentForMouseEvents); setAutoFillBackground(true); @@ -57,6 +70,19 @@ NotificationWidgetQt::NotificationWidgetQt(QWidget* parent, deleteLater(); // Ensure widget is actually destroyed }); auto_close_timer_->setInterval(duration_ms); + + // Setup media player for notification sound + if (!cvars::notification_sound_path.empty()) { + std::filesystem::path sound_path = cvars::notification_sound_path; + if (std::filesystem::exists(sound_path)) { + media_player_ = new QMediaPlayer(this); + audio_output_ = new QAudioOutput(this); + media_player_->setAudioOutput(audio_output_); + media_player_->setSource( + QUrl::fromLocalFile(SafeQString(xe::path_to_utf8(sound_path)))); + audio_output_->setVolume(1.0); + } + } } void NotificationWidgetQt::Show() { @@ -74,6 +100,11 @@ void NotificationWidgetQt::Show() { show(); raise(); auto_close_timer_->start(); + + // Play sound if configured + if (media_player_) { + media_player_->play(); + } } } // namespace app diff --git a/src/xenia/ui/notification_widget_qt.h b/src/xenia/ui/notification_widget_qt.h index dd0bec006..954e50e7b 100644 --- a/src/xenia/ui/notification_widget_qt.h +++ b/src/xenia/ui/notification_widget_qt.h @@ -10,7 +10,9 @@ #ifndef XENIA_UI_NOTIFICATION_WIDGET_QT_H_ #define XENIA_UI_NOTIFICATION_WIDGET_QT_H_ +#include #include +#include #include #include @@ -29,6 +31,8 @@ class NotificationWidgetQt : public QWidget { private: QTimer* auto_close_timer_; + QMediaPlayer* media_player_; + QAudioOutput* audio_output_; }; } // namespace app