7#include <AppKit/AppKit.h>
11#ifndef QT_NO_SYSTEMTRAYICON
13#include <qtemporaryfile.h>
14#include <qimagewriter.h>
17#include <QtCore/private/qcore_mac_p.h>
25#include <QtGui/private/qcoregraphics_p.h>
35void QCocoaSystemTrayIcon::init()
37 m_statusItem = [[NSStatusBar.systemStatusBar statusItemWithLength:NSSquareStatusItemLength] retain];
39 m_delegate = [[QStatusItemDelegate alloc] initWithSysTray:
this];
43 m_statusItem.button.target = m_delegate;
44 m_statusItem.button.action = @selector(statusItemClicked);
45 [m_statusItem.button sendActionOn:NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown];
48void QCocoaSystemTrayIcon::cleanup()
51 if (center.delegate == m_delegate)
52 center.delegate = nil;
54 [NSStatusBar.systemStatusBar removeStatusItem:m_statusItem];
55 [m_statusItem release];
62QRect QCocoaSystemTrayIcon::geometry()
const
67 if (NSWindow *window = m_statusItem.button.window) {
68 if (QCocoaScreen *screen = QCocoaScreen::get(window.screen))
69 return screen->mapFromNative(window.frame).toRect();
78 QList<QSize> sorted = sizes;
79 std::sort(sorted.begin(), sorted.end(), heightCompareFunction);
83void QCocoaSystemTrayIcon::updateIcon(
const QIcon &icon)
92 const int padding = 4;
93 const int menuHeight = NSStatusBar.systemStatusBar.thickness;
94 const int maxImageHeight = menuHeight - padding;
100 qreal devicePixelRatio = qApp->devicePixelRatio();
101 const int maxPixmapHeight = maxImageHeight * devicePixelRatio;
103 for (
const QSize& size : sortByHeight(icon.availableSizes())) {
108 if (size.height() <= maxPixmapHeight) {
111 if (!selectedSize.isValid())
118 if (!selectedSize.isValid())
119 selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight));
121 QPixmap pixmap = icon.pixmap(selectedSize);
125 if (devicePixelRatio > 1.0 && selectedSize.height() < maxPixmapHeight / 2)
126 devicePixelRatio = 1.0;
129 if (pixmap.height() > maxPixmapHeight)
130 pixmap = pixmap.scaledToHeight(maxPixmapHeight, Qt::SmoothTransformation);
134 QSize fullHeightSize(!pixmap.isNull() ? pixmap.width():
135 menuHeight * devicePixelRatio,
136 menuHeight * devicePixelRatio);
137 QPixmap fullHeightPixmap(fullHeightSize);
138 fullHeightPixmap.fill(Qt::transparent);
139 if (!pixmap.isNull()) {
140 QPainter p(&fullHeightPixmap);
141 QRect r = pixmap.rect();
142 r.moveCenter(fullHeightPixmap.rect().center());
143 p.drawPixmap(r, pixmap);
145 fullHeightPixmap.setDevicePixelRatio(devicePixelRatio);
147 auto *nsimage = [NSImage imageFromQImage:fullHeightPixmap.toImage()];
148 [nsimage setTemplate:icon.isMask()];
149 m_statusItem.button.image = nsimage;
150 m_statusItem.button.imageScaling = NSImageScaleProportionallyDown;
153void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu)
155 auto *nsMenu = menu ?
static_cast<QCocoaMenu *>(menu)->nsMenu() : nil;
156 if (m_statusItem.menu == nsMenu)
159 if (m_statusItem.menu) {
160 [NSNotificationCenter.defaultCenter removeObserver:m_delegate
161 name:NSMenuDidBeginTrackingNotification
162 object:m_statusItem.menu
166 m_statusItem.menu = nsMenu;
168 if (m_statusItem.menu) {
174 [NSNotificationCenter.defaultCenter addObserver:m_delegate
175 selector:@selector(statusItemMenuBeganTracking:)
176 name:NSMenuDidBeginTrackingNotification
177 object:m_statusItem.menu
182void QCocoaSystemTrayIcon::updateToolTip(
const QString &toolTip)
187 m_statusItem.button.toolTip = toolTip.toNSString();
190bool QCocoaSystemTrayIcon::isSystemTrayAvailable()
const
195bool QCocoaSystemTrayIcon::supportsMessages()
const
200void QCocoaSystemTrayIcon::showMessage(
const QString &title,
const QString &message,
201 const QIcon& icon, MessageIcon,
int msecs)
207 notification.title = title.toNSString();
208 notification.informativeText = message.toNSString();
209 notification.contentImage = [NSImage imageFromQIcon:icon];
212 center.delegate = m_delegate;
214 [center deliverNotification:[notification autorelease]];
217 NSTimeInterval timeout = msecs / 1000.0;
218 [center performSelector:@selector(removeDeliveredNotification:) withObject:notification afterDelay:timeout];
222void QCocoaSystemTrayIcon::emitActivated()
224 auto *mouseEvent = NSApp.currentEvent;
226 auto activationReason = QPlatformSystemTrayIcon::Unknown;
228 if (mouseEvent.clickCount == 2) {
229 activationReason = QPlatformSystemTrayIcon::DoubleClick;
231 auto mouseButton = cocoaButton2QtButton(mouseEvent);
232 if (mouseButton == Qt::MiddleButton)
233 activationReason = QPlatformSystemTrayIcon::MiddleClick;
234 else if (mouseButton == Qt::RightButton)
235 activationReason = QPlatformSystemTrayIcon::Context;
237 activationReason = QPlatformSystemTrayIcon::Trigger;
240 emit activated(activationReason);
245@implementation QStatusItemDelegate
247- (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)platformSystemTray
249 if ((self = [super init]))
250 self.platformSystemTray = platformSystemTray;
257 self.platformSystemTray =
nullptr;
261- (
void)statusItemClicked
263 self.platformSystemTray->emitActivated();
266- (
void)statusItemMenuBeganTracking:(NSNotification*)notification
268 self.platformSystemTray->emitActivated();
274 Q_UNUSED(notification);
280 [center removeDeliveredNotification:notification];
281 emit self.platformSystemTray->messageClicked();
static bool heightCompareFunction(QSize a, QSize b)
#define NSUserNotificationCenter
static QList< QSize > sortByHeight(const QList< QSize > &sizes)
#define NSUserNotification