diff --git a/.github/workflows/feature.yml b/.github/workflows/feature.yml
index 563b597e..75c92f55 100644
--- a/.github/workflows/feature.yml
+++ b/.github/workflows/feature.yml
@@ -24,6 +24,8 @@ jobs:
- name: Build
run: |
brew install automake
+ brew install autoconf
+ brew install libtool
make VERSION="${GITHUB_SHA::7}" debug
make debug-dmg
shasum -a 256 build/Debug/ShadowsocksX-NG.dmg > build/Debug/ShadowsocksX-NG.dmg.checksum
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 282d72c8..b1fb13e5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -21,6 +21,8 @@ jobs:
- name: Build
run: |
brew install automake
+ brew install autoconf
+ brew install libtool
make VERSION="${GITHUB_REF_NAME}" release
make release-dmg
shasum -a 256 build/Release/ShadowsocksX-NG.dmg > build/Release/ShadowsocksX-NG.dmg.checksum
diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift
index 015db581..09d42bfb 100755
--- a/ShadowsocksX-NG/AppDelegate.swift
+++ b/ShadowsocksX-NG/AppDelegate.swift
@@ -627,42 +627,43 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
}
func handleFoundSSURL(_ note: Notification) {
- let sendNotify = {
- (title: String, subtitle: String, infoText: String) in
-
+ let sendNotify = { (title: String, subtitle: String, infoText: String) in
let userNote = NSUserNotification()
userNote.title = title
userNote.subtitle = subtitle
userNote.informativeText = infoText
userNote.soundName = NSUserNotificationDefaultSoundName
- NSUserNotificationCenter.default
- .deliver(userNote);
+ NSUserNotificationCenter.default.deliver(userNote)
}
if let userInfo = (note as NSNotification).userInfo {
- let urls: [URL] = userInfo["urls"] as! [URL]
+ // 检查错误
+ if let error = userInfo["error"] as? String {
+ sendNotify("Scan Failed", "", error.localized)
+ return
+ }
+
+ // 使用新的通知信息
+ let title = (userInfo["title"] as? String) ?? ""
+ let subtitle = (userInfo["subtitle"] as? String) ?? ""
+ let body = (userInfo["body"] as? String) ?? ""
- let mgr = ServerProfileManager.instance
- let addCount = mgr.addServerProfileByURL(urls: urls)
+ let urls: [URL] = userInfo["urls"] as! [URL]
+ let addCount = ServerProfileManager.instance.addServerProfileByURL(urls: urls)
if addCount > 0 {
- var subtitle: String = ""
- if userInfo["source"] as! String == "qrcode" {
- subtitle = "By scan QR Code".localized
- } else if userInfo["source"] as! String == "url" {
- subtitle = "By handle SS URL".localized
- } else if userInfo["source"] as! String == "pasteboard" {
- subtitle = "By import from pasteboard".localized
- }
-
- sendNotify("Add \(addCount) Shadowsocks Server Profile".localized, subtitle, "")
+ sendNotify(
+ title.localized,
+ subtitle.localized,
+ "Successfully added \(addCount) server configuration(s)".localized
+ )
} else {
- if userInfo["source"] as! String == "qrcode" {
- sendNotify("", "", "Not found valid QRCode of shadowsocks profile".localized)
- } else if userInfo["source"] as! String == "url" {
- sendNotify("", "", "Not found valid URL of shadowsocks profile".localized)
- }
+ sendNotify(
+ title.localized,
+ subtitle.localized,
+ body.localized
+ )
}
}
}
diff --git a/ShadowsocksX-NG/Info.plist b/ShadowsocksX-NG/Info.plist
index 5fc2d778..737a3a28 100644
--- a/ShadowsocksX-NG/Info.plist
+++ b/ShadowsocksX-NG/Info.plist
@@ -49,5 +49,7 @@
MainMenu
NSPrincipalClass
SWBApplication
+ NSScreenCaptureUsageDescription
+ ShadowsocksX-NG needs Screen Recording permission to scan QR codes on your screen
diff --git a/ShadowsocksX-NG/Utils.m b/ShadowsocksX-NG/Utils.m
index 20f0fb76..0cef8ffa 100644
--- a/ShadowsocksX-NG/Utils.m
+++ b/ShadowsocksX-NG/Utils.m
@@ -10,71 +10,153 @@
#import
#import
-void ScanQRCodeOnScreen(void) {
+void ScanQRCodeOnScreen(void) {
+ /* check system version and permission status */
+ if (@available(macOS 10.12, *)) {
+ BOOL hasPermission = CGPreflightScreenCaptureAccess();
+ NSLog(@"Screen Recording Permission Status: %@", hasPermission ? @"Granted" : @"Not Granted");
+
+ if (!hasPermission) {
+ NSLog(@"Requesting Screen Recording Permission...");
+ CGRequestScreenCaptureAccess();
+
+ /* check permission status after request */
+ hasPermission = CGPreflightScreenCaptureAccess();
+ NSLog(@"Screen Recording Permission Status After Request: %@", hasPermission ? @"Granted" : @"Not Granted");
+
+ if (!hasPermission) {
+ NSLog(@"Screen Recording Permission Denied");
+
+ /* send notification about permission missing */
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"NOTIFY_FOUND_SS_URL"
+ object:nil
+ userInfo:@{
+ @"urls": @[],
+ @"source": @"qrcode",
+ @"error": @"Screen Recording permission required. Please grant permission in System Preferences and restart ShadowsocksX-NG"
+ }];
+
+ /* open system privacy settings */
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"]];
+ return;
+ }
+ }
+
+ NSLog(@"Proceeding with screen capture...");
+ }
+
/* displays[] Quartz display ID's */
CGDirectDisplayID *displays = nil;
-
- CGError err = CGDisplayNoErr;
CGDisplayCount dspCount = 0;
- /* How many active displays do we have? */
- err = CGGetActiveDisplayList(0, NULL, &dspCount);
+ /* variables for collecting scan information */
+ NSMutableDictionary *scanInfo = [NSMutableDictionary dictionary];
+ NSMutableArray *foundSSUrls = [NSMutableArray array];
+ NSMutableArray *foundQRCodes = [NSMutableArray array];
- /* If we are getting an error here then their won't be much to display. */
- if(err != CGDisplayNoErr)
- {
- NSLog(@"Could not get active display count (%d)\n", err);
+ /* How many active displays do we have? */
+ CGError err = CGGetActiveDisplayList(0, NULL, &dspCount);
+
+ if(err != CGDisplayNoErr) {
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"NOTIFY_FOUND_SS_URL"
+ object:nil
+ userInfo:@{
+ @"urls": @[],
+ @"source": @"qrcode",
+ @"error": @"Failed to get display list"
+ }];
return;
}
+ scanInfo[@"displayCount"] = @(dspCount);
+ NSLog(@"Found %d displays", dspCount);
+
/* Allocate enough memory to hold all the display IDs we have. */
displays = calloc((size_t)dspCount, sizeof(CGDirectDisplayID));
// Get the list of active displays
- err = CGGetActiveDisplayList(dspCount,
- displays,
- &dspCount);
-
- /* More error-checking here. */
- if(err != CGDisplayNoErr)
- {
- NSLog(@"Could not get active display list (%d)\n", err);
+ err = CGGetActiveDisplayList(dspCount, displays, &dspCount);
+
+ if(err != CGDisplayNoErr) {
+ free(displays);
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"NOTIFY_FOUND_SS_URL"
+ object:nil
+ userInfo:@{
+ @"urls": @[],
+ @"source": @"qrcode",
+ @"error": @"Failed to get display information"
+ }];
return;
}
- NSMutableArray* foundSSUrls = [NSMutableArray array];
-
CIDetector *detector = [CIDetector detectorOfType:@"CIDetectorTypeQRCode"
- context:nil
- options:@{ CIDetectorAccuracy:CIDetectorAccuracyHigh }];
+ context:nil
+ options:@{ CIDetectorAccuracy:CIDetectorAccuracyHigh }];
- for (unsigned int displaysIndex = 0; displaysIndex < dspCount; displaysIndex++)
- {
- /* Make a snapshot image of the current display. */
+ int totalQRCodesFound = 0;
+ int validSSUrlsFound = 0;
+
+ for (unsigned int displaysIndex = 0; displaysIndex < dspCount; displaysIndex++) {
CGImageRef image = CGDisplayCreateImage(displays[displaysIndex]);
NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image]];
+
+ /* count total QR codes found */
+ totalQRCodesFound += (int)features.count;
+
for (CIQRCodeFeature *feature in features) {
- NSLog(@"%@", feature.messageString);
- if ( [feature.messageString hasPrefix:@"ss://"] )
- {
+ NSLog(@"Found QR Code: %@", feature.messageString);
+ [foundQRCodes addObject:feature.messageString];
+
+ if ([feature.messageString hasPrefix:@"ss://"]) {
NSURL *url = [NSURL URLWithString:feature.messageString];
if (url) {
[foundSSUrls addObject:url];
+ validSSUrlsFound++;
}
}
}
- CGImageRelease(image);
+ CGImageRelease(image);
}
free(displays);
+ /* prepare notification information */
+ NSString *notificationTitle;
+ NSString *notificationSubtitle;
+ NSString *notificationBody;
+
+ if (totalQRCodesFound == 0) {
+ notificationTitle = [NSString stringWithFormat:@"Scanned %d displays", dspCount];
+ notificationSubtitle = @"No QR codes found";
+ notificationBody = @"Try adjusting the QR code position on your screen";
+ } else if (validSSUrlsFound == 0) {
+ notificationTitle = [NSString stringWithFormat:@"Found %d QR code(s)", totalQRCodesFound];
+ notificationSubtitle = @"No valid Shadowsocks URLs";
+ notificationBody = @"QR codes found are not Shadowsocks configuration";
+ } else {
+ notificationTitle = [NSString stringWithFormat:@"Found %d Shadowsocks URL(s)", validSSUrlsFound];
+ notificationSubtitle = [NSString stringWithFormat:@"Scanned %d displays, found %d QR codes", dspCount, totalQRCodesFound];
+ notificationBody = @"Processing Shadowsocks configuration...";
+ }
+
[[NSNotificationCenter defaultCenter]
postNotificationName:@"NOTIFY_FOUND_SS_URL"
object:nil
- userInfo: @{ @"urls": foundSSUrls,
- @"source": @"qrcode"
- }
- ];
+ userInfo:@{
+ @"urls": foundSSUrls,
+ @"source": @"qrcode",
+ @"title": notificationTitle,
+ @"subtitle": notificationSubtitle,
+ @"body": notificationBody,
+ @"scanInfo": @{
+ @"displayCount": @(dspCount),
+ @"totalQRCodes": @(totalQRCodesFound),
+ @"validURLs": @(validSSUrlsFound)
+ }
+ }];
}
NSImage* createQRImage(NSString *string, NSSize size) {