This commit is contained in:
2025-11-03 13:25:41 +08:00
parent ffea9d2022
commit c7021e382e
12 changed files with 384 additions and 27 deletions

View File

@@ -0,0 +1,94 @@
//
// KBKeyboardPermissionManager.m
//
#import "KBKeyboardPermissionManager.h"
#import <Security/Security.h>
#import "KBConfig.h"
// Keychain 访
static NSString * const kKBPermService = @"com.keyBoardst.perm";
static NSString * const kKBPermAccount = @"full_access"; // /0/1/2
@implementation KBKeyboardPermissionManager
+ (instancetype)shared {
static KBKeyboardPermissionManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBKeyboardPermissionManager new]; });
return m;
}
#pragma mark - App side
- (BOOL)isKeyboardEnabled {
// AppDelegate activeInputModes bundle id
for (UITextInputMode *mode in [UITextInputMode activeInputModes]) {
NSString *identifier = nil;
@try { identifier = [mode valueForKey:@"identifier"]; } @catch (__unused NSException *e) { identifier = nil; }
if ([identifier isKindOfClass:NSString.class] && [identifier rangeOfString:KB_KEYBOARD_EXTENSION_BUNDLE_ID].location != NSNotFound) {
return YES;
}
}
return NO;
}
- (KBFARecord)lastKnownFullAccess {
NSData *data = [self keychainRead];
if (data.length == 0) return KBFARecordUnknown;
uint8_t v = 0; [data getBytes:&v length:1];
if (v > KBFARecordGranted) v = KBFARecordUnknown;
return (KBFARecord)v;
}
- (void)presentPermissionIfNeededFrom:(UIViewController *)presenting {
BOOL enabled = [self isKeyboardEnabled];
KBFARecord fa = [self lastKnownFullAccess];
//
// -
// - 访Unknown
BOOL needGuide = (!enabled) || (enabled && fa == KBFARecordDenied);
if (!needGuide || !presenting) return;
Class cls = NSClassFromString(@"KBPermissionViewController");
if (!cls) return; // App
UIViewController *guide = [cls new];
guide.modalPresentationStyle = UIModalPresentationFullScreen;
[presenting presentViewController:guide animated:YES completion:nil];
}
#pragma mark - Extension side
- (void)reportFullAccessFromExtension:(BOOL)granted {
uint8_t v = granted ? KBFARecordGranted : KBFARecordDenied;
NSData *data = [NSData dataWithBytes:&v length:1];
[self keychainWrite:data];
}
#pragma mark - Keychain shared blob
- (NSMutableDictionary *)baseKCQuery {
NSMutableDictionary *q = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKBPermService,
(__bridge id)kSecAttrAccount: kKBPermAccount } mutableCopy];
q[(__bridge id)kSecAttrAccessGroup] = KB_KEYCHAIN_ACCESS_GROUP;
return q;
}
- (BOOL)keychainWrite:(NSData *)data {
NSMutableDictionary *query = [self baseKCQuery];
SecItemDelete((__bridge CFDictionaryRef)query);
query[(__bridge id)kSecValueData] = data ?: [NSData data];
query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
OSStatus st = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
return (st == errSecSuccess);
}
- (NSData *)keychainRead {
NSMutableDictionary *query = [self baseKCQuery];
query[(__bridge id)kSecReturnData] = @YES;
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
CFTypeRef dataRef = NULL; OSStatus st = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
if (st != errSecSuccess || !dataRef) return nil;
return (__bridge_transfer NSData *)dataRef;
}
@end