diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt
index fa41283bcca..aef67ab2ef8 100644
--- a/src/mumble/CMakeLists.txt
+++ b/src/mumble/CMakeLists.txt
@@ -309,6 +309,8 @@ set(MUMBLE_SOURCES
 	"widgets/SearchDialogTree.h"
 	"widgets/SemanticSlider.cpp"
 	"widgets/SemanticSlider.h"
+	"widgets/TrayIcon.cpp"
+	"widgets/TrayIcon.h"
 
 
 	"${SHARED_SOURCE_DIR}/ACL.cpp"
diff --git a/src/mumble/Global.cpp b/src/mumble/Global.cpp
index 06cab015838..de46978b0fd 100644
--- a/src/mumble/Global.cpp
+++ b/src/mumble/Global.cpp
@@ -93,6 +93,7 @@ void Global::migrateDataDir(const QDir &toDir) {
 
 Global::Global(const QString &qsConfigPath) {
 	mw              = 0;
+	trayIcon        = 0;
 	db              = 0;
 	pluginManager   = 0;
 	nam             = 0;
diff --git a/src/mumble/Global.h b/src/mumble/Global.h
index c129d26e214..3f624e67fa4 100644
--- a/src/mumble/Global.h
+++ b/src/mumble/Global.h
@@ -34,6 +34,7 @@ class OverlayClient;
 class LogEmitter;
 class DeveloperConsole;
 class TalkingUI;
+class TrayIcon;
 
 class QNetworkAccessManager;
 
@@ -50,6 +51,7 @@ struct Global Q_DECL_FINAL {
 	static Global &get();
 
 	MainWindow *mw;
+	TrayIcon *trayIcon;
 	Settings s;
 	boost::shared_ptr< ServerHandler > sh;
 	boost::shared_ptr< AudioInput > ai;
diff --git a/src/mumble/Log_unix.cpp b/src/mumble/Log_unix.cpp
index 7e9159dcbab..c6006edef34 100644
--- a/src/mumble/Log_unix.cpp
+++ b/src/mumble/Log_unix.cpp
@@ -1,14 +1,25 @@
-// Copyright 2012-2023 The Mumble Developers. All rights reserved.
+// Copyright 2012-2024 The Mumble Developers. All rights reserved.
 // Use of this source code is governed by a BSD-style license
 // that can be found in the LICENSE file at the root of the
 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
 
 #include "Log.h"
-#include "MainWindow.h"
-#include "Settings.h"
-
-#include <QDBusInterface>
+#include "Global.h"
+#include "widgets/TrayIcon.h"
 
 void Log::postNotification(MsgType mt, const QString &plain) {
-	// FIXME
+	QSystemTrayIcon::MessageIcon msgIcon;
+	switch (mt) {
+		case DebugInfo:
+		case CriticalError:
+			msgIcon = QSystemTrayIcon::Critical;
+			break;
+		case Warning:
+			msgIcon = QSystemTrayIcon::Warning;
+			break;
+		default:
+			msgIcon = QSystemTrayIcon::Information;
+			break;
+	}
+	Global::get().trayIcon->showMessage(msgName(mt), plain, msgIcon);
 }
diff --git a/src/mumble/Log_win.cpp b/src/mumble/Log_win.cpp
index 04384099f0f..c6006edef34 100644
--- a/src/mumble/Log_win.cpp
+++ b/src/mumble/Log_win.cpp
@@ -1,10 +1,25 @@
-// Copyright 2012-2023 The Mumble Developers. All rights reserved.
+// Copyright 2012-2024 The Mumble Developers. All rights reserved.
 // Use of this source code is governed by a BSD-style license
 // that can be found in the LICENSE file at the root of the
 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
 
 #include "Log.h"
+#include "Global.h"
+#include "widgets/TrayIcon.h"
 
 void Log::postNotification(MsgType mt, const QString &plain) {
-	// FIXME
+	QSystemTrayIcon::MessageIcon msgIcon;
+	switch (mt) {
+		case DebugInfo:
+		case CriticalError:
+			msgIcon = QSystemTrayIcon::Critical;
+			break;
+		case Warning:
+			msgIcon = QSystemTrayIcon::Warning;
+			break;
+		default:
+			msgIcon = QSystemTrayIcon::Information;
+			break;
+	}
+	Global::get().trayIcon->showMessage(msgName(mt), plain, msgIcon);
 }
diff --git a/src/mumble/main.cpp b/src/mumble/main.cpp
index be28b37cf8e..241550fd297 100644
--- a/src/mumble/main.cpp
+++ b/src/mumble/main.cpp
@@ -51,6 +51,8 @@
 #include "VersionCheck.h"
 #include "Global.h"
 
+#include "widgets/TrayIcon.h"
+
 #include <QLocale>
 #include <QScreen>
 #include <QtCore/QProcess>
@@ -684,6 +686,8 @@ int main(int argc, char **argv) {
 	Global::get().mw = new MainWindow(nullptr);
 	Global::get().mw->show();
 
+	Global::get().trayIcon = new TrayIcon(Global::get().mw);
+
 	Global::get().talkingUI = new TalkingUI();
 
 	// Set TalkingUI's position
diff --git a/src/mumble/widgets/TrayIcon.cpp b/src/mumble/widgets/TrayIcon.cpp
new file mode 100644
index 00000000000..d8064d86320
--- /dev/null
+++ b/src/mumble/widgets/TrayIcon.cpp
@@ -0,0 +1,70 @@
+// Copyright 2024 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "TrayIcon.h"
+
+#include "../ClientUser.h"
+#include "../Global.h"
+#include "../MainWindow.h"
+
+#include <QDebug>
+
+TrayIcon::TrayIcon(QObject *parent) : QSystemTrayIcon(parent), m_requestedIcon(nullptr) {
+	setIcon(Global::get().mw->qiIcon);
+	setToolTip("Mumble");
+	show();
+}
+
+TrayIcon::updateIcon() {
+	ClientUser *p = ClientUser::get(Global::get().uiSession);
+
+	if (Global::get().s.bDeaf) {
+		m_requestedIcon = &Global::get().mw->qiIconDeafSelf;
+	} else if (p && p->bDeaf) {
+		m_requestedIcon = &Global::get().mw->qiIconDeafServer;
+	} else if (Global::get().s.bMute) {
+		m_requestedIcon = &Global::get().mw->qiIconMuteSelf;
+	} else if (p && p->bMute) {
+		m_requestedIcon = &Global::get().mw->qiIconMuteServer;
+	} else if (p && p->bSuppress) {
+		m_requestedIcon = &Global::get().mw->qiIconMuteSuppressed;
+	} else if (Global::get().s.bStateInTray && Global::get().bPushToMute) {
+		m_requestedIcon = &Global::get().mw->qiIconMutePushToMute;
+	} else if (p && Global::get().s.bStateInTray) {
+		switch (p->tsState) {
+			case Settings::Talking:
+			case Settings::MutedTalking:
+				m_requestedIcon = &Global::get().mw->qiTalkingOn;
+				break;
+			case Settings::Whispering:
+				m_requestedIcon = &Global::get().mw->qiTalkingWhisper;
+				break;
+			case Settings::Shouting:
+				m_requestedIcon = &Global::get().mw->qiTalkingShout;
+				break;
+			case Settings::Passive:
+			default:
+				m_requestedIcon = &Global::get().mw->qiTalkingOff;
+				break;
+		}
+	} else {
+		m_requestedIcon = &Global::get().mw->qiIcon;
+	}
+
+	setIcon(*m_requestedIcon);
+}
+
+TrayIcon::on_icon_clicked(QSystemTrayIcon::ActivationReason reason) {
+	qDebug() << reason;
+	switch (reason) {
+		case QSystemTrayIcon::Unknown:
+		case QSystemTrayIcon::Context:
+		case QSystemTrayIcon::DoubleClick:
+		case QSystemTrayIcon::Trigger:
+		case QSystemTrayIcon::MiddleClick:
+		default:
+			break;
+	}
+}
diff --git a/src/mumble/widgets/TrayIcon.h b/src/mumble/widgets/TrayIcon.h
new file mode 100644
index 00000000000..ba45d805c19
--- /dev/null
+++ b/src/mumble/widgets/TrayIcon.h
@@ -0,0 +1,25 @@
+// Copyright 2024 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef MUMBLE_MUMBLE_WIDGETS_TRAYICCON_H_
+#define MUMBLE_MUMBLE_WIDGETS_TRAYICCON_H_
+
+#include <QtWidgets/QSystemTrayIcon>
+
+class TrayIcon : public QSystemTrayIcon {
+	Q_OBJECT
+
+public:
+	TrayIcon(QObject *parent);
+
+	void updateIcon();
+
+private:
+	QIcon *m_statusIcon;
+
+	void on_icon_clicked(QSystemTrayIcon::ActivationReason reason);
+};
+
+#endif // MUMBLE_MUMBLE_WIDGETS_TRAYICCON_H_