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) {