Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qohossystemtrayicon.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "qohosjsutils.h"
9#include <QtCore/private/qcore_ohos_p.h>
10#include <QtCore/qdatetime.h>
11#include <QtCore/qobject.h>
12#include <QtGui/private/qhighdpiscaling_p.h>
13#include <QtGui/private/qohosimageconversions_p.h>
14#include <QtGui/qguiapplication.h>
15#include <QtGui/qicon.h>
16#include <QtGui/qpa/qplatformmenu.h>
17#include <QtGui/qscreen.h>
18#include <algorithm>
19#include <optional>
20
21QT_BEGIN_NAMESPACE
22
23namespace {
24
25constexpr int quickOperationHeight = 300;
26
27const std::string ohosSystemTrayItemTitle = "Qt Application";
28
29const auto trayIconGeometry = QRect(0, 0, 24, 24);
30
31const auto notificationIconSize = QSize(128,128);
32
33std::string applyWorkaroundForEmptyHoverTips(const std::string &hoverTips)
34{
35 return !hoverTips.empty() ? hoverTips : " ";
36}
37
39{
40 return [](QOhosJsState &jsState) {
41 return QNapi::Array::New(jsState.env());
42 };
43}
44
46{
47 auto optQAbility = jsState.defaultQAbility();
48 if (!optQAbility)
49 qOhosReportFatalErrorAndAbort("%s: no default Ability instance", Q_FUNC_INFO);
50
51 return optQAbility->eval<QNapi::Object>("context");
52}
53
55 QOhosJsState &jsState, std::function<void()> leftClickListener)
56{
57 return registerQOhosOnOffMethodsBasedEventHandler(
58 jsState.eval<QNapi::Object>("@kit.StatusBarExtensionKit.statusBarManager"),
59 "statusBarIconClick",
60 [leftClickListener = std::move(leftClickListener)](const QOhosCallbackInfo &cbInfo) {
61 auto eventData = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
62 auto optIconClickType = QNapi::getOptionalPropOrEmpty<QNapi::String>(
63 QNapi::getOptionalPropOrEmpty<QNapi::Object>(eventData, "data"),
64 "iconClickType");
65
66 if (optIconClickType.IsEmpty()) {
67 qOhosPrintfDebug(
68 "%s: no 'iconClickType' in the event (%s), ignoring it",
69 Q_FUNC_INFO, QNapi::toJsonString(eventData).c_str());
70 return;
71 }
72
73 if (optIconClickType.Utf8Value() == "leftClick")
74 leftClickListener();
75 });
76}
77
78void removeIconFromOhosStatusBar(QOhosJsState &jsState)
79{
80 jsState.eval(
81 "@kit.StatusBarExtensionKit.statusBarManager.removeFromStatusBar(*)",
82 {getContextForStatusBarManager(jsState)});
83
84 qOhosPrintfDebug("%s: Successfully removed icon from status bar", Q_FUNC_INFO);
85}
86
87std::shared_ptr<void> addItemToOhosStatusBar(QOhosJsState &jsState, QNapi::Object statusBarItem)
88{
89 jsState.eval(
90 "@kit.StatusBarExtensionKit.statusBarManager.addToStatusBar(*)",
91 {getContextForStatusBarManager(jsState), statusBarItem});
92
93 qOhosPrintfDebug("%s: Successfully added icon to status bar [Statusbar] ", Q_FUNC_INFO);
94
95 return QtOhos::makeDestroyNotifier(
96 []() {
97 QOhosJsThreadGateway::runAndWait(&removeIconFromOhosStatusBar, Q_FUNC_INFO);
98 });
99}
100
101void updateOhosStatusBarIcon(QOhosJsState &jsState, QNapi::Object iconData)
102{
103 qOhosPrintfDebug("%s", Q_FUNC_INFO);
104
105 if (iconData.IsEmpty()) {
106 qOhosPrintfError("%s: Icon data is empty", Q_FUNC_INFO);
107 return;
108 }
109
110 jsState.eval(
111 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarIcon(*)",
112 {getContextForStatusBarManager(jsState), iconData});
113
114 qOhosPrintfDebug("%s: Successfully updated status bar icon", Q_FUNC_INFO);
115}
116
117QNapi::Promise updateOhosStatusBarHoverTips(QOhosJsState &jsState, const std::string &hoverTips)
118{
119 return jsState.evalToPromiseOrRejectOnThrow(
120 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarHoverTips(*)",
121 {getContextForStatusBarManager(jsState), applyWorkaroundForEmptyHoverTips(hoverTips)});
122}
123
124void updateOhosStatusBarMenu(QOhosJsState &jsState, QNapi::Array statusBarGroupMenus)
125{
126 jsState.eval(
127 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarMenu(*)",
128 {getContextForStatusBarManager(jsState), statusBarGroupMenus});
129
130 qOhosPrintfDebug("%s: Successfully updated status bar menu", Q_FUNC_INFO);
131}
132
134 QOhosJsState &jsState, QNapi::Object statusBarIcon, const std::string &title,
135 QNapi::Array statusBarGroupMenus, std::optional<std::string> hoverTips)
136{
137 auto *env = jsState.env();
138
139 auto quickOperation = QNapi::makeObject(
140 env,
141 {
142 {"title", title},
143 {"height", quickOperationHeight},
144 {"abilityName", ""},
145 });
146
147 auto statusBarItem = QNapi::makeObject(
148 env,
149 {
150 {"icons", statusBarIcon},
151 {"quickOperation", quickOperation},
152 {"statusBarGroupMenu", statusBarGroupMenus},
153 });
154
155 if (hoverTips.has_value())
156 statusBarItem.set("hoverTips", applyWorkaroundForEmptyHoverTips(hoverTips.value()));
157
158 return statusBarItem;
159}
160
162 const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
163{
164 auto iconImage = icon.pixmap(imageSize).toImage();
165 if (!iconImage.isNull())
166 return iconImage;
167
168 auto fallbackImage = QImage(imageSize, QImage::Format_RGBA8888);
169 fallbackImage.fill(fallbackContentColor);
170 return fallbackImage;
171}
172
173QNapi::Object makeJsPixelMapFromIcon(QOhosJsState &jsState, const QIcon &icon)
174{
175 const QSize iconSize(48, 48);
176 return makeDisplayDensityScaledJsPixelMapFromQImage(
177 jsState, convertIconToScaledImage(icon, iconSize, Qt::transparent));
178}
179
180QNapi::Object makeJsStatusBarIcon(QOhosJsState &jsState, const QIcon &icon)
181{
182 auto *env = jsState.env();
183
184 auto trayIcon = makeJsPixelMapFromIcon(jsState, icon);
185
186 auto imageSize = trayIcon.eval<QNapi::Object>("getImageInfoSync().size");
187
188 int imageWidth = imageSize.get<QNapi::Number>("width");
189 int imageHeight = imageSize.get<QNapi::Number>("height");
190 qOhosPrintfDebug("%s: ceated PixelMap size: %dx%d", Q_FUNC_INFO, imageWidth, imageHeight);
191
192 return QNapi::makeObject(
193 env,
194 {
195 {"white", trayIcon},
196 {"black", trayIcon},
197 });
198}
199
201 QOhosJsState &jsState, const std::string &title, const std::string &text)
202{
203 using ContentType = QtOhosQpa::enums::ohos::notificationManager::ContentType;
204
205 return QNapi::makeObject(
206 jsState.env(),
207 {
208 {"notificationContentType", jsState.mapOhosEnumToJs(ContentType::NOTIFICATION_CONTENT_BASIC_TEXT)},
209 {
210 "normal",
211 QNapi::makeObject(
212 jsState.env(),
213 {
214 {"title", title},
215 {"text", text},
216 }),
217 },
218 });
219}
220
222 QOhosJsState &jsState, const std::string &title, const std::string &content,
223 const QIcon &icon, std::optional<int> optAutoDeletedDelayMs)
224{
225 auto iconPixelMap = makeDisplayDensityScaledJsPixelMapFromQImage(
226 jsState, convertIconToScaledImage(icon, notificationIconSize, Qt::transparent));
227 auto notificationRequest = QNapi::makeObject(
228 jsState.env(),
229 {
230 {"content", makeJsNotificationContent(jsState, title, content)},
231 {"smallIcon", iconPixelMap},
232 });
233 if (optAutoDeletedDelayMs.has_value()) {
234 notificationRequest.set(
235 "autoDeletedTime",
236 QDateTime::currentMSecsSinceEpoch() + optAutoDeletedDelayMs.value());
237 }
238
239 return notificationRequest;
240}
241
242class QOhosSystemTrayIcon final : public QPlatformSystemTrayIcon
243{
244public:
246
249 void updateIcon(const QIcon &icon) override;
250 void updateToolTip(const QString &tooltip) override;
251 void updateMenu(QPlatformMenu *menu) override;
252 QRect geometry() const override;
254 const QString &title, const QString &msg, const QIcon &icon,
255 MessageIcon iconType, int msecs) override;
256
259
260 QPlatformMenu *createMenu() const override;
261
262private:
263 QIcon m_icon;
264 std::optional<QString> m_optToolTip;
265 QPlatformMenu *m_menu = nullptr;
266
267 struct JsScopeData
268 {
269 std::shared_ptr<void> m_statusBarItemHandle;
270 std::shared_ptr<void> m_iconLeftClickListenerHandle;
271 };
272
273 std::shared_ptr<JsScopeData> m_jsScopeData;
274};
275
276QOhosSystemTrayIcon::QOhosSystemTrayIcon() = default;
277
278void QOhosSystemTrayIcon::init()
279{
280 if (m_jsScopeData)
281 return;
282
283 auto jsStatusBarGroupMenusFactory =
284 m_menu != nullptr
285 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
286 : makeEmptyJsArrayFactory();
287
288 auto selfRef = QtOhos::makeQThreadSafeRef(this);
289 m_jsScopeData = QOhosJsThreadGateway::eval(
290 [&](QOhosJsState &jsState) {
291 auto jsStatusBarIcon = makeJsStatusBarIcon(jsState, m_icon);
292 auto jsStatusBarGroupMenus = jsStatusBarGroupMenusFactory(jsState);
293 auto optHoverTipsString = qTransform(
294 m_optToolTip,
295 [](const auto &toolTip) {
296 return toolTip.toStdString();
297 });
298 auto jsStatusBarItem = makeJsStatusBarItem(
299 jsState, jsStatusBarIcon, ohosSystemTrayItemTitle, jsStatusBarGroupMenus, optHoverTipsString);
300 return QtOhos::makeProxyWithJsThreadDeleter(
301 QtOhos::moveToSharedPtr(
302 JsScopeData {
303 .m_statusBarItemHandle = addItemToOhosStatusBar(jsState, jsStatusBarItem),
304 .m_iconLeftClickListenerHandle = registerOhosIconLeftClickListener(
305 jsState,
306 [selfRef]() {
307 selfRef.visitInQtThreadIfAlive(
308 [](auto &self) {
309 Q_EMIT self.activated(QPlatformSystemTrayIcon::Trigger);
310 });
311 }),
312 }));
313 },
314 Q_FUNC_INFO);
315
316 qOhosPrintfDebug("%s: System tray icon initialized", Q_FUNC_INFO);
317}
318
319void QOhosSystemTrayIcon::cleanup()
320{
321 m_jsScopeData.reset();
322}
323
324void QOhosSystemTrayIcon::updateIcon(const QIcon &icon)
325{
326 if (!m_jsScopeData)
327 return;
328
329 m_icon = icon;
330
331 QOhosJsThreadGateway::runAndWait(
332 [&](QOhosJsState &jsState) {
333 updateOhosStatusBarIcon(jsState, makeJsStatusBarIcon(jsState, icon));
334 },
335 Q_FUNC_INFO);
336}
337
338void QOhosSystemTrayIcon::updateToolTip(const QString &tooltip)
339{
340 m_optToolTip = tooltip;
341
342 if (m_jsScopeData) {
343 QOhosJsThreadGateway::invokeAndWaitForContinue(
344 [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
345 updateOhosStatusBarHoverTips(jsState, tooltip.toStdString())
346 .onCatch(QtOhos::makeErrorLoggingJsCallback("updateStatusBarHoverTips()"))
347 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
348 },
349 Q_FUNC_INFO);
350 }
351}
352
353QRect QOhosSystemTrayIcon::geometry() const
354{
355 return trayIconGeometry;
356}
357
358void QOhosSystemTrayIcon::showMessage(
359 const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int msecs)
360{
361 Q_UNUSED(iconType);
362 QOhosJsThreadGateway::invokeAndWaitForContinue(
363 [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
364 auto notificationRequest = makeJsNotificationRequest(
365 jsState, title.toStdString(), msg.toStdString(), icon,
366 msecs > 0 ? std::optional(msecs) : std::nullopt);
367 jsState.evalToPromiseOrRejectOnThrow("@ohos.notificationManager.publish(*)", {notificationRequest})
368 .onCatch(QtOhos::makeErrorLoggingJsCallback("publish()"))
369 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
370 },
371 Q_FUNC_INFO);
372}
373
374bool QOhosSystemTrayIcon::isSystemTrayAvailable() const
375{
376 return true;
377}
378
379bool QOhosSystemTrayIcon::supportsMessages() const
380{
381 return true;
382}
383
384QPlatformMenu *QOhosSystemTrayIcon::createMenu() const
385{
386 if (m_menu == nullptr)
387 return makeQOhosStatusBarMenu().release();
388 return m_menu;
389}
390
391void QOhosSystemTrayIcon::updateMenu(QPlatformMenu *menu)
392{
393 qOhosPrintfDebug("%s: Updating status bar menu", Q_FUNC_INFO);
394
395 m_menu = menu;
396
397 if (!m_jsScopeData) {
398 qOhosPrintfDebug("%s: System tray not initialized yet", Q_FUNC_INFO);
399 return;
400 }
401
402 auto jsStatusBarGroupMenusFactory =
403 m_menu != nullptr
404 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
405 : makeEmptyJsArrayFactory();
406
407 QOhosJsThreadGateway::runAndWait(
408 [&](QOhosJsState &jsState) {
409 updateOhosStatusBarMenu(jsState, jsStatusBarGroupMenusFactory(jsState));
410 },
411 Q_FUNC_INFO);
412}
413
414}
415
417{
418 return std::make_unique<QOhosSystemTrayIcon>();
419}
420
421QT_END_NAMESPACE
void init() override
This method is called to initialize the platform dependent implementation.
void cleanup() override
This method is called to cleanup the platform dependent implementation.
void showMessage(const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int msecs) override
Shows a balloon message for the entry with the given title, message msg and icon for the time specifi...
void updateMenu(QPlatformMenu *menu) override
This method is called when the system tray menu did change.
QPlatformMenu * createMenu() const override
This method allows platforms to use a different QPlatformMenu for system tray menus than what would n...
QRect geometry() const override
This method returns the geometry of the platform dependent system tray icon on the screen.
bool supportsMessages() const override
Returns true if the system tray supports messages on the platform.
bool isSystemTrayAvailable() const override
Returns true if the system tray is available on the platform.
void updateToolTip(const QString &tooltip) override
This method is called when the tooltip text did change.
void updateIcon(const QIcon &icon) override
This method is called when the icon did change.
QNapi::Promise updateOhosStatusBarHoverTips(QOhosJsState &jsState, const std::string &hoverTips)
std::function< QNapi::Array(QOhosJsState &)> makeEmptyJsArrayFactory()
QNapi::Object makeJsStatusBarItem(QOhosJsState &jsState, QNapi::Object statusBarIcon, const std::string &title, QNapi::Array statusBarGroupMenus, std::optional< std::string > hoverTips)
void updateOhosStatusBarMenu(QOhosJsState &jsState, QNapi::Array statusBarGroupMenus)
std::shared_ptr< void > registerOhosIconLeftClickListener(QOhosJsState &jsState, std::function< void()> leftClickListener)
QImage convertIconToScaledImage(const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
QNapi::Object makeJsStatusBarIcon(QOhosJsState &jsState, const QIcon &icon)
QNapi::Object makeJsPixelMapFromIcon(QOhosJsState &jsState, const QIcon &icon)
void updateOhosStatusBarIcon(QOhosJsState &jsState, QNapi::Object iconData)
std::shared_ptr< void > addItemToOhosStatusBar(QOhosJsState &jsState, QNapi::Object statusBarItem)
std::string applyWorkaroundForEmptyHoverTips(const std::string &hoverTips)
QNapi::Object makeJsNotificationRequest(QOhosJsState &jsState, const std::string &title, const std::string &content, const QIcon &icon, std::optional< int > optAutoDeletedDelayMs)
QNapi::Object getContextForStatusBarManager(QOhosJsState &jsState)
constexpr int quickOperationHeight
void removeIconFromOhosStatusBar(QOhosJsState &jsState)
const std::string ohosSystemTrayItemTitle
QNapi::Object makeJsNotificationContent(QOhosJsState &jsState, const std::string &title, const std::string &text)
QT_BEGIN_NAMESPACE std::unique_ptr< QPlatformSystemTrayIcon > makeQOhosSystemTrayIcon()