Skip to content

Commit

Permalink
Replace deprecated NSNotificationCentre with UNNotificationCentre
Browse files Browse the repository at this point in the history
Sadly this requires the apps to be signed, so it only really works for store-distributed apps.
But it's required because of the deprecations.

Fixes fyne-io#1833
  • Loading branch information
andydotxyz committed Aug 19, 2021
1 parent 2af008e commit fc94fa3
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 138 deletions.
74 changes: 23 additions & 51 deletions app/app_darwin.go
@@ -1,87 +1,59 @@
// +build !ci

// +build !ios

package app

/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#cgo LDFLAGS: -framework Foundation -framework UserNotifications
#include <AppKit/AppKit.h>
#include <stdbool.h>
#include <stdlib.h>
bool isBundled();
bool isDarkMode();
void sendNotification(const char *title, const char *content);
void watchTheme();
void sendNotification(char *title, char *content);
*/
import "C"
import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"unsafe"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"

"golang.org/x/sys/execabs"
)

func defaultVariant() fyne.ThemeVariant {
if C.isDarkMode() {
return theme.VariantDark
}
return theme.VariantLight
}

func rootConfigDir() string {
homeDir, _ := os.UserHomeDir()

desktopConfig := filepath.Join(filepath.Join(homeDir, "Library"), "Preferences")
return filepath.Join(desktopConfig, "fyne")
}

func (a *fyneApp) OpenURL(url *url.URL) error {
cmd := a.exec("open", url.String())
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
return cmd.Run()
}

func (a *fyneApp) SendNotification(n *fyne.Notification) {
if C.isBundled() {
title := C.CString(n.Title)
defer C.free(unsafe.Pointer(title))
content := C.CString(n.Content)
defer C.free(unsafe.Pointer(content))
titleStr := C.CString(n.Title)
defer C.free(unsafe.Pointer(titleStr))
contentStr := C.CString(n.Content)
defer C.free(unsafe.Pointer(contentStr))

C.sendNotification(title, content)
C.sendNotification(titleStr, contentStr)
return
}

title := escapeNotificationString(n.Title)
content := escapeNotificationString(n.Content)
template := `display notification "%s" with title "%s"`
script := fmt.Sprintf(template, content, title)

err := execabs.Command("osascript", "-e", script).Start()
if err != nil {
fyne.LogError("Failed to launch darwin notify script", err)
}
fallbackNotification(n.Title, n.Content)
}

func escapeNotificationString(in string) string {
noSlash := strings.ReplaceAll(in, "\\", "\\\\")
return strings.ReplaceAll(noSlash, "\"", "\\\"")
}

//export themeChanged
func themeChanged() {
fyne.CurrentApp().Settings().(*settings).setupTheme()
//export fallbackSend
func fallbackSend(cTitle, cContent *C.char) {
title := C.GoString(cTitle)
content := C.GoString(cContent)
fallbackNotification(title, content)
}

func watchTheme() {
C.watchTheme()
func fallbackNotification(title, content string) {
template := `display notification "%s" with title "%s"`
script := fmt.Sprintf(template, escapeNotificationString(content), escapeNotificationString(title))

err := execabs.Command("osascript", "-e", script).Start()
if err != nil {
fyne.LogError("Failed to launch darwin notify script", err)
}
}
78 changes: 37 additions & 41 deletions app/app_darwin.m
@@ -1,55 +1,51 @@
// +build !ci
// +build !ios

extern void themeChanged();
#import <UserNotifications/UserNotifications.h>

#import <Foundation/Foundation.h>
static int notifyNum = 0;

@interface FyneUserNotificationCenterDelegate : NSObject<NSUserNotificationCenterDelegate>
extern void fallbackSend(char *cTitle, char *cBody);

- (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center
shouldPresentNotification:(NSUserNotification*)notification;
void doSendNotification(UNUserNotificationCenter *center, NSString *title, NSString *body) {
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
[content autorelease];
content.title = title;
content.body = body;

@end
notifyNum++;
NSString *identifier = [NSString stringWithFormat:@"fyne-notify-%d", notifyNum];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
content:content trigger:nil];

@implementation FyneUserNotificationCenterDelegate

- (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center
shouldPresentNotification:(NSUserNotification*)notification
{
return YES;
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(@"Could not send notification: %@", error);
}
}];
}

@end

void sendNSUserNotification(const char *, const char *);

bool isBundled() {
return [[NSBundle mainBundle] bundleIdentifier] != nil;
}

bool isDarkMode() {
NSString *style = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
return [@"Dark" isEqualToString:style];
}

void sendNotification(const char *title, const char *body) {
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
if (center.delegate == nil) {
center.delegate = [[FyneUserNotificationCenterDelegate new] autorelease];
}

NSString *uuid = [[NSUUID UUID] UUIDString];
NSUserNotification *notification = [[NSUserNotification new] autorelease];
notification.title = [NSString stringWithUTF8String:title];
notification.informativeText = [NSString stringWithUTF8String:body];
notification.identifier = [NSString stringWithFormat:@"%@-fyne-notify-%@", [[NSBundle mainBundle] bundleIdentifier], uuid];
[center scheduleNotification:notification];
}

void watchTheme() {
[[NSDistributedNotificationCenter defaultCenter] addObserverForName:@"AppleInterfaceThemeChangedNotification" object:nil queue:nil
usingBlock:^(NSNotification *note) {
themeChanged(); // calls back into Go
}];
void sendNotification(char *cTitle, char *cBody) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
NSString *title = [NSString stringWithUTF8String:cTitle];
NSString *body = [NSString stringWithUTF8String:cBody];

UNAuthorizationOptions options = UNAuthorizationOptionAlert;
[center requestAuthorizationWithOptions:options
completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (!granted) {
if (error != NULL) {
NSLog(@"Error asking for permission to send notifications %@", error);
// this happens if our app was not signed, so do it the old way
fallbackSend((char *)[title UTF8String], (char *)[body UTF8String]);
} else {
NSLog(@"Unable to get permission to send notifications");
}
} else {
doSendNotification(center, title, body);
}
}];
}
54 changes: 54 additions & 0 deletions app/app_desktop_darwin.go
@@ -0,0 +1,54 @@
// +build !ci

// +build !ios

package app

/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#include <AppKit/AppKit.h>
bool isBundled();
bool isDarkMode();
void watchTheme();
*/
import "C"
import (
"net/url"
"os"
"path/filepath"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)

func defaultVariant() fyne.ThemeVariant {
if C.isDarkMode() {
return theme.VariantDark
}
return theme.VariantLight
}

func rootConfigDir() string {
homeDir, _ := os.UserHomeDir()

desktopConfig := filepath.Join(filepath.Join(homeDir, "Library"), "Preferences")
return filepath.Join(desktopConfig, "fyne")
}

func (a *fyneApp) OpenURL(url *url.URL) error {
cmd := a.exec("open", url.String())
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
return cmd.Run()
}

//export themeChanged
func themeChanged() {
fyne.CurrentApp().Settings().(*settings).setupTheme()
}

func watchTheme() {
C.watchTheme()
}
18 changes: 18 additions & 0 deletions app/app_desktop_darwin.m
@@ -0,0 +1,18 @@
// +build !ci
// +build !ios

extern void themeChanged();

#import <Foundation/Foundation.h>

bool isDarkMode() {
NSString *style = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
return [@"Dark" isEqualToString:style];
}

void watchTheme() {
[[NSDistributedNotificationCenter defaultCenter] addObserverForName:@"AppleInterfaceThemeChangedNotification" object:nil queue:nil
usingBlock:^(NSNotification *note) {
themeChanged(); // calls back into Go
}];
}
9 changes: 0 additions & 9 deletions app/app_mobile_ios.go
Expand Up @@ -36,15 +36,6 @@ func (a *fyneApp) OpenURL(url *url.URL) error {
return nil
}

func (a *fyneApp) SendNotification(n *fyne.Notification) {
titleStr := C.CString(n.Title)
defer C.free(unsafe.Pointer(titleStr))
contentStr := C.CString(n.Content)
defer C.free(unsafe.Pointer(contentStr))

C.sendNotification(titleStr, contentStr)
}

func defaultVariant() fyne.ThemeVariant {
return systemTheme
}
37 changes: 0 additions & 37 deletions app/app_mobile_ios.m
Expand Up @@ -3,50 +3,13 @@
// +build ios

#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>

void openURL(char *urlStr) {
UIApplication *app = [UIApplication sharedApplication];
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlStr]];
[app openURL:url options:@{} completionHandler:nil];
}

static int notifyNum = 0;

void doSendNotification(UNUserNotificationCenter *center, NSString *title, NSString *body) {
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
[content autorelease];
content.title = title;
content.body = body;

notifyNum++;
NSString *identifier = [NSString stringWithFormat:@"fyne-notify-%d", notifyNum];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
content:content trigger:nil];

[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(@"Could not send notification: %@", error);
}
}];
}

void sendNotification(char *cTitle, char *cBody) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
NSString *title = [NSString stringWithUTF8String:cTitle];
NSString *body = [NSString stringWithUTF8String:cBody];

UNAuthorizationOptions options = UNAuthorizationOptionAlert;
[center requestAuthorizationWithOptions:options
completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (!granted) {
NSLog(@"Unable to get permission to send notifications");
} else {
doSendNotification(center, title, body);
}
}];
}

char *documentsPath() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = paths.firstObject;
Expand Down

0 comments on commit fc94fa3

Please sign in to comment.