Przeglądaj źródła

Merge pull request #1501 from hiddenSharp429/develop

Feat(QRCode): Enhance 'Scan QR Code From Screen' notifications # #1250
Kebin Liu 4 miesięcy temu
rodzic
commit
719203afc6

+ 2 - 0
.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

+ 2 - 0
.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

+ 24 - 23
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
+                )
             }
         }
     }

+ 2 - 0
ShadowsocksX-NG/Info.plist

@@ -49,5 +49,7 @@
 	<string>MainMenu</string>
 	<key>NSPrincipalClass</key>
 	<string>SWBApplication</string>
+	<key>NSScreenCaptureUsageDescription</key>
+	<string>ShadowsocksX-NG needs Screen Recording permission to scan QR codes on your screen</string>
 </dict>
 </plist>

+ 114 - 32
ShadowsocksX-NG/Utils.m

@@ -10,71 +10,153 @@
 #import <CoreImage/CoreImage.h>
 #import <AppKit/AppKit.h>
 
-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) {