Detect if app is running on a macOS beta version - swift

I would like to be able to somehow detect if my app is running on a beta version of macOS 11, as there are some known bugs I want to inform users about. I only want to show such an alert to macOS 11 beta users, meaning not macOS 10.15 users nor users on the final version of macOS 11. I could of course just submit an app update to remove the alert when macOS 11 is close to being done, but it would be nice to have something reusable I could use in multiple apps and for future macOS beta versions.
Constraints:
The app is sandboxed.
The app is in the App Store, so no private APIs.
The app doesn't have a network entitlement, so the detection needs to be offline.
I don't want to bundle a list of known macOS build numbers and compare that.
My thinking is that maybe it's possible to use some kind of sniffing. Maybe there are some APIs that return different results when the macOS version is a beta version.

I believe you're out of luck. About This Mac uses PrivateFrameworks/Seeding.framework, here the important disassembly:
/* #class SDBuildInfo */
+(char)currentBuildIsSeed {
return 0x0;
}
So it seems this is a build time compiler flag. The plists in the framework don't contain this flag unfortunately.
Sample private API usage: kaloprominat/currentBuildIsSeed.py
For the crazy ones: It would be possible to read the binary and compare the assembly for the function. I'd start with the class-dump code, which gets you different fat binaries and the function offset.

This is far from perfect but macOS BigSur release notes mention:
Known Issues
In Swift, the authorizationStatus() property of CLLocationManager is incorrectly exposed as a method instead of a property. (62853845)
This^ is new API introduced in MacOS11 / iOS14
So one could detect this for this particular beta.
import CoreLocation
func isMacOS11Beta() -> Bool {
var propertiesCount = UInt32()
let classToInspect = CLLocationManager.self
var isMacOS11OrHigher = false
var isMacOS11Beta = false
let propertiesInAClass = class_copyPropertyList(CLLocationManager.self, &propertiesCount)
if classToInspect.responds(to: NSSelectorFromString("authorizationStatus")) {
isMacOS11OrHigher = true
isMacOS11Beta = true
for i in 0 ..< Int(propertiesCount) {
if let property = propertiesInAClass?[i], let propertyName = NSString(utf8String: property_getName(property)) as String? {
if propertyName == "authorizationStatus" {
isMacOS11Beta = false
}
}
}
free(propertiesInAClass)
}
return isMacOS11OrHigher && isMacOS11Beta
}

Related

Detecting if Mac has a backlit keyboard

It’s quite easy to detect if Mac has an illuminated keyboard with ioreg at the command line:
ioreg -c IOResources -d 3 | grep '"KeyboardBacklight" =' | sed 's/^.*= //g'
But how can I programmatically get this IOKit boolean property using the latest Swift? I’m looking for some sample code.
I figured out the following with some trial and error:
Get the "IOResources" node from the IO registry.
Get the "KeyboardBacklight" property from that node.
(Conditionally) convert the property value to a boolean.
I have tested this on an MacBook Air (with keyboard backlight) and on an iMac (without keyboard backlight), and it produced the correct result in both cases.
import Foundation
import IOKit
func keyboardHasBacklight() -> Bool {
let port: mach_port_t
if #available(macOS 12.0, *) {
port = kIOMainPortDefault // New name as of macOS 12
} else {
port = kIOMasterPortDefault // Old name up to macOS 11
}
let service = IOServiceGetMatchingService(port, IOServiceMatching(kIOResourcesClass))
guard service != IO_OBJECT_NULL else {
// Could not read IO registry node. You have to decide whether
// to treat this as a fatal error or not.
return false
}
guard let cfProp = IORegistryEntryCreateCFProperty(service, "KeyboardBacklight" as CFString,
kCFAllocatorDefault, 0)?.takeRetainedValue(),
let hasBacklight = cfProp as? Bool
else {
// "KeyboardBacklight" property not present, or not a boolean.
// This happens on Macs without keyboard backlight.
return false
}
// Successfully read boolean "KeyboardBacklight" property:
return hasBacklight
}
As an addendum to Martin’s excellent answer, here are the notes I got from Apple’s Developer Technical Support:
Calling I/O Kit from Swift is somewhat challenging. You have
two strategies here:
You can wrap the I/O Kit API in a Swift-friendly wrapper and then use that to accomplish your task.
You can just go straight to your task, resulting in lots of ugly low-level Swift.
Pulling random properties out of the I/O Registry is not the best path
to long-term binary compatibility. Our general policy here is that we
only support properties that have symbolic constants defined in the
headers (most notably IOKitKeys.h, but there are a bunch of others).
KeyboardBacklight has no symbolic constant and thus isn’t supported.
Make sure you code defensively. This property might go away, change
meaning, change its type, and so on. Your code must behave reasonable
in all such scenarios.
Please make sure you file an enhancement request for a proper API to
get this info, making sure to include a high-level description of your
overall goal.

64 bit show stopper error for appbundle flutter application

I am trying to publish our flutter application to the google play store and Im getting what appears to be a show stopper error...
Error
This release is not compliant with the Google Play 64-bit requirement
The following APKs or App Bundles are available to 64-bit devices, but they only have 32-bit native code: 3.
Include 64-bit and 32-bit native code in your app. Use the Android App Bundle publishing format to automatically ensure that each device architecture receives only the native code it needs. This avoids increasing the overall size of your app. Learn More
Does anyone know how to fix this? Im not seeing anything when I google this error. We are just trying to get a Closed Alpha test out there.
I was able to finally fix this by adding this to build.gradle
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
splits {
abi {
include "armeabi-v7a", "arm64-v8a"
}
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "arm64-v8a":2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}

How to resolve 'scanLocation' was deprecated in iOS 13.0

When trying to use a Scanner I am getting the warning that 'scanLocation' was deprecated in iOS 13.0. Since being able to scan from the next location is rather fundamental to scanning a String, wondering what to use instead of scanLocation. Apple's documentation for Scanner does not even mention the deprecation, let alone suggest what has taken the place of scanLocation.
Example of using scanLocation, which is deprecated:
while !scanner.isAtEnd {
print(scanner.scanUpToCharacters(from: brackets))
let block = scanner.string[scanner.currentIndex...]
print(block)
scanner.scanLocation = scanner.scanLocation + 1
}
tl;dr - use currentIndex instead of scanLocation when using Scanner in Swift.
Shame on Apple for the poor documentation. But based on information in the NSScanner.h file for the Objective-C version of Scanner, only in Swift, the scanLocation property has been deprecated and replaced with the currentIndex property.
#rmaddy already gave the correct answer, but this shows how to increment the currentIndex since it is different from just adding 1 to the scanLocation.
while !scanner.isAtEnd {
print(scanner.scanUpToCharacters(from: brackets))
let block = scanner.string[scanner.currentIndex...]
print(block)
scanner.currentIndex = scanner.string.index(after: scanner.currentIndex)
}

IOCreatePlugInInterfaceForService returns mysterious error

I am trying to use some old IOKit functionality in a new Swift 4.0 Mac app (not iOS). I have created a bridging header to use an existing Objective C third party framework, DDHidLib, and I am current working in Xcode 9.
The code that attempts to create a plug in interface for a usb gamepad falls over on IOCreatePlugInInterfaceForService, returning a non-zero error.
The truly bizarre thing is I have an older app created in a previous version of Xcode that uses the same framework and works correctly after opening in the new Xcode 9. This previous project is still Swift using a bridging header for the same Obj-C framework. I have checked the build settings and tried to make everything match, but I get the same result; the old app works but any new apps do not.
Is there a way to either: find out the exact differences in build settings/compilers to see what the elusive difference may be, OR to step into the IOCreatePlugInInterfaceForService IOKit method to see what may be causing the error to be returned in one project but not another?
EDIT: Here is the method that is failing:
- (BOOL) createDeviceInterfaceWithError: (NSError **) error_; {
io_name_t className;
IOCFPlugInInterface ** plugInInterface = NULL;
SInt32 score = 0;
NSError * error = nil;
BOOL result = NO;
mDeviceInterface = NULL;
NSXReturnError(IOObjectGetClass(mHidDevice, className));
if (error)
goto done;
NSXReturnError(IOCreatePlugInInterfaceForService(mHidDevice, kIOHIDDeviceUserClientTypeID,kIOCFPlugInInterfaceID,&plugInInterface,&score));
if (error)
goto done;
//Call a method of the intermediate plug-in to create the device interface
NSXReturnError((*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &mDeviceInterface));
if (error)
goto done;
result = YES;
done:
if (plugInInterface != NULL)
{
(*plugInInterface)->Release(plugInInterface);
}
if (error_)
*error_ = error;
return result;
}
In the old version that works, IOCreatePlugInInterfaceForService always returns a value of 0. In all the versions that don't work, the return value appears to always be -536870210. The mHidDevice in this function is the io_object_t handle for the device.
EDIT2: Here is the IORegistryExplorer page for the device
Finally managed to resolve this after weeks of head scratching. The new Xcode 9 uses app sandboxing to basically prevent access to USB, bluetooth, camera and microphone etc. by default in a new app. Once I switched this off it reverted to it's expected behaviour.
Glad it was such a simple answer in the end but disappointed Xcode does not provide more descriptive error messages or responses to let a user know they are essentially preventing themselves from accessing the parts of the system they need.
Looks like kIOReturnNoResources is returned if the loop at the end of IOCreatePlugInInterfaceForService completes with haveOne == false for whatever reason. Perhaps Start() is returning false because another process or driver already has exclusive access? I'd check what clients the device has in IORegistryExplorer.
This error also happens when an application is trying to access the camera or bluetooth on MacOS 10.14 and higher. Permission shall be granted either explicitly by user (pop-up window), or through the Security & Privacy. The application should check for permission as shown here.

MCNearbyServiceAdvertiser on macOS 10.13 (High Sierra) not working

I am trying to advertise a service with Multipeer Connectivity in macOS 10.13 as such:
override init() {
self.serviceAdvertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: serviceID);
super.init();
self.serviceAdvertiser.delegate = self;
self.serviceAdvertiser.startAdvertisingPeer();
}
where
private let serviceID = "sample-test";
private let peerID = MCPeerID(displayName: Host.current().localizedName!);
Instead of getting the proper delegate callback methods getting called the advertiser immediately fails and this is what I get in the console:
2017-10-16 11:22:35.568607-0700 macApp[3060:288948] [] tcp_listener_socket_create bind(fd 3) failed: [1] Operation not permitted
2017-10-16 11:22:35.569223-0700 macApp[3060:288940] [MCNearbyServiceAdvertiser] Server did not publish: errorDict [{
NSNetServicesErrorCode = 1;
NSNetServicesErrorDomain = 1;
}].
Any idea how to solve this?
UPDATE:
Running the exact same code in an iOS Simulator works fine so I'm guessing it has something to do with some permissions on the Mac machine.
Seeing that the log says that this is a permission issue I went ahead and enabled the root user on the Mac and tried running the same code to no avail.
I am thinking of turning off System Integrity Protection but I have a hard time coming to terms with the fact that Apple would publish this framework if all these security compromises are required in order to use it. Will keep investigating.
After days of struggle the solution is very simple. Make sure you enable the networking entitlements for your target. See attached snapshot: