Skip to content

Commit

Permalink
Merge pull request #1501 from hiddenSharp429/develop
Browse files Browse the repository at this point in the history
Feat(QRCode): Enhance 'Scan QR Code From Screen' notifications # #1250
  • Loading branch information
lkebin authored Oct 29, 2024
2 parents 4c731d3 + 3fccede commit 719203a
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 55 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 24 additions & 23 deletions ShadowsocksX-NG/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions ShadowsocksX-NG/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -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>
146 changes: 114 additions & 32 deletions ShadowsocksX-NG/Utils.m
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 719203a

Please sign in to comment.