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 "qohosenums.h"
6#include "qohosjsutils.h"
9#include <QtCore/qdatetime.h>
10#include <QtCore/qobject.h>
11#include <QtGui/private/qhighdpiscaling_p.h>
12#include <QtGui/private/qohosimageconversions_p.h>
13#include <QtGui/qguiapplication.h>
14#include <QtGui/qicon.h>
15#include <QtGui/qpa/qplatformmenu.h>
16#include <QtGui/qscreen.h>
17#include <algorithm>
18#include <qohosplugincore.h>
19
20QT_BEGIN_NAMESPACE
21
22namespace {
23
24constexpr int quickOperationHeight = 300;
25
26const std::string ohosSystemTrayItemTitle = "Qt Application";
27
28const auto trayIconGeometry = QRect(0, 0, 24, 24);
29
30const auto notificationIconSize = QSize(128,128);
31
32std::string applyWorkaroundForEmptyHoverTips(const std::string &hoverTips)
33{
34 return !hoverTips.empty() ? hoverTips : " ";
35}
36
38{
39 return [](QtOhos::JsState &jsState) {
40 return QNapi::Array::New(jsState.env());
41 };
42}
43
45{
47 if (!qUiAbilityPeer)
48 qOhosReportFatalErrorAndAbort("%s: Default QAbilityPeer is not a QUiAbilityPeer", Q_FUNC_INFO);
49
50 return qUiAbilityPeer->qAbility().eval<QNapi::Object>("context");
51}
52
54 QtOhos::JsState &jsState, std::function<void()> leftClickListener)
55{
56 return registerQOhosOnOffMethodsBasedEventHandler(
57 jsState.eval<QNapi::Object>("@kit.StatusBarExtensionKit.statusBarManager"),
58 "statusBarIconClick",
59 [leftClickListener = std::move(leftClickListener)](const QtOhos::CallbackInfo &cbInfo) {
60 auto eventData = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
61 auto optIconClickType = QNapi::getOptionalPropOrEmpty<QNapi::String>(
62 QNapi::getOptionalPropOrEmpty<QNapi::Object>(eventData, "data"),
63 "iconClickType");
64
65 if (optIconClickType.IsEmpty()) {
66 qOhosPrintfDebug(
67 "%s: no 'iconClickType' in the event (%s), ignoring it",
68 Q_FUNC_INFO, QNapi::toJsonString(eventData).c_str());
69 return;
70 }
71
72 if (optIconClickType.Utf8Value() == "leftClick")
73 leftClickListener();
74 });
75}
76
78{
79 jsState.eval(
80 "@kit.StatusBarExtensionKit.statusBarManager.removeFromStatusBar(*)",
81 {getContextForStatusBarManager(jsState)});
82
83 qOhosPrintfDebug("%s: Successfully removed icon from status bar", Q_FUNC_INFO);
84}
85
86std::shared_ptr<void> addItemToOhosStatusBar(QtOhos::JsState &jsState, QNapi::Object statusBarItem)
87{
88 jsState.eval(
89 "@kit.StatusBarExtensionKit.statusBarManager.addToStatusBar(*)",
90 {getContextForStatusBarManager(jsState), statusBarItem});
91
92 qOhosPrintfDebug("%s: Successfully added icon to status bar [Statusbar] ", Q_FUNC_INFO);
93
94 return QtOhos::makeDestroyNotifier(
95 []() {
96 QtOhos::runInJsThreadAndWait(&removeIconFromOhosStatusBar, Q_FUNC_INFO);
97 });
98}
99
100void updateOhosStatusBarIcon(QtOhos::JsState &jsState, QNapi::Object iconData)
101{
102 qOhosPrintfDebug("%s", Q_FUNC_INFO);
103
104 if (iconData.IsEmpty()) {
105 qOhosPrintfError("%s: Icon data is empty", Q_FUNC_INFO);
106 return;
107 }
108
109 jsState.eval(
110 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarIcon(*)",
111 {getContextForStatusBarManager(jsState), iconData});
112
113 qOhosPrintfDebug("%s: Successfully updated status bar icon", Q_FUNC_INFO);
114}
115
116void updateOhosStatusBarMenu(QtOhos::JsState &jsState, QNapi::Array statusBarGroupMenus)
117{
118 jsState.eval(
119 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarMenu(*)",
120 {getContextForStatusBarManager(jsState), statusBarGroupMenus});
121
122 qOhosPrintfDebug("%s: Successfully updated status bar menu", Q_FUNC_INFO);
123}
124
126 QtOhos::JsState &jsState, QNapi::Object statusBarIcon, const std::string &title,
127 QNapi::Array statusBarGroupMenus, QOhosOptional<std::string> hoverTips)
128{
129 auto *env = jsState.env();
130
131 auto quickOperation = QNapi::makeObject(
132 env,
133 {
134 {"title", title},
135 {"height", quickOperationHeight},
136 {"abilityName", ""},
137 });
138
139 auto statusBarItem = QNapi::makeObject(
140 env,
141 {
142 {"icons", statusBarIcon},
143 {"quickOperation", quickOperation},
144 {"statusBarGroupMenu", statusBarGroupMenus},
145 });
146
147 if (hoverTips.has_value())
148 statusBarItem.set("hoverTips", applyWorkaroundForEmptyHoverTips(hoverTips.value()));
149
150 return statusBarItem;
151}
152
154 const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
155{
156 auto iconImage = icon.pixmap(imageSize).toImage();
157 if (!iconImage.isNull())
158 return iconImage;
159
160 auto fallbackImage = QImage(imageSize, QImage::Format_RGBA8888);
161 fallbackImage.fill(fallbackContentColor);
162 return fallbackImage;
163}
164
165QNapi::Object makeJsPixelMapFromIcon(QtOhos::JsState &jsState, const QIcon &icon, bool isWhiteIcon)
166{
167 const QSize iconSize(48, 48);
168 QImage iconImage = icon.pixmap(iconSize).toImage();
169 auto pixelMap =
170 createDisplayDensityScaledJsMonochromePixelMapFromIconImage(jsState, iconImage, isWhiteIcon);
171 if (pixelMap)
172 return *pixelMap;
173
174 QImage defaultImage(iconSize, QImage::Format_RGBA8888);
175 defaultImage.fill(isWhiteIcon ? Qt::white : Qt::black);
176 return makeDisplayDensityScaledJsPixelMapFromQImage(jsState, defaultImage);
177}
178
179QNapi::Object makeJsStatusBarIcon(QtOhos::JsState &jsState, const QIcon &icon)
180{
181 auto *env = jsState.env();
182
183 auto whitePixelMap = makeJsPixelMapFromIcon(jsState, icon, true);
184 auto blackPixelMap = makeJsPixelMapFromIcon(jsState, icon, false);
185
186 auto imageSize = whitePixelMap.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", whitePixelMap},
196 {"black", blackPixelMap},
197 });
198}
199
201 QtOhos::JsState &jsState, const std::string &title, const std::string &text)
202{
203 using ContentType = QtOhos::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 QtOhos::JsState &jsState, const std::string &title, const std::string &content,
223 const QIcon &icon, QOhosOptional<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 QOhosOptional<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 = QtOhos::evalInJsThread(
290 [&](QtOhos::JsState &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
332 [&](QtOhos::JsState &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) {
344 [&](QtOhos::JsState &jsState, QOhosTaskPromise<> taskPromise) {
345 jsState.evalToPromiseOrRejectOnThrow(
346 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarHoverTips(*)",
347 {getContextForStatusBarManager(jsState), applyWorkaroundForEmptyHoverTips(tooltip.toStdString())})
348 .onCatch(QtOhos::makeErrorLoggingJsCallback("updateStatusBarHoverTips()"))
349 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
350 },
351 Q_FUNC_INFO);
352 }
353}
354
355QRect QOhosSystemTrayIcon::geometry() const
356{
357 return trayIconGeometry;
358}
359
360void QOhosSystemTrayIcon::showMessage(
361 const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int msecs)
362{
363 Q_UNUSED(iconType);
365 [&](QtOhos::JsState &jsState, QOhosTaskPromise<> taskPromise) {
366 auto notificationRequest = makeJsNotificationRequest(
367 jsState, title.toStdString(), msg.toStdString(), icon,
368 msecs > 0 ? makeQOhosOptional(msecs) : makeEmptyQOhosOptional());
369 jsState.evalToPromiseOrRejectOnThrow("@ohos.notificationManager.publish(*)", {notificationRequest})
370 .onCatch(QtOhos::makeErrorLoggingJsCallback("publish()"))
371 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
372 },
373 Q_FUNC_INFO);
374}
375
376bool QOhosSystemTrayIcon::isSystemTrayAvailable() const
377{
378 return true;
379}
380
381bool QOhosSystemTrayIcon::supportsMessages() const
382{
383 return true;
384}
385
386QPlatformMenu *QOhosSystemTrayIcon::createMenu() const
387{
388 if (m_menu == nullptr)
389 return makeQOhosStatusBarMenu().release();
390 return m_menu;
391}
392
393void QOhosSystemTrayIcon::updateMenu(QPlatformMenu *menu)
394{
395 qOhosPrintfDebug("%s: Updating status bar menu", Q_FUNC_INFO);
396
397 m_menu = menu;
398
399 if (!m_jsScopeData) {
400 qOhosPrintfDebug("%s: System tray not initialized yet", Q_FUNC_INFO);
401 return;
402 }
403
404 auto jsStatusBarGroupMenusFactory =
405 m_menu != nullptr
406 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
407 : makeEmptyJsArrayFactory();
408
410 [&](QtOhos::JsState &jsState) {
411 updateOhosStatusBarMenu(jsState, jsStatusBarGroupMenusFactory(jsState));
412 },
413 Q_FUNC_INFO);
414}
415
416}
417
419{
420 return std::make_unique<QOhosSystemTrayIcon>();
421}
422
423QT_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.
virtual std::shared_ptr< QAbilityPeer > defaultQAbilityPeer()=0
static std::shared_ptr< QUiAbilityPeer > tryCastFromQAbilityPeerOrNull(std::shared_ptr< QAbilityPeer > qAbilityPeer)
QNapi::Object makeJsStatusBarIcon(QtOhos::JsState &jsState, const QIcon &icon)
QImage convertIconToScaledImage(const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
QNapi::Object makeJsStatusBarItem(QtOhos::JsState &jsState, QNapi::Object statusBarIcon, const std::string &title, QNapi::Array statusBarGroupMenus, QOhosOptional< std::string > hoverTips)
std::shared_ptr< void > addItemToOhosStatusBar(QtOhos::JsState &jsState, QNapi::Object statusBarItem)
QNapi::Object getContextForStatusBarManager(QtOhos::JsState &jsState)
QNapi::Object makeJsNotificationContent(QtOhos::JsState &jsState, const std::string &title, const std::string &text)
std::string applyWorkaroundForEmptyHoverTips(const std::string &hoverTips)
std::function< QNapi::Array(QtOhos::JsState &)> makeEmptyJsArrayFactory()
void removeIconFromOhosStatusBar(QtOhos::JsState &jsState)
QNapi::Object makeJsNotificationRequest(QtOhos::JsState &jsState, const std::string &title, const std::string &content, const QIcon &icon, QOhosOptional< int > optAutoDeletedDelayMs)
void updateOhosStatusBarIcon(QtOhos::JsState &jsState, QNapi::Object iconData)
constexpr int quickOperationHeight
std::shared_ptr< void > registerOhosIconLeftClickListener(QtOhos::JsState &jsState, std::function< void()> leftClickListener)
const std::string ohosSystemTrayItemTitle
QNapi::Object makeJsPixelMapFromIcon(QtOhos::JsState &jsState, const QIcon &icon, bool isWhiteIcon)
void updateOhosStatusBarMenu(QtOhos::JsState &jsState, QNapi::Array statusBarGroupMenus)
void runInJsThreadAndWait(const std::function< void(JsState &)> &task, std::string callerContextName={})
void invokeInJsThreadAndWaitForContinue(std::function< void(JsState &, QOhosTaskPromise<>)> &&task, std::string callerContextName={})
QT_BEGIN_NAMESPACE std::unique_ptr< QPlatformSystemTrayIcon > makeQOhosSystemTrayIcon()