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
6#include "qohosenums.h"
8#include "qohosjsutils.h"
11#include <QtCore/qdatetime.h>
12#include <QtCore/qobject.h>
13#include <QtGui/private/qhighdpiscaling_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 <cmath>
20#include <qohosplugincore.h>
21
22QT_BEGIN_NAMESPACE
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
35{
36 return [](QtOhos::JsState &jsState) {
37 return QNapi::Array::New(jsState.env());
38 };
39}
40
42{
44 if (!qUiAbilityPeer)
45 qOhosReportFatalErrorAndAbort("%s: Default QAbilityPeer is not a QUiAbilityPeer", Q_FUNC_INFO);
46
47 return qUiAbilityPeer->qAbility().eval<QNapi::Object>("context");
48}
49
51 QtOhos::JsState &jsState, std::function<void()> leftClickListener)
52{
53 return QtOhos::registerOnOffMethodsBasedEventHandler(
54 jsState.eval<QNapi::Object>("@kit.StatusBarExtensionKit.statusBarManager"),
55 "statusBarIconClick",
56 [leftClickListener = std::move(leftClickListener)](const QtOhos::CallbackInfo &cbInfo) {
57 auto eventData = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
58 auto optIconClickType = QNapi::getOptionalPropOrEmpty<QNapi::String>(
59 QNapi::getOptionalPropOrEmpty<QNapi::Object>(eventData, "data"),
60 "iconClickType");
61
62 if (optIconClickType.IsEmpty()) {
63 qOhosPrintfDebug(
64 "%s: no 'iconClickType' in the event (%s), ignoring it",
65 Q_FUNC_INFO, QNapi::toJsonString(eventData).c_str());
66 return;
67 }
68
69 if (optIconClickType.Utf8Value() == "leftClick")
70 leftClickListener();
71 });
72}
73
74void addItemToOhosStatusBar(QtOhos::JsState &jsState, QNapi::Object statusBarItem)
75{
76 jsState.eval(
77 "@kit.StatusBarExtensionKit.statusBarManager.addToStatusBar(*)",
78 {getContextForStatusBarManager(jsState), statusBarItem});
79
80 qOhosPrintfDebug("%s: Successfully added icon to status bar [Statusbar] ", Q_FUNC_INFO);
81}
82
84{
85 jsState.eval(
86 "@kit.StatusBarExtensionKit.statusBarManager.removeFromStatusBar(*)",
87 {getContextForStatusBarManager(jsState)});
88
89 qOhosPrintfDebug("%s: Successfully removed icon from status bar", Q_FUNC_INFO);
90}
91
92void updateOhosStatusBarIcon(QtOhos::JsState &jsState, QNapi::Object iconData)
93{
94 qOhosPrintfDebug("%s", Q_FUNC_INFO);
95
96 if (iconData.IsEmpty()) {
97 qOhosPrintfError("%s: Icon data is empty", Q_FUNC_INFO);
98 return;
99 }
100
101 jsState.eval(
102 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarIcon(*)",
103 {getContextForStatusBarManager(jsState), iconData});
104
105 qOhosPrintfDebug("%s: Successfully updated status bar icon", Q_FUNC_INFO);
106}
107
108void updateOhosStatusBarMenu(QtOhos::JsState &jsState, QNapi::Array statusBarGroupMenus)
109{
110 jsState.eval(
111 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarMenu(*)",
112 {getContextForStatusBarManager(jsState), statusBarGroupMenus});
113
114 qOhosPrintfDebug("%s: Successfully updated status bar menu", Q_FUNC_INFO);
115}
116
118 QtOhos::JsState &jsState, QNapi::Object statusBarIcon, const std::string &title,
119 QNapi::Array statusBarGroupMenus)
120{
121 auto *env = jsState.env();
122
123 auto quickOperation = QNapi::makeObject(
124 env,
125 {
126 {"title", title},
127 {"height", quickOperationHeight},
128 {"abilityName", ""},
129 });
130
131 return QNapi::makeObject(
132 env,
133 {
134 {"icons", statusBarIcon},
135 {"quickOperation", quickOperation},
136 {"statusBarGroupMenu", statusBarGroupMenus},
137 });
138}
139
141{
142 constexpr auto fallbackDisplayDensity = 1.0;
143
144 auto primaryDisplay = jsState.eval<QNapi::Object>("@ohos.display.getPrimaryDisplaySync()");
145 auto displayInfo = QOhosDisplayInfo::makeFromOhosDisplayObject(jsState, primaryDisplay);
146
147 double density;
148 if (displayInfo.densityPixels > 0.0) {
149 density = std::lround(displayInfo.densityPixels);
150 } else {
151 qOhosPrintfDebug("%s: invalid display densityPixels: %.3f", Q_FUNC_INFO, displayInfo.densityPixels);
152 density = 0.0;
153 }
154
155 return density > 0.0 ? density : fallbackDisplayDensity;
156}
157
159{
160 qOhosPrintfDebug(
161 "%s: image dimensions: %dx%d, format: %d, bytes: %lld",
162 Q_FUNC_INFO, image.width(), image.height(), static_cast<int>(image.format()), image.sizeInBytes());
163
164 const int sourceWidth = image.width();
165 const int sourceHeight = image.height();
166
167 double density = getPrimaryDisplayPixelDensity(jsState);
168
169 const double widthVp = std::lround(sourceWidth / density);
170 const double heightVp = std::lround(sourceHeight / density);
171
172 QImage newImage = image.scaled(widthVp, heightVp, Qt::KeepAspectRatio, Qt::SmoothTransformation);
173
174 const int width = newImage.width();
175 const int height = newImage.height();
176 const double finalWidthVp = width / density;
177 const double finalHeightVp = height / density;
178
179 qOhosPrintfDebug(
180 "%s: density=%.3f, vp %.2f x %.2f -> %.2f x %.2f, px %dx%d -> %dx%d",
181 Q_FUNC_INFO, density, widthVp, heightVp, finalWidthVp, finalHeightVp, sourceWidth, sourceHeight, width, height);
182
183 return createNapiPixelMapFromQImage(jsState, newImage);
184}
185
187 const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
188{
189 auto iconImage = icon.pixmap(imageSize).toImage();
190 if (!iconImage.isNull())
191 return iconImage;
192
193 auto fallbackImage = QImage(imageSize, QImage::Format_RGBA8888);
194 fallbackImage.fill(fallbackContentColor);
195 return fallbackImage;
196}
197
198QImage convertToMonochromeIcon(const QImage &sourceImage, bool useWhite)
199{
200 QImage monochromeImage = sourceImage.copy();
201 const int colorValue = useWhite ? 255 : 0;
202
203 for (int y = 0; y < monochromeImage.height(); ++y) {
204 for (int x = 0; x < monochromeImage.width(); ++x) {
205 const QRgb pixel = monochromeImage.pixel(x, y);
206 const int alpha = qAlpha(pixel);
207
208 if (alpha > 0)
209 monochromeImage.setPixel(x, y, qRgba(colorValue, colorValue, colorValue, alpha));
210 }
211 }
212
213 return monochromeImage;
214}
215
216QNapi::Object makeJsPixelMapFromIcon(QtOhos::JsState &jsState, const QIcon &icon, bool isWhiteIcon)
217{
218 const QSize iconSize(48, 48);
219
220 QImage iconImage = icon.pixmap(iconSize).toImage();
221 if (iconImage.isNull()) {
222 QImage defaultImage(iconSize, QImage::Format_RGBA8888);
223 defaultImage.fill(isWhiteIcon ? Qt::white : Qt::black);
224 return makeDisplayDensityScaledJsPixelMapFromQImage(jsState, defaultImage);
225 }
226
227 qOhosPrintfDebug(
228 "%s: original Icon dimensions: %dx%d, format: %d, bytes: %lld",
229 Q_FUNC_INFO, iconImage.width(), iconImage.height(),
230 static_cast<int>(iconImage.format()), iconImage.sizeInBytes());
231
232 return makeDisplayDensityScaledJsPixelMapFromQImage(
233 jsState, convertToMonochromeIcon(iconImage, isWhiteIcon));
234}
235
236QNapi::Object makeJsStatusBarIcon(QtOhos::JsState &jsState, const QIcon &icon)
237{
238 auto *env = jsState.env();
239
240 auto whitePixelMap = makeJsPixelMapFromIcon(jsState, icon, true);
241 auto blackPixelMap = makeJsPixelMapFromIcon(jsState, icon, false);
242
243 auto imageSize = whitePixelMap.eval<QNapi::Object>("getImageInfoSync().size");;
244
245 int imageWidth = imageSize.get<QNapi::Number>("width");
246 int imageHeight = imageSize.get<QNapi::Number>("height");
247 qOhosPrintfDebug("%s: ceated PixelMap size: %dx%d", Q_FUNC_INFO, imageWidth, imageHeight);
248
249 return QNapi::makeObject(
250 env,
251 {
252 {"white", whitePixelMap},
253 {"black", blackPixelMap},
254 });
255}
256
258 QtOhos::JsState &jsState, const std::string &title, const std::string &text)
259{
260 using ContentType = QtOhos::enums::ohos::notificationManager::ContentType;
261
262 return QNapi::makeObject(
263 jsState.env(),
264 {
265 {"notificationContentType", jsState.mapOhosEnumToJs(ContentType::NOTIFICATION_CONTENT_BASIC_TEXT)},
266 {
267 "normal",
268 QNapi::makeObject(
269 jsState.env(),
270 {
271 {"title", title},
272 {"text", text},
273 }),
274 },
275 });
276}
277
279 QtOhos::JsState &jsState, const std::string &title, const std::string &content,
280 const QIcon &icon, QOhosOptional<int> optAutoDeletedDelayMs)
281{
282 auto iconPixelMap = makeDisplayDensityScaledJsPixelMapFromQImage(
283 jsState, convertIconToScaledImage(icon, notificationIconSize, Qt::transparent));
284 auto notificationRequest = QNapi::makeObject(
285 jsState.env(),
286 {
287 {"content", makeJsNotificationContent(jsState, title, content)},
288 {"smallIcon", iconPixelMap},
289 });
290 if (optAutoDeletedDelayMs.hasValue()) {
291 notificationRequest.set(
292 "autoDeletedTime",
293 QDateTime::currentMSecsSinceEpoch() + optAutoDeletedDelayMs.value());
294 }
295
296 return notificationRequest;
297}
298
299class QOhosSystemTrayIcon final : public QPlatformSystemTrayIcon
300{
301public:
304
307 void updateIcon(const QIcon &icon) override;
308 void updateToolTip(const QString &tooltip) override;
309 void updateMenu(QPlatformMenu *menu) override;
310 QRect geometry() const override;
312 const QString &title, const QString &msg, const QIcon &icon,
313 MessageIcon iconType, int msecs) override;
314
317
318 QPlatformMenu *createMenu() const override;
319
320private:
321 bool m_initialized = false;
322 QIcon m_icon;
323 QPlatformMenu *m_menu = nullptr;
324
325 struct JsScopeData
326 {
327 std::shared_ptr<void> m_iconLeftClickListenerHandle;
328 };
329
330 std::shared_ptr<JsScopeData> m_jsScopeData;
331};
332
333QOhosSystemTrayIcon::QOhosSystemTrayIcon() = default;
334
335QOhosSystemTrayIcon::~QOhosSystemTrayIcon()
336{
337 cleanup();
338}
339
340void QOhosSystemTrayIcon::init()
341{
342 if (m_initialized)
343 return;
344
345 auto jsStatusBarGroupMenusFactory =
346 m_menu != nullptr
347 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
348 : makeEmptyJsArrayFactory();
349
350 m_jsScopeData = QtOhos::makeProxyWithJsThreadDeleter(std::make_shared<JsScopeData>());
351
352 auto selfRef = QtOhos::makeQThreadSafeRef(this);
354 [&](QtOhos::JsState &jsState) {
355 auto jsStatusBarIcon = makeJsStatusBarIcon(jsState, m_icon);
356 auto jsStatusBarGroupMenus = jsStatusBarGroupMenusFactory(jsState);
357 auto jsStatusBarItem = makeJsStatusBarItem(
358 jsState, jsStatusBarIcon, ohosSystemTrayItemTitle, jsStatusBarGroupMenus);
359 addItemToOhosStatusBar(jsState, jsStatusBarItem);
360 m_jsScopeData->m_iconLeftClickListenerHandle = registerOhosIconLeftClickListener(
361 jsState,
362 [selfRef]() {
363 selfRef.visitInQtThreadIfAlive(
364 [](auto &self) {
365 Q_EMIT self.activated(QPlatformSystemTrayIcon::Trigger);
366 });
367 });
368 });
369
370 m_initialized = true;
371
372 qOhosPrintfDebug("%s: System tray icon initialized", Q_FUNC_INFO);
373}
374
375void QOhosSystemTrayIcon::cleanup()
376{
377 if (!m_initialized)
378 return;
379
381
382 m_jsScopeData.reset();
383
384 m_initialized = false;
385}
386
387void QOhosSystemTrayIcon::updateIcon(const QIcon &icon)
388{
389 if (!m_initialized)
390 return;
391
392 m_icon = icon;
393
395 [&](QtOhos::JsState &jsState) {
396 updateOhosStatusBarIcon(jsState, makeJsStatusBarIcon(jsState, icon));
397 });
398}
399
400void QOhosSystemTrayIcon::updateToolTip(const QString &tooltip)
401{
402 Q_UNUSED(tooltip);
403}
404
405QRect QOhosSystemTrayIcon::geometry() const
406{
407 return trayIconGeometry;
408}
409
410void QOhosSystemTrayIcon::showMessage(
411 const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int msecs)
412{
413 Q_UNUSED(iconType);
415 [&](QtOhos::JsState &jsState, std::function<void()> continueFunc) {
416 auto notificationRequest = makeJsNotificationRequest(
417 jsState, title.toStdString(), msg.toStdString(), icon,
418 msecs > 0 ? makeQOhosOptional(msecs) : makeEmptyQOhosOptional());
419 jsState.eval<QNapi::Promise>("@ohos.notificationManager.publish(*)", {notificationRequest})
420 .onCatch(QtOhos::makeErrorLoggingJsCallback("publish()"))
421 .onFinally(std::move(continueFunc));
422 });
423}
424
425bool QOhosSystemTrayIcon::isSystemTrayAvailable() const
426{
427 return true;
428}
429
430bool QOhosSystemTrayIcon::supportsMessages() const
431{
432 return true;
433}
434
435QPlatformMenu *QOhosSystemTrayIcon::createMenu() const
436{
437 if (m_menu == nullptr)
438 return makeQOhosStatusBarMenu().release();
439 return m_menu;
440}
441
442void QOhosSystemTrayIcon::updateMenu(QPlatformMenu *menu)
443{
444 qOhosPrintfDebug("%s: Updating status bar menu", Q_FUNC_INFO);
445
446 m_menu = menu;
447
448 if (!m_initialized) {
449 qOhosPrintfDebug("%s: System tray not initialized yet", Q_FUNC_INFO);
450 return;
451 }
452
453 auto jsStatusBarGroupMenusFactory =
454 m_menu != nullptr
455 ? static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
456 : makeEmptyJsArrayFactory();
457
459 [&](QtOhos::JsState &jsState) {
460 updateOhosStatusBarMenu(jsState, jsStatusBarGroupMenusFactory(jsState));
461 });
462}
463
464}
465
467{
468 return std::make_unique<QOhosSystemTrayIcon>();
469}
470
471QT_END_NAMESPACE
std::enable_if_t< qohosplugincore_h_detail::isQOhosOptional< QOhosInvokeResult< Func, T > >, QOhosInvokeResult< Func, T > > andThen(Func &&func) const
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)
QImage convertToMonochromeIcon(const QImage &sourceImage, bool useWhite)
QNapi::Object makeJsStatusBarIcon(QtOhos::JsState &jsState, const QIcon &icon)
QImage convertIconToScaledImage(const QIcon &icon, const QSize &imageSize, const QColor &fallbackContentColor)
void addItemToOhosStatusBar(QtOhos::JsState &jsState, QNapi::Object statusBarItem)
QNapi::Object getContextForStatusBarManager(QtOhos::JsState &jsState)
QNapi::Object makeDisplayDensityScaledJsPixelMapFromQImage(QtOhos::JsState &jsState, const QImage &image)
QNapi::Object makeJsNotificationContent(QtOhos::JsState &jsState, const std::string &title, const std::string &text)
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
QNapi::Object makeJsStatusBarItem(QtOhos::JsState &jsState, QNapi::Object statusBarIcon, const std::string &title, QNapi::Array statusBarGroupMenus)
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)
double getPrimaryDisplayPixelDensity(QtOhos::JsState &jsState)
void updateOhosStatusBarMenu(QtOhos::JsState &jsState, QNapi::Array statusBarGroupMenus)
void runInJsThreadAndWait(const std::function< void(JsState &)> &task)
void invokeInJsThreadAndWaitForContinue(std::function< void(JsState &, std::function< void()>)> &&task)
QT_BEGIN_NAMESPACE std::unique_ptr< QPlatformSystemTrayIcon > makeQOhosSystemTrayIcon()