Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qcocoasystemtrayicon.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2012 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Christoph Schleifenbaum <christoph.schleifenbaum@kdab.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5/****************************************************************************
6**
7** Copyright (c) 2007-2008, Apple, Inc.
8**
9** All rights reserved.
10**
11** Redistribution and use in source and binary forms, with or without
12** modification, are permitted provided that the following conditions are met:
13**
14** * Redistributions of source code must retain the above copyright notice,
15** this list of conditions and the following disclaimer.
16**
17** * Redistributions in binary form must reproduce the above copyright notice,
18** this list of conditions and the following disclaimer in the documentation
19** and/or other materials provided with the distribution.
20**
21** * Neither the name of Apple, Inc. nor the names of its contributors
22** may be used to endorse or promote products derived from this software
23** without specific prior written permission.
24**
25** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36**
37****************************************************************************/
38
39#include <AppKit/AppKit.h>
40
42
43#ifndef QT_NO_SYSTEMTRAYICON
44
45#include <qtemporaryfile.h>
46#include <qimagewriter.h>
47#include <qdebug.h>
48
49#include <QtCore/private/qcore_mac_p.h>
50
51#include "qcocoamenu.h"
52#include "qcocoansmenu.h"
53
54#include "qcocoahelpers.h"
55#include "qcocoaintegration.h"
56#include "qcocoascreen.h"
57#include <QtGui/private/qcoregraphics_p.h>
58
59#warning NSUserNotification was deprecated in macOS 11. \
60We should be using UserNotifications.framework instead. \
61See QTBUG-110998 for more information.
62#define NSUserNotificationCenter QT_IGNORE_DEPRECATIONS(NSUserNotificationCenter)
63#define NSUserNotification QT_IGNORE_DEPRECATIONS(NSUserNotification)
64
66
67void QCocoaSystemTrayIcon::init()
68{
69 m_statusItem = [[NSStatusBar.systemStatusBar statusItemWithLength:NSSquareStatusItemLength] retain];
70
71 m_delegate = [[QStatusItemDelegate alloc] initWithSysTray:this];
72
73 // In case the status item does not have a menu assigned to it
74 // we fall back to the item's button to detect activation.
75 m_statusItem.button.target = m_delegate;
76 m_statusItem.button.action = @selector(statusItemClicked);
77 [m_statusItem.button sendActionOn:NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown];
78}
79
80void QCocoaSystemTrayIcon::cleanup()
81{
82 NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
83 if (center.delegate == m_delegate)
84 center.delegate = nil;
85
86 [NSStatusBar.systemStatusBar removeStatusItem:m_statusItem];
87 [m_statusItem release];
88 m_statusItem = nil;
89
91 m_delegate = nil;
92}
93
94QRect QCocoaSystemTrayIcon::geometry() const
95{
96 if (!m_statusItem)
97 return QRect();
98
99 if (NSWindow *window = m_statusItem.button.window) {
101 return screen->mapFromNative(window.frame).toRect();
102 }
103
104 return QRect();
105}
106
107static bool heightCompareFunction (QSize a, QSize b) { return (a.height() < b.height()); }
108static QList<QSize> sortByHeight(const QList<QSize> &sizes)
109{
110 QList<QSize> sorted = sizes;
111 std::sort(sorted.begin(), sorted.end(), heightCompareFunction);
112 return sorted;
113}
114
115void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
116{
117 if (!m_statusItem)
118 return;
119
120 // The recommended maximum title bar icon height is 18 points
121 // (device independent pixels). The menu height on past and
122 // current OS X versions is 22 points. Provide some future-proofing
123 // by deriving the icon height from the menu height.
124 const int padding = 4;
125 const int menuHeight = NSStatusBar.systemStatusBar.thickness;
126 const int maxImageHeight = menuHeight - padding;
127
128 // Select pixmap based on the device pixel height. Ideally we would use
129 // the devicePixelRatio of the target screen, but that value is not
130 // known until draw time. Use qApp->devicePixelRatio, which returns the
131 // devicePixelRatio for the "best" screen on the system.
132 qreal devicePixelRatio = qApp->devicePixelRatio();
133 const int maxPixmapHeight = maxImageHeight * devicePixelRatio;
134 QSize selectedSize;
135 for (const QSize& size : sortByHeight(icon.availableSizes())) {
136 // Select a pixmap based on the height. We want the largest pixmap
137 // with a height smaller or equal to maxPixmapHeight. The pixmap
138 // may rectangular; assume it has a reasonable size. If there is
139 // not suitable pixmap use the smallest one the icon can provide.
140 if (size.height() <= maxPixmapHeight) {
141 selectedSize = size;
142 } else {
143 if (!selectedSize.isValid())
144 selectedSize = size;
145 break;
146 }
147 }
148
149 // Handle SVG icons, which do not return anything for availableSizes().
150 if (!selectedSize.isValid())
151 selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight));
152
153 QPixmap pixmap = icon.pixmap(selectedSize);
154
155 // Draw a low-resolution icon if there is not enough pixels for a retina
156 // icon. This prevents showing a small icon on retina displays.
157 if (devicePixelRatio > 1.0 && selectedSize.height() < maxPixmapHeight / 2)
158 devicePixelRatio = 1.0;
159
160 // Scale large pixmaps to fit the available menu bar area.
161 if (pixmap.height() > maxPixmapHeight)
162 pixmap = pixmap.scaledToHeight(maxPixmapHeight, Qt::SmoothTransformation);
163
164 // The icon will be stretched over the full height of the menu bar
165 // therefore we create a second pixmap which has the full height
166 QSize fullHeightSize(!pixmap.isNull() ? pixmap.width():
167 menuHeight * devicePixelRatio,
168 menuHeight * devicePixelRatio);
169 QPixmap fullHeightPixmap(fullHeightSize);
170 fullHeightPixmap.fill(Qt::transparent);
171 if (!pixmap.isNull()) {
172 QPainter p(&fullHeightPixmap);
173 QRect r = pixmap.rect();
174 r.moveCenter(fullHeightPixmap.rect().center());
175 p.drawPixmap(r, pixmap);
176 }
177 fullHeightPixmap.setDevicePixelRatio(devicePixelRatio);
178
179 auto *nsimage = [NSImage imageFromQImage:fullHeightPixmap.toImage()];
180 [nsimage setTemplate:icon.isMask()];
181 m_statusItem.button.image = nsimage;
182 m_statusItem.button.imageScaling = NSImageScaleProportionallyDown;
183}
184
185void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu)
186{
187 auto *nsMenu = menu ? static_cast<QCocoaMenu *>(menu)->nsMenu() : nil;
188 if (m_statusItem.menu == nsMenu)
189 return;
190
191 if (m_statusItem.menu) {
192 [NSNotificationCenter.defaultCenter removeObserver:m_delegate
193 name:NSMenuDidBeginTrackingNotification
194 object:m_statusItem.menu
195 ];
196 }
197
198 m_statusItem.menu = nsMenu;
199
200 if (m_statusItem.menu) {
201 // When a menu is assigned, NSStatusBarButtonCell will intercept the mouse
202 // down to pop up the menu, and we never see the NSStatusBarButton action.
203 // To ensure we emit the 'activated' signal in both cases we detect when
204 // menu starts tracking, which happens before the menu delegate is sent
205 // the menuWillOpen callback we use to emit aboutToShow for the menu.
206 [NSNotificationCenter.defaultCenter addObserver:m_delegate
207 selector:@selector(statusItemMenuBeganTracking:)
208 name:NSMenuDidBeginTrackingNotification
209 object:m_statusItem.menu
210 ];
211 }
212}
213
214void QCocoaSystemTrayIcon::updateToolTip(const QString &toolTip)
215{
216 if (!m_statusItem)
217 return;
218
219 m_statusItem.button.toolTip = toolTip.toNSString();
220}
221
222bool QCocoaSystemTrayIcon::isSystemTrayAvailable() const
223{
224 return true;
225}
226
227bool QCocoaSystemTrayIcon::supportsMessages() const
228{
229 return true;
230}
231
232void QCocoaSystemTrayIcon::showMessage(const QString &title, const QString &message,
233 const QIcon& icon, MessageIcon, int msecs)
234{
235 if (!m_statusItem)
236 return;
237
238 auto *notification = [[NSUserNotification alloc] init];
239 notification.title = title.toNSString();
240 notification.informativeText = message.toNSString();
241 notification.contentImage = [NSImage imageFromQIcon:icon];
242
243 NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
244 center.delegate = m_delegate;
245
246 [center deliverNotification:[notification autorelease]];
247
248 if (msecs) {
249 NSTimeInterval timeout = msecs / 1000.0;
250 [center performSelector:@selector(removeDeliveredNotification:) withObject:notification afterDelay:timeout];
251 }
252}
253
254void QCocoaSystemTrayIcon::emitActivated()
255{
256 auto *mouseEvent = NSApp.currentEvent;
257
258 auto activationReason = QPlatformSystemTrayIcon::Unknown;
259
260 if (mouseEvent.clickCount == 2) {
261 activationReason = QPlatformSystemTrayIcon::DoubleClick;
262 } else {
263 auto mouseButton = cocoaButton2QtButton(mouseEvent);
264 if (mouseButton == Qt::MiddleButton)
265 activationReason = QPlatformSystemTrayIcon::MiddleClick;
266 else if (mouseButton == Qt::RightButton)
267 activationReason = QPlatformSystemTrayIcon::Context;
268 else
269 activationReason = QPlatformSystemTrayIcon::Trigger;
270 }
271
272 emit activated(activationReason);
273}
274
276
277@implementation QStatusItemDelegate
278
279- (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)platformSystemTray
280{
281 if ((self = [super init]))
282 self.platformSystemTray = platformSystemTray;
283
284 return self;
285}
286
287- (void)dealloc
288{
289 self.platformSystemTray = nullptr;
290 [super dealloc];
291}
292
293- (void)statusItemClicked
294{
295 self.platformSystemTray->emitActivated();
296}
297
298- (void)statusItemMenuBeganTracking:(NSNotification*)notification
299{
300 self.platformSystemTray->emitActivated();
301}
302
303- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
304{
305 Q_UNUSED(center);
306 Q_UNUSED(notification);
307 return YES;
308}
309
310- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
311{
312 [center removeDeliveredNotification:notification];
313 emit self.platformSystemTray->messageClicked();
314}
315
316@end
317
318#endif // QT_NO_SYSTEMTRAYICON
DarwinBluetooth::DeviceInquiryDelegate * m_delegate
static QCocoaScreen * get(NSScreen *nsScreen)
The QIcon class provides scalable icons in different modes and states.
Definition qicon.h:20
bool isMask() const
Definition qicon.cpp:1827
QSize actualSize(const QSize &size, Mode mode=Normal, State state=Off) const
Returns the actual size of the icon for the requested size, mode, and state.
Definition qicon.cpp:926
QPixmap pixmap(const QSize &size, Mode mode=Normal, State state=Off) const
Returns a pixmap with the requested size, mode, and state, generating one if necessary.
Definition qicon.cpp:834
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
Definition qpixmap.h:27
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr void moveCenter(const QPoint &p) noexcept
Moves the rectangle, leaving the center point at the given position.
Definition qrect.h:328
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
Combined button and popup list for selecting options.
@ RightButton
Definition qnamespace.h:59
@ MiddleButton
Definition qnamespace.h:60
@ SmoothTransformation
@ transparent
Definition qnamespace.h:47
QTextStream & center(QTextStream &stream)
Calls QTextStream::setFieldAlignment(QTextStream::AlignCenter) on stream and returns stream.
QString self
Definition language.cpp:58
Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum)
Returns the Qt::Button that corresponds to an NSEvent.buttonNumber.
static bool heightCompareFunction(QSize a, QSize b)
#define NSUserNotificationCenter
static QList< QSize > sortByHeight(const QList< QSize > &sizes)
#define NSUserNotification
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
GLboolean GLboolean GLboolean b
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLboolean r
[2]
GLuint object
[3]
GLbitfield GLuint64 timeout
[4]
GLuint GLsizei const GLchar * message
GLuint name
GLuint GLsizei const GLuint const GLintptr const GLsizeiptr * sizes
GLfloat GLfloat p
[1]
QScreen * screen
[1]
Definition main.cpp:29
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
#define Q_UNUSED(x)
double qreal
Definition qtypes.h:187
QFileSelector selector
[1]
QString title
[35]
sem release()
widget render & pixmap
aWidget window() -> setWindowTitle("New Window Title")
[2]
QMenu menu
[5]