1

Build Environment: QT 4.7, OS X 10.6
Run Environment: OS X 10.6 through OS X 10.13, Windows XP and later

I'm working in a very large, highly graphics-intensive QT app. I need to find out what version of OS X / MacOS I am running on - 10.6... 10.10... 10.12 etc.

I am looking for a c or c++ function in OS X I can call live; this is a runtime issue. It needs to work from 10.6 onwards. I am writing in c++ so I can use a c solution as conveniently as c++.

I have this:

#ifdef Q_OS_WIN
    QApplication::setGraphicsSystem("raster");
#else
    QApplication::setGraphicsSystem("native");
#endif

The above works to determine if it's windows or OS X I am building for. Inside the else in the above fragment I need to do some further checking; I don't need the "native" graphics system except in OS X 10.12, where the QT "raster" system has problems. I prefer the "raster" system because it is much faster, but later machines are faster too and so if I can only call for the "native" system on a modern machine running recent OS's, that should work out.

I have users - large numbers of them - running under earlier versions of OS X, consequently whatever is used here has to be general enough to work on all versions of the OS 10.6 and up. QT 4.7 itself seems to be clueless about OS versions it doesn't explicitly know about; using QSysInfo::MacVersion, it just reports "Unknown OS version."

Ideally, I imagine something of the following form:

int v = majorOSRevision();
int r = minorOSRevision();
int s = stepOSRevision();

Are there such direct, simple calls within the OS X API?

fyngyrz
  • 2,458
  • 2
  • 36
  • 43
  • @Pablo... hmm. I can't tell if you're trolling or just confused. In any case, the answer is **yes.** I am writing, generally, in C++. I can drop back to c anytime, of course, so I can use either approach. Does that answer your question? – fyngyrz Jan 17 '18 at 01:13
  • No time to write a proper answer. Search for gestalt... – Macmade Jan 17 '18 at 01:14
  • gestalt has been deprecated as of 10.8, so I'm worried about using it – fyngyrz Jan 17 '18 at 01:26
  • Dup of https://stackoverflow.com/questions/11072804/how-do-i-determine-the-os-version-at-runtime-in-os-x-or-ios-without-using-gesta ... have to use a combination of old and new if you need to go back to 10.6. – Eljay Jan 17 '18 at 01:44
  • I don't think there is single approach that will be undeprecated and yet work back to OS 10.6. Of course, deprecated doesn't mean it's definitely going away soon. Gestalt is still in the 10.13 SDK. – JWWalker Jan 17 '18 at 01:44
  • OP does not actually need a method that “works” back to 10.6. Per the question, what they really need to know is whether they are on 10.12 or not. Thus, a call that returned the version number for recent versions but returned an error indication for older versions would suffice. (Incidentally, have you tested all versions of 10.12, such as 10.12.6? Did you [report the problems to Apple](http://bugreport.apple.com)?) – Eric Postpischil Jan 17 '18 at 01:53
  • Eric, yes, I reported the problem when 10.12 initially came out. The problem's been there for all versions of 10.12, and none of the previous versions back to 10.6... you're right, I think what i need to know is 10.12 and later; faster machines make the "native" mode reasonable to use. It would be nice, of course, to be able to generally know what version the OS was... I'm astonished there isn't an obvious set of calls for this. But there you go. – fyngyrz Jan 17 '18 at 02:07
  • @Eljay, if you don't know what version of the OS you are running on, how are you to decide what API to use? The page you link to is unclear about an actual atomic solution to the problem that is workable with 10.6 and forward. – fyngyrz Jan 17 '18 at 02:12
  • @fyngyrz • test to see if the newer routine exists, if it does use it, otherwise fall back to the older routine. – Eljay Jan 17 '18 at 03:19
  • @Eljay, how does one test if an API routine exists from c or c++? – fyngyrz Jan 17 '18 at 12:37
  • @fyngyrz • you can't do it from C or C++ (afaik). You'll have to use the Objective-C way of detecting if a method exists. In Objective-C, you use [respondsToSelector](https://stackoverflow.com/questions/2732863/check-if-a-method-exists). – Eljay Jan 17 '18 at 13:06
  • @Eljay, right, but I'm writing in c++ (or c). Thank you for that though. I have found a solution as marked below. – fyngyrz Jan 17 '18 at 13:32

2 Answers2

3

You could execute a command like sw_vers and read its output.

Example code, using QProcess:

osxversion.h

#ifndef OSXVERSION_H
#define OSXVERSION_H

#include <QProcess>

class OSXVersion : public QProcess
{
    Q_OBJECT

    int majr;
    int minr;
    int step;

    OSXVersion();

public:

    int majorOSRevision() const { return majr; }
    int minorOSRevision() const { return minr; }
    int stepOSRevision() const { return step; }

    static OSXVersion * getVersion();

private slots:
    void dataReady();
};

#endif // OSXVERSION_H

osxversion.cpp

#include "osxversion.h"

OSXVersion::OSXVersion() : QProcess(0)
{
    majr = 0;
    minr = 0;
    step = 0;
    connect(this, SIGNAL(readyRead()), this, SLOT(dataReady()));
}

OSXVersion * OSXVersion::getVersion()
{
    OSXVersion * v = new OSXVersion();
    v->start("sw_vers -productName");
    v->waitForFinished();
    return v;
}

void OSXVersion::dataReady()
{
    int * v[3] = {&majr, &minr, &step};

    QByteArray data = readAll();
    QList<QByteArray> tokens =  data.split(':');
    tokens = tokens[tokens.size() - 1].split('.');
    for(int i=0; i<tokens.size(); i++)
    {
        *(v[i]) = QString(tokens[i]).toInt();
    }
}

main.cpp

#include <QCoreApplication>
#include <QDebug>

#include "osxversion.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    OSXVersion * version = OSXVersion::getVersion();

    qDebug() << "OSX Version: "
             << version->majorOSRevision()
             << "."
             << version->minorOSRevision()
             << "."
             << version->stepOSRevision();

    delete version;

    return a.exec();
}
p-a-o-l-o
  • 9,807
  • 2
  • 22
  • 35
  • Thank you for pointing out sw_vers; that did exactly what I needed and it works from 10.6.8 through 10.12.6, perfect. Silly they didn't have that capability in the system API from day 1, but oh well. For future readers, I was able to use `sw_vers -productVersion` to get the major, minor and revision levels of OS X. I approached it as a simple use of `QString params="-productVersion"`, `QProcess p`, `p.start("sw_vers", params)`, `p.waitForFinished(-1)`, `QString p_stdout = p.readAll()` and then parsing the result rather than the above, but the idea is the same. – fyngyrz Jan 17 '18 at 13:44
1

OS X / macOS has always stored its product info in one definitive plist file, which is located at /System/Library/CoreServices/SystemVersion.plist

If we view this file on High Sierra, we can see the following

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>ProductBuildVersion</key>
    <string>17C205</string>
    <key>ProductCopyright</key>
    <string>1983-2018 Apple Inc.</string>
    <key>ProductName</key>
    <string>Mac OS X</string>
    <key>ProductUserVisibleVersion</key>
    <string>10.13.2</string>
    <key>ProductVersion</key>
    <string>10.13.2</string>
</dict>
</plist>

As it's a plist, use Qt's QSettings to read any value, according to the given key

#include <QSettings>
QString getSysVersion()
{
    QSettings settings("/System/Library/CoreServices/SystemVersion.plist", QSettings::NativeFormat);
    return settings.value("ProductUserVisibleVersion").toString();
}

Now we have the version in a QString, we can obtain major, minor and build components

QString version = getSysVersion();
QStringList components = version.split(".");
int maj = components[0].toInt();
int min = components[1].toInt();
int build = components[2].toInt();
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85