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>
20#include <qohosplugincore.h>
36 return !hoverTips.empty() ? hoverTips :
" ";
42 return QNapi::Array::New(jsState.env());
50 qOhosReportFatalErrorAndAbort(
"%s: Default QAbilityPeer is not a QUiAbilityPeer", Q_FUNC_INFO);
52 return qUiAbilityPeer->qAbility().eval<QNapi::Object>(
"context");
58 return QtOhos::registerOnOffMethodsBasedEventHandler(
59 jsState.eval<QNapi::Object>(
"@kit.StatusBarExtensionKit.statusBarManager"),
61 [leftClickListener = std::move(leftClickListener)](
const QtOhos::
CallbackInfo &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"),
67 if (optIconClickType.IsEmpty()) {
69 "%s: no 'iconClickType' in the event (%s), ignoring it",
70 Q_FUNC_INFO, QNapi::toJsonString(eventData).c_str());
74 if (optIconClickType.Utf8Value() ==
"leftClick")
82 "@kit.StatusBarExtensionKit.statusBarManager.addToStatusBar(*)",
83 {getContextForStatusBarManager(jsState), statusBarItem});
85 qOhosPrintfDebug(
"%s: Successfully added icon to status bar [Statusbar] ", Q_FUNC_INFO);
91 "@kit.StatusBarExtensionKit.statusBarManager.removeFromStatusBar(*)",
92 {getContextForStatusBarManager(jsState)});
94 qOhosPrintfDebug(
"%s: Successfully removed icon from status bar", Q_FUNC_INFO);
99 qOhosPrintfDebug(
"%s", Q_FUNC_INFO);
101 if (iconData.IsEmpty()) {
102 qOhosPrintfError(
"%s: Icon data is empty", Q_FUNC_INFO);
107 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarIcon(*)",
108 {getContextForStatusBarManager(jsState), iconData});
110 qOhosPrintfDebug(
"%s: Successfully updated status bar icon", Q_FUNC_INFO);
116 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarMenu(*)",
117 {getContextForStatusBarManager(jsState), statusBarGroupMenus});
119 qOhosPrintfDebug(
"%s: Successfully updated status bar menu", Q_FUNC_INFO);
123 QtOhos::
JsState &jsState, QNapi::Object statusBarIcon,
const std::string &title,
124 QNapi::Array statusBarGroupMenus, QOhosOptional<std::string> hoverTips)
126 auto *env = jsState.env();
128 auto quickOperation = QNapi::makeObject(
132 {
"height", quickOperationHeight},
136 auto statusBarItem = QNapi::makeObject(
139 {
"icons", statusBarIcon},
140 {
"quickOperation", quickOperation},
141 {
"statusBarGroupMenu", statusBarGroupMenus},
144 if (hoverTips.has_value())
145 statusBarItem.set(
"hoverTips", applyWorkaroundForEmptyHoverTips(hoverTips.value()));
147 return statusBarItem;
152 constexpr auto fallbackDisplayDensity = 1.0;
154 auto primaryDisplay = jsState.eval<QNapi::Object>(
"@ohos.display.getPrimaryDisplaySync()");
155 auto displayInfo = QOhosDisplayInfo::makeFromOhosDisplayObject(jsState, primaryDisplay);
158 if (displayInfo.densityPixels > 0.0) {
159 density =
std::lround(displayInfo.densityPixels);
161 qOhosPrintfDebug(
"%s: invalid display densityPixels: %.3f", Q_FUNC_INFO, displayInfo.densityPixels);
165 return density > 0.0 ? density : fallbackDisplayDensity;
171 "%s: image dimensions: %dx%d, format: %d, bytes: %lld",
172 Q_FUNC_INFO, image.width(), image.height(),
static_cast<
int>(image.format()), image.sizeInBytes());
174 const int sourceWidth = image.width();
175 const int sourceHeight = image.height();
179 const double widthVp =
std::lround(sourceWidth / density);
180 const double heightVp =
std::lround(sourceHeight / density);
182 QImage newImage = image.scaled(widthVp, heightVp, Qt::KeepAspectRatio, Qt::SmoothTransformation);
184 const int width = newImage.width();
185 const int height = newImage.height();
186 const double finalWidthVp = width / density;
187 const double finalHeightVp = height / density;
190 "%s: density=%.3f, vp %.2f x %.2f -> %.2f x %.2f, px %dx%d -> %dx%d",
191 Q_FUNC_INFO, density, widthVp, heightVp, finalWidthVp, finalHeightVp, sourceWidth, sourceHeight, width, height);
193 return createNapiPixelMapFromQImage(jsState, newImage);
197 const QIcon &icon,
const QSize &imageSize,
const QColor &fallbackContentColor)
199 auto iconImage = icon.pixmap(imageSize).toImage();
200 if (!iconImage.isNull())
203 auto fallbackImage = QImage(imageSize, QImage::Format_RGBA8888);
204 fallbackImage.fill(fallbackContentColor);
205 return fallbackImage;
210 QImage monochromeImage = sourceImage.copy();
211 const int colorValue = useWhite ? 255 : 0;
213 for (
int y = 0; y < monochromeImage.height(); ++y) {
214 for (
int x = 0; x < monochromeImage.width(); ++x) {
215 const QRgb pixel = monochromeImage.pixel(x, y);
216 const int alpha = qAlpha(pixel);
219 monochromeImage.setPixel(x, y, qRgba(colorValue, colorValue, colorValue, alpha));
223 return monochromeImage;
228 const QSize iconSize(48, 48);
230 QImage iconImage = icon.pixmap(iconSize).toImage();
231 if (iconImage.isNull()) {
232 QImage defaultImage(iconSize, QImage::Format_RGBA8888);
233 defaultImage.fill(isWhiteIcon ? Qt::white : Qt::black);
234 return makeDisplayDensityScaledJsPixelMapFromQImage(jsState, defaultImage);
238 "%s: original Icon dimensions: %dx%d, format: %d, bytes: %lld",
239 Q_FUNC_INFO, iconImage.width(), iconImage.height(),
240 static_cast<
int>(iconImage.format()), iconImage.sizeInBytes());
242 return makeDisplayDensityScaledJsPixelMapFromQImage(
243 jsState, convertToMonochromeIcon(iconImage, isWhiteIcon));
248 auto *env = jsState.env();
250 auto whitePixelMap = makeJsPixelMapFromIcon(jsState, icon,
true);
251 auto blackPixelMap = makeJsPixelMapFromIcon(jsState, icon,
false);
253 auto imageSize = whitePixelMap.eval<QNapi::Object>(
"getImageInfoSync().size");;
255 int imageWidth = imageSize.get<QNapi::Number>(
"width");
256 int imageHeight = imageSize.get<QNapi::Number>(
"height");
257 qOhosPrintfDebug(
"%s: ceated PixelMap size: %dx%d", Q_FUNC_INFO, imageWidth, imageHeight);
259 return QNapi::makeObject(
262 {
"white", whitePixelMap},
263 {
"black", blackPixelMap},
272 return QNapi::makeObject(
275 {
"notificationContentType", jsState.mapOhosEnumToJs(ContentType::NOTIFICATION_CONTENT_BASIC_TEXT)},
290 const QIcon &icon, QOhosOptional<
int> optAutoDeletedDelayMs)
292 auto iconPixelMap = makeDisplayDensityScaledJsPixelMapFromQImage(
293 jsState, convertIconToScaledImage(icon, notificationIconSize, Qt::transparent));
294 auto notificationRequest = QNapi::makeObject(
297 {
"content", makeJsNotificationContent(jsState, title, content)},
298 {
"smallIcon", iconPixelMap},
300 if (optAutoDeletedDelayMs.has_value()) {
301 notificationRequest.set(
303 QDateTime::currentMSecsSinceEpoch() + optAutoDeletedDelayMs.value());
306 return notificationRequest;
309class QOhosSystemTrayIcon
final :
public QPlatformSystemTrayIcon
322 const QString &title,
const QString &msg,
const QIcon &icon,
323 MessageIcon iconType,
int msecs)
override;
331 bool m_initialized =
false;
333 QOhosOptional<QString> m_optToolTip;
334 QPlatformMenu *m_menu =
nullptr;
338 std::shared_ptr<
void> m_iconLeftClickListenerHandle;
341 std::shared_ptr<JsScopeData> m_jsScopeData;
351void QOhosSystemTrayIcon::
init()
356 auto jsStatusBarGroupMenusFactory =
358 ?
static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
359 : makeEmptyJsArrayFactory();
361 m_jsScopeData = QtOhos::makeProxyWithJsThreadDeleter(std::make_shared<JsScopeData>());
363 auto selfRef = QtOhos::makeQThreadSafeRef(
this);
366 auto jsStatusBarIcon = makeJsStatusBarIcon(jsState, m_icon);
367 auto jsStatusBarGroupMenus = jsStatusBarGroupMenusFactory(jsState);
368 auto optHoverTipsString = qTransform(
370 [](
const auto &toolTip) {
371 return toolTip.toStdString();
373 auto jsStatusBarItem = makeJsStatusBarItem(
374 jsState, jsStatusBarIcon, ohosSystemTrayItemTitle, jsStatusBarGroupMenus, optHoverTipsString);
375 addItemToOhosStatusBar(jsState, jsStatusBarItem);
376 m_jsScopeData->m_iconLeftClickListenerHandle = registerOhosIconLeftClickListener(
379 selfRef.visitInQtThreadIfAlive(
381 Q_EMIT self.activated(QPlatformSystemTrayIcon::Trigger);
387 m_initialized =
true;
389 qOhosPrintfDebug(
"%s: System tray icon initialized", Q_FUNC_INFO);
399 m_jsScopeData.reset();
401 m_initialized =
false;
413 updateOhosStatusBarIcon(jsState, makeJsStatusBarIcon(jsState, icon));
420 m_optToolTip = tooltip;
424 [&](
QtOhos::
JsState &jsState, QOhosTaskPromise<> taskPromise) {
425 jsState.evalToPromiseOrRejectOnThrow(
426 "@kit.StatusBarExtensionKit.statusBarManager.updateStatusBarHoverTips(*)",
427 {getContextForStatusBarManager(jsState), applyWorkaroundForEmptyHoverTips(tooltip.toStdString())})
428 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"updateStatusBarHoverTips()"))
429 .onFinally(
std::move(taskPromise).makeChained(Q_FUNC_INFO));
437 return trayIconGeometry;
441 const QString &title,
const QString &msg,
const QIcon &icon, MessageIcon iconType,
int msecs)
445 [&](
QtOhos::
JsState &jsState, QOhosTaskPromise<> taskPromise) {
446 auto notificationRequest = makeJsNotificationRequest(
447 jsState, title.toStdString(), msg.toStdString(), icon,
448 msecs > 0 ? makeQOhosOptional(msecs) : makeEmptyQOhosOptional());
449 jsState.evalToPromiseOrRejectOnThrow(
"@ohos.notificationManager.publish(*)", {notificationRequest})
450 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"publish()"))
451 .onFinally(
std::move(taskPromise).makeChained(Q_FUNC_INFO));
468 if (m_menu ==
nullptr)
469 return makeQOhosStatusBarMenu().release();
475 qOhosPrintfDebug(
"%s: Updating status bar menu", Q_FUNC_INFO);
479 if (!m_initialized) {
480 qOhosPrintfDebug(
"%s: System tray not initialized yet", Q_FUNC_INFO);
484 auto jsStatusBarGroupMenusFactory =
486 ?
static_cast<QOhosStatusBarMenu *>(m_menu)->makeJsStatusBarGroupMenusFactory()
487 : makeEmptyJsArrayFactory();
491 updateOhosStatusBarMenu(jsState, jsStatusBarGroupMenusFactory(jsState));
500 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...
~QOhosSystemTrayIcon() override
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)
const auto notificationIconSize
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)
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::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)
const auto trayIconGeometry
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)
double getPrimaryDisplayPixelDensity(QtOhos::JsState &jsState)
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()