Skip to content

Commit e3e7d01

Browse files
committed
Toast notifications support
1 parent 17ec58c commit e3e7d01

13 files changed

+1985
-44
lines changed

ChangeLog.txt

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Default Qt Client
1313
- Option to choose bitween text or emoji for user and channel info in channel list
1414
- Last login time shown in "User Accounts" dialog
1515
- Ability to sort by "Last Login Time" in "User Accounts" dialog
16+
- Ability to send toast notifications
1617
- WebRTC updated from r4332 to r6818
1718
- Qt updated to 6.9.0beta3 on macOS and Windows
1819
- Fixed gender changed from female to male when migrating to 5.17 release

Client/qtTeamTalk/3rdparty/WinToast/wintoastlib.cpp

+1,490
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
/**
2+
* MIT License
3+
*
4+
* Copyright (C) 2016-2023 WinToast v1.3.0 - Mohammed Boujemaoui <mohabouje@gmail.com>
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
7+
* this software and associated documentation files (the "Software"), to deal in
8+
* the Software without restriction, including without limitation the rights to
9+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10+
* the Software, and to permit persons to whom the Software is furnished to do so,
11+
* subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*/
23+
24+
#ifndef WINTOASTLIB_H
25+
#define WINTOASTLIB_H
26+
27+
#include <Windows.h>
28+
#include <sdkddkver.h>
29+
#include <WinUser.h>
30+
#include <ShObjIdl.h>
31+
#include <wrl/implements.h>
32+
#include <wrl/event.h>
33+
#include <windows.ui.notifications.h>
34+
#include <strsafe.h>
35+
#include <Psapi.h>
36+
#include <ShlObj.h>
37+
#include <roapi.h>
38+
#include <propvarutil.h>
39+
#include <functiondiscoverykeys.h>
40+
#include <iostream>
41+
#include <winstring.h>
42+
#include <string.h>
43+
#include <vector>
44+
#include <map>
45+
#include <memory>
46+
47+
using namespace Microsoft::WRL;
48+
using namespace ABI::Windows::Data::Xml::Dom;
49+
using namespace ABI::Windows::Foundation;
50+
using namespace ABI::Windows::UI::Notifications;
51+
using namespace Windows::Foundation;
52+
53+
namespace WinToastLib {
54+
55+
class IWinToastHandler {
56+
public:
57+
enum WinToastDismissalReason {
58+
UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
59+
ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
60+
TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
61+
};
62+
63+
virtual ~IWinToastHandler() = default;
64+
virtual void toastActivated() const = 0;
65+
virtual void toastActivated(int actionIndex) const = 0;
66+
virtual void toastActivated(const char* response) const = 0;
67+
virtual void toastDismissed(WinToastDismissalReason state) const = 0;
68+
virtual void toastFailed() const = 0;
69+
};
70+
71+
class WinToastTemplate {
72+
public:
73+
enum class Scenario { Default, Alarm, IncomingCall, Reminder };
74+
enum Duration { System, Short, Long };
75+
enum AudioOption { Default = 0, Silent, Loop };
76+
enum TextField { FirstLine = 0, SecondLine, ThirdLine };
77+
78+
enum WinToastTemplateType {
79+
ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
80+
ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
81+
ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
82+
ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
83+
Text01 = ToastTemplateType::ToastTemplateType_ToastText01,
84+
Text02 = ToastTemplateType::ToastTemplateType_ToastText02,
85+
Text03 = ToastTemplateType::ToastTemplateType_ToastText03,
86+
Text04 = ToastTemplateType::ToastTemplateType_ToastText04
87+
};
88+
89+
enum AudioSystemFile {
90+
DefaultSound,
91+
IM,
92+
Mail,
93+
Reminder,
94+
SMS,
95+
Alarm,
96+
Alarm2,
97+
Alarm3,
98+
Alarm4,
99+
Alarm5,
100+
Alarm6,
101+
Alarm7,
102+
Alarm8,
103+
Alarm9,
104+
Alarm10,
105+
Call,
106+
Call1,
107+
Call2,
108+
Call3,
109+
Call4,
110+
Call5,
111+
Call6,
112+
Call7,
113+
Call8,
114+
Call9,
115+
Call10,
116+
};
117+
118+
enum CropHint {
119+
Square,
120+
Circle,
121+
};
122+
123+
WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
124+
~WinToastTemplate();
125+
126+
void setFirstLine(_In_ std::wstring const& text);
127+
void setSecondLine(_In_ std::wstring const& text);
128+
void setThirdLine(_In_ std::wstring const& text);
129+
void setTextField(_In_ std::wstring const& txt, _In_ TextField pos);
130+
void setAttributionText(_In_ std::wstring const& attributionText);
131+
void setImagePath(_In_ std::wstring const& imgPath, _In_ CropHint cropHint = CropHint::Square);
132+
void setHeroImagePath(_In_ std::wstring const& imgPath, _In_ bool inlineImage = false);
133+
void setAudioPath(_In_ WinToastTemplate::AudioSystemFile audio);
134+
void setAudioPath(_In_ std::wstring const& audioPath);
135+
void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
136+
void setDuration(_In_ Duration duration);
137+
void setExpiration(_In_ INT64 millisecondsFromNow);
138+
void setScenario(_In_ Scenario scenario);
139+
void addAction(_In_ std::wstring const& label);
140+
void addInput();
141+
142+
std::size_t textFieldsCount() const;
143+
std::size_t actionsCount() const;
144+
bool hasImage() const;
145+
bool hasHeroImage() const;
146+
std::vector<std::wstring> const& textFields() const;
147+
std::wstring const& textField(_In_ TextField pos) const;
148+
std::wstring const& actionLabel(_In_ std::size_t pos) const;
149+
std::wstring const& imagePath() const;
150+
std::wstring const& heroImagePath() const;
151+
std::wstring const& audioPath() const;
152+
std::wstring const& attributionText() const;
153+
std::wstring const& scenario() const;
154+
INT64 expiration() const;
155+
WinToastTemplateType type() const;
156+
WinToastTemplate::AudioOption audioOption() const;
157+
Duration duration() const;
158+
bool isToastGeneric() const;
159+
bool isInlineHeroImage() const;
160+
bool isCropHintCircle() const;
161+
bool isInput() const;
162+
163+
private:
164+
bool _hasInput{false};
165+
166+
std::vector<std::wstring> _textFields{};
167+
std::vector<std::wstring> _actions{};
168+
std::wstring _imagePath{};
169+
std::wstring _heroImagePath{};
170+
bool _inlineHeroImage{false};
171+
std::wstring _audioPath{};
172+
std::wstring _attributionText{};
173+
std::wstring _scenario{L"Default"};
174+
INT64 _expiration{0};
175+
AudioOption _audioOption{WinToastTemplate::AudioOption::Default};
176+
WinToastTemplateType _type{WinToastTemplateType::Text01};
177+
Duration _duration{Duration::System};
178+
CropHint _cropHint{CropHint::Square};
179+
};
180+
181+
class WinToast {
182+
public:
183+
enum WinToastError {
184+
NoError = 0,
185+
NotInitialized,
186+
SystemNotSupported,
187+
ShellLinkNotCreated,
188+
InvalidAppUserModelID,
189+
InvalidParameters,
190+
InvalidHandler,
191+
NotDisplayed,
192+
UnknownError
193+
};
194+
195+
enum ShortcutResult {
196+
SHORTCUT_UNCHANGED = 0,
197+
SHORTCUT_WAS_CHANGED = 1,
198+
SHORTCUT_WAS_CREATED = 2,
199+
200+
SHORTCUT_MISSING_PARAMETERS = -1,
201+
SHORTCUT_INCOMPATIBLE_OS = -2,
202+
SHORTCUT_COM_INIT_FAILURE = -3,
203+
SHORTCUT_CREATE_FAILED = -4
204+
};
205+
206+
enum ShortcutPolicy {
207+
/* Don't check, create, or modify a shortcut. */
208+
SHORTCUT_POLICY_IGNORE = 0,
209+
/* Require a shortcut with matching AUMI, don't create or modify an existing one. */
210+
SHORTCUT_POLICY_REQUIRE_NO_CREATE = 1,
211+
/* Require a shortcut with matching AUMI, create if missing, modify if not matching. This is the default. */
212+
SHORTCUT_POLICY_REQUIRE_CREATE = 2,
213+
};
214+
215+
WinToast(void);
216+
virtual ~WinToast();
217+
static WinToast* instance();
218+
static bool isCompatible();
219+
static bool isSupportingModernFeatures();
220+
static bool isWin10AnniversaryOrHigher();
221+
static std::wstring configureAUMI(_In_ std::wstring const& companyName, _In_ std::wstring const& productName,
222+
_In_ std::wstring const& subProduct = std::wstring(),
223+
_In_ std::wstring const& versionInformation = std::wstring());
224+
static std::wstring const& strerror(_In_ WinToastError error);
225+
virtual bool initialize(_Out_opt_ WinToastError* error = nullptr);
226+
virtual bool isInitialized() const;
227+
virtual bool hideToast(_In_ INT64 id);
228+
virtual INT64 showToast(_In_ WinToastTemplate const& toast, _In_ IWinToastHandler* eventHandler,
229+
_Out_opt_ WinToastError* error = nullptr);
230+
virtual void clear();
231+
virtual enum ShortcutResult createShortcut();
232+
233+
std::wstring const& appName() const;
234+
std::wstring const& appUserModelId() const;
235+
void setAppUserModelId(_In_ std::wstring const& aumi);
236+
void setAppName(_In_ std::wstring const& appName);
237+
void setShortcutPolicy(_In_ ShortcutPolicy policy);
238+
239+
protected:
240+
struct NotifyData {
241+
NotifyData(){};
242+
NotifyData(_In_ ComPtr<IToastNotification> notify, _In_ EventRegistrationToken activatedToken,
243+
_In_ EventRegistrationToken dismissedToken, _In_ EventRegistrationToken failedToken) :
244+
_notify(notify), _activatedToken(activatedToken), _dismissedToken(dismissedToken), _failedToken(failedToken) {}
245+
246+
~NotifyData() {
247+
RemoveTokens();
248+
}
249+
250+
void RemoveTokens() {
251+
if (!_readyForDeletion) {
252+
return;
253+
}
254+
255+
if (_previouslyTokenRemoved) {
256+
return;
257+
}
258+
259+
if (!_notify.Get()) {
260+
return;
261+
}
262+
263+
_notify->remove_Activated(_activatedToken);
264+
_notify->remove_Dismissed(_dismissedToken);
265+
_notify->remove_Failed(_failedToken);
266+
_previouslyTokenRemoved = true;
267+
}
268+
269+
void markAsReadyForDeletion() {
270+
_readyForDeletion = true;
271+
}
272+
273+
bool isReadyForDeletion() const {
274+
return _readyForDeletion;
275+
}
276+
277+
IToastNotification* notification() {
278+
return _notify.Get();
279+
}
280+
281+
private:
282+
ComPtr<IToastNotification> _notify{nullptr};
283+
EventRegistrationToken _activatedToken{};
284+
EventRegistrationToken _dismissedToken{};
285+
EventRegistrationToken _failedToken{};
286+
bool _readyForDeletion{false};
287+
bool _previouslyTokenRemoved{false};
288+
};
289+
290+
bool _isInitialized{false};
291+
bool _hasCoInitialized{false};
292+
ShortcutPolicy _shortcutPolicy{SHORTCUT_POLICY_REQUIRE_CREATE};
293+
std::wstring _appName{};
294+
std::wstring _aumi{};
295+
std::map<INT64, NotifyData> _buffer{};
296+
297+
void markAsReadyForDeletion(_In_ INT64 id);
298+
HRESULT validateShellLinkHelper(_Out_ bool& wasChanged);
299+
HRESULT createShellLinkHelper();
300+
HRESULT setImageFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isToastGeneric, bool isCropHintCircle);
301+
HRESULT setHeroImageHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isInlineImage);
302+
HRESULT setBindToastGenericHelper(_In_ IXmlDocument* xml);
303+
HRESULT
304+
setAudioFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path,
305+
_In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
306+
HRESULT setTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text, _In_ UINT32 pos);
307+
HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text);
308+
HRESULT addActionHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& action, _In_ std::wstring const& arguments);
309+
HRESULT addDurationHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& duration);
310+
HRESULT addScenarioHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& scenario);
311+
HRESULT addInputHelper(_In_ IXmlDocument* xml);
312+
ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
313+
void setError(_Out_opt_ WinToastError* error, _In_ WinToastError value);
314+
};
315+
} // namespace WinToastLib
316+
#endif // WINTOASTLIB_H

Client/qtTeamTalk/CMakeLists.txt

+18-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ endif()
3434
# TextToSpeech was introduced in Qt 6.4
3535
find_package (Qt6 COMPONENTS Widgets Xml Network Multimedia TextToSpeech)
3636

37+
if (WIN32)
38+
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/WinToast)
39+
set(WINTOAST_SOURCES
40+
3rdparty/WinToast/wintoastlib.cpp
41+
)
42+
endif()
43+
3744
if (Qt5_FOUND OR Qt6_FOUND)
3845

3946
if (MSVC)
@@ -124,6 +131,14 @@ if (Qt5_FOUND OR Qt6_FOUND)
124131
resources.qrc mainwindow.rc
125132
)
126133

134+
if (WIN32)
135+
list(APPEND QTTEAMTALK_SOURCES ${WINTOAST_SOURCES})
136+
endif()
137+
138+
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
139+
list(APPEND QTTEAMTALK_SOURCES utilui.mm)
140+
endif()
141+
127142
if (Qt6_FOUND)
128143
set (TEAMTALK_LINK_FLAGS Qt6::Widgets Qt6::Xml Qt6::Network Qt6::Multimedia Qt6::TextToSpeech)
129144
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
@@ -148,7 +163,9 @@ if (Qt5_FOUND OR Qt6_FOUND)
148163
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
149164
find_library (CARBON_LIBRARY Carbon)
150165
find_library (IOKIT_LIBRARY IOKit)
151-
list (APPEND TEAMTALK_LINK_FLAGS ${CARBON_LIBRARY} ${IOKIT_LIBRARY})
166+
find_library (FOUNDATION_FRAMEWORK Foundation)
167+
find_library (APPKIT_FRAMEWORK AppKit)
168+
list (APPEND TEAMTALK_LINK_FLAGS ${CARBON_LIBRARY} ${IOKIT_LIBRARY} ${FOUNDATION_FRAMEWORK} ${APPKIT_FRAMEWORK})
152169

153170
set (MACOSX_BUNDLE_ICON_FILE teamtalk.icns)
154171
set (TEAMTALK_ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/teamtalk.icns)

Client/qtTeamTalk/mainwindow.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,10 @@ void MainWindow::initialScreenReaderSetup()
899899
#if defined(ENABLE_TOLK)
900900
ttSettings->setValue(SETTINGS_TTS_ENGINE, TTSENGINE_TOLK);
901901
#elif defined(Q_OS_LINUX)
902-
ttSettings->setValue(SETTINGS_TTS_ENGINE, QFile::exists(TTSENGINE_NOTIFY_PATH) ? TTSENGINE_NOTIFY : TTSENGINE_QT);
902+
if (QFile::exists(NOTIFY_PATH))
903+
ttSettings->value(SETTINGS_TTS_TOAST, true);
904+
else
905+
ttSettings->setValue(SETTINGS_TTS_ENGINE, TTSENGINE_QT);
903906
#endif
904907
ttSettings->setValue(SETTINGS_DISPLAY_VU_METER_UPDATES, false);
905908
}

Client/qtTeamTalk/preferences.ui

+8-1
Original file line numberDiff line numberDiff line change
@@ -1576,7 +1576,7 @@
15761576
</widget>
15771577
<widget class="QWidget" name="ttsTab">
15781578
<attribute name="title">
1579-
<string>Text To Speech</string>
1579+
<string>Notification</string>
15801580
</attribute>
15811581
<layout class="QHBoxLayout" name="horizontalLayout_22">
15821582
<item>
@@ -1861,6 +1861,13 @@
18611861
</property>
18621862
</widget>
18631863
</item>
1864+
<item row="10" column="0" colspan="2">
1865+
<widget class="QCheckBox" name="ttsToastChkBox">
1866+
<property name="text">
1867+
<string>Use toast notification</string>
1868+
</property>
1869+
</widget>
1870+
</item>
18641871
</layout>
18651872
</item>
18661873
<item>

0 commit comments

Comments
 (0)