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 <QSocketNotifier> |
22 | |
23 | namespace BluezQt |
24 | { |
25 | #ifdef Q_OS_LINUX |
26 | enum rfkill_type { |
27 | RFKILL_TYPE_ALL = 0, |
28 | RFKILL_TYPE_WLAN, |
29 | RFKILL_TYPE_BLUETOOTH, |
30 | RFKILL_TYPE_UWB, |
31 | RFKILL_TYPE_WIMAX, |
32 | RFKILL_TYPE_WWAN, |
33 | }; |
34 | |
35 | enum rfkill_operation { |
36 | RFKILL_OP_ADD = 0, |
37 | RFKILL_OP_DEL, |
38 | RFKILL_OP_CHANGE, |
39 | RFKILL_OP_CHANGE_ALL, |
40 | }; |
41 | |
42 | struct rfkill_event { |
43 | quint32 idx; |
44 | quint8 type; |
45 | quint8 op; |
46 | quint8 soft; |
47 | quint8 hard; |
48 | }; |
49 | #endif |
50 | |
51 | Rfkill::Rfkill(QObject *parent) |
52 | : QObject(parent) |
53 | , d(new RfkillPrivate) |
54 | { |
55 | init(); |
56 | } |
57 | |
58 | Rfkill::~Rfkill() |
59 | { |
60 | #ifdef Q_OS_LINUX |
61 | if (d->m_readFd != -1) { |
62 | ::close(fd: d->m_readFd); |
63 | } |
64 | |
65 | if (d->m_writeFd != -1) { |
66 | ::close(fd: d->m_writeFd); |
67 | } |
68 | #endif |
69 | } |
70 | |
71 | Rfkill::State Rfkill::state() const |
72 | { |
73 | return d->m_state; |
74 | } |
75 | |
76 | void Rfkill::block() |
77 | { |
78 | if (d->m_state == SoftBlocked || d->m_state == HardBlocked) { |
79 | return; |
80 | } |
81 | |
82 | setSoftBlock(1); |
83 | } |
84 | |
85 | void Rfkill::unblock() |
86 | { |
87 | if (d->m_state == Unblocked) { |
88 | return; |
89 | } |
90 | |
91 | setSoftBlock(0); |
92 | } |
93 | |
94 | void Rfkill::devReadyRead() |
95 | { |
96 | State oldState = d->m_state; |
97 | |
98 | updateRfkillDevices(); |
99 | |
100 | if (d->m_state != oldState) { |
101 | Q_EMIT stateChanged(state: d->m_state); |
102 | } |
103 | } |
104 | |
105 | void Rfkill::init() |
106 | { |
107 | #ifdef Q_OS_LINUX |
108 | d->m_readFd = ::open(file: "/dev/rfkill" , O_RDONLY | O_CLOEXEC); |
109 | |
110 | if (d->m_readFd == -1) { |
111 | qCWarning(BLUEZQT) << "Cannot open /dev/rfkill for reading!" ; |
112 | return; |
113 | } |
114 | |
115 | if (::fcntl(fd: d->m_readFd, F_SETFL, O_NONBLOCK) < 0) { |
116 | ::close(fd: d->m_readFd); |
117 | d->m_readFd = -1; |
118 | return; |
119 | } |
120 | |
121 | updateRfkillDevices(); |
122 | |
123 | QSocketNotifier *notifier = new QSocketNotifier(d->m_readFd, QSocketNotifier::Read, this); |
124 | connect(sender: notifier, signal: &QSocketNotifier::activated, context: this, slot: &Rfkill::devReadyRead); |
125 | #endif |
126 | } |
127 | |
128 | bool Rfkill::openForWriting() |
129 | { |
130 | #ifndef Q_OS_LINUX |
131 | return false; |
132 | #else |
133 | if (d->m_writeFd != -1) { |
134 | return true; |
135 | } |
136 | |
137 | d->m_writeFd = ::open(file: "/dev/rfkill" , O_WRONLY | O_CLOEXEC); |
138 | |
139 | if (d->m_writeFd == -1) { |
140 | qCWarning(BLUEZQT) << "Cannot open /dev/rfkill for writing!" ; |
141 | return false; |
142 | } |
143 | |
144 | if (::fcntl(fd: d->m_writeFd, F_SETFL, O_NONBLOCK) < 0) { |
145 | ::close(fd: d->m_writeFd); |
146 | d->m_writeFd = -1; |
147 | return false; |
148 | } |
149 | |
150 | return true; |
151 | #endif |
152 | } |
153 | |
154 | #ifdef Q_OS_LINUX |
155 | static Rfkill::State getState(const rfkill_event &event) |
156 | { |
157 | if (event.hard) { |
158 | return Rfkill::HardBlocked; |
159 | } else if (event.soft) { |
160 | return Rfkill::SoftBlocked; |
161 | } |
162 | return Rfkill::Unblocked; |
163 | } |
164 | #endif |
165 | |
166 | void Rfkill::updateRfkillDevices() |
167 | { |
168 | #ifdef Q_OS_LINUX |
169 | if (d->m_readFd == -1) { |
170 | return; |
171 | } |
172 | |
173 | rfkill_event event; |
174 | while (::read(fd: d->m_readFd, buf: &event, nbytes: sizeof(event)) == sizeof(event)) { |
175 | if (event.type != RFKILL_TYPE_BLUETOOTH) { |
176 | continue; |
177 | } |
178 | |
179 | switch (event.op) { |
180 | case RFKILL_OP_ADD: |
181 | case RFKILL_OP_CHANGE: |
182 | d->m_devices[event.idx] = getState(event); |
183 | break; |
184 | |
185 | case RFKILL_OP_DEL: |
186 | d->m_devices.remove(key: event.idx); |
187 | break; |
188 | |
189 | case RFKILL_OP_CHANGE_ALL: |
190 | for (auto it = d->m_devices.begin(); it != d->m_devices.end(); ++it) { |
191 | it.value() = getState(event); |
192 | } |
193 | break; |
194 | |
195 | default: |
196 | break; |
197 | } |
198 | } |
199 | |
200 | // Update global state |
201 | d->m_state = Unknown; |
202 | |
203 | for (State state : std::as_const(t&: d->m_devices)) { |
204 | Q_ASSERT(state != Unknown); |
205 | |
206 | if (d->m_state == Unknown) { |
207 | d->m_state = state; |
208 | } else if (state > d->m_state) { |
209 | d->m_state = state; |
210 | } |
211 | } |
212 | |
213 | qCDebug(BLUEZQT) << "Rfkill global state changed:" << d->m_state; |
214 | #endif |
215 | } |
216 | |
217 | bool Rfkill::setSoftBlock(quint8 soft) |
218 | { |
219 | #ifndef Q_OS_LINUX |
220 | Q_UNUSED(soft) |
221 | return false; |
222 | #else |
223 | if (!openForWriting()) { |
224 | return false; |
225 | } |
226 | |
227 | rfkill_event event; |
228 | ::memset(s: &event, c: 0, n: sizeof(event)); |
229 | event.op = RFKILL_OP_CHANGE_ALL; |
230 | event.type = RFKILL_TYPE_BLUETOOTH; |
231 | event.soft = soft; |
232 | |
233 | bool ret = ::write(fd: d->m_writeFd, buf: &event, n: sizeof(event)) == sizeof(event); |
234 | qCDebug(BLUEZQT) << "Setting Rfkill soft block succeeded:" << ret; |
235 | return ret; |
236 | #endif |
237 | } |
238 | |
239 | } // namespace BluezQt |
240 | |
241 | #include "moc_rfkill.cpp" |
242 | |