Skip to content

Commit

Permalink
FEAT(client): Implement tray icon highlighting
Browse files Browse the repository at this point in the history
Previously, only "window highlighting" was supported
by Mumble. However, when Mumble was minimized to tray,
there was no way to observe that highlighting.

As requested in mumble-voip#4584, this commit introduces highlighting
to the tray icon. When a highlight message is received, the
tray icon will flash the "information icon" every two seconds
until the MainWindow of Mumble receives focus again.

To enable this for a specific message type, the user needs to
check the "highlight" box in the "Messages" configuration dialog.

Fixes mumble-voip#4584
  • Loading branch information
Hartmnt committed Jan 2, 2025
1 parent 01e3ea3 commit 2d02a04
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 6 deletions.
4 changes: 3 additions & 1 deletion src/mumble/Log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ Log::Log(QObject *p) : QObject(p) {
#endif
uiLastId = 0;
qdDate = QDate::currentDate();

QObject::connect(this, &Log::highlightSpawned, Global::get().mw, &MainWindow::highlightWindow);
}

// Display order in settingsscreen, allows to insert new events without breaking config-compatibility with older
Expand Down Expand Up @@ -812,7 +814,7 @@ void Log::log(MsgType mt, const QString &console, const QString &terse, bool own
if (!(Global::get().mw->isActiveWindow() && Global::get().mw->qdwLog->isVisible())) {
// Message notification with window highlight
if (flags & Settings::LogHighlight) {
QApplication::alert(Global::get().mw);
emit highlightSpawned();
}

// Message notification with balloon tooltips
Expand Down
3 changes: 3 additions & 0 deletions src/mumble/Log.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ public slots:
signals:
/// Signal emitted when there was a message received whose type was configured to spawn a notification
void notificationSpawned(QString title, QString body, QSystemTrayIcon::MessageIcon icon);

/// Signal emitted when there was a message received whose type was configured to highlight the application
void highlightSpawned();
};

class LogMessage {
Expand Down
13 changes: 13 additions & 0 deletions src/mumble/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ MainWindow::MainWindow(QWidget *p)
SvgIcon::addSvgPixmapsToIcon(qiTalkingOn, QLatin1String("skin:talking_on.svg"));
SvgIcon::addSvgPixmapsToIcon(qiTalkingShout, QLatin1String("skin:talking_alt.svg"));
SvgIcon::addSvgPixmapsToIcon(qiTalkingWhisper, QLatin1String("skin:talking_whisper.svg"));
SvgIcon::addSvgPixmapsToIcon(m_iconInformation, QLatin1String("skin:Information_icon.svg"));

#ifdef Q_OS_MAC
if (QFile::exists(QLatin1String("skin:mumble.icns")))
Expand Down Expand Up @@ -718,6 +719,14 @@ void MainWindow::changeEvent(QEvent *e) {
}
}

// Unhighlight the tray icon when receiving focus
if (e->type() == QEvent::ActivationChange) {
if (isActiveWindow() && Global::get().trayIcon != nullptr) {
emit windowActivated();
}
return;
}

QWidget::changeEvent(e);
}

Expand Down Expand Up @@ -3633,6 +3642,10 @@ void MainWindow::showRaiseWindow() {
});
}

void MainWindow::highlightWindow() {
QApplication::alert(this);
}

void MainWindow::on_qaTalkingUIToggle_triggered() {
if (!Global::get().talkingUI) {
qCritical("MainWindow: Attempting to show Talking UI before it has been created!");
Expand Down
5 changes: 5 additions & 0 deletions src/mumble/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindow {
QIcon qiIcon, qiIconMutePushToMute, qiIconMuteSelf, qiIconMuteServer, qiIconDeafSelf, qiIconDeafServer,
qiIconMuteSuppressed;
QIcon qiTalkingOn, qiTalkingWhisper, qiTalkingShout, qiTalkingOff;
QIcon m_iconInformation;
std::unordered_map< unsigned int, qt_unique_ptr< UserLocalNicknameDialog > > qmUserNicknameTracker;

/// "Action" for when there are no actions available
Expand Down Expand Up @@ -387,6 +388,8 @@ public slots:
void toggleSearchDialogVisibility();
/// Enables or disables the recording feature
void enableRecording(bool recordingAllowed);
/// Invokes OS native window highlighting
void highlightWindow();
signals:
/// Signal emitted when the server and the client have finished
/// synchronizing (after a new connection).
Expand All @@ -404,6 +407,8 @@ public slots:

/// Signal emitted when the window manager notifies the Mumble MainWindow that the application was just minimized
void windowMinimized();
/// Signal emitted whenever the Mumble MainWindow regains the active state from the window manager
void windowActivated();

public:
MainWindow(QWidget *parent);
Expand Down
44 changes: 42 additions & 2 deletions src/mumble/widgets/TrayIcon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include <QApplication>

TrayIcon::TrayIcon() : QSystemTrayIcon(Global::get().mw), m_statusIcon(Global::get().mw->qiIcon) {
setIcon(m_statusIcon);
applyIcon(m_statusIcon);

setToolTip("Mumble");

Expand All @@ -27,6 +27,13 @@ TrayIcon::TrayIcon() : QSystemTrayIcon(Global::get().mw), m_statusIcon(Global::g
Global::get().l, &Log::notificationSpawned, this,
[this](QString title, QString body, QSystemTrayIcon::MessageIcon icon) { showMessage(title, body, icon); });

QObject::connect(Global::get().l, &Log::highlightSpawned, this, &TrayIcon::on_timer_triggered);
QObject::connect(Global::get().mw, &MainWindow::windowActivated, this, &TrayIcon::on_tray_unhighlight);

m_highlightTimer = new QTimer(this);
m_highlightTimer->setSingleShot(true);
QObject::connect(m_highlightTimer, &QTimer::timeout, this, &TrayIcon::on_timer_triggered);

QObject::connect(this, &QSystemTrayIcon::activated, this, &TrayIcon::on_icon_clicked);

// messageClicked is buggy in Qt on some platforms and we can not do anything about this (QTBUG-87329)
Expand Down Expand Up @@ -88,7 +95,7 @@ void TrayIcon::on_icon_update() {

if (&newIcon.get() != &m_statusIcon.get()) {
m_statusIcon = newIcon;
setIcon(m_statusIcon);
applyIcon(m_statusIcon);
}
}

Expand Down Expand Up @@ -129,6 +136,11 @@ void TrayIcon::updateContextMenu() {
m_contextMenu->addAction(Global::get().mw->qaQuit);
}

void TrayIcon::applyIcon(QIcon &icon) {
setIcon(icon);
m_currentIcon = &icon;
}

void TrayIcon::toggleShowHide() {
if (Global::get().mw->isVisible() && !Global::get().mw->isMinimized()) {
on_hideAction_triggered();
Expand Down Expand Up @@ -175,3 +187,31 @@ void TrayIcon::on_windowMinimized() {

on_hideAction_triggered();
}

void TrayIcon::on_tray_unhighlight() {
if (m_highlightTimer == nullptr || !m_highlightTimer->isActive()) {
return;
}

m_highlightTimer->stop();
applyIcon(m_statusIcon);
}

void TrayIcon::on_timer_triggered() {
// We implement tray icon "highlighting" by blinking the
// current status icon every few seconds until the MainWindow
// receives focus.
// This will only be happening, if the user selects "highlight"
// for a specific message in the messages settings table.
// Normal window highlighting - which desktops usually implement
// by blinking the application in the task bar - is invisible
// if the application is hidden to tray.

if (m_currentIcon == &m_statusIcon.get()) {
applyIcon(Global::get().mw->m_iconInformation);
m_highlightTimer->start(500);
} else {
applyIcon(m_statusIcon);
m_highlightTimer->start(2000);
}
}
14 changes: 11 additions & 3 deletions src/mumble/widgets/TrayIcon.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <functional>

#include <QAction>
#include <QTimer>
#include <QtWidgets/QMenu>
#include <QtWidgets/QSystemTrayIcon>

Expand All @@ -24,17 +25,24 @@ public slots:
void on_hideAction_triggered();
void on_showAction_triggered();

void on_icon_update();
void on_tray_unhighlight();

private:
std::reference_wrapper< QIcon > m_statusIcon;
QMenu *m_contextMenu = nullptr;
QAction *m_showAction = nullptr;
QAction *m_hideAction = nullptr;
QIcon *m_currentIcon = nullptr;
QMenu *m_contextMenu = nullptr;
QAction *m_showAction = nullptr;
QAction *m_hideAction = nullptr;
QTimer *m_highlightTimer = nullptr;

void updateContextMenu();
void applyIcon(QIcon &icon);

private slots:
void on_icon_clicked(QSystemTrayIcon::ActivationReason reason);
void on_windowMinimized();
void on_timer_triggered();
};

#endif // MUMBLE_MUMBLE_WIDGETS_TRAYICON_H_

0 comments on commit 2d02a04

Please sign in to comment.