1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtPositioning module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qgeosatelliteinfosource_gypsy_p.h"
41 
42 #ifdef Q_LOCATION_GYPSY_DEBUG
43 #include <QDebug>
44 #endif
45 #include <QFile>
46 
47 QT_BEGIN_NAMESPACE
48 
49 #define UPDATE_TIMEOUT_COLD_START 120000
50 
51 
52 // Callback function for 'satellites-changed' -signal
satellites_changed(GypsySatellite * satellite,GPtrArray * satellites,gpointer userdata)53 static void satellites_changed (GypsySatellite *satellite,
54                                 GPtrArray *satellites,
55                                 gpointer userdata)
56 {
57 #ifdef Q_LOCATION_GYPSY_DEBUG
58     qDebug() << "QGeoSatelliteInfoSourceGypsy Gypsy satellites-changed -signal received.";
59 #endif
60     ((QGeoSatelliteInfoSourceGypsy *)userdata)->satellitesChanged(satellite, satellites);
61 }
62 
SatelliteGypsyEngine(QGeoSatelliteInfoSource * parent)63 SatelliteGypsyEngine::SatelliteGypsyEngine(QGeoSatelliteInfoSource *parent) :
64     m_owner(parent)
65 {
66 }
~SatelliteGypsyEngine()67 SatelliteGypsyEngine::~SatelliteGypsyEngine()
68 {
69 }
70 
71 // Glib symbols
eng_g_signal_connect(gpointer instance,const gchar * detailed_signal,GCallback c_handler,gpointer data)72 gulong SatelliteGypsyEngine::eng_g_signal_connect(gpointer instance,
73                                                   const gchar *detailed_signal,
74                                                   GCallback c_handler,
75                                                   gpointer data)
76 {
77     return ::g_signal_connect(instance, detailed_signal, c_handler, data);
78 }
eng_g_signal_handlers_disconnect_by_func(gpointer instance,gpointer func,gpointer data)79 guint SatelliteGypsyEngine::eng_g_signal_handlers_disconnect_by_func (gpointer instance,
80                                                                       gpointer func,
81                                                                       gpointer data)
82 {
83     return ::g_signal_handlers_disconnect_by_func(instance, func, data);
84 }
85 
eng_g_free(gpointer mem)86 void SatelliteGypsyEngine::eng_g_free(gpointer mem)
87 {
88     return ::g_free(mem);
89 }
90 // Gypsy symbols
eng_gypsy_control_get_default(void)91 GypsyControl *SatelliteGypsyEngine::eng_gypsy_control_get_default (void)
92 {
93     return ::gypsy_control_get_default();
94 }
eng_gypsy_control_create(GypsyControl * control,const char * device_name,GError ** error)95 char *SatelliteGypsyEngine::eng_gypsy_control_create (GypsyControl *control, const char *device_name, GError **error)
96 {
97     return ::gypsy_control_create(control, device_name, error);
98 }
eng_gypsy_device_new(const char * object_path)99 GypsyDevice *SatelliteGypsyEngine::eng_gypsy_device_new (const char *object_path)
100 {
101     return ::gypsy_device_new(object_path);
102 }
eng_gypsy_satellite_new(const char * object_path)103 GypsySatellite *SatelliteGypsyEngine::eng_gypsy_satellite_new (const char *object_path)
104 {
105     return ::gypsy_satellite_new (object_path);
106 }
eng_gypsy_device_start(GypsyDevice * device,GError ** error)107 gboolean SatelliteGypsyEngine::eng_gypsy_device_start (GypsyDevice *device, GError **error)
108 {
109     return ::gypsy_device_start(device, error);
110 }
eng_gypsy_device_stop(GypsyDevice * device,GError ** error)111 gboolean SatelliteGypsyEngine::eng_gypsy_device_stop (GypsyDevice *device, GError **error)
112 {
113     // Unfortunately this cannot be done; calling this will stop the GPS device
114     // (basically makes gypsy-daemon unusable for anyone), regardless of applications
115     // using it (see bug http://bugs.meego.com/show_bug.cgi?id=11707).
116     Q_UNUSED(device);
117     Q_UNUSED(error);
118     return true;
119     //return ::gypsy_device_stop (device, error);
120 }
eng_gypsy_device_get_fix_status(GypsyDevice * device,GError ** error)121 GypsyDeviceFixStatus SatelliteGypsyEngine::eng_gypsy_device_get_fix_status (GypsyDevice *device, GError **error)
122 {
123     return ::gypsy_device_get_fix_status (device, error);
124 }
eng_gypsy_satellite_get_satellites(GypsySatellite * satellite,GError ** error)125 GPtrArray *SatelliteGypsyEngine::eng_gypsy_satellite_get_satellites (GypsySatellite *satellite, GError **error)
126 {
127     return ::gypsy_satellite_get_satellites (satellite, error);
128 }
eng_gypsy_satellite_free_satellite_array(GPtrArray * satellites)129 void SatelliteGypsyEngine::eng_gypsy_satellite_free_satellite_array (GPtrArray *satellites)
130 {
131     return ::gypsy_satellite_free_satellite_array(satellites);
132 }
133 // GConf symbols (mockability due to X11 requirement)
eng_gconf_client_get_default(void)134 GConfClient *SatelliteGypsyEngine::eng_gconf_client_get_default(void)
135 {
136     return ::gconf_client_get_default();
137 }
eng_gconf_client_get_string(GConfClient * client,const gchar * key,GError ** err)138 gchar *SatelliteGypsyEngine::eng_gconf_client_get_string(GConfClient *client, const gchar *key, GError** err)
139 {
140     return ::gconf_client_get_string(client, key, err);
141 }
142 
QGeoSatelliteInfoSourceGypsy(QObject * parent)143 QGeoSatelliteInfoSourceGypsy::QGeoSatelliteInfoSourceGypsy(QObject *parent) : QGeoSatelliteInfoSource(parent),
144     m_engine(0), m_satellite(0), m_device(0), m_requestTimer(this), m_updatesOngoing(false), m_requestOngoing(false)
145 {
146     m_requestTimer.setSingleShot(true);
147     QObject::connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout()));
148 }
149 
createEngine()150 void QGeoSatelliteInfoSourceGypsy::createEngine()
151 {
152     delete m_engine;
153     m_engine = new SatelliteGypsyEngine(this);
154 }
155 
~QGeoSatelliteInfoSourceGypsy()156 QGeoSatelliteInfoSourceGypsy::~QGeoSatelliteInfoSourceGypsy()
157 {
158     GError *error = NULL;
159     if (m_device) {
160         m_engine->eng_gypsy_device_stop (m_device, &error);
161         g_object_unref(m_device);
162     }
163     if (m_satellite)
164         g_object_unref(m_satellite);
165     if (error)
166         g_error_free(error);
167     delete m_engine;
168 }
169 
satellitesChanged(GypsySatellite * satellite,GPtrArray * satellites)170 void QGeoSatelliteInfoSourceGypsy::satellitesChanged(GypsySatellite *satellite,
171                                                      GPtrArray *satellites)
172 {
173     if (!satellite || !satellites)
174         return;
175     // We have satellite data and assume it is valid.
176     // If a single updateRequest was active, send signals right away.
177     // If a periodic timer was running (meaning that the client wishes
178     // to have updates at defined intervals), store the data for later sending.
179     QList<QGeoSatelliteInfo> lastSatellitesInView;
180     QList<QGeoSatelliteInfo> lastSatellitesInUse;
181 
182     unsigned int i;
183     for (i = 0; i < satellites->len; i++) {
184         GypsySatelliteDetails *details = (GypsySatelliteDetails *)satellites->pdata[i];
185         QGeoSatelliteInfo info;
186         info.setAttribute(QGeoSatelliteInfo::Elevation, details->elevation);
187         info.setAttribute(QGeoSatelliteInfo::Azimuth, details->azimuth);
188         info.setSignalStrength(details->snr);
189         if (details->in_use)
190             lastSatellitesInUse.append(info);
191         lastSatellitesInView.append(info);
192     }
193     bool sendUpdates(false);
194     // If a single updateRequest() has been issued:
195     if (m_requestOngoing) {
196         sendUpdates = true;
197         m_requestTimer.stop();
198         m_requestOngoing = false;
199         // If there is no regular updates ongoing, disconnect now.
200         if (!m_updatesOngoing) {
201             m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this);
202         }
203     }
204     // If regular updates are to be delivered as they come:
205     if (m_updatesOngoing)
206         sendUpdates = true;
207 
208     if (sendUpdates) {
209         emit satellitesInUseUpdated(lastSatellitesInUse);
210         emit satellitesInViewUpdated(lastSatellitesInView);
211     }
212 }
213 
init()214 int QGeoSatelliteInfoSourceGypsy::init()
215 {
216     GError *error = NULL;
217     char *path;
218     GConfClient *client;
219     gchar *device_name;
220 
221     g_type_init ();
222     createEngine();
223 
224     client = m_engine->eng_gconf_client_get_default();
225     if (!client) {
226         qWarning ("QGeoSatelliteInfoSourceGypsy client creation failed.");
227         return -1;
228     }
229     device_name = m_engine->eng_gconf_client_get_string(client, "/apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice", NULL);
230     g_object_unref(client);
231     QString deviceName(QString::fromLatin1(device_name));
232     if (deviceName.isEmpty() ||
233             (deviceName.trimmed().at(0) == '/' && !QFile::exists(deviceName.trimmed()))) {
234         qWarning ("QGeoSatelliteInfoSourceGypsy Empty/nonexistent GPS device name detected.");
235         qWarning ("Use gconftool-2 to set it, e.g. on terminal: ");
236         qWarning ("gconftool-2 -t string -s /apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice /dev/ttyUSB0");
237         m_engine->eng_g_free(device_name);
238         return -1;
239     }
240     GypsyControl *control = NULL;
241     control = m_engine->eng_gypsy_control_get_default();
242     if (!control) {
243         qWarning("QGeoSatelliteInfoSourceGypsy unable to create Gypsy control.");
244         m_engine->eng_g_free(device_name);
245         return -1;
246     }
247     // (path is the DBus path)
248     path = m_engine->eng_gypsy_control_create (control, device_name, &error);
249     m_engine->eng_g_free(device_name);
250     g_object_unref(control);
251     if (!path) {
252         qWarning ("QGeoSatelliteInfoSourceGypsy error creating client.");
253         if (error) {
254             qWarning ("error message: %s", error->message);
255             g_error_free (error);
256         }
257         return -1;
258     }
259     m_device = m_engine->eng_gypsy_device_new (path);
260     m_satellite = m_engine->eng_gypsy_satellite_new (path);
261     m_engine->eng_g_free(path);
262     if (!m_device || !m_satellite) {
263         qWarning ("QGeoSatelliteInfoSourceGypsy error creating satellite device.");
264         qWarning ("Is GPS device set correctly? If not, use gconftool-2 to set it, e.g.: ");
265         qWarning ("gconftool-2 -t string -s /apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice /dev/ttyUSB0");
266         if (m_device)
267             g_object_unref(m_device);
268         if (m_satellite)
269             g_object_unref(m_satellite);
270         return -1;
271     }
272     m_engine->eng_gypsy_device_start (m_device, &error);
273     if (error) {
274         qWarning ("QGeoSatelliteInfoSourceGypsy error starting device: %s ",
275                    error->message);
276         g_error_free(error);
277         g_object_unref(m_device);
278         g_object_unref(m_satellite);
279         return -1;
280     }
281     return 0;
282 }
283 
minimumUpdateInterval() const284 int QGeoSatelliteInfoSourceGypsy::minimumUpdateInterval() const
285 {
286     return 1;
287 }
288 
error() const289 QGeoSatelliteInfoSource::Error QGeoSatelliteInfoSourceGypsy::error() const
290 {
291     return NoError;
292 }
293 
startUpdates()294 void QGeoSatelliteInfoSourceGypsy::startUpdates()
295 {
296     if (m_updatesOngoing)
297         return;
298     // If there is a request timer ongoing, we've connected to the signal already
299     if (!m_requestTimer.isActive()) {
300         m_engine->eng_g_signal_connect (m_satellite, "satellites-changed",
301                           G_CALLBACK (satellites_changed), this);
302     }
303     m_updatesOngoing = true;
304 }
305 
stopUpdates()306 void QGeoSatelliteInfoSourceGypsy::stopUpdates()
307 {
308     if (!m_updatesOngoing)
309         return;
310     m_updatesOngoing = false;
311     // Disconnect only if there is no single update request ongoing. Once single update request
312     // is completed and it notices that there is no active update ongoing, it will disconnect
313     // the signal.
314     if (!m_requestTimer.isActive())
315         m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this);
316 }
317 
requestUpdate(int timeout)318 void QGeoSatelliteInfoSourceGypsy::requestUpdate(int timeout)
319 {
320     if (m_requestOngoing)
321         return;
322     if (timeout < 0) {
323         emit requestTimeout();
324         return;
325     }
326     m_requestOngoing = true;
327     GError *error = 0;
328     // If GPS has a fix a already, request current data.
329     GypsyDeviceFixStatus fixStatus = m_engine->eng_gypsy_device_get_fix_status(m_device, &error);
330     if (!error && (fixStatus != GYPSY_DEVICE_FIX_STATUS_INVALID &&
331             fixStatus != GYPSY_DEVICE_FIX_STATUS_NONE)) {
332 #ifdef Q_LOCATION_GYPSY_DEBUG
333         qDebug() << "QGeoSatelliteInfoSourceGypsy fix available, requesting current satellite data";
334 #endif
335         GPtrArray *satelliteData = m_engine->eng_gypsy_satellite_get_satellites(m_satellite, &error);
336         if (!error) {
337             // The fix was available and we have satellite data to deliver right away.
338             satellitesChanged(m_satellite, satelliteData);
339             m_engine->eng_gypsy_satellite_free_satellite_array(satelliteData);
340             return;
341         }
342     }
343     // No fix is available. If updates are not ongoing already, start them.
344     m_requestTimer.setInterval(timeout == 0? UPDATE_TIMEOUT_COLD_START: timeout);
345     if (!m_updatesOngoing) {
346         m_engine->eng_g_signal_connect (m_satellite, "satellites-changed",
347                           G_CALLBACK (satellites_changed), this);
348     }
349     m_requestTimer.start();
350     if (error) {
351 #ifdef Q_LOCATION_GYPSY_DEBUG
352         qDebug() << "QGeoSatelliteInfoSourceGypsy error asking fix status or satellite data: " << error->message;
353 #endif
354         g_error_free(error);
355     }
356 }
357 
requestUpdateTimeout()358 void QGeoSatelliteInfoSourceGypsy::requestUpdateTimeout()
359 {
360 #ifdef Q_LOCATION_GYPSY_DEBUG
361     qDebug("QGeoSatelliteInfoSourceGypsy request update timeout occurred.");
362 #endif
363     // If we end up here, there has not been valid satellite update.
364     // Emit timeout and disconnect from signal if regular updates are not
365     // ongoing (as we were listening just for one single requestUpdate).
366     if (!m_updatesOngoing) {
367         m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this);
368     }
369     m_requestOngoing = false;
370     emit requestTimeout();
371 }
372 
373 QT_END_NAMESPACE
374