How to get the OS X system version? - cocoa

I want to get the OS X system version, such as: 10.5.4, 10.4.8, etc. I want to get it in my app, how do I do this? Thanks!

You can read the property list at "/System/Library/CoreServices/SystemVersion.plist and extract the "ProductVersion" key, this is how the OS X installer application does it. Here's an example:
NSString *versionString;
NSDictionary * sv = [NSDictionary dictionaryWithContentsOfFile:#"/System/Library/CoreServices/SystemVersion.plist"];
versionString = [sv objectForKey:#"ProductVersion"];
Alternatively, the command swvers -productVersion will do the same.

You can use Gestalt:
SInt32 version = 0;
Gestalt( gestaltSystemVersion, &version );
BOOL leopard = ( version >= 0x1050 );
if ( leopard )
{
//draw it this way
}
else
{
//draw it that way
}
Keep in mind if you're checking if a method is available or not, it's better to test that directly using respondsToSelector:.

NSString *osver()
{
SInt32 versionMajor=0, versionMinor=0, versionBugFix=0;
Gestalt(gestaltSystemVersionMajor, &versionMajor);
Gestalt(gestaltSystemVersionMinor, &versionMinor);
Gestalt(gestaltSystemVersionBugFix, &versionBugFix);
return [NSString stringWithFormat:#"%d.%d.%d", versionMajor, versionMinor, versionBugFix];
}

-[NSProcessInfo operatingSystemVersionString] is human readable and localized. Appropriate for displaying to user or using in bug emails and such, but not appropriate for parsing.

Again, you can use Gestalt. Look at the documentation for more information; specifically, you'll want to pass the gestaltSystemVersionMajor, gestaltSystemVersionMinor, and gestaltSystemVersionBugFix constants in the "System Version Constants" portion of the Gestalt Manager Reference documentation

After 10_10, 8_0 were presented the better & simplest way would be
[NSProcessInfo processInfo].operatingSystemVersion
which will return
NSOperatingSystemVersion struct
with all 3 numbers.

There's also a Cocoa wrapper around the Gestalt calls others have mentioned in the Google Toolbox for Mac:
http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/GTMSystemVersion.h

use this method it will return Mac OS X version
+(SInt32) OSVersion;
{
SInt32 osxMinorVersion;
Gestalt(gestaltSystemVersionMinor, &osxMinorVersion);
return osxMinorVersion;
}

Related

Mac Catalyst - control window resize

I have an app for ipad/iphone, now adding also mac support by Mac Catalyst. On Mac, I want to control window resizing, in order to allow only some sizes and aspects. It goes beyond simple minimal height and weight, or aspect. I want to allow user to resize window freely, but when app gets too high and narrow, I want to also seemlessly increase width, to keep some minimal aspect.
I believe that in AppKit it can be done through NSWindowDelegate.windowWillResize() (get user defined size, count required size and return it). However I am getting error "NSWindowDelegate is unavailable in Mac Catalyst" . Is it possible to achieve the result I want by Catalyst means?
Answering my own question. It is NOT possible to create own NSWindowDelegate with windowWillResize() implemented in Catalyst. However, it IS possible to create a new target only for mac, and use it as a plugin from catalyst target.
First I load mac-only plugin (using Bundle.load() ), and instantiate its principalClass. Then I get NSWindow from UIWindow, which is easy through Dynamic library. Then I pass NSWindow to method of a plugin, which then can set own NSWindowDelegate, because it does not run in catalyst.
Sample code:
guard let bundle = Bundle(url: bundleURL) else { return }
let succ = bundle.load()
if (succ) {
let macUtilsClass = bundle.principalClass! as! MacUtilsProtocol.Type
self.macUtils = macUtilsClass.init()
var dnsw: NSObject? = nil
if (ProcessInfo.processInfo.isOperatingSystemAtLeast(
OperatingSystemVersion(majorVersion: 11, minorVersion: 0, patchVersion: 0))) {
dnsw = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(AppDelegate.ref!.window).attachedWindow
}
else {
dnsw = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(AppDelegate.ref!.window)
}
self.macUtils.SetupMainWindow(win: dnsw!)
}

Get POSIX path of active Finder window with JXA AppleScript

I would like the JXA equivalent of this AppleScript snippet:
tell application "Finder"
# Get path
set currentTarget to target of window 1
set posixPath to (POSIX path of (currentTarget as alias))
# Show dialog
display dialog posixPath buttons {"OK"}
end tell
The closest I got was using the url property to initialize a Foundation NSURL object and access its fileSystemRepresentation property like so:
// Get path
var finder = Application('Finder')
var currentTarget = finder.finderWindows[0].target()
var fileURLString = currentTarget.url()
// I'd like to get rid of this step
var fileURL = $.NSURL.alloc.initWithString(fileURLString)
var posixPath = fileURL.fileSystemRepresentation
// Show dialog
finder.includeStandardAdditions = true
finder.displayAlert('', {buttons: ['Ok'], message: posixPath})
But this seems unnecessarily complex. Is there a nicer way to get to the POSIX path without using Foundation API or manual string wrangling?
If I naively try this:
finder.finderWindows[0].target().posixPath()
I get this error:
app.startupDisk.folders.byName("Users").folders.byName("kymer").folders.byName("Desktop").posixPath()
--> Error -1728: Can't get object.
This SO answer seems relevant, but I can't seem to adapt it to fit my needs:
App = Application.currentApplication()
App.includeStandardAdditions = true
SystemEvents = Application('System Events')
var pathToMe = App.pathTo(this)
var containerPOSIXPath = SystemEvents.files[pathToMe.toString()].container().posixPath()
Any help would be greatly appreciated!
The fact that such a simple piece of AppleScript code has no straightforward JXA translation is a testament to the sorry state of JXA and macOS automation based on OSA scripting in general:
foo's excellent answer contains helpful background information.
Another pointer is that the last time release notes were published was for OS X 10.11 (El Capitan) (as of this writing, we're on the verge of macOS 10.13 (High Sierra's) release).
This third-party July 2017 blog post announces more broadly that "that question was finally answered at WWDC last month: Apple has abandoned its automation technologies, leaving them to wither and die."
As your own example suggests, among the two dying automation scripting languages AppleScript - despite all its warts - is the more mature, reliable choice.
To solve your problem in JXA, it looks like you've come up with the best approach yourself.
Let me package it as a helper function that perhaps easies the pain somewhat - to be clear: such a helper function should NOT be necessary:
// Helper function: Given a Finder window, returns its folder's POSIX path.
// Note: No need for an ObjC.import() statement, because NSURL is
// a Foundation class, and all Foundation classes are implicitly
// available.
function posixPath(finderWin) {
return $.NSURL.alloc.initWithString(finderWin.target.url()).fileSystemRepresentation
}
// Get POSIX path of Finder's frontmost window:
posixPath(Application('Finder').finderWindows[0])
In theory you'd write something like:
finder.finderWindows[0].target({as:"alias"})
but this doesn't work and there's nothing in the documentation to indicate it's supported. But this is SOP for JXA which, like Apple's earlier Scripting Bridge, suffers numerous design flaws and omissions, which never have (and likely never will be) fixed.[1]
FWIW, here's how you do it in Node.js, using NodeAutomation:
$ node
> Object.assign(this,require('nodeautomation'));undefined
> const fn = app('Finder')
> var file = fn.FinderWindows[0].target({asType:k.alias}) // returns File object
> file.toString() // converts File object to POSIX path string
'/Users/jsmith/dev/nodeautomation'
(Be aware that NodeAutomation is a very low-priority project for me, given that Mac Automation looks to be pretty much on its last legs at Apple. Caveat emptor, etc. For non-trivial scripting I strongly recommend sticking to AppleScript as it's the only officially supported solution that actually works right.)
[1] For instance, another JXA limitation is that most apps' move and duplicate commands are seriously crippled cos the JXA authors forgot to implement insertion reference forms. (BTW, I reported all these problems before JXA was even released, and appscript had all this stuff solved a decade ago, so they've no excuse for not getting it right either.)
#Kymer, you said:
But this seems unnecessarily complex. Is there a nicer way to get to
the POSIX path without using Cocoa API or manual string wrangling?
You're on the right track. Here's the best method I know of. If there are better, I too would like to know about them. But, this seems to work well as fast, and works for both files and folders.
var finderApp = Application("Finder");
var itemList = finderApp.selection();
var oItem = itemList[0];
var oItemPaths = getPathInfo(oItem);
/* --- oItemPaths Object Keys ---
oItemPaths.itemClass
oItemPaths.fullPath
oItemPaths.parentPath
oItemPaths.itemName
*/
console.log(JSON.stringify(oItemPaths, undefined, 4))
function getPathInfo(pFinderItem) {
var itemClass = pFinderItem.class(); // returns "folder" if item is a folder.
var itemURL = pFinderItem.url();
var fullPath = decodeURI(itemURL).slice(7);
//--- Remove Trailing "/", if any, to handle folder item ---
var pathElem = fullPath.replace(/\/$/,"").split('/')
var itemName = pathElem.pop();
var parentPath = pathElem.join('/');
return {
itemClass: itemClass,
fullPath: fullPath,
parentPath: parentPath,
itemName: itemName
};
}
Here's a fairly simple example function that just grabs the window's target and then strips off the leading file:// from its url.
/*
pathToFrontWindow()
returns path to front Finder window
*/
function pathToFrontWindow() {
if ( finder.windows.length ) {
return decodeURI( finder.windows[0].target().url().slice(7) )
} else {
return ""
}
}
(() => {
// getFinderDirectory :: () -> String
const getFinderDirectory = () =>
Application('Finder')
.insertionLocation()
.url()
.slice(7);
return getFinderDirectory();
})();

Xcode - preprocessor directive check which SDK is being used

I am making some changes to an existing library which uses the "addTimeInterval:" function which was deprecated in OS X v10.6. I would like to supress the warning using a preprocessor directive to check which SDK version the current build is using. Something like this:
NSDate *newDate= nil;
#if OS X v10.6 (or newer)
newDate= [someOtherDate dateByAddingTimeInterval:60];
#else
newDate= [someOtherDate addTimeInterval:60];
#endif
Is this at all possible using Xcode 4?
How about +[NSDate dateWithTimeIntervalSinceNow:] that's been around since 10.0?
Maybe instead of doing a compile-time check, you could do a runtime check:
if ([[NSDate class] instancesRespondToSelector:#selector(dateByAddingTimeInterval:)]) {
newDate = [someOtherDate dateByAddingTimeInterval:60];
} else {
newDate = [someOtherDate addTimeInterval:60];
}
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
newDate = [someOtherDate dateByAddingTimeInterval:60];
#else
newDate = [someOtherDate addTimeInterval:60];
#endif
But it won't work with 10.5 if you build it with 10.6 SDK. need a runtime check as #Dave said.
You can use the exact same technique I described in CLLocation getDistanceFrom: vs distanceFromLocation:
Juste replace CLLocation with NSDate, getDistanceFrom: with addTimeInterval: and distanceFromLocation: with dateByAddingTimeInterval: in the instructions and you'll be able to always use dateByAddingTimeInterval: no matter what SDK you are using and no matter what OS version you are running.

How do you implement global keyboard hooks in Mac OS X?

I know this can be done for Windows and that XGrabKey can be used for X11, but what about Mac OS X? I want create a class that allows setting shortcut keys that can be invoked even when the application windows are inactive.
This is not (yet?) supported in Cocoa. You can still use the old Carbon library for this (which is 64 bit compatible), but unfortunately Apple decided to remove all documentation on the subject.
There's a nice blog article here: http://dbachrach.com/blog/2005/11/program-global-hotkeys-in-cocoa-easily/
The article is a bit lengthy for my taste, so here is the short version:
- (id)init {
self = [super init];
if (self) {
EventHotKeyRef hotKeyRef;
EventHotKeyID hotKeyId;
EventTypeSpec eventType;
eventType.eventClass = kEventClassKeyboard;
eventType.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler(&mbHotKeyHandler, 1, &eventType, NULL, NULL);
hotKeyId.signature = 'hotk';
hotKeyId.id = 1337;
RegisterEventHotKey(kVK_ANSI_C, cmdKey + shiftKey, hotKeyCopyId, GetApplicationEventTarget(), 0, &hotKeyRef);
}
}
OSStatus mbHotKeyHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData) {
// Your hotkey was pressed!
return noErr;
}
The hotkey is registered with the RegisterEventHotKey(…) call. In this case it registers CMD + Shift + C.
The ANSI keys are defined in HIToolbox/Events.h, so you can look around there for other keys (just press CMD + Shift + O in XCode, and type Events.h to find it).
You have to do a bit more work if you want multiple hotkeys or if you want to call methods from your handler, but that's all in the link near the top of this answer.
I've been looking for a simple answer to this question, so I hope this helps someone else...
Take a look at addGlobalMonitorForEventsMatchingMask:handler: class methods of NSEvent. Also you may find Shortcut Recorder handy.

How can I tell if Voice Over is turned on in System Preferences?

Is there an way, ideally backwards compatible to Mac OS X 10.3, to tell if "Voice Over" is activated in System Preferences?
This appears to be stored in a preferences file for Universal Access. The app identifier is "com.apple.universalaccess" and the key containing the flag for whether VoiceOver is on or off is "voiceOverOnOffKey". You should be able to retrieve this using the CFPreferences API, something looking like:
CFBooleanRef flag = CFPreferencesCopyAppValue(CFSTR("voiceOverOnOffKey"), CFSTR("com.apple.universalaccess"));
If anyone has the same question, it could be good to know, that Voice Over status is accessible via convenient interface now:
NSWorkspace.shared.isVoiceOverEnabled
Based on Petes excellent answer I’ve created this Swift 4.2 solution, which I find much easier to read. I also think it’s more handy to use a computed property in this case instead of a function.
var hasVoiceOverActivated: Bool {
let key = "voiceOverOnOffKey" as CFString
let id = "com.apple.universalaccess" as CFString
if let voiceOverActivated = CFPreferencesCopyAppValue(key, id) as? Bool {
return voiceOverActivated
}
return false
}
VoiceOver and Accessibility in general are very important topics and it is sad that the lack of Apples documentation especially for macOS makes it so hard for developers to implement it properly.
Solution in Swift 4 is as follows:
func NSIsVoiceOverRunning() -> Bool {
if let flag = CFPreferencesCopyAppValue("voiceOverOnOffKey" as CFString, "com.apple.universalaccess" as CFString) {
if let voiceOverOn = flag as? Bool {
return voiceOverOn
}
}
return false
}
Furthermore, to make a text announcement with VoiceOver on macOS, do the following:
let message = "Hello, World!"
NSAccessibilityPostNotificationWithUserInfo(NSApp.mainWindow!,
NSAccessibilityNotificationName.announcementRequested,
[NSAccessibilityNotificationUserInfoKey.announcement: message,
NSAccessibilityNotificationUserInfoKey.priority:
NSAccessibilityPriorityLevel.high.rawValue])

Resources