From b03341ac8ddd0c933d4281a3f925215dc87c1695 Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:04:24 +0000 Subject: [PATCH 1/8] feat: Add multiplayer_room_overlay.h --- src/citron/util/multiplayer_room_overlay.h | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/citron/util/multiplayer_room_overlay.h diff --git a/src/citron/util/multiplayer_room_overlay.h b/src/citron/util/multiplayer_room_overlay.h new file mode 100644 index 000000000..d93580f18 --- /dev/null +++ b/src/citron/util/multiplayer_room_overlay.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "citron/multiplayer/state.h" +#include "citron/multiplayer/chat_room.h" + +class GMainWindow; +class QSizeGrip; + +class MultiplayerRoomOverlay : public QWidget { + Q_OBJECT + +public: + explicit MultiplayerRoomOverlay(GMainWindow* parent); + ~MultiplayerRoomOverlay() override; + + void SetVisible(bool visible); + bool IsVisible() const { return is_visible; } + +public slots: + // These slots are connected to the main window to prevent crashes. + void OnEmulationStarting(); + void OnEmulationStopping(); + +protected: + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + bool eventFilter(QObject* watched, QEvent* event) override; + +private slots: + void UpdateRoomData(); + +private: + void UpdatePosition(); + void ConnectToRoom(); + void DisconnectFromRoom(); + + GMainWindow* main_window; + QTimer update_timer; + + // UI Elements + QLabel* players_online_label; + ChatRoom* chat_room_widget; + QGridLayout* main_layout; + QSizeGrip* size_grip; + + // Network and Data + MultiplayerState* multiplayer_state = nullptr; + std::shared_ptr room_member; + + // Display settings + bool is_visible = false; + QColor background_color; + QColor border_color; + + // Layout + int padding = 12; + int border_width = 1; + int corner_radius = 10; + + // Drag functionality + bool is_dragging = false; + bool has_been_moved = false; + QPoint drag_start_pos; + QPoint widget_start_pos; +}; From bfa94f4ca66d475d38fc02e2ee8483cc4476c403 Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:04:51 +0000 Subject: [PATCH 2/8] feat: Add multiplayer_room_overlay.cpp --- src/citron/util/multiplayer_room_overlay.cpp | 235 +++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 src/citron/util/multiplayer_room_overlay.cpp diff --git a/src/citron/util/multiplayer_room_overlay.cpp b/src/citron/util/multiplayer_room_overlay.cpp new file mode 100644 index 000000000..020fd0766 --- /dev/null +++ b/src/citron/util/multiplayer_room_overlay.cpp @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "citron/main.h" +#include "citron/bootmanager.h" +#include "citron/util/multiplayer_room_overlay.h" +#include "network/network.h" +#include "network/room.h" + +MultiplayerRoomOverlay::MultiplayerRoomOverlay(GMainWindow* parent) + : QWidget(parent), main_window(parent) { + + setAttribute(Qt::WA_TranslucentBackground, true); + setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint); + setFocusPolicy(Qt::ClickFocus); + + background_color = QColor(20, 20, 20, 180); + border_color = QColor(60, 60, 60, 120); + + main_layout = new QGridLayout(this); + main_layout->setContentsMargins(padding, padding, 0, 0); // No margins on bottom/right for grip + main_layout->setSpacing(6); + + players_online_label = new QLabel(this); + chat_room_widget = new ChatRoom(this); + size_grip = new QSizeGrip(this); + + players_online_label->setFont(QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Bold)); + players_online_label->setStyleSheet(QString::fromUtf8("color: #E0E0E0;")); + players_online_label->setText(QString::fromUtf8("Players Online: 0")); + players_online_label->setAttribute(Qt::WA_TransparentForMouseEvents, true); + + size_grip->setFixedSize(16, 16); + + if (main_window) { + GRenderWindow* render_window = main_window->findChild(); + if (render_window) { + render_window->installEventFilter(this); + } + } + + main_layout->addWidget(players_online_label, 0, 0, 1, 2); + main_layout->addWidget(chat_room_widget, 1, 0, 1, 2); + main_layout->addWidget(size_grip, 1, 1, 1, 1, Qt::AlignBottom | Qt::AlignRight); + + main_layout->setRowStretch(1, 1); + main_layout->setColumnStretch(0, 1); + + setLayout(main_layout); + + update_timer.setSingleShot(false); + connect(&update_timer, &QTimer::timeout, this, &MultiplayerRoomOverlay::UpdateRoomData); + + setMinimumSize(280, 220); + resize(320, 280); + UpdatePosition(); +} + +MultiplayerRoomOverlay::~MultiplayerRoomOverlay() { + DisconnectFromRoom(); +} + +void MultiplayerRoomOverlay::OnEmulationStarting() { + // When emulation starts, resume updates if we are visible. + if (is_visible) { + ConnectToRoom(); + update_timer.start(500); + } +} + +void MultiplayerRoomOverlay::OnEmulationStopping() { + // CRASH FIX: When emulation stops, immediately disconnect from network objects. + update_timer.stop(); + DisconnectFromRoom(); +} + +void MultiplayerRoomOverlay::SetVisible(bool visible) { + if (is_visible == visible) return; + is_visible = visible; + + if (visible) { + show(); + // Only start connecting and updating if emulation is running. + if (main_window && main_window->IsEmulationRunning()) { + ConnectToRoom(); + update_timer.start(500); + } + } else { + hide(); + update_timer.stop(); + DisconnectFromRoom(); + } +} + +void MultiplayerRoomOverlay::paintEvent(QPaintEvent* event) { + Q_UNUSED(event) + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + QPainterPath background_path; + background_path.addRoundedRect(rect(), corner_radius, corner_radius); + painter.fillPath(background_path, background_color); + painter.setPen(QPen(border_color, border_width)); + painter.drawPath(background_path); +} + +void MultiplayerRoomOverlay::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); if (!has_been_moved) UpdatePosition(); } +bool MultiplayerRoomOverlay::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::MouseButtonPress) { if (chat_room_widget->hasFocus()) { chat_room_widget->clearFocus(); } } return QObject::eventFilter(watched, event); } + +#if defined(Q_OS_LINUX) +void MultiplayerRoomOverlay::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + if (size_grip->geometry().contains(event->pos())) { + // Let the size grip handle the event + } else if (!childAt(event->pos()) || childAt(event->pos()) == this) { + if (windowHandle()) windowHandle()->startSystemMove(); + } + } + QWidget::mousePressEvent(event); +} +void MultiplayerRoomOverlay::mouseMoveEvent(QMouseEvent* event) { QWidget::mouseMoveEvent(event); } +#else +void MultiplayerRoomOverlay::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + if (size_grip->geometry().contains(event->pos())) { + // Let the size grip handle the event + } else if (!childAt(event->pos()) || childAt(event->pos()) == this) { + is_dragging = true; + drag_start_pos = event->globalPosition().toPoint(); + widget_start_pos = this->pos(); + setCursor(Qt::ClosedHandCursor); + } + } + QWidget::mousePressEvent(event); +} +void MultiplayerRoomOverlay::mouseMoveEvent(QMouseEvent* event) { + if (is_dragging) { + QPoint delta = event->globalPosition().toPoint() - drag_start_pos; + move(widget_start_pos + delta); + } + QWidget::mouseMoveEvent(event); // Corrected typo here +} +#endif + +void MultiplayerRoomOverlay::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton && is_dragging) { + is_dragging = false; + has_been_moved = true; + setCursor(Qt::ArrowCursor); + } + QWidget::mouseReleaseEvent(event); +} + +void MultiplayerRoomOverlay::ConnectToRoom() { + if (!main_window) return; + multiplayer_state = main_window->GetMultiplayerState(); + if (!multiplayer_state) return; + + if (multiplayer_state->IsClientRoomVisible()) { + chat_room_widget->setEnabled(false); + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Chat available in main window.")); + return; + } + + chat_room_widget->setEnabled(true); + auto& room_network = multiplayer_state->GetRoomNetwork(); + room_member = room_network.GetRoomMember().lock(); + + if (room_member) { + chat_room_widget->Initialize(&room_network); + } else { + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Not connected to a room.")); + } +} + +void MultiplayerRoomOverlay::DisconnectFromRoom() { + chat_room_widget->Clear(); + room_member.reset(); + multiplayer_state = nullptr; + players_online_label->setText(QString::fromUtf8("Players Online: 0")); +} + +void MultiplayerRoomOverlay::UpdateRoomData() { + if (!multiplayer_state) { + ConnectToRoom(); + return; + } + + if (multiplayer_state->IsClientRoomVisible()) { + if (chat_room_widget->isEnabled()) { + chat_room_widget->setEnabled(false); + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Chat available in main window.")); + } + } else { + if (!chat_room_widget->isEnabled()) { + ConnectToRoom(); + } + } + + if (room_member && room_member->GetState() >= Network::RoomMember::State::Joined) { + const auto& members = room_member->GetMemberInformation(); + QString label_text = QString::fromStdString("Players Online: %1").arg(members.size()); + players_online_label->setText(label_text); + if (chat_room_widget->isEnabled()) { + chat_room_widget->SetPlayerList(members); + } + } else { + players_online_label->setText(QString::fromUtf8("Players Online: 0")); + if (!room_member && !multiplayer_state->IsClientRoomVisible()) { + chat_room_widget->Clear(); + chat_room_widget->AppendStatusMessage(tr("Not connected to a room.")); + ConnectToRoom(); + } + } +} + +void MultiplayerRoomOverlay::UpdatePosition() { + if (!main_window) return; + if (!has_been_moved) { + QPoint main_window_pos = main_window->mapToGlobal(QPoint(0, 0)); + move(main_window_pos.x() + main_window->width() - this->width() - 10, main_window_pos.y() + 10); + } +} From ee47c4d095c1ec8308107336e0a1dbd488ed561b Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:05:32 +0000 Subject: [PATCH 3/8] feat: Add multiplayer_room_overlay files --- src/citron/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/citron/CMakeLists.txt b/src/citron/CMakeLists.txt index 2c05e7360..ebf5e99aa 100644 --- a/src/citron/CMakeLists.txt +++ b/src/citron/CMakeLists.txt @@ -223,6 +223,8 @@ add_executable(citron util/overlay_dialog.ui util/performance_overlay.cpp util/performance_overlay.h + util/multiplayer_room_overlay.cpp + util/multiplayer_room_overlay.h util/vram_overlay.cpp util/vram_overlay.h util/sequence_dialog/sequence_dialog.cpp From d76f2faa69d5902150658b5266a29c28d84687e5 Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:08:25 +0000 Subject: [PATCH 4/8] feat: Add multiplayer-room-overlay --- src/citron/main.h | 322 +++++++--------------------------------------- 1 file changed, 48 insertions(+), 274 deletions(-) diff --git a/src/citron/main.h b/src/citron/main.h index 30ab3f2cc..a994a583b 100644 --- a/src/citron/main.h +++ b/src/citron/main.h @@ -6,14 +6,12 @@ #include #include - #include #include #include #include #include #include - #include "common/announce_multiplayer_room.h" #include "common/common_types.h" #include "configuration/qt_config.h" @@ -39,11 +37,9 @@ class LoadingScreen; class MicroProfileDialog; class OverlayDialog; class PerformanceOverlay; +class MultiplayerRoomOverlay; class VramOverlay; class ProfilerWidget; - -// Forward declaration -class PerformanceOverlay; class ControllerDialog; class QLabel; class MultiplayerState; @@ -58,7 +54,6 @@ enum class GameListShortcutTarget; enum class DumpRomFSTarget; enum class InstalledEntryType; class GameListPlaceholder; - class QtAmiiboSettingsDialog; class QtControllerSelectorDialog; class QtProfileSelectionDialog; @@ -66,75 +61,21 @@ class QtSoftwareKeyboardDialog; class QtNXWebEngineView; class UpdaterDialog; -enum class StartGameType { - Normal, // Can use custom configuration - Global, // Only uses global configuration -}; +enum class StartGameType { Normal, Global }; -namespace Core { - enum class SystemResultStatus : u32; - class System; -} // namespace Core - -namespace Core::Frontend { - struct CabinetParameters; - struct ControllerParameters; - struct InlineAppearParameters; - struct InlineTextParameters; - struct KeyboardInitializeParameters; - struct ProfileSelectParameters; -} // namespace Core::Frontend - -namespace DiscordRPC { - class DiscordInterface; -} - -namespace PlayTime { - class PlayTimeManager; -} - -namespace FileSys { - class ContentProvider; - class ManualContentProvider; - class VfsFilesystem; -} // namespace FileSys - -namespace InputCommon { - class InputSubsystem; -} - -namespace Service::AM { - struct FrontendAppletParameters; - enum class AppletId : u32; -} // namespace Service::AM - -namespace Service::AM::Frontend { - enum class SwkbdResult : u32; - enum class SwkbdTextCheckResult : u32; - enum class SwkbdReplyType : u32; - enum class WebExitReason : u32; -} // namespace Service::AM::Frontend - -namespace Service::NFC { - class NfcDevice; -} // namespace Service::NFC - -namespace Service::NFP { - enum class CabinetMode : u8; -} // namespace Service::NFP - -namespace Ui { - class MainWindow; -} - -enum class EmulatedDirectoryTarget { - NAND, - SDMC, -}; - -namespace VkDeviceInfo { - class Record; -} +namespace Core { enum class SystemResultStatus : u32; class System; } +namespace Core::Frontend { struct CabinetParameters; struct ControllerParameters; struct InlineAppearParameters; struct InlineTextParameters; struct KeyboardInitializeParameters; struct ProfileSelectParameters; } +namespace DiscordRPC { class DiscordInterface; } +namespace PlayTime { class PlayTimeManager; } +namespace FileSys { class ContentProvider; class ManualContentProvider; class VfsFilesystem; } +namespace InputCommon { class InputSubsystem; } +namespace Service::AM { struct FrontendAppletParameters; enum class AppletId : u32; } +namespace Service::AM::Frontend { enum class SwkbdResult : u32; enum class SwkbdTextCheckResult : u32; enum class SwkbdReplyType : u32; enum class WebExitReason : u32; } +namespace Service::NFC { class NfcDevice; } +namespace Service::NFP { enum class CabinetMode : u8; } +namespace Ui { class MainWindow; } +enum class EmulatedDirectoryTarget { NAND, SDMC }; +namespace VkDeviceInfo { class Record; } class VolumeButton : public QPushButton { Q_OBJECT @@ -142,16 +83,12 @@ public: explicit VolumeButton(QWidget* parent = nullptr) : QPushButton(parent), scroll_multiplier(1) { connect(&scroll_timer, &QTimer::timeout, this, &VolumeButton::ResetMultiplier); } - signals: void VolumeChanged(); - protected: void wheelEvent(QWheelEvent* event) override; - private slots: void ResetMultiplier(); - private: int scroll_multiplier; QTimer scroll_timer; @@ -160,94 +97,45 @@ private: class GMainWindow : public QMainWindow { Q_OBJECT - - /// Max number of recently loaded items to keep track of static const int max_recent_files_item = 10; - friend class PerformanceOverlay; friend class VramOverlay; - - enum { - CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, - CREATE_SHORTCUT_MSGBOX_SUCCESS, - CREATE_SHORTCUT_MSGBOX_ERROR, - CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, - }; - + enum { CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, CREATE_SHORTCUT_MSGBOX_SUCCESS, CREATE_SHORTCUT_MSGBOX_ERROR, CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING }; public: void filterBarSetChecked(bool state); void UpdateUITheme(); explicit GMainWindow(std::unique_ptr config_, bool has_broken_vulkan); ~GMainWindow() override; - bool DropAction(QDropEvent* event); void AcceptDropEvent(QDropEvent* event); - - /** - * This is the new function to provide access to the MultiplayerState. - */ - MultiplayerState* GetMultiplayerState() { - return multiplayer_state; - } - + MultiplayerState* GetMultiplayerState() { return multiplayer_state; } + bool IsEmulationRunning() const { return emulation_running; } signals: - - /** - * Signal that is emitted when a new EmuThread has been created and an emulation session is - * about to start. At this time, the core system emulation has been initialized, and all - * emulation handles and memory should be valid. - * - * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to - * access/change emulation state). - */ void EmulationStarting(EmuThread* emu_thread); - - /** - * Signal that is emitted when emulation is about to stop. At this time, the EmuThread and core - * system emulation handles and memory are still valid, but are about become invalid. - */ void EmulationStopping(); - - // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); - void UpdateInstallProgress(); - void AmiiboSettingsFinished(bool is_success, const std::string& name); - void ControllerSelectorReconfigureFinished(bool is_success); - void ErrorDisplayFinished(); - void ProfileSelectorFinishedSelection(std::optional uuid); - - void SoftwareKeyboardSubmitNormalText(Service::AM::Frontend::SwkbdResult result, - std::u16string submitted_text, bool confirmed); - void SoftwareKeyboardSubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type, - std::u16string submitted_text, s32 cursor_position); - + void SoftwareKeyboardSubmitNormalText(Service::AM::Frontend::SwkbdResult result, std::u16string submitted_text, bool confirmed); + void SoftwareKeyboardSubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position); void WebBrowserExtractOfflineRomFS(); void WebBrowserClosed(Service::AM::Frontend::WebExitReason exit_reason, std::string last_url); - void SigInterrupt(); - public slots: void OnLoadComplete(); void OnExecuteProgram(std::size_t program_index); void OnExit(); void OnSaveConfig(); - void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr nfp_device); + void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, std::shared_ptr nfp_device); void AmiiboSettingsRequestExit(); - void ControllerSelectorReconfigureControllers( - const Core::Frontend::ControllerParameters& parameters); + void ControllerSelectorReconfigureControllers(const Core::Frontend::ControllerParameters& parameters); void ControllerSelectorRequestExit(); - void SoftwareKeyboardInitialize( - bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters); + void SoftwareKeyboardInitialize(bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters); void SoftwareKeyboardShowNormal(); - void SoftwareKeyboardShowTextCheck( - Service::AM::Frontend::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message); + void SoftwareKeyboardShowTextCheck(Service::AM::Frontend::SwkbdTextCheckResult text_check_result, std::u16string text_check_message); void SoftwareKeyboardShowInline(Core::Frontend::InlineAppearParameters appear_parameters); void SoftwareKeyboardHideInline(); void SoftwareKeyboardInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); @@ -256,98 +144,51 @@ public slots: void ErrorDisplayRequestExit(); void ProfileSelectorSelectProfile(const Core::Frontend::ProfileSelectParameters& parameters); void ProfileSelectorRequestExit(); - void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args, - bool is_local); + void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args, bool is_local); void WebBrowserRequestExit(); void OnAppFocusStateChanged(Qt::ApplicationState state); void OnTasStateChanged(); - private: - /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry. - void LinkActionShortcut(QAction* action, const QString& action_name, - const bool tas_allowed = false); - + void LinkActionShortcut(QAction* action, const QString& action_name, const bool tas_allowed = false); void RegisterMetaTypes(); void RegisterAutoloaderContents(); - void InitializeWidgets(); void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); - void SetDefaultUIGeometry(); void RestoreUIState(); - void ConnectWidgetEvents(); void ConnectMenuEvents(); void UpdateMenuState(); - void SetupPrepareForSleep(); - void PreventOSSleep(); void AllowOSSleep(); - bool LoadROM(const QString& filename, Service::AM::FrontendAppletParameters params); - void BootGame(const QString& filename, Service::AM::FrontendAppletParameters params, - StartGameType with_config = StartGameType::Normal); + void BootGame(const QString& filename, Service::AM::FrontendAppletParameters params, StartGameType with_config = StartGameType::Normal); void BootGameFromList(const QString& filename, StartGameType with_config); void ShutdownGame(); - void ShowTelemetryCallout(); void SetDiscordEnabled(bool state); void LoadAmiibo(const QString& filename); - bool SelectAndSetCurrentUser(const Core::Frontend::ProfileSelectParameters& parameters); - - /** - * Stores the filename in the recently loaded files list. - * The new filename is stored at the beginning of the recently loaded files list. - * After inserting the new entry, duplicates are removed meaning that if - * this was inserted from \a OnMenuRecentFile(), the entry will be put on top - * and remove from its previous position. - * - * Finally, this function calls \a UpdateRecentFiles() to update the UI. - * - * @param filename the filename to store - */ void StoreRecentFile(const QString& filename); - - /** - * Updates the recent files menu. - * Menu entries are rebuilt from the configuration file. - * If there is no entry in the menu, the menu is greyed out. - */ void UpdateRecentFiles(); - - /** - * If the emulation is running, - * asks the user if he really want to close the emulator - * - * @return true if the user confirmed - */ bool ConfirmClose(); bool ConfirmChangeGame(); bool ConfirmForceLockedExit(); void RequestGameExit(); void changeEvent(QEvent* event) override; void closeEvent(QCloseEvent* event) override; - - std::string CreateTASFramesString( - std::array frames) const; - - #ifdef __unix__ - void SetupSigInterrupts(); - static void HandleSigInterrupt(int); - void OnSigInterruptNotifierActivated(); - void SetGamemodeEnabled(bool state); - #endif - - Service::AM::FrontendAppletParameters ApplicationAppletParameters(); - Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, - Service::AM::AppletId applet_id); - - // This will hold and provide all discovered Autoloader content. - std::unique_ptr autoloader_provider; - + std::string CreateTASFramesString(std::array frames) const; + #ifdef __unix__ + void SetupSigInterrupts(); + static void HandleSigInterrupt(int); + void OnSigInterruptNotifierActivated(); + void SetGamemodeEnabled(bool state); + #endif + Service::AM::FrontendAppletParameters ApplicationAppletParameters(); + Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, Service::AM::AppletId applet_id); + std::unique_ptr autoloader_provider; private slots: void OnStartGame(); void OnRestartGame(); @@ -357,22 +198,17 @@ private slots: void OnPrepareForSleep(bool prepare_sleep); void OnMenuReportCompatibility(); void OnOpenSupport(); - /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path, u64 program_id); - void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, - const std::string& game_path); + void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, const std::string& game_path); void OnTransferableShaderCacheOpenFile(u64 program_id); void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); - void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, - const std::string& game_path); + void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, const std::string& game_path); void OnGameListRemovePlayTimeData(u64 program_id); void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); void OnGameListVerifyIntegrity(const std::string& game_path); void OnGameListCopyTID(u64 program_id); - void OnGameListNavigateToGamedbEntry(u64 program_id, - const CompatibilityList& compatibility_list); - void OnGameListCreateShortcut(u64 program_id, const std::string& game_path, - GameListShortcutTarget target); + void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); + void OnGameListCreateShortcut(u64 program_id, const std::string& game_path, GameListShortcutTarget target); void OnGameListOpenDirectory(const QString& directory); void OnGameListAddDirectory(); void OnGameListShowList(bool show); @@ -411,16 +247,13 @@ private slots: void OnToggleGridView(); void OnToggleStatusBar(); void OnTogglePerformanceOverlay(); + void OnToggleMultiplayerRoomOverlay(); void OnToggleVramOverlay(); void OnDisplayTitleBars(bool); - - // Performance overlay access methods double GetCurrentFPS() const; double GetCurrentFrameTime() const; u32 GetShadersBuilding() const; double GetEmulationSpeed() const; - - // VRAM overlay access methods u64 GetTotalVram() const; u64 GetUsedVram() const; u64 GetBufferMemoryUsage() const; @@ -448,7 +281,6 @@ private slots: void OnShutdownBeginDialog(); void OnEmulationStopped(); void OnEmulationStopTimeExpired(); - private: QString GetGameListErrorRemoving(InstalledEntryType type) const; void RemoveBaseContent(u64 program_id, InstalledEntryType type); @@ -460,12 +292,10 @@ private: void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); void RemovePlayTimeData(u64 program_id); void RemoveCacheStorage(u64 program_id); - bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, - u64* selected_title_id, u8* selected_content_record_type); + bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, u64* selected_title_id, u8* selected_content_record_type); ContentManager::InstallResult InstallNCA(const QString& filename); void MigrateConfigFiles(); - void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, - std::string_view gpu_vendor = {}); + void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, std::string_view gpu_vendor = {}); void UpdateDockedButton(); void UpdateAPIText(); void UpdateFilterText(); @@ -485,54 +315,28 @@ private: bool CheckFirmwarePresence(); void SetFirmwareVersion(); void ConfigureFilesystemProvider(const std::string& filepath); - /** - * Open (or not) the right confirm dialog based on current setting and game exit lock - * @returns true if the player confirmed or the settings do no require it - */ bool ConfirmShutdownGame(); - QString GetTasStateDescription() const; bool CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title); - bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, - std::filesystem::path& out_icon_path); - bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, - const std::filesystem::path& icon_path, - const std::filesystem::path& command, const std::string& arguments, - const std::string& categories, const std::string& keywords, - const std::string& name); - /** - * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog - * The only difference is that it returns a boolean. - * - * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button. - */ - bool question(QWidget* parent, const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = - QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); - + bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, std::filesystem::path& out_icon_path); + bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, const std::filesystem::path& icon_path, const std::filesystem::path& command, const std::string& arguments, const std::string& categories, const std::string& keywords, const std::string& name); + bool question(QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); std::unique_ptr ui; - std::unique_ptr system; std::unique_ptr discord_rpc; std::unique_ptr play_time_manager; std::shared_ptr input_subsystem; - MultiplayerState* multiplayer_state = nullptr; - GRenderWindow* render_window; GameList* game_list; LoadingScreen* loading_screen; QTimer shutdown_timer; OverlayDialog* shutdown_dialog{}; PerformanceOverlay* performance_overlay{}; + MultiplayerRoomOverlay* multiplayer_room_overlay{}; VramOverlay* vram_overlay{}; - GameListPlaceholder* game_list_placeholder; - std::vector vk_device_records; - - // Status bar elements QLabel* message_label = nullptr; QLabel* shader_building_label = nullptr; QLabel* res_scale_label = nullptr; @@ -550,72 +354,42 @@ private: QWidget* volume_popup = nullptr; QSlider* volume_slider = nullptr; QTimer status_bar_update_timer; - std::unique_ptr config; - - // Whether emulation is currently running in citron. bool emulation_running = false; std::unique_ptr emu_thread; - // The path to the game currently running QString current_game_path; - // Whether a user was set on the command line (skips UserSelector if it's forced to show up) bool user_flag_cmd_line = false; - bool auto_paused = false; bool auto_muted = false; QTimer mouse_hide_timer; QTimer update_input_timer; - QString startup_icon_theme; bool os_dark_mode = false; - - // FS std::shared_ptr vfs; std::unique_ptr provider; - - // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; WaitTreeWidget* waitTreeWidget; ControllerDialog* controller_dialog; - QAction* actions_recent_files[max_recent_files_item]; - - // stores default icon theme search paths for the platform QStringList default_theme_paths; - HotkeyRegistry hotkey_registry; - QTranslator translator; - - // Install progress dialog QProgressDialog* install_progress; - - // Last game booted, used for multi-process apps QString last_filename_booted; - - // Applets QtAmiiboSettingsDialog* cabinet_applet = nullptr; QtControllerSelectorDialog* controller_applet = nullptr; QtProfileSelectionDialog* profile_select_applet = nullptr; QDialog* error_applet = nullptr; QtSoftwareKeyboardDialog* software_keyboard = nullptr; QtNXWebEngineView* web_applet = nullptr; - - // True if amiibo file select is visible bool is_amiibo_file_select_active{}; - - // True if load file select is visible bool is_load_file_select_active{}; - - // True if TAS recording dialog is visible bool is_tas_recording_dialog_active{}; - #ifdef __unix__ QSocketNotifier* sig_interrupt_notifier; static std::array sig_interrupt_fds; #endif - protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; From 850a9f8f6d0ec3507da2c2b0dca1c5340e1a4056 Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:09:11 +0000 Subject: [PATCH 5/8] feat: Add multiplayer-room-overlay --- src/citron/main.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/citron/main.cpp b/src/citron/main.cpp index fec137141..eb2cf8f11 100644 --- a/src/citron/main.cpp +++ b/src/citron/main.cpp @@ -180,6 +180,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #endif #include "citron/util/clickable_label.h" #include "citron/util/performance_overlay.h" +#include "citron/util/multiplayer_room_overlay.h" #include "citron/util/vram_overlay.h" #include "citron/vk_device_info.h" @@ -1095,6 +1096,12 @@ void GMainWindow::InitializeWidgets() { performance_overlay = new PerformanceOverlay(this); performance_overlay->hide(); + multiplayer_room_overlay = new MultiplayerRoomOverlay(this); + multiplayer_room_overlay->hide(); + + connect(this, &GMainWindow::EmulationStarting, multiplayer_room_overlay, &MultiplayerRoomOverlay::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, multiplayer_room_overlay, &MultiplayerRoomOverlay::OnEmulationStopping); + vram_overlay = new VramOverlay(this); vram_overlay->hide(); @@ -1379,6 +1386,7 @@ void GMainWindow::InitializeHotkeys() { LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar")); LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay")); LinkActionShortcut(ui->action_Show_Vram_Overlay, QStringLiteral("Toggle VRAM Overlay")); + LinkActionShortcut(ui->action_Show_Multiplayer_Room_Overlay, QStringLiteral("Toggle Multiplayer Room Overlay")); LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen")); LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot")); LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true); @@ -1608,6 +1616,7 @@ void GMainWindow::ConnectMenuEvents() { connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar); connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar); connect_menu(ui->action_Show_Performance_Overlay, &GMainWindow::OnTogglePerformanceOverlay); + connect_menu(ui->action_Show_Multiplayer_Room_Overlay, &GMainWindow::OnToggleMultiplayerRoomOverlay); connect_menu(ui->action_Show_Vram_Overlay, &GMainWindow::OnToggleVramOverlay); connect_menu(ui->action_Toggle_Grid_View, &GMainWindow::OnToggleGridView); @@ -4930,6 +4939,16 @@ void GMainWindow::OnTogglePerformanceOverlay() { } } +void GMainWindow::OnToggleMultiplayerRoomOverlay() { + if (multiplayer_room_overlay) { + const bool is_checked = ui->action_Show_Multiplayer_Room_Overlay->isChecked(); + multiplayer_room_overlay->SetVisible(is_checked); + + // We will add a setting for this later if needed. + // UISettings::values.show_multiplayer_room_overlay = is_checked; + } +} + void GMainWindow::OnToggleVramOverlay() { if (vram_overlay) { const bool is_checked = ui->action_Show_Vram_Overlay->isChecked(); From 25c46b247c620d706400982d83dd793da507004f Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:09:44 +0000 Subject: [PATCH 6/8] feat: Add multiplayer-room-overlay --- src/citron/main.ui | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/citron/main.ui b/src/citron/main.ui index 2b2db061a..de91bf454 100644 --- a/src/citron/main.ui +++ b/src/citron/main.ui @@ -128,6 +128,7 @@ + @@ -335,6 +336,17 @@ Show VRAM Monitor + + + true + + + Show &Multiplayer Room Overlay + + + Show Multiplayer Room Overlay + + true From 15aeee4addf2b73a41af444589678c875237c963 Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:10:20 +0000 Subject: [PATCH 7/8] feat: Add multiplayer-room-overlay --- src/citron/multiplayer/state.h | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/citron/multiplayer/state.h b/src/citron/multiplayer/state.h index 82f20bde2..8ed327885 100644 --- a/src/citron/multiplayer/state.h +++ b/src/citron/multiplayer/state.h @@ -34,20 +34,12 @@ public: QAction* show_room, Core::System& system_); ~MultiplayerState(); - /** - * This is the new function to safely access the multiplayer session. - */ std::shared_ptr GetSession() { return announce_multiplayer_session; } - /** - * Close all open multiplayer related dialogs - */ void Close(); - void SetNotificationStatus(NotificationStatus state); - void UpdateNotificationStatus(); ClickableLabel* GetStatusText() const { @@ -60,18 +52,14 @@ public: void retranslateUi(); - /** - * Whether a public room is being hosted or not. - * When this is true, Web Services configuration should be disabled. - */ + Network::RoomNetwork& GetRoomNetwork() { + return room_network; + } + + bool IsClientRoomVisible() const; // NEW: Declaration for our check + bool IsHostingPublicRoom() const; - void UpdateCredentials(); - - /** - * Updates the multiplayer dialogs with a new game list model. - * This model should be the original model of the game list. - */ void UpdateGameList(QStandardItemModel* game_list); public slots: From 805ca4e04f3b8da346db1178dbfb12ec4cc05c83 Mon Sep 17 00:00:00 2001 From: collecting Date: Fri, 24 Oct 2025 00:11:33 +0000 Subject: [PATCH 8/8] Feat: Add multiplayer-room-overlay --- src/citron/multiplayer/state.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/citron/multiplayer/state.cpp b/src/citron/multiplayer/state.cpp index cd3494c55..e65f44884 100644 --- a/src/citron/multiplayer/state.cpp +++ b/src/citron/multiplayer/state.cpp @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: GPL-2.0-or-later #include #include @@ -334,3 +335,8 @@ void MultiplayerState::UpdateGameList(QStandardItemModel* game_list) { host_room->UpdateGameList(game_list); } } + +// NEW: Definition for our check +bool MultiplayerState::IsClientRoomVisible() const { + return client_room && client_room->isVisible(); +}