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 <optional>
19#include <qohosplugincore.h>
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 [](QtOhos::JsState &jsState) {
41 return QNapi::Array::New(jsState.env());
42 };
43}
44
46{
48 if (!qUiAbilityPeer)
49 qOhosReportFatalErrorAndAbort("%s: Default QAbilityPeer is not a QUiAbilityPeer", Q_FUNC_INFO);
50
51 return qUiAbilityPeer->qAbility().eval<QNapi::Object>("context");
52}
53
55 QtOhos::JsState &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 QtOhos::CallbackInfo &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
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(QtOhos::JsState &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 QtOhos::runInJsThreadAndWait(&removeIconFromOhosStatusBar, Q_FUNC_INFO);
98 });
99}
100
101void updateOhosStatusBarIcon(QtOhos::JsState &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
117void updateOhosStatusBarMenu(QtOhos::JsState &jsState, QNapi::Array statusBarGroupMenus)
118{
119 jsState.eval(
120 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarMenu(*)",
121 {getContextForStatusBarManager(jsState), statusBarGroupMenus});
122
123 qOhosPrintfDebug("%s: Successfully updated status bar menu", Q_FUNC_INFO);
124}
125
127 QtOhos::JsState &jsState, QNapi::Object statusBarIcon, const std::string &title,
128 QNapi::Array statusBarGroupMenus, std::optional<std::string> hoverTips)
129{
130 auto *env = jsState.env();
131
132 auto quickOperation = QNapi::makeObject(
133 env,
134 {
135 {"title", title},
136 {"height", quickOperationHeight},
137 {"abilityName", ""},
138 });
139
140 auto statusBarItem = QNapi::makeObject(
141 env,
142 {
143 {"icons", statusBarIcon},
144 {"quickOperation", quickOperation},
145 {"statusBarGroupMenu", statusBarGroupMenus},
146 });
147
148 if (hoverTips.has_value())
149 statusBarItem.set("hoverTips", applyWorkaroundForEmptyHoverTips(hoverTips.value()));
150
151 return statusBarItem;
152}
153
155 const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
156{
157 auto iconImage = icon.pixmap(imageSize).toImage();
158 if (!iconImage.isNull())
159 return iconImage;
160
161 auto fallbackImage = QImage(imageSize, QImage::Format_RGBA8888);
162 fallbackImage.fill(fallbackContentColor);
163 return fallbackImage;
164}
165
166QNapi::Object makeJsPixelMapFromIcon(QtOhos::JsState &jsState, const QIcon &icon)
167{
168 const QSize iconSize(48, 48);
169 return makeDisplayDensityScaledJsPixelMapFromQImage(
170 jsState, convertIconToScaledImage(icon, iconSize, Qt::transparent));
171}
172
173QNapi::Object makeJsStatusBarIcon(QtOhos::JsState &jsState, const QIcon &icon)
174{
175 auto *env = jsState.env();
176
177 auto trayIcon = makeJsPixelMapFromIcon(jsState, icon);
178
179 auto imageSize = trayIcon.eval<QNapi::Object>("getImageInfoSync().size");
180
181 int imageWidth = imageSize.get<QNapi::Number>("width");
182 int imageHeight = imageSize.get<QNapi::Number>("height");
183 qOhosPrintfDebug("%s: ceated PixelMap size: %dx%d", Q_FUNC_INFO, imageWidth, imageHeight);
184
185 return QNapi::makeObject(
186 env,
187 {
188 {"white", trayIcon},
189 {"black", trayIcon},
190 });
191}
192
194 QtOhos::JsState &jsState, const std::string &title, const std::string &text)
195{
196 using ContentType = QtOhos::enums::ohos::notificationManager::ContentType;
197
198 return QNapi::makeObject(
199 jsState.env(),
200 {
201 {"notificationContentType", jsState.mapOhosEnumToJs(ContentType::NOTIFICATION_CONTENT_BASIC_TEXT)},
202 {
203 "normal",
204 QNapi::makeObject(
205 jsState.env(),
206 {
207 {"title", title},
208 {"text", text},
209 }),
210 },
211 });
212}
213
215 QtOhos::JsState &jsState, const std::string &title, const std::string &content,
216 const QIcon &icon, std::optional<int> optAutoDeletedDelayMs)
217{
218 auto iconPixelMap = makeDisplayDensityScaledJsPixelMapFromQImage(
219 jsState, convertIconToScaledImage(icon, notificationIconSize, Qt::transparent));
220 auto notificationRequest = QNapi::makeObject(
221 jsState.env(),
222 {
223 {"content", makeJsNotificationContent(jsState, title, content)},
224 {"smallIcon", iconPixelMap},
225 });
226 if (optAutoDeletedDelayMs.has_value()) {
227 notificationRequest.set(
228 "autoDeletedTime",
229 QDateTime::currentMSecsSinceEpoch() + optAutoDeletedDelayMs.value());
230 }
231
232 return notificationRequest;
233}
234
235class QOhosSystemTrayIcon final : public QPlatformSystemTrayIcon
236{
237public:
239
242 void updateIcon(const QIcon &icon) override;
243 void updateToolTip(const QString &tooltip) override;
244 void updateMenu(QPlatformMenu *menu) override;
245 QRect geometry() const override;
247 const QString &title, const QString &msg, const QIcon &icon,
248 MessageIcon iconType, int msecs) override;
249
252
253 QPlatformMenu *createMenu() const override;
254
255private:
256 QIcon m_icon;
257 std::optional<QString> m_optToolTip;
258 QPlatformMenu *m_menu = nullptr;
259
260 struct JsScopeData
261 {
262 std::shared_ptr<void> m_statusBarItemHandle;
263 std::shared_ptr<void> m_iconLeftClickListenerHandle;
264 };
265
266 std::shared_ptr<JsScopeData> m_jsScopeData;
267};
268
269QOhosSystemTrayIcon::QOhosSystemTrayIcon() = default;
270
271void QOhosSystemTrayIcon::init()
272{
273 if (m_jsScopeData)
274 return;
275
276 auto jsStatusBarGroupMenusFactory =
277 m_menu != nullptr
278 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
279 : makeEmptyJsArrayFactory();
280
281 auto selfRef = QtOhos::makeQThreadSafeRef(this);
282 m_jsScopeData = QtOhos::evalInJsThread(
283 [&](QtOhos::JsState &jsState) {
284 auto jsStatusBarIcon = makeJsStatusBarIcon(jsState, m_icon);
285 auto jsStatusBarGroupMenus = jsStatusBarGroupMenusFactory(jsState);
286 auto optHoverTipsString = qTransform(
287 m_optToolTip,
288 [](const auto &toolTip) {
289 return toolTip.toStdString();
290 });
291 auto jsStatusBarItem = makeJsStatusBarItem(
292 jsState, jsStatusBarIcon, ohosSystemTrayItemTitle, jsStatusBarGroupMenus, optHoverTipsString);
293 return QtOhos::makeProxyWithJsThreadDeleter(
294 QtOhos::moveToSharedPtr(
295 JsScopeData {
296 .m_statusBarItemHandle = addItemToOhosStatusBar(jsState, jsStatusBarItem),
297 .m_iconLeftClickListenerHandle = registerOhosIconLeftClickListener(
298 jsState,
299 [selfRef]() {
300 selfRef.visitInQtThreadIfAlive(
301 [](auto &self) {
302 Q_EMIT self.activated(QPlatformSystemTrayIcon::Trigger);
303 });
304 }),
305 }));
306 },
307 Q_FUNC_INFO);
308
309 qOhosPrintfDebug("%s: System tray icon initialized", Q_FUNC_INFO);
310}
311
312void QOhosSystemTrayIcon::cleanup()
313{
314 m_jsScopeData.reset();
315}
316
317void QOhosSystemTrayIcon::updateIcon(const QIcon &icon)
318{
319 if (!m_jsScopeData)
320 return;
321
322 m_icon = icon;
323
325 [&](QtOhos::JsState &jsState) {
326 updateOhosStatusBarIcon(jsState, makeJsStatusBarIcon(jsState, icon));
327 },
328 Q_FUNC_INFO);
329}
330
331void QOhosSystemTrayIcon::updateToolTip(const QString &tooltip)
332{
333 m_optToolTip = tooltip;
334
335 if (m_jsScopeData) {
337 [&](QtOhos::JsState &jsState, QOhosTaskPromise<> taskPromise) {
338 jsState.evalToPromiseOrRejectOnThrow(
339 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarHoverTips(*)",
340 {getContextForStatusBarManager(jsState), applyWorkaroundForEmptyHoverTips(tooltip.toStdString())})
341 .onCatch(QtOhos::makeErrorLoggingJsCallback("updateStatusBarHoverTips()"))
342 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
343 },
344 Q_FUNC_INFO);
345 }
346}
347
348QRect QOhosSystemTrayIcon::geometry() const
349{
350 return trayIconGeometry;
351}
352
353void QOhosSystemTrayIcon::showMessage(
354 const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int msecs)
355{
356 Q_UNUSED(iconType);
358 [&](QtOhos::JsState &jsState, QOhosTaskPromise<> taskPromise) {
359 auto notificationRequest = makeJsNotificationRequest(
360 jsState, title.toStdString(), msg.toStdString(), icon,
361 msecs > 0 ? std::optional(msecs) : std::nullopt);
362 jsState.evalToPromiseOrRejectOnThrow("@ohos.notificationManager.publish(*)", {notificationRequest})
363 .onCatch(QtOhos::makeErrorLoggingJsCallback("publish()"))
364 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
365 },
366 Q_FUNC_INFO);
367}
368
369bool QOhosSystemTrayIcon::isSystemTrayAvailable() const
370{
371 return true;
372}
373
374bool QOhosSystemTrayIcon::supportsMessages() const
375{
376 return true;
377}
378
379QPlatformMenu *QOhosSystemTrayIcon::createMenu() const
380{
381 if (m_menu == nullptr)
382 return makeQOhosStatusBarMenu().release();
383 return m_menu;
384}
385
386void QOhosSystemTrayIcon::updateMenu(QPlatformMenu *menu)
387{
388 qOhosPrintfDebug("%s: Updating status bar menu", Q_FUNC_INFO);
389
390 m_menu = menu;
391
392 if (!m_jsScopeData) {
393 qOhosPrintfDebug("%s: System tray not initialized yet", Q_FUNC_INFO);
394 return;
395 }
396
397 auto jsStatusBarGroupMenusFactory =
398 m_menu != nullptr
399 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
400 : makeEmptyJsArrayFactory();
401
403 [&](QtOhos::JsState &jsState) {
404 updateOhosStatusBarMenu(jsState, jsStatusBarGroupMenusFactory(jsState));
405 },
406 Q_FUNC_INFO);
407}
408
409}
410
412{
413 return std::make_unique<QOhosSystemTrayIcon>();
414}
415
416QT_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 makeJsNotificationRequest(QtOhos::JsState &jsState, const std::string &title, const std::string &content, const QIcon &icon, std::optional< int > optAutoDeletedDelayMs)
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)
QNapi::Object makeJsPixelMapFromIcon(QtOhos::JsState &jsState, const QIcon &icon)
std::string applyWorkaroundForEmptyHoverTips(const std::string &hoverTips)
QNapi::Object makeJsStatusBarItem(QtOhos::JsState &jsState, QNapi::Object statusBarIcon, const std::string &title, QNapi::Array statusBarGroupMenus, std::optional< std::string > hoverTips)
std::function< QNapi::Array(QtOhos::JsState &)> makeEmptyJsArrayFactory()
void removeIconFromOhosStatusBar(QtOhos::JsState &jsState)
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
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()