[Crash] Add lifecycle breadcrumbs for unattributable native crashes (#7813)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions

* [Crash] Add lifecycle breadcrumbs for unattributable native crashes

Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>

* Restore the native split host button on non-macOS styles

The merged quick-host control composed a QPushButton and a separate autoRaise arrow QToolButton, which Fusion draws as two adjacent controls. Version one of the quick-host branch used a single QToolButton with MenuButtonPopup, which every style draws as one native split button.

Use that split button on every style that can draw one, and keep the plain primary + settings button pair as the native macOS fallback.

* Enable hover zoom and the hover panel by default

Both hover triggers now default to Hover, with the existing 400 ms zoom delay and 200 ms panel delay. The Off trigger remains available for users who want the old behavior.

---------

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
This commit is contained in:
sentry[bot] 2026-11-08 23:06:21 +01:00 • committed by GitHub
parent 40ebd22549
commit 9793a0df4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 114 additions and 73 deletions

View file

@ -182,42 +182,35 @@ GameSelector::GameSelector(AbstractClient *_client,
hostGameAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(hostGameAction, &QAction::triggered, this, &GameSelector::actQuickHost);
// A plain QPushButton, so the quick host sits in the same control family as the
// join buttons. A QPushButton does not take its text, tooltip or enabled state from
// an action, so those are mirrored by hand below and in updateHostControlsEnabled().
hostGameButton = new QPushButton(this);
hostGameButton->setObjectName("hostGameButton");
connect(hostGameButton, &QPushButton::clicked, hostGameAction, &QAction::trigger);
hostGameMenu = new QMenu(this);
hostGameMenu->setObjectName("hostGameMenu");
hostGameMenu->addAction(hostGameAction);
gameSettingsAction = hostGameMenu->addAction(QString());
gameSettingsAction->setObjectName("gameSettingsAction");
gameSettingsAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(gameSettingsAction, &QAction::triggered, this, &GameSelector::actCreate);
// The secondary control: a flat arrow next to the primary button on styles that
// support a split control. The native macOS style does not, so there it is a plain
// sibling button instead (see applyHostGameStyle()).
hostGameMenuButton = new QToolButton(this);
hostGameMenuButton->setObjectName("hostGameMenuButton");
hostGameMenuButton->setArrowType(Qt::DownArrow);
hostGameMenuButton->setAutoRaise(true);
hostGameMenuButton->setPopupMode(QToolButton::InstantPopup);
hostGameMenuButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
// A split QToolButton in its native style: the primary half quick-hosts, and the
// arrow opens the menu with the full form. This is the control on every style that
// can draw one; QMacStyle draws the menu half as a second square bezel instead.
hostGameSplitButton = new QToolButton(this);
hostGameSplitButton->setObjectName("hostGameSplitButton");
hostGameSplitButton->setDefaultAction(hostGameAction);
hostGameSplitButton->setPopupMode(QToolButton::MenuButtonPopup);
hostGameSplitButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
hostGameSplitButton->setMenu(hostGameMenu);
// The native macOS style has no split rendering, so there the primary stays a plain
// QPushButton next to a plain settings button. A QPushButton does not take its text,
// tooltip or enabled state from an action, so those are mirrored by hand below and
// in updateHostControlsEnabled().
hostGameButton = new QPushButton(this);
hostGameButton->setObjectName("hostGameButton");
connect(hostGameButton, &QPushButton::clicked, hostGameAction, &QAction::trigger);
hostGameSettingsButton = new QPushButton(this);
hostGameSettingsButton->setObjectName("hostGameSettingsButton");
connect(hostGameSettingsButton, &QPushButton::clicked, gameSettingsAction, &QAction::trigger);
hostGameButtonWidget = new QWidget(this);
auto *hostGameButtonLayout = new QHBoxLayout(hostGameButtonWidget);
hostGameButtonLayout->setContentsMargins(0, 0, 0, 0);
hostGameButtonLayout->setSpacing(0);
hostGameButtonLayout->addWidget(hostGameButton);
hostGameButtonLayout->addWidget(hostGameMenuButton);
applyHostGameStyle();
// The shortcuts belong to the whole room tab, not just the game list, so they also
@ -244,7 +237,9 @@ GameSelector::GameSelector(AbstractClient *_client,
}
buttonLayout->addStretch();
if (room) {
buttonLayout->addWidget(hostGameButtonWidget);
// Only the form that matches the active style is visible; hidden widgets take no room.
buttonLayout->addWidget(hostGameSplitButton);
buttonLayout->addWidget(hostGameButton);
buttonLayout->addWidget(hostGameSettingsButton);
}
buttonLayout->addWidget(joinButton);
@ -435,23 +430,17 @@ void GameSelector::clearHostError()
void GameSelector::applyHostGameStyle()
{
if (hostGameButton == nullptr || hostGameMenu == nullptr || hostGameMenuButton == nullptr ||
hostGameSettingsButton == nullptr) {
if (hostGameSplitButton == nullptr || hostGameButton == nullptr || hostGameSettingsButton == nullptr) {
return;
}
// The primary button always quick-hosts. The native macOS style has no split-button
// rendering (a QToolButton menu becomes a second square bezel), so the secondary
// action is a plain sibling button there; every other style gets the split arrow.
if (usesMacStyle()) {
hostGameMenuButton->setMenu(nullptr);
hostGameMenuButton->setVisible(false);
hostGameSettingsButton->setVisible(true);
} else {
hostGameMenuButton->setMenu(hostGameMenu);
hostGameMenuButton->setVisible(true);
hostGameSettingsButton->setVisible(false);
}
// Every style that can draw a split button gets one. The native macOS style cannot
// (QMacStyle draws the menu half as a second square bezel), so it gets the plain
// primary button and a sibling settings button instead.
const bool macStyle = usesMacStyle();
hostGameSplitButton->setVisible(!macStyle);
hostGameButton->setVisible(macStyle);
hostGameSettingsButton->setVisible(macStyle);
// The appearance settings can switch the style while the room is open.
updateHostGameToolTip();
@ -482,8 +471,8 @@ void GameSelector::updateHostControlsEnabled()
if (hostGameButton) {
hostGameButton->setEnabled(hostControlsEnabled);
}
if (hostGameMenuButton) {
hostGameMenuButton->setEnabled(hostControlsEnabled);
if (hostGameSplitButton) {
hostGameSplitButton->setEnabled(hostControlsEnabled);
}
if (hostGameSettingsButton) {
hostGameSettingsButton->setEnabled(hostControlsEnabled);
@ -740,10 +729,10 @@ void GameSelector::retranslateUi()
filterButton->setText(tr("&Filter games"));
clearFilterButton->setText(tr("C&lear filter"));
if (hostGameAction) {
hostGameAction->setText(tr("Host with last settings"));
hostGameAction->setText(tr("Host game"));
if (hostGameButton) {
// A QPushButton does not draw text from an action; the button keeps the short
// label while the menu item spells out what the remembered settings do.
// A QPushButton does not draw text from an action; on macOS this button is the
// primary action, so it carries the same label.
hostGameButton->setText(tr("Host game"));
}
updateHostGameToolTip();
@ -755,11 +744,6 @@ void GameSelector::retranslateUi()
// secondary action, so it carries the same label.
hostGameSettingsButton->setText(tr("Game settings…"));
}
if (hostGameMenuButton) {
// The split arrow has no text of its own; name it for the menu it opens.
hostGameMenuButton->setToolTip(tr("More host options"));
hostGameMenuButton->setAccessibleName(tr("More host options"));
}
}
joinButton->setText(tr("&Join"));
joinAsJudgeButton->setText(tr("Join as judge"));
@ -794,6 +778,9 @@ void GameSelector::updateHostGameToolTip()
if (hostGameButton) {
hostGameButton->setToolTip(quickHostTooltip);
}
if (hostGameSplitButton) {
hostGameSplitButton->setToolTip(quickHostTooltip);
}
if (hostGameSettingsButton) {
hostGameSettingsButton->setToolTip(tr("Open the game settings before hosting"));
}

View file

@ -164,10 +164,9 @@ private:
QPushButton *filterButton; /**< Button to open the filter dialog. */
QPushButton *clearFilterButton; /**< Button to clear active filters. */
QPushButton *hostGameButton = nullptr; /**< Host button, in the same family as the join buttons. */
QToolButton *hostGameSplitButton = nullptr; /**< Split host button, on every style that can draw one. */
QPushButton *hostGameButton = nullptr; /**< Primary quick-host button, on the native macOS style only. */
QPushButton *hostGameSettingsButton = nullptr; /**< Opens the host form; a sibling button on macOS. */
QToolButton *hostGameMenuButton = nullptr; /**< Split arrow that opens the host menu (non-macOS styles only). */
QWidget *hostGameButtonWidget = nullptr; /**< Container that keeps the split button and its arrow together. */
QMenu *hostGameMenu = nullptr; /**< Menu behind the split arrow (non-macOS styles only). */
QAction *hostGameAction = nullptr; /**< Quick-host action, carrying the keyboard shortcut. */
QAction *gameSettingsAction = nullptr; /**< Opens the host form with the current settings. */
@ -193,9 +192,9 @@ private:
/**
* @brief Rebuilds the host control for the active style.
*
* The primary button always quick-hosts. Every other style gets the split button,
* whose arrow opens the menu; the native macOS style has no split control, so it
* gets a plain sibling button for the secondary action instead.
* The primary button always quick-hosts. Every style that can draw a split control
* gets one, whose arrow opens the menu; the native macOS style has no split control,
* so it gets a plain sibling button for the secondary action instead.
*/
void applyHostGameStyle();
/** @brief Marks a quick-host command as in flight or finished, then updates the host controls. */

View file

@ -1,5 +1,6 @@
#include "user_list_widget.h"
#include "../../../../client/crash/crash_reporter.h"
#include "../../../../client/settings/cache_settings.h"
#include "../../interface/pixel_map_generator.h"
#include "../../interface/theme_manager.h"
@ -1221,6 +1222,7 @@ void UserListWidget::processUserInfo(Section section, const ServerInfo_User &use
bool UserListWidget::deleteUser(const QString &userName)
{
CrashReporter::addBreadcrumb("users", "user list row removed");
if (sectioned) {
// The user may own several rows (one per section). Drop them all.
bool removed = false;

View file

@ -1,5 +1,6 @@
#include "tab_supervisor.h"
#include "../../../client/crash/crash_reporter.h"
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../intents/contexts/context_connect_to_server.h"
@ -593,6 +594,7 @@ void TabSupervisor::startLocal(const QList<AbstractClient *> &_clients)
*/
void TabSupervisor::stop()
{
CrashReporter::addBreadcrumb("tabs", "tab supervisor stopping");
if ((!client) && localClients.isEmpty()) {
return;
}
@ -1003,6 +1005,7 @@ void TabSupervisor::updateLatencyTooltip(const LatencyTracker::Stats &stats)
void TabSupervisor::gameJoined(const Event_GameJoined &event)
{
CrashReporter::addBreadcrumb("tabs", "game joined");
QMap<int, QString> roomGameTypes;
TabRoom *room = roomTabs.value(event.game_info().room_id());
if (room) {
@ -1055,6 +1058,7 @@ void TabSupervisor::gameLeft(TabGame *tab)
void TabSupervisor::addRoomTab(const ServerInfo_Room &info, bool setCurrent)
{
CrashReporter::addBreadcrumb("tabs", "room tab added");
auto *tab = new TabRoom(this, client, userInfo, info);
connect(tab, &TabRoom::maximizeClient, this, &TabSupervisor::maximizeMainWindow);
connect(tab, &TabRoom::roomClosing, this, &TabSupervisor::roomLeft);
@ -1472,6 +1476,7 @@ void TabSupervisor::actShowPopup(const QString &message)
void TabSupervisor::processUserLeft(const QString &userName)
{
CrashReporter::addBreadcrumb("users", "user left");
TabMessage *tab = messageTabs.value(userName);
if (tab) {
tab->processUserLeft();