1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
#import <AppKit/AppKit.h>
#import <UserNotifications/UserNotifications.h>
#include "common/desktop.h"
#include "common/private_desktop.h"
#include <dispatch/dispatch.h>
#include <CoreFoundation/CoreFoundation.h>
#include <errno.h>
#include <limits.h>
#include <mach-o/dyld.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static size_t copy_str(char *buf, size_t cap, const char *s)
{
size_t len = s ? strlen(s) : 0;
if (!len || len >= cap) return 0;
memcpy(buf, s, len + 1);
return len;
}
bool desktop_open_uri(const char *uri)
{
@autoreleasepool {
NSString *str = [NSString stringWithUTF8String:uri];
NSURL *url = str ? [NSURL URLWithString:str] : nil;
return url && [[NSWorkspace sharedWorkspace] openURL:url];
}
}
/* Show banners while the application is in front, too. */
@interface YaslNotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
@end
@implementation YaslNotificationDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
(void)center;
(void)notification;
completionHandler(UNNotificationPresentationOptionBanner);
}
@end
static YaslNotificationDelegate *notification_delegate;
/* Main-thread state. Notifications made before the answer to the one
* authorization request wait in the queue and are posted when it is granted. */
static NSMutableArray<UNNotificationRequest *> *notification_queue;
static bool authorization_requested;
static bool authorization_granted;
static bool authorization_denied;
/* UNUserNotificationCenter raises an exception outside an app bundle. */
static bool notifications_available(void)
{
NSBundle *bundle = [NSBundle mainBundle];
return bundle.bundleIdentifier && [bundle.bundlePath.pathExtension isEqualToString:@"app"];
}
bool desktop_notify(const char *app_id, const char *title, const char *body)
{
(void)app_id;
if (!notifications_available()) {
errno = ENOTSUP;
return false;
}
if (authorization_denied) {
errno = EACCES;
return false;
}
@autoreleasepool {
NSString *heading = [NSString stringWithUTF8String:title];
NSString *message = [NSString stringWithUTF8String:body];
if (!heading || !message) {
errno = EINVAL;
return false;
}
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
if (!notification_delegate) {
notification_delegate = [[YaslNotificationDelegate alloc] init];
center.delegate = notification_delegate;
}
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = heading;
content.body = message;
UNNotificationRequest *request =
[UNNotificationRequest requestWithIdentifier:[[NSUUID UUID] UUIDString]
content:content
trigger:nil];
[content release];
if (authorization_granted) {
[center addNotificationRequest:request withCompletionHandler:nil];
return true;
}
if (!notification_queue)
notification_queue = [[NSMutableArray alloc] init];
[notification_queue addObject:request];
if (!authorization_requested) {
authorization_requested = true;
[center requestAuthorizationWithOptions:UNAuthorizationOptionAlert
completionHandler:^(BOOL granted, NSError *error) {
(void)error;
/* The handler runs on a background queue; the state is main-thread only. */
dispatch_async(dispatch_get_main_queue(), ^{
if (!granted) {
authorization_denied = true;
[notification_queue removeAllObjects];
return;
}
authorization_granted = true;
UNUserNotificationCenter *granted_center = [UNUserNotificationCenter currentNotificationCenter];
for (UNNotificationRequest *queued in notification_queue)
[granted_center addNotificationRequest:queued withCompletionHandler:nil];
[notification_queue removeAllObjects];
});
}];
}
return true;
}
}
uint32_t desktop_appearance(void)
{
@autoreleasepool {
uint32_t flags = 0;
NSAppearanceName name = [NSApp.effectiveAppearance
bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
if ([name isEqualToString:NSAppearanceNameDarkAqua])
flags |= YASL_PRIVATE_APPEARANCE_DARK;
else if (name)
flags |= YASL_PRIVATE_APPEARANCE_LIGHT;
if ([NSWorkspace sharedWorkspace].accessibilityDisplayShouldIncreaseContrast)
flags |= YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST;
return flags;
}
}
size_t desktop_executable_path(char *buf, size_t cap)
{
char raw[PATH_MAX];
uint32_t size = sizeof(raw);
char resolved[PATH_MAX];
if (_NSGetExecutablePath(raw, &size) != 0 || !realpath(raw, resolved)) return 0;
return copy_str(buf, cap, resolved);
}
size_t desktop_resource_dir(char *buf, size_t cap)
{
@autoreleasepool {
NSBundle *bundle = [NSBundle mainBundle];
if (!bundle.bundleIdentifier || ![bundle.bundlePath.pathExtension isEqualToString:@"app"])
return 0;
NSString *path = bundle.resourcePath;
return path ? copy_str(buf, cap, path.fileSystemRepresentation) : 0;
}
}
size_t desktop_default_locale(char *buf, size_t cap)
{
/* Identifiers such as "zh-Hans_CN" are not POSIX names, so build the name
* from the language and region codes alone. */
char name[64] = "en_US.UTF-8";
CFLocaleRef locale = CFLocaleCopyCurrent();
if (locale) {
CFStringRef language = CFLocaleGetValue(locale, kCFLocaleLanguageCode);
CFStringRef region = CFLocaleGetValue(locale, kCFLocaleCountryCode);
char lang[16], reg[16];
if (language && CFStringGetCString(language, lang, sizeof(lang), kCFStringEncodingUTF8) && lang[0]) {
if (region && CFStringGetCString(region, reg, sizeof(reg), kCFStringEncodingUTF8) && reg[0])
snprintf(name, sizeof(name), "%s_%s.UTF-8", lang, reg);
else
snprintf(name, sizeof(name), "%s.UTF-8", lang);
}
CFRelease(locale);
}
return copy_str(buf, cap, name);
}
|