I suspect there are many others that have wanted to use GPS on their Jolla phone application but have not been successful in their attempts. In that case this should help you a bit.
If you are in hurry, just skip to the end, DBus code is there in all its ugliness. Everything in between is mostly background.
At the moment SailfishOS' Qt is something between versions 5.1 and 5.2. For GPS (or more accurately, the positioning information in general) this is unfortunate as old QtLocation subsystem no longer exists and new QtPositioning isn't there yet. After numerous web seaches I came in conclusion that following options are available right now:
- Older QtLocation (possibly with custom import module). Reading between the lines I got the impression that if you wish to publish your app on Jolla's official shop this is very much frowned upon there.
- Import custom module based on QtPositioning and use it. Slightly better, but this may later bite you back when phone is updated and official (yet possibly slightly different, as API is not yet completely stable) system interferes with your custom solution. So this also is frowned upon.
- Hack to the DBUS and obtain location from there. Also frowned upon, but apparently least of these options. Of course no one bothered to mention how to do it...
Well, I went for the last option anyway, planning to upgrade to QtPositioning when it becomes properly available. It's way easier anyway than all this DBus mess.
Now, if you have read what I have written about my experience on working on Qt (previously zilch but at least I had browsed quickly some docs in the past), QML (
maybe I had heard of it somewhere, not sure) or DBus (at least I had heard
of it) you may already have guess how easy my progress to the depths of DBus was.
Well, actually, it wasn't as bad as I first expected, browsing the lower level documentation. As I had
map drawing system running already, so I was getting familiar with "the Qt way". And since Qt appears to have everything and kitchen sink included (for the better or the worse, depending on your view), I figured there might be DBus too... And what do you know, there is.
Now, with that hurdle passed, proceed to the next. SailfishOS uses actually older implementation of Geoclue, references found
here. The older version is very badly documented and examples are just about eually good - unfortunately even those are using C API (not available here, at least without more custom modules) that doesn't translate direcly to Qt/DBus combination. And Qt's DBus subsystem is
very picky about defining call signatures (damn you Geoclue for using almost completely unnecessary structs to pass data I don't even care about - but Qt does, just enough to not to co-operate with my efforts). So good luck there, especially since Qt documentation mentions structs, arrays and dictionaries in passing - but completely neglects to mention how they should be used.
Did I remember to tell you how frustrating some things were?
Even Google isn't very helpful when trying to find something this obscure. But eventually, after hundreds of searches (no, I am not kidding there) with varying search terms I managed to scrape together enough information to write my own code. Other search providers were even less helpful.
Eventually I scraped together enough information to work on. So, I implemented everything according to Geoclue and... nothing. No position information. Based on all the examples I had I should have updates coming, but no. Que debugging information, and when trying to send "GetPosition" I get result in tune of "Can't be done as there are no location providers" (I don't remember the exact phrase used). Wut? But this Maps app here has position coming in...
Lightbulb moment...
At this point I had managed to gain enough knowledge of DBus in general and Qt's abilities so I could started to get
creative. On terminal you can use dbus-monitor to, well, monitor what's going on dbus. So I started logging everything (if you want to do this too, log in file - there will be
lots of events across all subsystems), started Maps app, and then quit soon after starting and started reading the logs.
And what do you know, the Maps app actually doesn't seem to use the Geoclue.Position interface; it accesses directly the Hybris (that's the GPS - and GPS only - provider) for location information (the interface for that is org.freedesktop.Geoclue.Providers.Hybris - so far I haven't found any relevant interface documentation about it or even Providers in general). So insert a short (and very very boring) montage of software hacking in all its glory here and soon the GPS position updates started coming in. Whew!
So, now for the actual implementation. As my project is in C++ (except the UI), the GPS interface is too. It
shouldn't be too difficult to build quick adapter to access this from
QML either, but I won't go to details of that here.
So, first your project. To use DBus you must have line
QT += dbus
In your .pro file.
It is best to implement your receiver as a separate class that provides its own interface for rest of your app, making it easy to switch to QtPositioning later when it becomes available. So the header (vastly simplified, relevant includes are left as exercise for the reader, that shouldn't be too difficult)
class PositionSource : public QObject {
Q_OBJECT
public:
PositionSource(QObject *parent);
public slots:
void positionChangedDBUS(QDBusMessage);
private:
QDBusInterface *masterInterface, *clientInterface, *hybrisInterface, *hereInterface;
};
And Cpp (again, I'm skipping everything but the most interesting parts). Also the Blogger's editor doesn't seem to like <> tags, so it may have messed something up during edits.
PositionSource::PositionSource(QObject *parent) :
QObject(parent)
{
masterInterface = new QDBusInterface("org.freedesktop.Geoclue.Master",
"/org/freedesktop/Geoclue/Master",
"",
QDBusConnection::sessionBus() );
QDBusReply<qdbusobjectpath> createReply = masterInterface->call("Create");
qDebug() << "create:" << createReply.value().path();
if (createReply.value().path().length() < 5) { // just quick-and-dirty check
qDebug() << "Could not create geoclue client";
return;
}
clientInterface = new QDBusInterface( "org.freedesktop.Geoclue.Master",
createReply.value().path(),
"",
QDBusConnection::sessionBus() );
QDBusMessage msg;
// GeoClue has "SetRequirements" function where you can specify your
// app's requirements. Note that if they are too strict (like you
// ask for precision no position provider can do), GeoClue doesn't
// seem to fall back to "lesser" provider but will fail to give you
// position information at all.
// I am not sure if that was reason for original problems with
// Geoclue.Position interface (found out about that only later, by
// reading actual geoclue sources) but proceed with caution if you
// want to try that.
// SetRequirements:
// accuracy level: GeoclueAccuracyLevel-enum
// minimum update time
// require updates
// resources, another enum again; (1<<2) should be GPS, (1<<10)-1 "anyting"
clientinterface->call("SetRequirements", 1,0, true, 1023);
// This most likely isn't very relevant when using Hybris directly, but I kept it anyway.
clientInterface->call("PositionStart");
// Then, register Hybris provider.
hybrisInterface = new QDBusInterface("org.freedesktop.Geoclue.Providers.Hybris",
"/org/freedesktop/Geoclue/Providers/Hybris",
"",
QDBusConnection::sessionBus() );
msg = hybrisInterface->call("AddReference");
if (msg.type() != QDBusMessage::ReplyMessage) { // quick and dirty check
qDebug() << "Couldn't access Hybris : " << msg;
status = POS_UNKNOWN;
return;
}
// SetOptions parameter appears to be: (I'm guessing this implies auto-detect)
// I have no idea on how to actually create the providerOpts so Qt would accept it
// (and Qt documentation is *wonderfully* vague about that), but I'm leaving this
// here in case someone has good ideas)
// This call doesn't seem to be necessary anyway since position is obtained anyway,
// but on different hardware this might have different results.
// array [
// dict: "gps-baudrate", 0;
// dict: "gps-device", "";
// ]
/*
msg = hybrisInterface->call("SetOptions", providerOpts);
if (msg.type() != QDBusMessage::ReplyMessage) {
qDebug() << "Couldn't set hybris options : " << msg;
}
*/
// These are just for general information
msg = hybrisInterface->call("GetProviderInfo");
qDebug() << " Hybris provider info: " << msg;
msg = hybrisInterface->call("GetStatus");
qDebug() << " Hybris status: " << msg;
// Now, if you really wanted to, you could do same for org.freedesktop.Geoclue.Providers.Here,
// to get information from it. But then again, why bother? (especially if you are concerned about
// privacy).
// Then connect the PositionChanged signal. I'm connecting also the Geoclue.Position, but
// so far it has never been triggered.
// Note that after cold-start it may take a long while before you start getting position fix,
// especially if you are indoors.
QDBusConnection::sessionBus().connect("org.freedesktop.Geoclue.Position",
"/org/freedesktop/Geoclue/Position",
"",
"PositionChanged", this, SLOT(positionChangedDBUS(QDBusMessage)));
QDBusConnection::sessionBus().connect("org.freedesktop.Geoclue.Providers.Hybris",
"/org/freedesktop/Geoclue/Providers/Hybris",
"",
"PositionChanged", this, SLOT(positionChangedDBUS(QDBusMessage)));
}
// The message slot. This only spits out the data, parsing is up to you.
void PositionSource::positionChangedDBUS(QDBusMessage msg)
{
// expecing int,int,double,double,double - flags (which data is valid), time, lat, lon, altitude
QList<QVariant> list = msg.arguments();
qDebug() << "PositionChanged:" << msg;
}
If you monitor the Geoclue.Position interface, there seem to be messages being sent but they certainly aren't being received by this code. No idea why.
Also note that you should also call RemoveReference for Providers (and
possibly for client) when you're done (so in your adapter destructor).
Unfortunately there is no way to explicitly tell GeoClue (as in client or provider) that you're done and it should shut down (at least no documented way on DBus interfaces) - RemoveReference only tells it that it
may
shut down if it pleases it to do so. On phone it appears that even after asking interface to close the GPS stays
active even when no one is using it, eating battery. Kinda stupid interface design if you ask me.
The code above should be runnable on the phone, but may not be in emulator (I didn't actually try, oh well). Since this is untested copy-paste from my code there may be some errors, sorry about those in advance.
Now, looking back at that mess I can make an educated guess why Jolla folks haven't been exactly forthcoming with this information, as this pretty much is accessing very very unofficial and possibly undocumented subsystems for location information. In this context this means that it may vanish without warning, but until QtPositioning rolls in, I unfortunately don't really see another (good) way to do this.
Update:
It seems that you can actually skip GeoClue master/clients completely (everything up to (down to?) "PositionStart" call in above code) and only create/connect to HybrisInterface. This seems to make position information lock slower (may take several minutes, up from 20 seconds or so I experience earlier while inside), but then RemoveReference on shutdown will actually stop GPS (well, make the icon go away at least) and hopefully also stop excessive battery drain after shutdown.