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>
22#include <qohosjsscopedataowner.h>
38 auto deferred = Napi::Promise::Deferred::New(env);
39 deferred.Reject(rejectValue);
40 return deferred.Promise();
45 auto deferred = Napi::Promise::Deferred::New(jsState.env());
48 [deferred](
const QOhosCallbackInfo &cbInfo) {
49 deferred.Resolve(cbInfo.Env().Undefined());
53 return deferred.Promise();
57 QOhosJsState &jsState,
const std::function<QNapi::Promise(QOhosJsState &)> &func)
61 }
catch (
const Napi::Error &error) {
62 return makeRejectedPromise(jsState.env(), error.Value());
67 QOhosJsState &jsState, std::function<QNapi::Promise(QOhosJsState &)> call)
69 auto callAttemptPromise = callToPromiseOrRejectOnThrow(jsState, call);
71 return callAttemptPromise.onCatch(
72 [call = std::move(call)](
const QOhosCallbackInfo &cbInfo)
mutable {
73 auto rejectedValue = cbInfo.getFirstArg<QNapi::Value>(Q_FUNC_INFO);
75 constexpr std::int64_t tooFrequentApiCallsErrorCode = 1010710003;
76 constexpr auto throttlingWindow = std::chrono::milliseconds(20);
78 if (QtOhos::tryExtractErrorInfoFromJsError(rejectedValue).optCode != tooFrequentApiCallsErrorCode)
79 return makeRejectedPromise(cbInfo.Env(), rejectedValue);
81 return makePromiseResolvedAfter(cbInfo.jsState(), throttlingWindow)
83 [call = std::move(call)](
const QOhosCallbackInfo &cbInfo) {
84 return callToPromiseOrRejectOnThrow(cbInfo.jsState(), call);
91 return !hoverTips.empty() ? hoverTips :
" ";
96 return [](QOhosJsState &jsState) {
97 return QNapi::Array::New(jsState.env());
103 auto optQAbility = jsState.defaultQAbility();
105 qOhosPrintfWarning(
"%s: no default Ability instance", Q_FUNC_INFO);
109 return optQAbility->eval<QNapi::Object>(
"context");
114 auto optContext = tryGetContextForStatusBarManager(jsState);
116 qOhosReportFatalErrorAndAbort(
"%s: no context for the status bar manager", Q_FUNC_INFO);
122 QOhosJsState &jsState,
std::function<
void()> leftClickListener)
124 return registerQOhosOnOffMethodsBasedEventHandler(
125 jsState.eval<QNapi::Object>(
"@kit.StatusBarExtensionKit.statusBarManager"),
126 "statusBarIconClick",
127 [leftClickListener = std::move(leftClickListener)](
const QOhosCallbackInfo &cbInfo) {
128 auto eventData = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
129 auto optIconClickType = QNapi::getOptionalPropOrEmpty<QNapi::String>(
130 QNapi::getOptionalPropOrEmpty<QNapi::Object>(eventData,
"data"),
133 if (optIconClickType.IsEmpty()) {
135 "%s: no 'iconClickType' in the event (%s), ignoring it",
136 Q_FUNC_INFO, QNapi::toJsonString(eventData).c_str());
140 if (optIconClickType.Utf8Value() ==
"leftClick")
147 auto optContext = tryGetContextForStatusBarManager(jsState);
150 "%s: no context for the status bar manager, skipping the removal of the icon",
152 return QtOhos::makeResolvedPromise(jsState.env());
156 "@kit.StatusBarExtensionKit.statusBarManager.removeFromStatusBar(*)",
159 qOhosPrintfDebug(
"%s: Successfully removed icon from status bar", Q_FUNC_INFO);
161 return QtOhos::makeResolvedPromise(jsState.env());
167 "@kit.StatusBarExtensionKit.statusBarManager.addToStatusBar(*)",
168 {getContextForStatusBarManager(jsState), statusBarItem});
170 qOhosPrintfDebug(
"%s: Successfully added icon to status bar [Statusbar] ", Q_FUNC_INFO);
172 return QtOhos::makeResolvedPromise(jsState.env());
177 qOhosPrintfDebug(
"%s", Q_FUNC_INFO);
179 if (iconData.IsEmpty()) {
180 qOhosPrintfError(
"%s: Icon data is empty", Q_FUNC_INFO);
181 return QtOhos::makeResolvedPromise(jsState.env());
185 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarIcon(*)",
186 {getContextForStatusBarManager(jsState), iconData});
188 qOhosPrintfDebug(
"%s: Successfully updated status bar icon", Q_FUNC_INFO);
190 return QtOhos::makeResolvedPromise(jsState.env());
195 return jsState.evalToPromiseOrRejectOnThrow(
196 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarHoverTips(*)",
197 {getContextForStatusBarManager(jsState), applyWorkaroundForEmptyHoverTips(hoverTips)});
203 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarMenu(*)",
204 {getContextForStatusBarManager(jsState), statusBarGroupMenus});
206 qOhosPrintfDebug(
"%s: Successfully updated status bar menu", Q_FUNC_INFO);
208 return QtOhos::makeResolvedPromise(jsState.env());
212 QOhosJsState &jsState, QNapi::Object statusBarIcon,
const std::string &title,
213 QNapi::Array statusBarGroupMenus,
std::optional<
std::string> hoverTips)
215 auto *env = jsState.env();
217 auto quickOperation = QNapi::makeObject(
221 {
"height", quickOperationHeight},
225 auto statusBarItem = QNapi::makeObject(
228 {
"icons", statusBarIcon},
229 {
"quickOperation", quickOperation},
230 {
"statusBarGroupMenu", statusBarGroupMenus},
233 if (hoverTips.has_value())
234 statusBarItem.set(
"hoverTips", applyWorkaroundForEmptyHoverTips(hoverTips.value()));
236 return statusBarItem;
240 const QIcon &icon,
const QSize &imageSize,
const QColor &fallbackContentColor)
242 auto iconImage = icon.pixmap(imageSize).toImage();
243 if (!iconImage.isNull())
246 auto fallbackImage = QImage(imageSize, QImage::Format_RGBA8888);
247 fallbackImage.fill(fallbackContentColor);
248 return fallbackImage;
253 const QSize iconSize(48, 48);
254 return makeDisplayDensityScaledJsPixelMapFromQImage(
255 jsState, convertIconToScaledImage(icon, iconSize, Qt::transparent));
260 auto *env = jsState.env();
262 auto trayIcon = makeJsPixelMapFromIcon(jsState, icon);
264 auto imageSize = trayIcon.eval<QNapi::Object>(
"getImageInfoSync().size");
266 int imageWidth = imageSize.get<QNapi::Number>(
"width");
267 int imageHeight = imageSize.get<QNapi::Number>(
"height");
268 qOhosPrintfDebug(
"%s: ceated PixelMap size: %dx%d", Q_FUNC_INFO, imageWidth, imageHeight);
270 return QNapi::makeObject(
279 QOhosJsState &jsState,
const std::string &title,
const std::string &text)
283 return QNapi::makeObject(
286 {
"notificationContentType", jsState.mapOhosEnumToJs(ContentType::NOTIFICATION_CONTENT_BASIC_TEXT)},
300 QOhosJsState &jsState,
const std::string &title,
const std::string &content,
301 const QIcon &icon,
std::optional<
int> optAutoDeletedDelayMs)
303 auto iconPixelMap = makeDisplayDensityScaledJsPixelMapFromQImage(
304 jsState, convertIconToScaledImage(icon, notificationIconSize, Qt::transparent));
305 auto notificationRequest = QNapi::makeObject(
308 {
"content", makeJsNotificationContent(jsState, title, content)},
309 {
"smallIcon", iconPixelMap},
311 if (optAutoDeletedDelayMs.has_value()) {
312 notificationRequest.set(
314 QDateTime::currentMSecsSinceEpoch() + optAutoDeletedDelayMs.value());
317 return notificationRequest;
320class QOhosSystemTrayIcon
final :
public QPlatformSystemTrayIcon
332 const QString &title,
const QString &msg,
const QIcon &icon,
333 MessageIcon iconType,
int msecs)
override;
342 std::optional<QString> m_optToolTip;
343 QPlatformMenu *m_menu =
nullptr;
347 std::shared_ptr<
void> m_iconLeftClickListenerHandle;
355void QOhosSystemTrayIcon::
init()
357 if (m_optJsScopeData)
360 auto jsStatusBarGroupMenusFactory =
362 ?
static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
363 : makeEmptyJsArrayFactory();
365 auto selfRef = QtOhos::makeQThreadSafeRef(
this);
366 m_optJsScopeData.emplace();
367 QOhosJsThreadGateway::invokeAndWaitForContinue(
368 [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
369 callWithStatusBarManagerThrottlingRetry(
371 [icon = m_icon, optToolTip = m_optToolTip, jsStatusBarGroupMenusFactory](
372 QOhosJsState &jsState) {
373 auto jsStatusBarIcon = makeJsStatusBarIcon(jsState, icon);
374 auto jsStatusBarGroupMenus = jsStatusBarGroupMenusFactory(jsState);
375 auto optHoverTipsString = qTransform(
377 [](
const auto &toolTip) {
378 return toolTip.toStdString();
380 auto jsStatusBarItem = makeJsStatusBarItem(
381 jsState, jsStatusBarIcon, ohosSystemTrayItemTitle, jsStatusBarGroupMenus,
384 return addItemToOhosStatusBar(jsState, jsStatusBarItem);
387 [
this, selfRef, taskPromise = std::move(taskPromise)](
const QOhosCallbackInfo &cbInfo) {
388 **m_optJsScopeData = JsScopeData {
389 .m_iconLeftClickListenerHandle = registerOhosIconLeftClickListener(
392 selfRef.visitInQtThreadIfAlive(
394 Q_EMIT self.activated(QPlatformSystemTrayIcon::Trigger);
399 m_optJsScopeData->addAsyncCleanup(
400 [](QOhosJsState &jsState, JsScopeData &, QOhosTaskPromise<> donePromise) {
401 callWithStatusBarManagerThrottlingRetry(jsState, &removeIconFromOhosStatusBar)
402 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"remove system tray icon"))
403 .onFinally(std::move(donePromise).makeChained(Q_FUNC_INFO));
409 [](
const QOhosCallbackInfo &cbInfo) {
410 QtOhos::logJsCallbackError(cbInfo,
"Failed to set up the status bar item");
411 qOhosReportFatalErrorAndAbort(
412 "%s: failed to set up the status bar item", Q_FUNC_INFO);
417 qOhosPrintfDebug(
"%s: System tray icon initialized", Q_FUNC_INFO);
422 m_optJsScopeData.reset();
427 if (!m_optJsScopeData)
432 QOhosJsThreadGateway::invokeAndWaitForContinue(
433 [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
434 callWithStatusBarManagerThrottlingRetry(
436 [icon](QOhosJsState &jsState) {
437 return updateOhosStatusBarIcon(jsState, makeJsStatusBarIcon(jsState, icon));
439 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"updateStatusBarIcon()"))
440 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
447 m_optToolTip = tooltip;
449 if (m_optJsScopeData) {
450 QOhosJsThreadGateway::invokeAndWaitForContinue(
451 [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
452 callWithStatusBarManagerThrottlingRetry(
454 [tooltip](QOhosJsState &jsState) {
455 return updateOhosStatusBarHoverTips(jsState, tooltip.toStdString());
457 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"updateStatusBarHoverTips()"))
458 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
466 return trayIconGeometry;
470 const QString &title,
const QString &msg,
const QIcon &icon, MessageIcon iconType,
int msecs)
473 QOhosJsThreadGateway::invokeAndWaitForContinue(
474 [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
475 auto notificationRequest = makeJsNotificationRequest(
476 jsState, title.toStdString(), msg.toStdString(), icon,
477 msecs > 0 ? std::optional(msecs) : std::nullopt);
478 jsState.evalToPromiseOrRejectOnThrow(
"@ohos.notificationManager.publish(*)", {notificationRequest})
479 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"publish()"))
480 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
497 if (m_menu ==
nullptr)
498 return makeQOhosStatusBarMenu().release();
504 qOhosPrintfDebug(
"%s: Updating status bar menu", Q_FUNC_INFO);
508 if (!m_optJsScopeData) {
509 qOhosPrintfDebug(
"%s: System tray not initialized yet", Q_FUNC_INFO);
513 auto jsStatusBarGroupMenusFactory =
515 ?
static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
516 : makeEmptyJsArrayFactory();
518 QOhosJsThreadGateway::invokeAndWaitForContinue(
519 [&](QOhosJsState &jsState, QOhosTaskPromise<> taskPromise) {
520 callWithStatusBarManagerThrottlingRetry(
522 [jsStatusBarGroupMenusFactory](QOhosJsState &jsState) {
523 return updateOhosStatusBarMenu(jsState, jsStatusBarGroupMenusFactory(jsState));
525 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"updateStatusBarMenu()"))
526 .onFinally(std::move(taskPromise).makeChained(Q_FUNC_INFO));
535 return std::make_unique<QOhosSystemTrayIcon>();
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::optional< QNapi::Object > tryGetContextForStatusBarManager(QOhosJsState &jsState)
std::function< QNapi::Array(QOhosJsState &)> makeEmptyJsArrayFactory()
QNapi::Promise removeIconFromOhosStatusBar(QOhosJsState &jsState)
QNapi::Object makeJsStatusBarItem(QOhosJsState &jsState, QNapi::Object statusBarIcon, const std::string &title, QNapi::Array statusBarGroupMenus, std::optional< std::string > hoverTips)
QNapi::Promise callToPromiseOrRejectOnThrow(QOhosJsState &jsState, const std::function< QNapi::Promise(QOhosJsState &)> &func)
const auto notificationIconSize
QNapi::Promise callWithStatusBarManagerThrottlingRetry(QOhosJsState &jsState, std::function< QNapi::Promise(QOhosJsState &)> call)
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)
std::string applyWorkaroundForEmptyHoverTips(const std::string &hoverTips)
QNapi::Promise updateOhosStatusBarMenu(QOhosJsState &jsState, QNapi::Array statusBarGroupMenus)
QNapi::Promise makePromiseResolvedAfter(QOhosJsState &jsState, std::chrono::milliseconds delay)
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)
QNapi::Promise addItemToOhosStatusBar(QOhosJsState &jsState, QNapi::Object statusBarItem)
const auto trayIconGeometry
constexpr int quickOperationHeight
QNapi::Promise updateOhosStatusBarIcon(QOhosJsState &jsState, QNapi::Object iconData)
const std::string ohosSystemTrayItemTitle
QNapi::Promise makeRejectedPromise(napi_env env, QNapi::Value rejectValue)
QNapi::Object makeJsNotificationContent(QOhosJsState &jsState, const std::string &title, const std::string &text)
int setJsTimeout(QOhosJsState &jsState, std::function< void(const QOhosCallbackInfo &)> timeoutFunc, std::chrono::milliseconds delay)
QT_BEGIN_NAMESPACE std::unique_ptr< QPlatformSystemTrayIcon > makeQOhosSystemTrayIcon()