diff --git a/lcl/interfaces/cocoa/cocoa_extra.pas b/lcl/interfaces/cocoa/cocoa_extra.pas index e9acb84149..11ebee253b 100644 --- a/lcl/interfaces/cocoa/cocoa_extra.pas +++ b/lcl/interfaces/cocoa/cocoa_extra.pas @@ -62,7 +62,74 @@ var NSPasteboardTypeRuler: NSPasteboardType; cvar; external; NSPasteboardTypeSound: NSPasteboardType; cvar; external; +const + UNAuthorizationOptionBadge = 1 shl 0; + UNAuthorizationOptionSound = 1 shl 1; + UNAuthorizationOptionAlert = 1 shl 2; + UNAuthorizationOptionCarPlay = 1 shl 3; + type + UNNotificationSound = objcclass external (NSObject) + class function defaultSound: UNNotificationSound; message 'defaultSound'; + end; + + UNNotificationContent = objcclass external (NSObject) + public + function title: NSString; message 'title'; + function subtitle: NSString; message 'subtitle'; + function body: NSString; message 'body'; + function badge: NSNumber; message 'badge'; + function sound: UNNotificationSound; message 'sound'; + end; + + UNMutableNotificationContent = objcclass external (UNNotificationContent) + public + procedure setTitle( newValue: NSString ); message 'setTitle:'; + procedure setSubtitle( newValue: NSString ); message 'setSubtitle:'; + procedure setBody( newValue: NSString ); message 'setBody:'; + procedure setBadge( newValue: NSNumber ); message 'setBadge:'; + procedure setSound( newValue: UNNotificationSound ); message 'setSound:'; + end; + + UNNotificationTrigger = objcclass external (NSObject) + public + function repeats: ObjCBool; message 'repeats'; + end; + + UNTimeIntervalNotificationTrigger = objcclass external (UNNotificationTrigger) + public + class function triggerWithTimeInterval_repeats( + timeInterval: NSTimeInterval; repeats_: ObjCBool ): id; + message 'triggerWithTimeInterval:repeats:'; + end; + + UNNotificationRequest = objcclass external (NSObject) + public + class function requestWithIdentifier_content_trigger( + identifier: NSString; + content: UNNotificationContent; + trigger: UNNotificationTrigger ): id; + message 'requestWithIdentifier:content:trigger:'; + function identifier: NSString; message 'identifier'; + function content: UNNotificationContent; message 'content'; + function trigger: UNNotificationTrigger; message 'trigger'; + end; + + UNAuthorizationOptions = NSUInteger; + + UNUserNotificationCenter = objcclass external (NSObject) + public + class function currentNotificationCenter: UNUserNotificationCenter; + message 'currentNotificationCenter'; + procedure requestAuthorizationWithOptions_completionHandler( + options: UNAuthorizationOptions; completionHandler: OpaqueCBlock = nil ); + message 'requestAuthorizationWithOptions:completionHandler:'; + procedure addNotificationRequest_withCompletionHandler( + request: UNNotificationRequest; + completionHandler: OpaqueCBlock = nil ); + message 'addNotificationRequest:withCompletionHandler:'; + end; + NSMenuFix = objccategory external (NSMenu) function itemAtIndex(index: NSInteger): NSMenuItem; message 'itemAtIndex:'; end; diff --git a/lcl/interfaces/cocoa/cocoatrayicon.inc b/lcl/interfaces/cocoa/cocoatrayicon.inc index 1af900bf7c..e072b566dc 100644 --- a/lcl/interfaces/cocoa/cocoatrayicon.inc +++ b/lcl/interfaces/cocoa/cocoatrayicon.inc @@ -154,15 +154,52 @@ begin StatusItemHandle.lclSetTrayIcon(ATrayIcon); end; -class function TCocoaWSCustomTrayIcon.ShowBalloonHint(const ATrayIcon: TCustomTrayIcon): Boolean; +// macOS 10.14+ required +// APP codesign required +// codesign --deep --force --verify --verbose --sign '-' 'your.app' +class function TCocoaWSCustomTrayIcon.newUserNotify(const ATrayIcon: TCustomTrayIcon): Boolean; +var + nc: UNUserNotificationCenter; + trigger: UNTimeIntervalNotificationTrigger; + message: UNMutableNotificationContent; + request: UNNotificationRequest; +const + options: NSInteger = UNAuthorizationOptionAlert or + UNAuthorizationOptionBadge or + UNAuthorizationOptionSound; +begin + Result:= True; + + nc:= UNUserNotificationCenter.currentNotificationCenter; + nc.requestAuthorizationWithOptions_completionHandler( options ); + + trigger:= UNTimeIntervalNotificationTrigger.triggerWithTimeInterval_repeats( + 0.01, False); + + message:= UNMutableNotificationContent.new; + message.setTitle( StrToNSString(ATrayIcon.BalloonTitle) ); + message.setBody( StrToNSString(ATrayIcon.BalloonHint) ); + message.setSound( UNNotificationSound.defaultSound ); + + request:= UNNotificationRequest.requestWithIdentifier_content_trigger( + NSString.string_, message, trigger ); + + nc.addNotificationRequest_withCompletionHandler( request ); + + message.release; +end; + +class function TCocoaWSCustomTrayIcon.legacyUserNotify(const ATrayIcon: TCustomTrayIcon): Boolean; var nc: NSUserNotificationCenter; message: NSUserNotification; begin Result := True; - message:= NSUserNotification.alloc.init; + message:= NSUserNotification.new; message.setTitle( StrToNSString(ATrayIcon.BalloonTitle) ); message.setInformativeText( StrToNSString(ATrayIcon.BalloonHint) ); + if NOT NSApplication(NSApp).isActive then + message.setSoundName( NSUserNotificationDefaultSoundName ); nc:= NSUserNotificationCenter.defaultUserNotificationCenter; if CocoaConfig.CocoaAlwaysPresentNotification then begin @@ -174,6 +211,14 @@ begin message.release; end; +class function TCocoaWSCustomTrayIcon.ShowBalloonHint(const ATrayIcon: TCustomTrayIcon): Boolean; +begin + if NSAppKitVersionNumber >= NSAppKitVersionNumber11_0 then + Result:= newUserNotify( ATrayIcon ) // APP codesign required + else + Result:= legacyUserNotify( ATrayIcon ); +end; + class function TCocoaWSCustomTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint; begin Result := Point(0, 0); diff --git a/lcl/interfaces/cocoa/cocoawsextctrls.pas b/lcl/interfaces/cocoa/cocoawsextctrls.pas index a6a3b085c0..f5fd4a4408 100644 --- a/lcl/interfaces/cocoa/cocoawsextctrls.pas +++ b/lcl/interfaces/cocoa/cocoawsextctrls.pas @@ -174,6 +174,9 @@ type { TCocoaWSCustomTrayIcon } TCocoaWSCustomTrayIcon = class(TWSCustomTrayIcon) + private + class function newUserNotify(const ATrayIcon: TCustomTrayIcon): Boolean; + class function legacyUserNotify(const ATrayIcon: TCustomTrayIcon): Boolean; published class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override; class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override; diff --git a/lcl/interfaces/lcl.lpk b/lcl/interfaces/lcl.lpk index a36b414196..167df15bdb 100644 --- a/lcl/interfaces/lcl.lpk +++ b/lcl/interfaces/lcl.lpk @@ -83,7 +83,7 @@ if TargetOS='darwin' then begin +' -framework OpenGL' +' ''-dylib_file'' ''/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib'''; end else if LCLWidgetType='cocoa' then - UsageLinkerOptions := '-framework Cocoa'; + UsageLinkerOptions := '-framework Cocoa -weak_framework UserNotifications'; end else if TargetOS='solaris' then begin UsageLibraryPath:='/usr/X11R6/lib'; end;"/>