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