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)
88 if (
auto *image = [NSImage internalImageFromQIcon:icon]) {
95 m_statusItem.button.image = [[image copy] autorelease];
96 m_statusItem.button.imageScaling = NSImageScaleProportionallyDown;
97 m_statusItem.length = NSVariableStatusItemLength;
105 const int padding = 4;
106 const int menuHeight = NSStatusBar.systemStatusBar.thickness;
107 const int maxImageHeight = menuHeight - padding;
113 qreal devicePixelRatio = qApp->devicePixelRatio();
114 const int maxPixmapHeight = maxImageHeight * devicePixelRatio;
116 for (
const QSize& size : sortByHeight(icon.availableSizes())) {
121 if (size.height() <= maxPixmapHeight) {
124 if (!selectedSize.isValid())
131 if (!selectedSize.isValid())
132 selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight));
134 QPixmap pixmap = icon.pixmap(selectedSize);
138 if (devicePixelRatio > 1.0 && selectedSize.height() < maxPixmapHeight / 2)
139 devicePixelRatio = 1.0;
142 if (pixmap.height() > maxPixmapHeight)
143 pixmap = pixmap.scaledToHeight(maxPixmapHeight, Qt::SmoothTransformation);
147 QSize fullHeightSize(!pixmap.isNull() ? pixmap.width():
148 menuHeight * devicePixelRatio,
149 menuHeight * devicePixelRatio);
150 QPixmap fullHeightPixmap(fullHeightSize);
151 fullHeightPixmap.fill(Qt::transparent);
152 if (!pixmap.isNull()) {
153 QPainter p(&fullHeightPixmap);
154 QRect r = pixmap.rect();
155 r.moveCenter(fullHeightPixmap.rect().center());
156 p.drawPixmap(r, pixmap);
158 fullHeightPixmap.setDevicePixelRatio(devicePixelRatio);
160 auto *nsimage = [NSImage imageFromQImage:fullHeightPixmap.toImage()];
161 [nsimage setTemplate:icon.isMask()];
162 m_statusItem.button.image = nsimage;
163 m_statusItem.button.imageScaling = NSImageScaleProportionallyDown;
166void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu)
168 auto *nsMenu = menu ?
static_cast<QCocoaMenu *>(menu)->nsMenu() : nil;
169 if (m_statusItem.menu == nsMenu)
172 if (m_statusItem.menu) {
173 [NSNotificationCenter.defaultCenter removeObserver:m_delegate
174 name:NSMenuDidBeginTrackingNotification
175 object:m_statusItem.menu
179 m_statusItem.menu = nsMenu;
181 if (m_statusItem.menu) {
187 [NSNotificationCenter.defaultCenter addObserver:m_delegate
188 selector:@selector(statusItemMenuBeganTracking:)
189 name:NSMenuDidBeginTrackingNotification
190 object:m_statusItem.menu
195void QCocoaSystemTrayIcon::updateToolTip(
const QString &toolTip)
200 m_statusItem.button.toolTip = toolTip.toNSString();
203bool QCocoaSystemTrayIcon::isSystemTrayAvailable()
const
208bool QCocoaSystemTrayIcon::supportsMessages()
const
213void QCocoaSystemTrayIcon::showMessage(
const QString &title,
const QString &message,
214 const QIcon& icon, MessageIcon,
int msecs)
220 notification.title = title.toNSString();
221 notification.informativeText = message.toNSString();
225 auto image = icon.pixmap(QSize(64, 64), qGuiApp->devicePixelRatio()).toImage();
230 image = qt_mac_padToSquareImage(image);
232 notification.contentImage = [NSImage imageFromQImage:image];
235 center.delegate = m_delegate;
237 [center deliverNotification:[notification autorelease]];
240 NSTimeInterval timeout = msecs / 1000.0;
241 [center performSelector:@selector(removeDeliveredNotification:) withObject:notification afterDelay:timeout];
245void QCocoaSystemTrayIcon::emitActivated()
247 auto *mouseEvent = NSApp.currentEvent;
249 auto activationReason = QPlatformSystemTrayIcon::Unknown;
251 if (mouseEvent.clickCount == 2) {
252 activationReason = QPlatformSystemTrayIcon::DoubleClick;
254 auto mouseButton = cocoaButton2QtButton(mouseEvent);
255 if (mouseButton == Qt::MiddleButton)
256 activationReason = QPlatformSystemTrayIcon::MiddleClick;
257 else if (mouseButton == Qt::RightButton)
258 activationReason = QPlatformSystemTrayIcon::Context;
260 activationReason = QPlatformSystemTrayIcon::Trigger;
263 emit activated(activationReason);
268@implementation QStatusItemDelegate
270- (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)platformSystemTray
272 if ((self = [super init]))
273 self.platformSystemTray = platformSystemTray;
280 self.platformSystemTray =
nullptr;
284- (
void)statusItemClicked
286 self.platformSystemTray->emitActivated();
289- (
void)statusItemMenuBeganTracking:(NSNotification*)notification
291 self.platformSystemTray->emitActivated();
297 Q_UNUSED(notification);
303 [center removeDeliveredNotification:notification];
304 emit self.platformSystemTray->messageClicked();
Combined button and popup list for selecting options.
static bool heightCompareFunction(QSize a, QSize b)
#define NSUserNotificationCenter
static QList< QSize > sortByHeight(const QList< QSize > &sizes)
#define NSUserNotification