qi3pc(0.4.1-4-g1b5b621) (Debug GNU 15.2.1)


./
File: qi3pc.cpp
Date: 2025-12-18 23:49:32
Lines:
262 of 451, 0 excluded
58.1%
Functions:
45 of 63, 0 excluded
71.4%
Branches:
198 of 814, 0 excluded
24.3%

Line Branch Exec Source
1 /* \author Hantz Vius
2 *
3 * \copyright Copyright (C) 2019-2025 Hantz Vius
4 *
5 * \license{
6 * This file is part of qi3pc.
7 *
8 * qi3pc is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.}
20 */
21
22 #include <QDebug>
23
24 #include "qi3pc.h"
25
26 Q_LOGGING_CATEGORY(Qi3pcLogger, "qi3pc", QtMsgType::QtDebugMsg)
27
28 qi3pc::qi3pc(QObject* parent)
29 : qi3pc(FindSocketPath(), parent)
30 {}
31
32 80 qi3pc::qi3pc(const QString& serverPath, QObject* parent)
33
2/4
✓ Branch 4 → 5 taken 80 times.
✗ Branch 4 → 63 not taken.
✓ Branch 5 → 6 taken 80 times.
✗ Branch 5 → 61 not taken.
80 : QObject(parent)
34 {
35 80 m_socketPath = serverPath;
36
37
1/2
✓ Branch 17 → 18 taken 80 times.
✗ Branch 17 → 32 not taken.
80 m_barConfigs = std::make_pair(QJsonObject(), QDateTime::currentMSecsSinceEpoch());
38
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 80 times.
80 if (m_socketPath.isEmpty()) {
39 throw std::invalid_argument("The provided server path must not be empty.");
40 }
41
42 80 QObject::connect(
43
1/2
✓ Branch 27 → 28 taken 80 times.
✗ Branch 27 → 37 not taken.
80 &m_eventSocket, &QLocalSocket::readyRead, this, &qi3pc::processEvent);
44 80 QObject::connect(
45
1/2
✓ Branch 29 → 30 taken 80 times.
✗ Branch 29 → 39 not taken.
80 &m_messageSocket, &QLocalSocket::readyRead, this, &qi3pc::processReply);
46 80 }
47
48 155 qi3pc::~qi3pc()
49 {
50 80 m_eventSocket.close();
51 80 m_messageSocket.close();
52
53 80 m_eventSocket.abort();
54 80 m_messageSocket.abort();
55 155 }
56
57 void
58 25 qi3pc::processEvent()
59 {
60
1/2
✓ Branch 2 → 3 taken 25 times.
✗ Branch 2 → 112 not taken.
25 Message m = processMessage(m_eventSocket);
61
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 25 times.
25 if (!m) {
62 return;
63 }
64
65
2/4
✓ Branch 6 → 7 taken 25 times.
✗ Branch 6 → 104 not taken.
✓ Branch 7 → 8 taken 25 times.
✗ Branch 7 → 104 not taken.
25 auto [data, type] = m.value();
66
67
2/2
✓ Branch 10 → 11 taken 10 times.
✓ Branch 10 → 44 taken 15 times.
25 if (type & (1u << 31)) {
68
1/9
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 14 not taken.
✗ Branch 11 → 16 not taken.
✗ Branch 11 → 18 not taken.
✗ Branch 11 → 20 not taken.
✗ Branch 11 → 22 not taken.
✗ Branch 11 → 24 not taken.
✓ Branch 11 → 26 taken 10 times.
✗ Branch 11 → 28 not taken.
10 switch (static_cast<IpcEvent>(type)) {
69 case IpcEvent::Workspace:
70 processWorkspaceEvent(data);
71 break;
72 case IpcEvent::Output:
73 processOutputEvent(data);
74 break;
75 case IpcEvent::Mode:
76 processModeEvent(data);
77 break;
78 case IpcEvent::Window:
79 processWindowEvent(data);
80 break;
81 case IpcEvent::BarUpdate:
82 processBarUpdateEvent(data);
83 break;
84 case IpcEvent::Binding:
85 processBindingEvent(data);
86 break;
87 case IpcEvent::Shutdown:
88 processShutdownEvent(data);
89 break;
90 10 case IpcEvent::Tick:
91
1/2
✓ Branch 26 → 27 taken 10 times.
✗ Branch 26 → 89 not taken.
10 processTickEvent(data);
92 10 break;
93 default:
94 std::string log = "Received event of unsupported type IpcType::%u";
95 qCWarning(Qi3pcLogger, log.c_str(), type, IpcType::Subscribe);
96 break;
97 }
98 } else {
99
2/3
✓ Branch 44 → 45 taken 5 times.
✓ Branch 44 → 52 taken 10 times.
✗ Branch 44 → 54 not taken.
15 switch (static_cast<IpcType>(type)) {
100 5 case IpcType::Subscribe:
101
4/8
✓ Branch 45 → 46 taken 5 times.
✗ Branch 45 → 94 not taken.
✓ Branch 46 → 47 taken 5 times.
✗ Branch 46 → 92 not taken.
✓ Branch 47 → 48 taken 5 times.
✗ Branch 47 → 90 not taken.
✓ Branch 48 → 49 taken 5 times.
✗ Branch 48 → 90 not taken.
5 emit subscribed(data["success"].toBool());
102 5 break;
103 10 case IpcType::Tick:
104
1/2
✓ Branch 52 → 53 taken 10 times.
✗ Branch 52 → 103 not taken.
10 processTickReply(data);
105 10 break;
106 default:
107 std::string log = "Received message IpcType::%u while expecting IpcEvent, IpcType::Subscribe (IpcType::%u),"
108 "or IpcType::Tick (IpcType::%u)";
109 qCWarning(Qi3pcLogger, log.c_str(), type, IpcType::Subscribe, IpcType::Tick);
110 break;
111 }
112 }
113
2/6
✗ Branch 70 → 71 not taken.
✓ Branch 70 → 72 taken 25 times.
✓ Branch 77 → 78 taken 25 times.
✗ Branch 77 → 80 not taken.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
75 }
114
115 void
116 65 qi3pc::processReply()
117 {
118
1/2
✓ Branch 2 → 3 taken 65 times.
✗ Branch 2 → 96 not taken.
65 Message m = processMessage(m_messageSocket);
119
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 65 times.
65 if (!m) {
120 return;
121 }
122
123
2/4
✓ Branch 6 → 7 taken 65 times.
✗ Branch 6 → 88 not taken.
✓ Branch 7 → 8 taken 65 times.
✗ Branch 7 → 88 not taken.
65 auto [data, type] = m.value();
124
125 65 std::string log;
126
10/14
✓ Branch 11 → 12 taken 15 times.
✓ Branch 11 → 14 taken 5 times.
✗ Branch 11 → 16 not taken.
✓ Branch 11 → 27 taken 5 times.
✓ Branch 11 → 29 taken 5 times.
✓ Branch 11 → 31 taken 5 times.
✓ Branch 11 → 33 taken 10 times.
✓ Branch 11 → 35 taken 5 times.
✓ Branch 11 → 37 taken 5 times.
✓ Branch 11 → 39 taken 5 times.
✗ Branch 11 → 41 not taken.
✗ Branch 11 → 52 not taken.
✓ Branch 11 → 54 taken 5 times.
✗ Branch 11 → 56 not taken.
65 switch (static_cast<IpcType>(type)) {
127 15 case IpcType::Command:
128
1/2
✓ Branch 12 → 13 taken 15 times.
✗ Branch 12 → 86 not taken.
15 processCommandReply(data);
129 15 break;
130 5 case IpcType::Workspaces:
131
1/2
✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 86 not taken.
5 processWorkspaceReply(data);
132 5 break;
133 case IpcType::Subscribe:
134 log = "Received reply of type IpcType::Subscribe (IpcType::%u): unexpected.";
135 qCWarning(Qi3pcLogger, log.c_str(), IpcType::Subscribe);
136 break;
137 5 case IpcType::Outputs:
138
1/2
✓ Branch 27 → 28 taken 5 times.
✗ Branch 27 → 86 not taken.
5 processOutputReply(data);
139 5 break;
140 5 case IpcType::Tree:
141
1/2
✓ Branch 29 → 30 taken 5 times.
✗ Branch 29 → 86 not taken.
5 processTreeReply(data);
142 5 break;
143 5 case IpcType::Marks:
144
1/2
✓ Branch 31 → 32 taken 5 times.
✗ Branch 31 → 86 not taken.
5 processMarkReply(data);
145 5 break;
146 10 case IpcType::BarConfig:
147
1/2
✓ Branch 33 → 34 taken 10 times.
✗ Branch 33 → 86 not taken.
10 processBarConfigReply(data);
148 10 break;
149 5 case IpcType::Version:
150
1/2
✓ Branch 35 → 36 taken 5 times.
✗ Branch 35 → 86 not taken.
5 processVersionReply(data);
151 5 break;
152 5 case IpcType::BindingModes:
153
1/2
✓ Branch 37 → 38 taken 5 times.
✗ Branch 37 → 86 not taken.
5 processBindingModesReply(data);
154 5 break;
155 5 case IpcType::Config:
156
1/2
✓ Branch 39 → 40 taken 5 times.
✗ Branch 39 → 86 not taken.
5 processConfigReply(data);
157 5 break;
158 case IpcType::Tick:
159 log = "Received reply of type IpcType::Tick (IpcType::%u): unexpected.";
160 qCWarning(Qi3pcLogger, log.c_str(), IpcType::Tick);
161 break;
162 case IpcType::Sync:
163 processSyncReply(data);
164 break;
165 5 case IpcType::BindingState:
166
1/2
✓ Branch 54 → 55 taken 5 times.
✗ Branch 54 → 86 not taken.
5 processBindingStateReply(data);
167 5 break;
168 default:
169 log = "Received reply of unsupported type IpcType::%u.";
170 qCWarning(Qi3pcLogger, log.c_str(), type);
171 break;
172 }
173
2/6
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 70 taken 65 times.
✓ Branch 75 → 76 taken 65 times.
✗ Branch 75 → 78 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
195 }
174
175 void
176 15 qi3pc::processCommandReply(const QJsonDocument& doc)
177 {
178
1/2
✓ Branch 2 → 3 taken 15 times.
✗ Branch 2 → 156 not taken.
15 auto results = doc.array();
179
2/4
✓ Branch 3 → 4 taken 15 times.
✗ Branch 3 → 154 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 30 taken 15 times.
15 if (results.empty()) {
180 qCWarning(Qi3pcLogger) << "Received empty command reply:" << doc.toJson();
181 emit commandRan({{false, ParseError{}}});
182 }
183
184 15 CommandResults result;
185
3/4
✓ Branch 32 → 33 taken 15 times.
✗ Branch 32 → 148 not taken.
✓ Branch 80 → 34 taken 20 times.
✓ Branch 80 → 81 taken 15 times.
35 for (auto jsonObject: std::as_const(results)) {
186
1/2
✓ Branch 35 → 36 taken 20 times.
✗ Branch 35 → 147 not taken.
20 auto object = jsonObject.toObject();
187
3/6
✓ Branch 36 → 37 taken 20 times.
✗ Branch 36 → 117 not taken.
✓ Branch 37 → 38 taken 20 times.
✗ Branch 37 → 115 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 55 taken 20 times.
20 if (!object.contains("success")) {
188 qCWarning(Qi3pcLogger) << "Command reply does not contain success key. Skipping:" << object;
189 continue;
190 }
191
192
4/6
✓ Branch 55 → 56 taken 20 times.
✗ Branch 55 → 129 not taken.
✓ Branch 56 → 57 taken 20 times.
✗ Branch 56 → 127 not taken.
✓ Branch 59 → 60 taken 10 times.
✓ Branch 59 → 66 taken 10 times.
20 if (auto success = object["success"].toBool(); success) {
193
1/2
✓ Branch 62 → 63 taken 10 times.
✗ Branch 62 → 131 not taken.
10 result.push_back({true, {}});
194 } else {
195
2/4
✓ Branch 66 → 67 taken 10 times.
✗ Branch 66 → 142 not taken.
✓ Branch 68 → 69 taken 10 times.
✗ Branch 68 → 138 not taken.
10 result.push_back({false, ParseError::FromJSON(object)});
196 }
197
1/2
✓ Branch 74 → 75 taken 20 times.
✗ Branch 74 → 77 not taken.
20 }
198
199
2/4
✓ Branch 81 → 82 taken 15 times.
✗ Branch 81 → 151 not taken.
✓ Branch 82 → 83 taken 15 times.
✗ Branch 82 → 149 not taken.
15 emit commandRan(result);
200
0/4
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 112 not taken.
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 112 not taken.
15 }
201
202 void
203 5 qi3pc::processWorkspaceReply(const QJsonDocument& doc)
204 {
205
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 10 not taken.
5 m_workspaces = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
206 5 emit workspacesUpdated(m_workspaces);
207 5 }
208
209 void
210 5 qi3pc::processOutputReply(const QJsonDocument& doc)
211 {
212
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 10 not taken.
5 m_outputs = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
213 5 emit outputsUpdated(m_outputs);
214 5 }
215
216 void
217 5 qi3pc::processTreeReply(const QJsonDocument& doc)
218 {
219
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 10 not taken.
5 m_tree = std::make_pair(doc.object(), QDateTime::currentMSecsSinceEpoch());
220 5 emit treeUpdated(m_tree);
221 5 }
222
223 void
224 5 qi3pc::processMarkReply(const QJsonDocument& doc)
225 {
226
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 10 not taken.
5 m_marks = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
227 5 emit marksUpdated(m_marks);
228 5 }
229
230 void
231 10 qi3pc::processBarConfigReply(const QJsonDocument& doc)
232 {
233
4/6
✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 71 not taken.
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 69 not taken.
✓ Branch 4 → 5 taken 5 times.
✓ Branch 4 → 24 taken 5 times.
20 if (const auto& ids = doc.array(); !ids.isEmpty()) {
234
3/4
✓ Branch 6 → 7 taken 5 times.
✗ Branch 6 → 53 not taken.
✓ Branch 22 → 8 taken 5 times.
✓ Branch 22 → 23 taken 5 times.
10 for (auto ref : ids) {
235
1/2
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 43 not taken.
5 if (auto id = ref.toString();
236
4/8
✓ Branch 12 → 13 taken 5 times.
✗ Branch 12 → 49 not taken.
✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 46 not taken.
✓ Branch 15 → 16 taken 5 times.
✗ Branch 15 → 46 not taken.
✓ Branch 17 → 18 taken 5 times.
✗ Branch 17 → 19 not taken.
5 m_barConfigs->first[id] == QJsonValue::Null) {
237
1/2
✓ Branch 18 → 19 taken 5 times.
✗ Branch 18 → 50 not taken.
5 emit newBarConfig(id);
238 5 }
239 }
240 } else {
241
3/6
✓ Branch 24 → 25 taken 5 times.
✗ Branch 24 → 58 not taken.
✓ Branch 25 → 26 taken 5 times.
✗ Branch 25 → 56 not taken.
✓ Branch 26 → 27 taken 5 times.
✗ Branch 26 → 54 not taken.
5 auto id = doc["id"].toString();
242
1/2
✓ Branch 29 → 30 taken 5 times.
✗ Branch 29 → 66 not taken.
5 auto config = doc.object();
243
3/6
✓ Branch 30 → 31 taken 5 times.
✗ Branch 30 → 63 not taken.
✓ Branch 32 → 33 taken 5 times.
✗ Branch 32 → 60 not taken.
✓ Branch 33 → 34 taken 5 times.
✗ Branch 33 → 60 not taken.
5 m_barConfigs->first[id] = config;
244 5 m_barConfigs->second = QDateTime::currentMSecsSinceEpoch();
245
1/2
✓ Branch 37 → 38 taken 5 times.
✗ Branch 37 → 64 not taken.
5 emit barConfigUpdated(config);
246 5 }
247 10 }
248
249 void
250 5 qi3pc::processVersionReply(const QJsonDocument& doc)
251 {
252
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 10 not taken.
5 m_version = std::make_pair(doc.object(), QDateTime::currentMSecsSinceEpoch());
253 5 emit versionUpdated(m_version);
254 5 }
255
256 void
257 5 qi3pc::processBindingModesReply(const QJsonDocument& doc)
258 {
259
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 10 not taken.
5 m_bindingModes = std::make_pair(doc.array(), QDateTime::currentMSecsSinceEpoch());
260 5 emit bindingModesUpdated(m_bindingModes);
261 5 }
262
263 void
264 5 qi3pc::processConfigReply(const QJsonDocument& doc)
265 {
266
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 10 not taken.
5 m_config = std::make_pair(doc.object(), QDateTime::currentMSecsSinceEpoch());
267 5 emit configUpdated(m_config);
268 5 }
269
270 void
271 10 qi3pc::processTickReply(const QJsonDocument& doc)
272 {
273
4/8
✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 13 not taken.
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 11 not taken.
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 9 not taken.
10 emit tickSent(doc["success"].toBool());
274 10 }
275
276 void
277 qi3pc::processSyncReply(const QJsonDocument& doc)
278 {
279 emit synced(doc["success"].toBool());
280 }
281
282 void
283 5 qi3pc::processBindingStateReply(const QJsonDocument& doc)
284 {
285
3/6
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 18 not taken.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 16 not taken.
✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 14 not taken.
5 m_bindingState = std::make_pair(doc["name"].toString(), QDateTime::currentMSecsSinceEpoch());
286 5 emit bindingStateUpdated(m_bindingState);
287 5 }
288
289 void
290 qi3pc::processWorkspaceEvent(const QJsonDocument& doc)
291 {
292 auto change = WorkspaceChangeFromString(doc["change"].toString());
293
294 if (change == WorkspaceChange::Unknown) {
295 qCWarning(Qi3pcLogger) << "Received workspace event with unknown change string" << doc["change"].toString();
296 return;
297 }
298
299 auto old = doc["old"].toObject();
300 auto current = doc["current"].toObject();
301 emit workspaceEvent(change, current, old);
302 }
303
304 void
305 qi3pc::processOutputEvent(const QJsonDocument& data)
306 {
307 auto change = OutputChangeFromString(data["change"].toString());
308
309 if (change == OutputChange::Unknown) {
310 qCWarning(Qi3pcLogger) << "Received output event with unknown change string" << data["change"].toString();
311 return;
312 }
313
314 emit outputEvent(change);
315 }
316
317 void
318 qi3pc::processModeEvent(const QJsonDocument& doc)
319 {
320 emit modeEvent(doc["change"].toString(), doc["pango_markup"].toBool());
321 }
322
323 void
324 qi3pc::processWindowEvent(const QJsonDocument& doc)
325 {
326 auto change = WindowChangeFromString(doc["change"].toString());
327
328 if (change == WindowChange::Unknown) {
329 qCWarning(Qi3pcLogger) << "Received window event with unknown change string" << doc["change"].toString();
330 return;
331 }
332
333 emit windowEvent(change, doc["container"].toObject());
334 }
335
336 void
337 qi3pc::processBarUpdateEvent(const QJsonDocument& doc)
338 {
339 emit barUpdateEvent(doc.object());
340 }
341
342 void
343 qi3pc::processBindingEvent(const QJsonDocument& doc)
344 {
345 auto change = BindingChangeFromString(doc["change"].toString());
346
347 if (change == BindingChange::Unknown) {
348 qCWarning(Qi3pcLogger) << "Received binding event with unknown change string" << doc["change"].toString();
349 return;
350 }
351
352 emit bindingEvent(change, doc["binding"].toObject(), doc["mode"].toString());
353 }
354
355 void
356 qi3pc::processShutdownEvent(const QJsonDocument& doc)
357 {
358 auto change = ShutdownChangeFromString(doc["change"].toString());
359
360 if (change == ShutdownChange::Unknown) {
361 qCWarning(Qi3pcLogger) << "Received shutdown event with unknown change string" << doc["change"].toString();
362 return;
363 }
364
365 emit shutdownEvent(change);
366 }
367
368 void
369 10 qi3pc::processTickEvent(const QJsonDocument& doc)
370 {
371
5/8
✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 21 not taken.
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 19 not taken.
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 17 not taken.
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 16 taken 5 times.
10 if(auto first = doc["first"].toBool(); !first) {
372
4/8
✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 29 not taken.
✓ Branch 9 → 10 taken 5 times.
✗ Branch 9 → 27 not taken.
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 25 not taken.
✓ Branch 11 → 12 taken 5 times.
✗ Branch 11 → 23 not taken.
5 emit tickEvent(doc["payload"].toString());
373 }
374 10 }
375
376 qi3pc::WorkspaceChange
377 qi3pc::WorkspaceChangeFromString(const QString& s)
378 {
379 if (s == "focus") {
380 return WorkspaceChange::Focus;
381 } else if (s == "init") {
382 return WorkspaceChange::Init;
383 } else if (s == "empty") {
384 return WorkspaceChange::Empty;
385 } else if (s == "urgent") {
386 return WorkspaceChange::Urgent;
387 } else if (s == "reload") {
388 return WorkspaceChange::Reload;
389 } else if (s == "rename") {
390 return WorkspaceChange::Rename;
391 } else if (s == "restored") {
392 return WorkspaceChange::Restored;
393 } else if (s == "move") {
394 return WorkspaceChange::Move;
395 } else {
396 return WorkspaceChange::Unknown;
397 }
398 }
399
400 qi3pc::WindowChange
401 qi3pc::WindowChangeFromString(const QString& s)
402 {
403 if (s == "new") {
404 return WindowChange::New;
405 } else if (s == "close") {
406 return WindowChange::Close;
407 } else if (s == "focus") {
408 return WindowChange::Focus;
409 } else if (s == "title") {
410 return WindowChange::Title;
411 } else if (s == "fullscreen_mode") {
412 return WindowChange::Fullscreen;
413 } else if (s == "move") {
414 return WindowChange::Move;
415 } else if (s == "floating") {
416 return WindowChange::Floating;
417 } else if (s == "urgent") {
418 return WindowChange::Urgent;
419 } else if (s == "mark") {
420 return WindowChange::Mark;
421 } else {
422 return WindowChange::Unknown;
423 }
424 }
425
426 qi3pc::ShutdownChange
427 qi3pc::ShutdownChangeFromString(const QString& s)
428 {
429 if (s == "restart") {
430 return ShutdownChange::Restart;
431 } else if (s == "exit") {
432 return ShutdownChange::Exit;
433 } else {
434 return ShutdownChange::Unknown;
435 }
436 }
437
438 qi3pc::OutputChange
439 qi3pc::OutputChangeFromString(const QString& s)
440 {
441 if (s == "unspecified") {
442 return OutputChange::Unspecified;
443 } else {
444 return OutputChange::Unknown;
445 }
446 }
447
448 qi3pc::BindingChange
449 qi3pc::BindingChangeFromString(const QString& s)
450 {
451 if (s == "run") {
452 return BindingChange::Run;
453 } else {
454 return BindingChange::Unknown;
455 }
456 }
457
458 qi3pc::Message
459 90 qi3pc::processMessage(QLocalSocket& socket)
460 {
461 char c[IpcMagicLength];
462
2/4
✓ Branch 2 → 3 taken 90 times.
✗ Branch 2 → 149 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 19 taken 90 times.
90 if (auto read_size = socket.read(c, IpcMagicLength);
463 read_size != IpcMagicLength) {
464 qCWarning(Qi3pcLogger) << "Insufficient data read for IPC magic string. Seeked"
465 << IpcMagicLength <<"bytes. - Read" << read_size << "bytes.";
466 return {};
467 }
468
469
2/4
✓ Branch 21 → 22 taken 90 times.
✗ Branch 21 → 115 not taken.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 41 taken 90 times.
180 if (auto s = std::string(c, IpcMagicLength); s != IpcMagicString) {
470 qCWarning(Qi3pcLogger) << "Unexpected magic string in message. Expected"
471 << qi3pc::IpcMagicString << ". - Found" << s << ".";
472 return {};
473
1/2
✓ Branch 43 → 44 taken 90 times.
✗ Branch 43 → 46 not taken.
90 }
474
475 quint32 size;
476
2/4
✓ Branch 45 → 47 taken 90 times.
✗ Branch 45 → 149 not taken.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 63 taken 90 times.
90 if (auto read_size = socket.read(reinterpret_cast<char*>(&size), sizeof size);
477 read_size != sizeof size) {
478 qCWarning(Qi3pcLogger) << "Insufficient data read for message size. Seeked"
479 << sizeof size <<"bytes. - Read" << read_size << "bytes.";
480 return {};
481 }
482
483 quint32 type;
484
2/4
✓ Branch 63 → 64 taken 90 times.
✗ Branch 63 → 149 not taken.
✗ Branch 64 → 65 not taken.
✓ Branch 64 → 80 taken 90 times.
90 if (auto read_size = socket.read(reinterpret_cast<char*>(&type), sizeof type);
485 read_size != sizeof type) {
486 qCWarning(Qi3pcLogger) << "Insufficient data read for message type. Seeked"
487 << sizeof type <<"bytes. - Read" << read_size << "bytes.";
488 return {};
489 }
490
491 90 QJsonParseError parseError;
492
1/2
✓ Branch 80 → 81 taken 90 times.
✗ Branch 80 → 149 not taken.
90 auto payload = socket.read(size);
493
494
3/4
✓ Branch 81 → 82 taken 90 times.
✗ Branch 81 → 147 not taken.
✓ Branch 82 → 83 taken 10 times.
✓ Branch 82 → 84 taken 80 times.
90 if (socket.bytesAvailable() > 0) {
495
1/2
✓ Branch 83 → 84 taken 10 times.
✗ Branch 83 → 147 not taken.
10 emit socket.readyRead();
496 }
497
498
1/2
✓ Branch 84 → 85 taken 90 times.
✗ Branch 84 → 147 not taken.
90 QJsonDocument data = QJsonDocument::fromJson(payload, &parseError);
499
1/2
✗ Branch 85 → 86 not taken.
✓ Branch 85 → 102 taken 90 times.
90 if (parseError.error != QJsonParseError::NoError) {
500 qCWarning(Qi3pcLogger) << "Parsing message body failed - JSON parse error:"
501 << parseError.errorString() << "in:" << payload;
502 return {};
503 }
504
505
1/2
✓ Branch 102 → 103 taken 90 times.
✗ Branch 102 → 144 not taken.
90 return std::make_pair(data, type);
506 90 }
507
508 QString
509 qi3pc::FindSocketPath()
510 {
511 if (auto path = QProcessEnvironment::systemEnvironment().value("I3SOCK");
512 !path.isEmpty()) {
513 return path;
514 }
515
516 // TODO: try to get socket path from root window property
517 return FindSocketPathFromI3Binary();
518 }
519
520 QString
521 qi3pc::FindSocketPathFromI3Binary()
522 {
523 QProcess process;
524 process.start("i3", QStringList("--get-socketpath"));
525 process.waitForReadyRead();
526 auto path = QString(process.readAllStandardOutput()).trimmed();
527 process.kill();
528 process.waitForFinished();
529 return path;
530 }
531
532 QString
533 qi3pc::socketPath() const
534 {
535 return m_socketPath;
536 }
537
538 const
539 qi3pc::DataArray&
540 5 qi3pc::workspaces() const
541 {
542 5 return m_workspaces;
543 }
544
545 const
546 qi3pc::DataObject&
547 5 qi3pc::tree() const
548 {
549 5 return m_tree;
550 }
551
552 const
553 qi3pc::DataArray&
554 5 qi3pc::outputs() const
555 {
556 5 return m_outputs;
557 }
558
559 const
560 qi3pc::DataArray&
561 5 qi3pc::marks() const
562 {
563 5 return m_marks;
564 }
565
566 const
567 qi3pc::DataObject&
568 5 qi3pc::barConfigs() const
569 {
570 5 return m_barConfigs;
571 }
572
573 const
574 qi3pc::DataObject&
575 5 qi3pc::version() const
576 {
577 5 return m_version;
578 }
579
580 const
581 qi3pc::DataArray&
582 5 qi3pc::bindingModes() const
583 {
584 5 return m_bindingModes;
585 }
586
587 const
588 qi3pc::DataObject&
589 5 qi3pc::config() const
590 {
591 5 return m_config;
592 }
593
594 const
595 qi3pc::DataString&
596 5 qi3pc::bindingState() const
597 {
598 5 return m_bindingState;
599 }
600
601 bool
602 75 qi3pc::connect()
603 {
604
605
1/2
✓ Branch 3 → 4 taken 75 times.
✗ Branch 3 → 20 not taken.
75 m_messageSocket.connectToServer(m_socketPath);
606
1/2
✓ Branch 5 → 6 taken 75 times.
✗ Branch 5 → 21 not taken.
75 m_eventSocket.connectToServer(m_socketPath);
607
608
2/2
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 9 taken 70 times.
75 if (m_messageSocket.state() != QLocalSocket::ConnectedState)
609 {
610 5 m_messageSocket.waitForConnected();
611 }
612
613
2/2
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 70 times.
75 if (m_eventSocket.state() != QLocalSocket::ConnectedState)
614 {
615 5 m_eventSocket.waitForConnected();
616 }
617
618
2/2
✓ Branch 13 → 14 taken 70 times.
✓ Branch 13 → 17 taken 5 times.
145 return m_messageSocket.state() == QLocalSocket::ConnectedState &&
619
1/2
✓ Branch 15 → 16 taken 70 times.
✗ Branch 15 → 17 not taken.
145 m_eventSocket.state() == QLocalSocket::ConnectedState;
620 }
621
622 bool
623 85 qi3pc::disconnect()
624 {
625 85 m_messageSocket.disconnectFromServer();
626 85 m_eventSocket.disconnectFromServer();
627
628
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 85 times.
85 if (m_messageSocket.state() != QLocalSocket::UnconnectedState)
629 {
630 m_messageSocket.waitForDisconnected();
631 }
632
633
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 85 times.
85 if (m_eventSocket.state() != QLocalSocket::UnconnectedState)
634 {
635 m_eventSocket.waitForDisconnected();
636 }
637
638
1/2
✓ Branch 11 → 12 taken 85 times.
✗ Branch 11 → 15 not taken.
170 return m_messageSocket.state() == QLocalSocket::UnconnectedState &&
639
1/2
✓ Branch 13 → 14 taken 85 times.
✗ Branch 13 → 15 not taken.
170 m_eventSocket.state() == QLocalSocket::UnconnectedState;
640 }
641
642 bool
643 20 qi3pc::isConnected()
644 {
645
2/4
✓ Branch 3 → 4 taken 20 times.
✗ Branch 3 → 6 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 20 times.
40 if (m_messageSocket.state() == QLocalSocket::ConnectingState ||
646
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 20 times.
20 m_eventSocket.state() == QLocalSocket::ConnectingState) {
647 m_messageSocket.waitForConnected();
648 m_eventSocket.waitForConnected();
649 }
650
651
2/2
✓ Branch 12 → 13 taken 5 times.
✓ Branch 12 → 16 taken 15 times.
25 return m_messageSocket.state() == QLocalSocket::ConnectedState &&
652
1/2
✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 16 not taken.
25 m_eventSocket.state() == QLocalSocket::ConnectedState;
653 }
654
655 void
656 10 qi3pc::sendTick(const QByteArray& payload)
657 {
658 10 WritePayload(m_eventSocket, payload, IpcType::Tick);
659 10 }
660
661 void
662 5 qi3pc::subscribe(const QStringList& events)
663 {
664
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 15 taken 5 times.
5 if (events.isEmpty()) {
665 qCWarning(Qi3pcLogger) << "Requested to subscibe to empty event list. Nothing to do.";
666 return;
667 }
668
669 5 WritePayload(
670
1/2
✓ Branch 18 → 19 taken 5 times.
✗ Branch 18 → 29 not taken.
5 m_eventSocket,
671
3/6
✓ Branch 15 → 16 taken 5 times.
✗ Branch 15 → 35 not taken.
✓ Branch 16 → 17 taken 5 times.
✗ Branch 16 → 33 not taken.
✓ Branch 17 → 18 taken 5 times.
✗ Branch 17 → 31 not taken.
10 QJsonDocument(QJsonArray::fromStringList(events)).toJson(),
672 IpcType::Subscribe);
673 5 m_eventSocket.flush();
674 }
675
676 void
677 80 qi3pc::WritePayload(QLocalSocket& socket, const QByteArray& payload, IpcType type)
678 {
679 80 QByteArray message;
680
1/2
✓ Branch 3 → 4 taken 80 times.
✗ Branch 3 → 16 not taken.
80 QDataStream stream(&message, QIODevice::WriteOnly);
681
682
1/2
✓ Branch 5 → 6 taken 80 times.
✗ Branch 5 → 17 not taken.
80 stream.writeRawData(qi3pc::IpcMagicString.data(), qi3pc::IpcMagicLength);
683
684 80 qint32 size = payload.size();
685
1/2
✓ Branch 7 → 8 taken 80 times.
✗ Branch 7 → 17 not taken.
80 stream.writeRawData(reinterpret_cast<const char*>(&size), sizeof size);
686
1/2
✓ Branch 8 → 9 taken 80 times.
✗ Branch 8 → 17 not taken.
80 stream.writeRawData(reinterpret_cast<const char*>(&type), sizeof type);
687
688
2/2
✓ Branch 9 → 10 taken 35 times.
✓ Branch 9 → 12 taken 45 times.
80 if (size > 0) {
689
1/2
✓ Branch 11 → 12 taken 35 times.
✗ Branch 11 → 17 not taken.
35 stream.writeRawData(payload.constData(), size);
690 }
691
692
1/2
✓ Branch 12 → 13 taken 80 times.
✗ Branch 12 → 17 not taken.
80 socket.write(message);
693 80 }
694
695 void
696 5 qi3pc::fetchWorkspaces()
697 {
698
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::Workspaces>();
699 5 }
700
701 void
702 5 qi3pc::fetchTree()
703 {
704
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::Tree>();
705 5 }
706
707 void
708 5 qi3pc::fetchOutputs()
709 {
710
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::Outputs>();
711 5 }
712
713 void
714 5 qi3pc::fetchMarks()
715 {
716
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::Marks>();
717 5 }
718
719 void
720 5 qi3pc::fetchVersion()
721 {
722
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::Version>();
723 5 }
724
725 void
726 5 qi3pc::fetchBindingModes()
727 {
728
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::BindingModes>();
729 5 }
730
731 void
732 5 qi3pc::fetchConfig()
733 {
734
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::Config>();
735 5 }
736
737 void
738 5 qi3pc::fetchBarConfigs()
739 {
740
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::BarConfig>();
741 5 }
742
743 void
744 5 qi3pc::fetchBarConfig(const QString& id)
745 {
746 5 QByteArray payload;
747
2/4
✓ Branch 2 → 3 taken 5 times.
✗ Branch 2 → 11 not taken.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 9 not taken.
5 payload.append(id.toStdString().c_str());
748
1/2
✓ Branch 6 → 7 taken 5 times.
✗ Branch 6 → 12 not taken.
5 sendMessage<IpcType::BarConfig>(payload);
749 5 }
750
751 void
752 5 qi3pc::fetchBindingState()
753 {
754
1/2
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
5 sendMessage<IpcType::BindingState>();
755 5 }
756
757 qi3pc::Error
758 30 qi3pc::ParseError::FromJSON(const QJsonObject& json)
759 {
760
4/6
✓ Branch 2 → 3 taken 30 times.
✗ Branch 2 → 30 not taken.
✓ Branch 3 → 4 taken 30 times.
✗ Branch 3 → 28 not taken.
✓ Branch 5 → 6 taken 10 times.
✓ Branch 5 → 7 taken 20 times.
30 if(!json.contains("parse_error")) {
761 10 return {};
762 }
763 20 auto ret = ParseError {};
764
2/2
✓ Branch 23 → 8 taken 60 times.
✓ Branch 23 → 24 taken 20 times.
80 for (const auto& [name, member]: ParseError::_MEMBERS) {
765
3/6
✓ Branch 10 → 11 taken 60 times.
✗ Branch 10 → 33 not taken.
✓ Branch 11 → 12 taken 60 times.
✗ Branch 11 → 31 not taken.
✓ Branch 13 → 14 taken 60 times.
✗ Branch 13 → 22 not taken.
60 if (json.contains(name)) {
766
3/6
✓ Branch 14 → 15 taken 60 times.
✗ Branch 14 → 38 not taken.
✓ Branch 15 → 16 taken 60 times.
✗ Branch 15 → 36 not taken.
✓ Branch 16 → 17 taken 60 times.
✗ Branch 16 → 34 not taken.
60 ret.*member = json[name].toString();
767 }
768 }
769
770 20 return ret;
771 20 }
772
773 bool
774 10 qi3pc::ParseError::operator==(const ParseError& other) const {
775
1/2
✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 9 not taken.
20 return error == other.error &&
776
2/4
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 9 not taken.
✓ Branch 7 → 8 taken 10 times.
✗ Branch 7 → 9 not taken.
20 input == other.input &&
777 20 errorPosition == other.errorPosition;
778 }
779
780 QString
781 20 qi3pc::ParseError::toString() const
782 {
783
1/2
✓ Branch 2 → 3 taken 20 times.
✗ Branch 2 → 36 not taken.
20 auto str = QString("{");
784
2/2
✓ Branch 16 → 4 taken 60 times.
✓ Branch 16 → 17 taken 20 times.
80 for (const auto& [name, member]: ParseError::_MEMBERS) {
785
5/10
✓ Branch 6 → 7 taken 60 times.
✗ Branch 6 → 30 not taken.
✓ Branch 7 → 8 taken 60 times.
✗ Branch 7 → 28 not taken.
✓ Branch 8 → 9 taken 60 times.
✗ Branch 8 → 26 not taken.
✓ Branch 9 → 10 taken 60 times.
✗ Branch 9 → 24 not taken.
✓ Branch 10 → 11 taken 60 times.
✗ Branch 10 → 22 not taken.
60 str += QString(name) + ":'" + this->*member + "',";
786 }
787
788
1/2
✓ Branch 17 → 18 taken 20 times.
✗ Branch 17 → 34 not taken.
40 return str + "}";
789 20 }
790