1/*
2 * BluezQt - Asynchronous Bluez wrapper library
3 *
4 * SPDX-FileCopyrightText: 2015 David Rosca <nowrep@gmail.com>
5 *
6 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8
9#include "rfkill.h"
10#include "rfkill_p.h"
11
12#include "debug.h"
13
14#ifdef Q_OS_LINUX
15#include <fcntl.h>
16#include <stdint.h>
17#include <string.h>
18#include <unistd.h>
19#endif
20
21#include <QMutex>
22#include <QMutexLocker>
23#include <QSocketNotifier>
24#include <QThread>
25#include <QWaitCondition>
26
27namespace BluezQt
28{
29#ifdef Q_OS_LINUX
30enum rfkill_type {
31 RFKILL_TYPE_ALL = 0,
32 RFKILL_TYPE_WLAN,
33 RFKILL_TYPE_BLUETOOTH,
34 RFKILL_TYPE_UWB,
35 RFKILL_TYPE_WIMAX,
36 RFKILL_TYPE_WWAN,
37};
38
39enum rfkill_operation {
40 RFKILL_OP_ADD = 0,
41 RFKILL_OP_DEL,
42 RFKILL_OP_CHANGE,
43 RFKILL_OP_CHANGE_ALL,
44};
45
46struct rfkill_event {
47 quint32 idx;
48 quint8 type;
49 quint8 op;
50 quint8 soft;
51 quint8 hard;
52};
53#endif
54
55RfkillThread::RfkillThread(QObject *parent)
56 : QThread(parent)
57{
58}
59
60void RfkillThread::interrupt()
61{
62 requestInterruption();
63 m_waitCondition.wakeOne();
64}
65
66void RfkillThread::run()
67{
68 while (!isInterruptionRequested()) {
69 QMutexLocker locker(&m_mutex);
70
71 quint8 wantedBlocked;
72 do {
73 wantedBlocked = m_pendingBlocked;
74 locker.unlock();
75 doSetSoftBlocked(blocked: wantedBlocked);
76 locker.relock();
77 // Check again in case it changed while we were busy.
78 } while (!isInterruptionRequested() && wantedBlocked != m_pendingBlocked);
79
80 if (!isInterruptionRequested()) {
81 m_waitCondition.wait(lockedMutex: &m_mutex);
82 }
83 }
84}
85
86void RfkillThread::setSoftBlocked(quint8 blocked)
87{
88 QMutexLocker locker(&m_mutex);
89 m_pendingBlocked = blocked;
90 m_waitCondition.wakeOne();
91}
92
93void RfkillThread::doSetSoftBlocked(quint8 blocked)
94{
95 int fd = ::open(file: "/dev/rfkill", O_WRONLY | O_CLOEXEC);
96 if (fd == -1) {
97 qCWarning(BLUEZQT) << "Cannot open /dev/rfkill for writing!";
98 return;
99 }
100
101 rfkill_event event;
102 ::memset(s: &event, c: 0, n: sizeof(event));
103 event.op = RFKILL_OP_CHANGE_ALL;
104 event.type = RFKILL_TYPE_BLUETOOTH;
105 event.soft = blocked;
106
107 if (::write(fd: fd, buf: &event, n: sizeof(event)) == sizeof(event)) {
108 qCDebug(BLUEZQT) << "Setting Rfkill soft block succeeded";
109 } else {
110 qCWarning(BLUEZQT) << "Setting Rfkill soft block failed:" << errno;
111 }
112 ::close(fd: fd);
113};
114
115Rfkill::Rfkill(QObject *parent)
116 : QObject(parent)
117 , d(new RfkillPrivate)
118{
119 init();
120}
121
122Rfkill::~Rfkill()
123{
124#ifdef Q_OS_LINUX
125 if (d->m_readFd != -1) {
126 ::close(fd: d->m_readFd);
127 }
128
129 if (d->m_thread) {
130 d->m_thread->interrupt();
131 d->m_thread->wait();
132 }
133#endif
134}
135
136Rfkill::State Rfkill::state() const
137{
138 return d->m_state;
139}
140
141void Rfkill::block()
142{
143 if (d->m_state == SoftBlocked || d->m_state == HardBlocked) {
144 return;
145 }
146
147 setSoftBlock(1);
148}
149
150void Rfkill::unblock()
151{
152 if (d->m_state == Unblocked) {
153 return;
154 }
155
156 setSoftBlock(0);
157}
158
159void Rfkill::devReadyRead()
160{
161 State oldState = d->m_state;
162
163 updateRfkillDevices();
164
165 if (d->m_state != oldState) {
166 Q_EMIT stateChanged(state: d->m_state);
167 }
168}
169
170void Rfkill::init()
171{
172#ifdef Q_OS_LINUX
173 d->m_readFd = ::open(file: "/dev/rfkill", O_RDONLY | O_CLOEXEC);
174
175 if (d->m_readFd == -1) {
176 qCWarning(BLUEZQT) << "Cannot open /dev/rfkill for reading!";
177 return;
178 }
179
180 if (::fcntl(fd: d->m_readFd, F_SETFL, O_NONBLOCK) < 0) {
181 ::close(fd: d->m_readFd);
182 d->m_readFd = -1;
183 return;
184 }
185
186 updateRfkillDevices();
187
188 QSocketNotifier *notifier = new QSocketNotifier(d->m_readFd, QSocketNotifier::Read, this);
189 connect(sender: notifier, signal: &QSocketNotifier::activated, context: this, slot: &Rfkill::devReadyRead);
190#endif
191}
192
193#ifdef Q_OS_LINUX
194static Rfkill::State getState(const rfkill_event &event)
195{
196 if (event.hard) {
197 return Rfkill::HardBlocked;
198 } else if (event.soft) {
199 return Rfkill::SoftBlocked;
200 }
201 return Rfkill::Unblocked;
202}
203#endif
204
205void Rfkill::updateRfkillDevices()
206{
207#ifdef Q_OS_LINUX
208 if (d->m_readFd == -1) {
209 return;
210 }
211
212 rfkill_event event;
213 while (::read(fd: d->m_readFd, buf: &event, nbytes: sizeof(event)) == sizeof(event)) {
214 if (event.type != RFKILL_TYPE_BLUETOOTH) {
215 continue;
216 }
217
218 switch (event.op) {
219 case RFKILL_OP_ADD:
220 case RFKILL_OP_CHANGE:
221 d->m_devices[event.idx] = getState(event);
222 break;
223
224 case RFKILL_OP_DEL:
225 d->m_devices.remove(key: event.idx);
226 break;
227
228 case RFKILL_OP_CHANGE_ALL:
229 for (auto it = d->m_devices.begin(); it != d->m_devices.end(); ++it) {
230 it.value() = getState(event);
231 }
232 break;
233
234 default:
235 break;
236 }
237 }
238
239 // Update global state
240 d->m_state = Unknown;
241
242 for (State state : std::as_const(t&: d->m_devices)) {
243 Q_ASSERT(state != Unknown);
244
245 if (d->m_state == Unknown) {
246 d->m_state = state;
247 } else if (state > d->m_state) {
248 d->m_state = state;
249 }
250 }
251
252 qCDebug(BLUEZQT) << "Rfkill global state changed:" << d->m_state;
253#endif
254}
255
256void Rfkill::setSoftBlock(quint8 soft)
257{
258#ifndef Q_OS_LINUX
259 Q_UNUSED(soft)
260#else
261 if (!d->m_thread) {
262 d->m_thread = new RfkillThread(this);
263 }
264 d->m_thread->setSoftBlocked(soft);
265 d->m_thread->start();
266#endif
267}
268
269} // namespace BluezQt
270
271#include "moc_rfkill.cpp"
272#include "moc_rfkill_p.cpp"
273

source code of bluez-qt/src/rfkill.cpp