Feat(QRCode): Enhance 'Scan QR Code From Screen' notifications # #1250

Improve the clarity of notifications in the QR code scanning feature:

1. Permission handling:
   - Show notification when screen recording permission is missing
   - Automatically open system settings for permission grant

2. Enhanced scanning status notifications:
   When no QR code found:
   - Title: "Scanned X displays"
   - Subtitle: "No QR codes found"
   - Body: "Try adjusting the QR code position on your screen"

   When invalid QR codes found:
   - Title: "Found X QR code(s)"
   - Subtitle: "No valid Shadowsocks URLs"
   - Body: "QR codes found are not Shadowsocks configuration"

   When valid QR codes found:
   - Title: "Found X Shadowsocks URL(s)"
   - Subtitle: "Scanned X displays, found X QR codes"
   - Body: "Successfully added X server configuration(s)"
This commit is contained in:
hiddenSharp429
2024-10-28 12:09:16 +08:00
parent 4c731d3dda
commit 2fc108699c
3 changed files with 139 additions and 54 deletions
+24 -23
View File
@@ -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 mgr = ServerProfileManager.instance
let addCount = mgr.addServerProfileByURL(urls: urls)
// 使用新的通知信息
let title = (userInfo["title"] as? String) ?? ""
let subtitle = (userInfo["subtitle"] as? String) ?? ""
let body = (userInfo["body"] as? String) ?? ""
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
View File
@@ -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>
+113 -31
View File
@@ -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);
err = CGGetActiveDisplayList(dspCount, displays, &dspCount);
/* More error-checking here. */
if(err != CGDisplayNoErr)
{
NSLog(@"Could not get active display list (%d)\n", err);
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) {