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#include <qohosjsscopedataowner.h>
21
23
24namespace {
25
26constexpr int quickOperationHeight = 300;
27
28const std::string ohosSystemTrayItemTitle = "Qt Application";
29
30const auto trayIconGeometry = QRect(0, 0, 24, 24);
31
32const auto notificationIconSize = QSize(128,128);
33
34std::string applyWorkaroundForEmptyHoverTips(const std::string &hoverTips)
35{
36 return !hoverTips.empty() ? hoverTips : " ";
37}
38
40{
41 return [](QOhosJsState &jsState) {
42 return QNapi::Array::New(jsState.env());
43 };
44}
45
47{
48 auto optQAbility = jsState.defaultQAbility();
49 if (!optQAbility)
50 qOhosReportFatalErrorAndAbort("%s: no default Ability instance", Q_FUNC_INFO);
51
52 return optQAbility->eval<QNapi::Object>("context");
53}
54
56 QOhosJsState &jsState, std::function<void()> leftClickListener)
57{
58 return registerQOhosOnOffMethodsBasedEventHandler(
59 jsState.eval<QNapi::Object>("@kit.StatusBarExtensionKit.statusBarManager"),
60 "statusBarIconClick",
61 [leftClickListener = std::move(leftClickListener)](const QOhosCallbackInfo &cbInfo) {
62 auto eventData = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
63 auto optIconClickType = QNapi::getOptionalPropOrEmpty<QNapi::String>(
64 QNapi::getOptionalPropOrEmpty<QNapi::Object>(eventData, "data"),
65 "iconClickType");
66
67 if (optIconClickType.IsEmpty()) {
68 qOhosPrintfDebug(
69 "%s: no 'iconClickType' in the event (%s), ignoring it",
70 Q_FUNC_INFO, QNapi::toJsonString(eventData).c_str());
71 return;
72 }
73
74 if (optIconClickType.Utf8Value() == "leftClick")
75 leftClickListener();
76 });
77}
78
79void removeIconFromOhosStatusBar(QOhosJsState &jsState)
80{
81 jsState.eval(
82 "@kit.StatusBarExtensionKit.statusBarManager.removeFromStatusBar(*)",
83 {getContextForStatusBarManager(jsState)});
84
85 qOhosPrintfDebug("%s: Successfully removed icon from status bar", Q_FUNC_INFO);
86}
87
88std::shared_ptr<void> addItemToOhosStatusBar(QOhosJsState &jsState, QNapi::Object statusBarItem)
89{
90 jsState.eval(
91 "@kit.StatusBarExtensionKit.statusBarManager.addToStatusBar(*)",
92 {getContextForStatusBarManager(jsState), statusBarItem});
93
94 qOhosPrintfDebug("%s: Successfully added icon to status bar [Statusbar] ", Q_FUNC_INFO);
95
96 return QtOhos::makeDestroyNotifier(
97 []() {
98 QOhosJsThreadGateway::runAndWait(&removeIconFromOhosStatusBar, Q_FUNC_INFO);
99 });
100}
101
102void updateOhosStatusBarIcon(QOhosJsState &jsState, QNapi::Object iconData)
103{
104 qOhosPrintfDebug("%s", Q_FUNC_INFO);
105
106 if (iconData.IsEmpty()) {
107 qOhosPrintfError("%s: Icon data is empty", Q_FUNC_INFO);
108 return;
109 }
110
111 jsState.eval(
112 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarIcon(*)",
113 {getContextForStatusBarManager(jsState), iconData});
114
115 qOhosPrintfDebug("%s: Successfully updated status bar icon", Q_FUNC_INFO);
116}
117
118QNapi::Promise updateOhosStatusBarHoverTips(QOhosJsState &jsState, const std::string &hoverTips)
119{
120 return jsState.evalToPromiseOrRejectOnThrow(
121 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarHoverTips(*)",
122 {getContextForStatusBarManager(jsState), applyWorkaroundForEmptyHoverTips(hoverTips)});
123}
124
125void updateOhosStatusBarMenu(QOhosJsState &jsState, QNapi::Array statusBarGroupMenus)
126{
127 jsState.eval(
128 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarMenu(*)",
129 {getContextForStatusBarManager(jsState), statusBarGroupMenus});
130
131 qOhosPrintfDebug("%s: Successfully updated status bar menu", Q_FUNC_INFO);
132}
133
135 QOhosJsState &jsState, QNapi::Object statusBarIcon, const std::string &title,
136 QNapi::Array statusBarGroupMenus, std::optional<std::string> hoverTips)
137{
138 auto *env = jsState.env();
139
140 auto quickOperation = QNapi::makeObject(
141 env,
142 {
143 {"title", title},
144 {"height", quickOperationHeight},
145 {"abilityName", ""},
146 });
147
148 auto statusBarItem = QNapi::makeObject(
149 env,
150 {
151 {"icons", statusBarIcon},
152 {"quickOperation", quickOperation},
153 {"statusBarGroupMenu", statusBarGroupMenus},
154 });
155
156 if (hoverTips.has_value())
157 statusBarItem.set("hoverTips", applyWorkaroundForEmptyHoverTips(hoverTips.value()));
158
159 return statusBarItem;
160}
161
163 const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
164{
165 auto iconImage = icon.pixmap(imageSize).toImage();
166 if (!iconImage.isNull())
167 return iconImage;
168
169 auto fallbackImage = QImage(imageSize, QImage::Format_RGBA8888);
170 fallbackImage.fill(fallbackContentColor);
171 return fallbackImage;
172}
173
174QNapi::Object makeJsPixelMapFromIcon(QOhosJsState &jsState, const QIcon &icon)
175{
176 const QSize iconSize(48, 48);
177 return makeDisplayDensityScaledJsPixelMapFromQImage(
178 jsState, convertIconToScaledImage(icon, iconSize, Qt::transparent));
179}
180
181QNapi::Object makeJsStatusBarIcon(QOhosJsState &jsState, const QIcon &icon)
182{
183 auto *env = jsState.env();
184
185 auto trayIcon = makeJsPixelMapFromIcon(jsState, icon);
186
187 auto imageSize = trayIcon.eval<QNapi::Object>("getImageInfoSync().size");
188
189 int imageWidth = imageSize.get<QNapi::Number>("width");
190 int imageHeight = imageSize.get<QNapi::Number>("height");
191 qOhosPrintfDebug("%s: ceated PixelMap size: %dx%d", Q_FUNC_INFO, imageWidth, imageHeight);
192
193 return QNapi::makeObject(
194 env,
195 {
196 {"white", trayIcon},
197 {"black", trayIcon},
198 });
199}
200
202 QOhosJsState &jsState, const std::string &title, const std::string &text)
203{
204 using ContentType = QtOhosQpa::enums::ohos::notificationManager::ContentType;
205
206 return QNapi::makeObject(
207 jsState.env(),
208 {
209 {"notificationContentType", jsState.mapOhosEnumToJs(ContentType::NOTIFICATION_CONTENT_BASIC_TEXT)},
210 {
211 "normal",
212 QNapi::makeObject(
213 jsState.env(),
214 {
215 {"title", title},
216 {"text", text},
217 }),
218 },
219 });
220}
221
223 QOhosJsState &jsState, const std::string &title, const std::string &content,
224 const QIcon &icon, std::optional<int> optAutoDeletedDelayMs)
225{
226 auto iconPixelMap = makeDisplayDensityScaledJsPixelMapFromQImage(
227 jsState, convertIconToScaledImage(icon, notificationIconSize, Qt::transparent));
228 auto notificationRequest = QNapi::makeObject(
229 jsState.env(),
230 {
231 {"content", makeJsNotificationContent(jsState, title, content)},
232 {"smallIcon", iconPixelMap},
233 });
234 if (optAutoDeletedDelayMs.has_value()) {
235 notificationRequest.set(
236 "autoDeletedTime",
237 QDateTime::currentMSecsSinceEpoch() + optAutoDeletedDelayMs.value());
238 }
239
240 return notificationRequest;
241}
242
243class QOhosSystemTrayIcon final : public QPlatformSystemTrayIcon
244{
245public:
247
250 void updateIcon(const QIcon &icon) override;
251 void updateToolTip(const QString &tooltip) override;
252 void updateMenu(QPlatformMenu *menu) override;
253 QRect geometry() const override;
255 const QString &title, const QString &msg, const QIcon &icon,
256 MessageIcon iconType, int msecs) override;
257
260
261 QPlatformMenu *createMenu() const override;
262
263private:
264 QIcon m_icon;
265 std::optional<QString> m_optToolTip;
266 QPlatformMenu *m_menu = nullptr;
267
268 struct JsScopeData
269 {
270 std::shared_ptr<void> m_statusBarItemHandle;
271 std::shared_ptr<void> m_iconLeftClickListenerHandle;
272 };
273
274 std::optional<QtOhos::JsScopeDataOwner<JsScopeData>> m_optJsScopeData;
275};
276
277QOhosSystemTrayIcon::QOhosSystemTrayIcon() = default;
278
279void QOhosSystemTrayIcon::init()
280{
281 if (m_optJsScopeData)
282 return;
283
284 auto jsStatusBarGroupMenusFactory =
285 m_menu != nullptr
286 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
287 : makeEmptyJsArrayFactory();
288
289 auto selfRef = QtOhos::makeQThreadSafeRef(this);
290 m_optJsScopeData.emplace();
291 QOhosJsThreadGateway::runAndWait(
292 [&](QOhosJsState &jsState) {
293 auto jsStatusBarIcon = makeJsStatusBarIcon(jsState, m_icon);
294 auto jsStatusBarGroupMenus = jsStatusBarGroupMenusFactory(jsState);
295 auto optHoverTipsString = qTransform(
296 m_optToolTip,
297 [](const auto &toolTip) {
298 return toolTip.toStdString();
299 });
300 auto jsStatusBarItem = makeJsStatusBarItem(
301 jsState, jsStatusBarIcon, ohosSystemTrayItemTitle, jsStatusBarGroupMenus, optHoverTipsString);
302 **m_optJsScopeData = 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_optJsScopeData.reset();
322}
323
324void QOhosSystemTrayIcon::updateIcon(const QIcon &icon)
325{
326 if (!m_optJsScopeData)
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_optJsScopeData) {
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_optJsScopeData) {
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.
Combined button and popup list for selecting options.
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()