1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtBluetooth module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "remoteselector.h" |
52 | #include "ui_remoteselector.h" |
53 | |
54 | #include <qbluetoothdeviceinfo.h> |
55 | #include <qbluetoothaddress.h> |
56 | #include <qbluetoothtransferrequest.h> |
57 | #include <qbluetoothtransferreply.h> |
58 | #include <qbluetoothlocaldevice.h> |
59 | |
60 | #include <QMovie> |
61 | #include <QMessageBox> |
62 | #include <QFileDialog> |
63 | #include <QCheckBox> |
64 | |
65 | #include "progress.h" |
66 | #include "pindisplay.h" |
67 | |
68 | QT_USE_NAMESPACE |
69 | |
70 | RemoteSelector::RemoteSelector(QWidget *parent) |
71 | : QDialog(parent), ui(new Ui::RemoteSelector), |
72 | m_localDevice(new QBluetoothLocalDevice), m_pindisplay(0), |
73 | m_pairingError(false) |
74 | { |
75 | ui->setupUi(this); |
76 | |
77 | //Using default Bluetooth adapter |
78 | QBluetoothAddress adapterAddress = m_localDevice->address(); |
79 | |
80 | /* |
81 | * In case of multiple Bluetooth adapters it is possible to |
82 | * set which adapter will be used by providing MAC Address. |
83 | * Example code: |
84 | * |
85 | * QBluetoothAddress adapterAddress("XX:XX:XX:XX:XX:XX"); |
86 | * m_discoveryAgent = new QBluetoothServiceDiscoveryAgent(adapterAddress); |
87 | */ |
88 | |
89 | m_discoveryAgent = new QBluetoothServiceDiscoveryAgent(adapterAddress); |
90 | |
91 | connect(sender: m_discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)), |
92 | receiver: this, SLOT(serviceDiscovered(QBluetoothServiceInfo))); |
93 | connect(sender: m_discoveryAgent, SIGNAL(finished()), receiver: this, SLOT(discoveryFinished())); |
94 | connect(sender: m_discoveryAgent, SIGNAL(canceled()), receiver: this, SLOT(discoveryFinished())); |
95 | |
96 | ui->remoteDevices->setColumnWidth(column: 3, width: 75); |
97 | ui->remoteDevices->setColumnWidth(column: 4, width: 100); |
98 | |
99 | connect(sender: m_localDevice, SIGNAL(pairingDisplayPinCode(QBluetoothAddress,QString)), |
100 | receiver: this, SLOT(displayPin(QBluetoothAddress,QString))); |
101 | connect(sender: m_localDevice, SIGNAL(pairingDisplayConfirmation(QBluetoothAddress,QString)), |
102 | receiver: this, SLOT(displayConfirmation(QBluetoothAddress,QString))); |
103 | connect(sender: m_localDevice, SIGNAL(pairingFinished(QBluetoothAddress,QBluetoothLocalDevice::Pairing)), |
104 | receiver: this, SLOT(pairingFinished(QBluetoothAddress,QBluetoothLocalDevice::Pairing))); |
105 | connect(sender: m_localDevice, SIGNAL(error(QBluetoothLocalDevice::Error)), |
106 | receiver: this, SLOT(pairingError(QBluetoothLocalDevice::Error))); |
107 | |
108 | ui->busyWidget->setMovie(new QMovie(":/icons/busy.gif" )); |
109 | ui->busyWidget->movie()->start(); |
110 | |
111 | ui->pairingBusy->setMovie(new QMovie(":/icons/pairing.gif" )); |
112 | ui->pairingBusy->hide(); |
113 | |
114 | ui->remoteDevices->clearContents(); |
115 | ui->remoteDevices->setRowCount(0); |
116 | } |
117 | |
118 | RemoteSelector::~RemoteSelector() |
119 | { |
120 | delete ui; |
121 | delete m_discoveryAgent; |
122 | delete m_localDevice; |
123 | } |
124 | |
125 | void RemoteSelector::startDiscovery(const QBluetoothUuid &uuid) |
126 | { |
127 | ui->stopButton->setDisabled(false); |
128 | if (m_discoveryAgent->isActive()) |
129 | m_discoveryAgent->stop(); |
130 | |
131 | m_discoveryAgent->setUuidFilter(uuid); |
132 | m_discoveryAgent->start(); |
133 | |
134 | if (!m_discoveryAgent->isActive() || |
135 | m_discoveryAgent->error() != QBluetoothServiceDiscoveryAgent::NoError) { |
136 | ui->status->setText(tr(s: "Cannot find remote services." )); |
137 | } else { |
138 | ui->status->setText(tr(s: "Scanning..." )); |
139 | ui->busyWidget->show(); |
140 | ui->busyWidget->movie()->start(); |
141 | } |
142 | } |
143 | |
144 | QBluetoothServiceInfo RemoteSelector::service() const |
145 | { |
146 | return m_service; |
147 | } |
148 | |
149 | void RemoteSelector::serviceDiscovered(const QBluetoothServiceInfo &serviceInfo) |
150 | { |
151 | #if 0 |
152 | qDebug() << "Discovered service on" |
153 | << serviceInfo.device().name() << serviceInfo.device().address().toString(); |
154 | qDebug() << "\tService name:" << serviceInfo.serviceName(); |
155 | qDebug() << "\tDescription:" |
156 | << serviceInfo.attribute(QBluetoothServiceInfo::ServiceDescription).toString(); |
157 | qDebug() << "\tProvider:" |
158 | << serviceInfo.attribute(QBluetoothServiceInfo::ServiceProvider).toString(); |
159 | qDebug() << "\tL2CAP protocol service multiplexer:" |
160 | << serviceInfo.protocolServiceMultiplexer(); |
161 | qDebug() << "\tRFCOMM server channel:" << serviceInfo.serverChannel(); |
162 | #endif |
163 | |
164 | QString remoteName; |
165 | if (serviceInfo.device().name().isEmpty()) |
166 | remoteName = serviceInfo.device().address().toString(); |
167 | else |
168 | remoteName = serviceInfo.device().name(); |
169 | |
170 | // QListWidgetItem *item = |
171 | // new QListWidgetItem(QString::fromLatin1("%1\t%2\t%3").arg(serviceInfo.device().address().toString(), |
172 | // serviceInfo.device().name(), serviceInfo.serviceName())); |
173 | |
174 | const QBluetoothAddress address = serviceInfo.device().address(); |
175 | for (QBluetoothServiceInfo &info : m_discoveredServices) { |
176 | if (info.device().address() == address){ |
177 | info = serviceInfo; |
178 | return; |
179 | } |
180 | } |
181 | |
182 | int row = ui->remoteDevices->rowCount(); |
183 | ui->remoteDevices->insertRow(row); |
184 | QTableWidgetItem *item = new QTableWidgetItem(address.toString()); |
185 | ui->remoteDevices->setItem(row, column: 0, item); |
186 | item = new QTableWidgetItem(serviceInfo.device().name()); |
187 | ui->remoteDevices->setItem(row, column: 1, item); |
188 | item = new QTableWidgetItem(serviceInfo.serviceName()); |
189 | |
190 | ui->remoteDevices->setItem(row, column: 2, item); |
191 | |
192 | QBluetoothLocalDevice::Pairing p = m_localDevice->pairingStatus(address); |
193 | |
194 | ui->remoteDevices->blockSignals(b: true); |
195 | |
196 | item = new QTableWidgetItem(); |
197 | if ((p&QBluetoothLocalDevice::Paired) || (p&QBluetoothLocalDevice::AuthorizedPaired)) |
198 | item->setCheckState(Qt::Checked); |
199 | else |
200 | item->setCheckState(Qt::Unchecked); |
201 | ui->remoteDevices->setItem(row, column: 3, item); |
202 | |
203 | item = new QTableWidgetItem(); |
204 | if (p&QBluetoothLocalDevice::AuthorizedPaired) |
205 | item->setCheckState(Qt::Checked); |
206 | else |
207 | item->setCheckState(Qt::Unchecked); |
208 | |
209 | ui->remoteDevices->setItem(row, column: 4, item); |
210 | |
211 | ui->remoteDevices->blockSignals(b: false); |
212 | |
213 | |
214 | m_discoveredServices.insert(akey: row, avalue: serviceInfo); |
215 | } |
216 | |
217 | void RemoteSelector::discoveryFinished() |
218 | { |
219 | ui->status->setText(tr(s: "Select the device to send to." )); |
220 | ui->stopButton->setDisabled(true); |
221 | ui->busyWidget->movie()->stop(); |
222 | ui->busyWidget->hide(); |
223 | } |
224 | |
225 | void RemoteSelector::startDiscovery() |
226 | { |
227 | startDiscovery(uuid: QBluetoothUuid(QBluetoothUuid::ObexObjectPush)); |
228 | } |
229 | |
230 | void RemoteSelector::on_refreshPB_clicked() |
231 | { |
232 | startDiscovery(); |
233 | ui->stopButton->setDisabled(false); |
234 | } |
235 | |
236 | void RemoteSelector::on_fileSelectPB_clicked() |
237 | { |
238 | ui->fileName->setText(QFileDialog::getOpenFileName()); |
239 | if (m_service.isValid()) |
240 | ui->sendButton->setDisabled(false); |
241 | } |
242 | |
243 | void RemoteSelector::on_sendButton_clicked() |
244 | { |
245 | QBluetoothTransferManager mgr; |
246 | QBluetoothTransferRequest req(m_service.device().address()); |
247 | |
248 | m_file = new QFile(ui->fileName->text()); |
249 | |
250 | Progress *p = new Progress; |
251 | p->setStatus(title: "Sending to: " + m_service.device().name(), filename: "Waiting for start" ); |
252 | p->show(); |
253 | |
254 | QBluetoothTransferReply *reply = mgr.put(request: req, data: m_file); |
255 | //mgr is default parent |
256 | //ensure that mgr doesn't take reply down when leaving scope |
257 | reply->setParent(this); |
258 | if (reply->error()){ |
259 | qDebug() << "Failed to send file" ; |
260 | p->finished(reply); |
261 | reply->deleteLater(); |
262 | return; |
263 | } |
264 | |
265 | connect(sender: reply, SIGNAL(transferProgress(qint64,qint64)), receiver: p, SLOT(uploadProgress(qint64,qint64))); |
266 | connect(sender: reply, SIGNAL(finished(QBluetoothTransferReply*)), receiver: p, SLOT(finished(QBluetoothTransferReply*))); |
267 | connect(sender: p, SIGNAL(rejected()), receiver: reply, SLOT(abort())); |
268 | } |
269 | |
270 | void RemoteSelector::on_stopButton_clicked() |
271 | { |
272 | m_discoveryAgent->stop(); |
273 | } |
274 | |
275 | QString RemoteSelector::addressToName(const QBluetoothAddress &address) const |
276 | { |
277 | for (const QBluetoothServiceInfo &info : m_discoveredServices) { |
278 | if (info.device().address() == address) |
279 | return info.device().name(); |
280 | } |
281 | return address.toString(); |
282 | } |
283 | |
284 | void RemoteSelector::displayPin(const QBluetoothAddress &address, QString pin) |
285 | { |
286 | if (m_pindisplay) |
287 | m_pindisplay->deleteLater(); |
288 | m_pindisplay = new pinDisplay(QString("Enter pairing pin on: %1" ).arg(a: addressToName(address)), pin, this); |
289 | m_pindisplay->show(); |
290 | } |
291 | |
292 | void RemoteSelector::displayConfirmation(const QBluetoothAddress &address, QString pin) |
293 | { |
294 | Q_UNUSED(address); |
295 | |
296 | if (m_pindisplay) |
297 | m_pindisplay->deleteLater(); |
298 | m_pindisplay = new pinDisplay(QString("Confirm this pin is the same" ), pin, this); |
299 | connect(sender: m_pindisplay, SIGNAL(accepted()), receiver: this, SLOT(displayConfAccepted())); |
300 | connect(sender: m_pindisplay, SIGNAL(rejected()), receiver: this, SLOT(displayConfReject())); |
301 | m_pindisplay->setOkCancel(); |
302 | m_pindisplay->show(); |
303 | } |
304 | |
305 | void RemoteSelector::displayConfAccepted() |
306 | { |
307 | m_localDevice->pairingConfirmation(confirmation: true); |
308 | } |
309 | void RemoteSelector::displayConfReject() |
310 | { |
311 | m_localDevice->pairingConfirmation(confirmation: false); |
312 | } |
313 | |
314 | void RemoteSelector::pairingFinished(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing status) |
315 | { |
316 | QBluetoothServiceInfo service; |
317 | int row = 0; |
318 | |
319 | ui->pairingBusy->hide(); |
320 | ui->pairingBusy->movie()->stop(); |
321 | |
322 | ui->remoteDevices->blockSignals(b: true); |
323 | |
324 | for (int i = 0; i < m_discoveredServices.count(); i++){ |
325 | if (m_discoveredServices.value(akey: i).device().address() == address){ |
326 | service = m_discoveredServices.value(akey: i); |
327 | row = i; |
328 | break; |
329 | } |
330 | } |
331 | |
332 | if (m_pindisplay) |
333 | delete m_pindisplay; |
334 | |
335 | QMessageBox msgBox; |
336 | if (m_pairingError) { |
337 | msgBox.setText("Pairing failed with " + address.toString()); |
338 | } else if (status == QBluetoothLocalDevice::Paired |
339 | || status == QBluetoothLocalDevice::AuthorizedPaired) { |
340 | msgBox.setText("Paired successfully with " + address.toString()); |
341 | } else { |
342 | msgBox.setText("Pairing released with " + address.toString()); |
343 | } |
344 | |
345 | if (service.isValid()){ |
346 | if (status == QBluetoothLocalDevice::AuthorizedPaired){ |
347 | ui->remoteDevices->item(row, column: 3)->setCheckState(Qt::Checked); |
348 | ui->remoteDevices->item(row, column: 4)->setCheckState(Qt::Checked); |
349 | } |
350 | else if (status == QBluetoothLocalDevice::Paired){ |
351 | ui->remoteDevices->item(row, column: 3)->setCheckState(Qt::Checked); |
352 | ui->remoteDevices->item(row, column: 4)->setCheckState(Qt::Unchecked); |
353 | } |
354 | else { |
355 | ui->remoteDevices->item(row, column: 3)->setCheckState(Qt::Unchecked); |
356 | ui->remoteDevices->item(row, column: 4)->setCheckState(Qt::Unchecked); |
357 | } |
358 | } |
359 | |
360 | m_pairingError = false; |
361 | msgBox.exec(); |
362 | |
363 | ui->remoteDevices->blockSignals(b: false); |
364 | } |
365 | |
366 | void RemoteSelector::pairingError(QBluetoothLocalDevice::Error error) |
367 | { |
368 | if (error != QBluetoothLocalDevice::PairingError) |
369 | return; |
370 | |
371 | m_pairingError = true; |
372 | pairingFinished(address: m_service.device().address(), status: QBluetoothLocalDevice::Unpaired); |
373 | } |
374 | |
375 | void RemoteSelector::on_remoteDevices_cellClicked(int row, int column) |
376 | { |
377 | Q_UNUSED(column); |
378 | |
379 | m_service = m_discoveredServices.value(akey: row); |
380 | if (!ui->fileName->text().isEmpty()) { |
381 | ui->sendButton->setDisabled(false); |
382 | } |
383 | } |
384 | |
385 | void RemoteSelector::on_remoteDevices_itemChanged(QTableWidgetItem* item) |
386 | { |
387 | int row = item->row(); |
388 | int column = item->column(); |
389 | m_service = m_discoveredServices.value(akey: row); |
390 | |
391 | if (column < 3) |
392 | return; |
393 | |
394 | if (item->checkState() == Qt::Unchecked && column == 3){ |
395 | m_localDevice->requestPairing(address: m_service.device().address(), pairing: QBluetoothLocalDevice::Unpaired); |
396 | return; // don't continue and start movie |
397 | } |
398 | else if ((item->checkState() == Qt::Checked && column == 3) || |
399 | (item->checkState() == Qt::Unchecked && column == 4)){ |
400 | m_localDevice->requestPairing(address: m_service.device().address(), pairing: QBluetoothLocalDevice::Paired); |
401 | ui->remoteDevices->blockSignals(b: true); |
402 | ui->remoteDevices->item(row, column)->setCheckState(Qt::PartiallyChecked); |
403 | ui->remoteDevices->blockSignals(b: false); |
404 | } |
405 | else if (item->checkState() == Qt::Checked && column == 4){ |
406 | m_localDevice->requestPairing(address: m_service.device().address(), pairing: QBluetoothLocalDevice::AuthorizedPaired); |
407 | ui->remoteDevices->blockSignals(b: true); |
408 | ui->remoteDevices->item(row, column)->setCheckState(Qt::PartiallyChecked); |
409 | ui->remoteDevices->blockSignals(b: false); |
410 | } |
411 | ui->pairingBusy->show(); |
412 | ui->pairingBusy->movie()->start(); |
413 | } |
414 | |