Files
OSGKeyboard/OSGKeyboardExt/Utilities/OSGHostArbiterProbe.m
T
rocky ad79c2ea13 chore(flow): expand host-return whitelist and add a DEBUG host bundle probe
- Rebuild the return-to-host whitelist from the CN/US top charts plus Apple
  system apps; drop the canOpenURL pre-check (LSApplicationQueriesSchemes
  is capped at 50) and let open() report failure.
- Never record an OSG bundle id as the host to return to.
- DEBUG only: probe the host bundle id through the keyboard arbiter
  callback and surface the log on a Home test card. Release builds keep
  the previous behaviour (no host identification).
2026-10-20 20:06:36 +02:00

134 lines
4.5 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// OSGHostArbiterProbe.m
// OSGKeyboard · Keyboard Extension
//
// 临时调试探针(仅 DEBUG),见头文件说明。所有私有符号都在运行时按字符串查找,
// 任何一步找不到都静默放弃,并保证原始实现照常执行。
#import "OSGHostArbiterProbe.h"
#ifdef DEBUG
#import <UIKit/UIKit.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <os/lock.h>
static NSString *const kArbiterClientClassName = @"_UIKeyboardArbiterClient";
static NSString *const kDestinationClassName = @"_UIKeyboardArbiterClientInputDestination";
static NSString *const kEnabledSelectorName = @"enabled";
static NSString *const kSharedClientSelectorName = @"automaticSharedArbiterClient";
static NSString *const kCheckConnectionSelectorName = @"checkConnection";
static NSString *const kKeyboardChangedSelectorName = @"queue_keyboardChanged:onComplete:";
static NSString *const kSourceBundleIdKey = @"_sourceBundleIdentifier";
static os_unfair_lock sLock = OS_UNFAIR_LOCK_INIT;
static OSGHostArbiterCaptureHandler _Nullable sHandler = nil;
static NSString *_Nullable sLastCapturedBundleId = nil;
static IMP _Nullable sOriginalKeyboardChangedIMP = NULL;
static BOOL sInstalled = NO;
#pragma mark - Trampolines
// 替换 -[_UIKeyboardArbiterClientInputDestination queue_keyboardChanged:onComplete:]。
// bundle ID 在参数 change 上,不在 self 上。
static void OSGSwizzledKeyboardChanged(id self, SEL _cmd, id change, id completion) {
NSString *bundleId = nil;
if (change) {
@try {
id value = [change valueForKey:kSourceBundleIdKey];
if ([value isKindOfClass:[NSString class]]) {
bundleId = [(NSString *)value copy];
}
} @catch (__unused NSException *exception) {
// 字段改名时 KVC 会抛异常;吞掉,保证原始实现继续执行。
}
}
NSString *detail = change ? NSStringFromClass([change class]) : @"change=nil";
os_unfair_lock_lock(&sLock);
// 读不到 ID 的回调同样覆盖旧值:宿主已经变了,旧 ID 不能再用。
sLastCapturedBundleId = bundleId.length > 0 ? bundleId : nil;
OSGHostArbiterCaptureHandler handler = sHandler;
os_unfair_lock_unlock(&sLock);
if (handler) {
handler(bundleId, detail);
}
if (sOriginalKeyboardChangedIMP) {
((void (*)(id, SEL, id, id))sOriginalKeyboardChangedIMP)(self, _cmd, change, completion);
}
}
// 替换 +[_UIKeyboardArbiterClient enabled]:iOS 26.4 用它控制是否派发上面的回调。
static BOOL OSGAlwaysEnabled(__unused id self, __unused SEL _cmd) {
return YES;
}
@implementation OSGHostArbiterProbe
+ (void)load {
if (@available(iOS 26.4, *)) {
[self installHooks];
}
}
+ (void)installHooks {
Class clientClass = NSClassFromString(kArbiterClientClassName);
if (clientClass) {
Method enabled = class_getClassMethod(clientClass, NSSelectorFromString(kEnabledSelectorName));
if (enabled) {
method_setImplementation(enabled, (IMP)&OSGAlwaysEnabled);
}
}
Class destinationClass = NSClassFromString(kDestinationClassName);
if (!destinationClass) return;
Method keyboardChanged = class_getInstanceMethod(
destinationClass,
NSSelectorFromString(kKeyboardChangedSelectorName)
);
if (!keyboardChanged) return;
sOriginalKeyboardChangedIMP = method_getImplementation(keyboardChanged);
method_setImplementation(keyboardChanged, (IMP)&OSGSwizzledKeyboardChanged);
sInstalled = YES;
}
+ (BOOL)isInstalled {
return sInstalled;
}
+ (NSString *)lastCapturedBundleId {
os_unfair_lock_lock(&sLock);
NSString *bundleId = [sLastCapturedBundleId copy];
os_unfair_lock_unlock(&sLock);
return bundleId;
}
+ (void)invalidateCapturedBundleId {
os_unfair_lock_lock(&sLock);
sLastCapturedBundleId = nil;
os_unfair_lock_unlock(&sLock);
}
+ (void)setCaptureHandler:(OSGHostArbiterCaptureHandler)handler {
os_unfair_lock_lock(&sLock);
sHandler = [handler copy];
os_unfair_lock_unlock(&sLock);
}
+ (void)pingArbiter {
Class clientClass = NSClassFromString(kArbiterClientClassName);
SEL sharedSelector = NSSelectorFromString(kSharedClientSelectorName);
if (!clientClass || ![clientClass respondsToSelector:sharedSelector]) return;
id client = ((id (*)(id, SEL))objc_msgSend)(clientClass, sharedSelector);
SEL checkSelector = NSSelectorFromString(kCheckConnectionSelectorName);
if (client && [client respondsToSelector:checkSelector]) {
((void (*)(id, SEL))objc_msgSend)(client, checkSelector);
}
}
@end
#endif