1 // qpwgraph_pipewire.cpp
2 //
3 /****************************************************************************
4    Copyright (C) 2021, rncbc aka Rui Nuno Capela. All rights reserved.
5 
6    This program is free software; you can redistribute it and/or
7    modify it under the terms of the GNU General Public License
8    as published by the Free Software Foundation; either version 2
9    of the License, or (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License along
17    with this program; if not, write to the Free Software Foundation, Inc.,
18    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 
20 *****************************************************************************/
21 
22 #include "qpwgraph_pipewire.h"
23 
24 #include "qpwgraph_canvas.h"
25 #include "qpwgraph_connect.h"
26 
27 #include <pipewire/pipewire.h>
28 
29 #include <QMutexLocker>
30 
31 
32 // Default port types...
33 #define DEFAULT_AUDIO_TYPE "32 bit float mono audio"
34 #define DEFAULT_MIDI_TYPE  "8 bit raw midi"
35 #define DEFAULT_VIDEO_TYPE "32 bit float RGBA video"
36 
37 
38 //----------------------------------------------------------------------------
39 // qpwgraph_pipewire::Data -- PipeWire graph data structs.
40 
41 struct qpwgraph_pipewire::Object
42 {
43 	enum Type { Node, Port, Link };
44 
Objectqpwgraph_pipewire::Object45 	Object(uint oid, Type otype) : id(oid), type(otype) {}
46 
47 	uint id;
48 	Type type;
49 };
50 
51 struct qpwgraph_pipewire::Node : public qpwgraph_pipewire::Object
52 {
Nodeqpwgraph_pipewire::Node53 	Node (uint node_id) : Object(node_id, Type::Node) {}
54 
55 	enum Types {
56 		None  = 0,
57 		Audio = 1,
58 		Video = 2,
59 		Midi  = 4
60 	};
61 
62 	QString node_name;
63 	qpwgraph_item::Mode node_mode;
64 	Types node_types;
65 	QList<qpwgraph_pipewire::Port *> node_ports;
66 };
67 
68 struct qpwgraph_pipewire::Port : public qpwgraph_pipewire::Object
69 {
Portqpwgraph_pipewire::Port70 	Port (uint port_id) : Object(port_id, Type::Port) {}
71 
72 	enum Flags {
73 		None     = 0,
74 		Physical = 1,
75 		Terminal = 2,
76 		Monitor  = 4,
77 		Control  = 8
78 	};
79 
80 	uint node_id;
81 	QString port_name;
82 	qpwgraph_item::Mode port_mode;
83 	uint port_type;
84 	Flags port_flags;
85 	QList<qpwgraph_pipewire::Link *> port_links;
86 };
87 
88 struct qpwgraph_pipewire::Link : public qpwgraph_pipewire::Object
89 {
Linkqpwgraph_pipewire::Link90 	Link (uint link_id) : Object(link_id, Type::Link) {}
91 
92 	uint port1_id;
93 	uint port2_id;
94 };
95 
96 struct qpwgraph_pipewire::Data
97 {
98 	struct pw_thread_loop *loop;
99 	struct pw_context *context;
100 
101 	struct pw_core *core;
102 	struct spa_hook core_listener;
103 
104 	struct pw_registry *registry;
105 	struct spa_hook registry_listener;
106 
107 	int pending_seq;
108 	int last_seq;
109 	int last_res;
110 	bool error;
111 };
112 
113 
114 // registry-events...
115 static
qpwgraph_registry_event_global(void * data,uint32_t id,uint32_t permissions,const char * type,uint32_t version,const struct spa_dict * props)116 void qpwgraph_registry_event_global (
117 	void *data,
118 	uint32_t id,
119 	uint32_t permissions,
120 	const char *type,
121 	uint32_t version,
122 	const struct spa_dict *props )
123 {
124 	qpwgraph_pipewire *pw = static_cast<qpwgraph_pipewire *> (data);
125 #ifdef CONFIG_DEBUG
126 	qDebug("qpwgraph_registry_event_global[%p]: id:%u type:%s/%u", pw, id, type, version);
127 #endif
128 
129 	if (props == nullptr)
130 		return;
131 
132 	int nchanged = 0;
133 
134 	if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
135 		QString node_name;
136 		const char *str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
137 		if (str == nullptr)
138 			str = spa_dict_lookup(props, PW_KEY_NODE_NICK);
139 		if (str == nullptr)
140 			str = spa_dict_lookup(props, PW_KEY_NODE_NAME);
141 		if (str == nullptr)
142 			str = "node";
143 		const char *app = spa_dict_lookup(props, PW_KEY_APP_NAME);
144 		if (app && !spa_streq(app, str)) {
145 			node_name += app;
146 			node_name += '/';
147 		}
148 		node_name += str;
149 		qpwgraph_item::Mode node_mode = qpwgraph_item::None;
150 		uint node_types = qpwgraph_pipewire::Node::None;
151 		str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
152 		if (str) {
153 			const QString media_class(str);
154 			if (media_class.contains("Source") ||
155 				media_class.contains("Output"))
156 				node_mode = qpwgraph_item::Output;
157 			else
158 			if (media_class.contains("Sink") ||
159 				media_class.contains("Input"))
160 				node_mode = qpwgraph_item::Input;
161 			if (media_class.contains("Audio"))
162 				node_types |= qpwgraph_pipewire::Node::Audio;
163 			if (media_class.contains("Video"))
164 				node_types |= qpwgraph_pipewire::Node::Video;
165 			if (media_class.contains("Midi"))
166 				node_types |= qpwgraph_pipewire::Node::Midi;
167 		}
168 		if (node_mode == qpwgraph_item::None) {
169 			str = spa_dict_lookup(props, PW_KEY_MEDIA_CATEGORY);
170 			if (str) {
171 				const QString media_category(str);
172 				if (media_category.contains("Duplex"))
173 					node_mode = qpwgraph_item::Duplex;
174 			}
175 		}
176 		if (pw->createNode(id, node_name, node_mode, node_types))
177 			++nchanged;
178 	}
179 	else
180 	if (spa_streq(type, PW_TYPE_INTERFACE_Port)) {
181 		// TODO: ?...
182 		const char *str = spa_dict_lookup(props, PW_KEY_NODE_ID);
183 		const uint node_id = (str ? uint(::atoi(str)) : 0);
184 		QString port_name;
185 		str = spa_dict_lookup(props, PW_KEY_PORT_ALIAS);
186 		if (str == nullptr)
187 			str = spa_dict_lookup(props, PW_KEY_PORT_NAME);
188 		if (str == nullptr)
189 			str = "port";
190 		port_name += str;
191 		qpwgraph_pipewire::Node *n = pw->findNode(node_id);
192 		uint port_type = qpwgraph_pipewire::otherPortType();
193 		str = spa_dict_lookup(props, PW_KEY_FORMAT_DSP);
194 		if (str)
195 			port_type = qpwgraph_item::itemType(str);
196 		else
197 		if (n && n->node_types == qpwgraph_pipewire::Node::Video)
198 			port_type = qpwgraph_pipewire::videoPortType();
199 		qpwgraph_item::Mode port_mode = qpwgraph_item::None;
200 		str = spa_dict_lookup(props, PW_KEY_PORT_DIRECTION);
201 		if (str) {
202 			if (spa_streq(str, "in"))
203 				port_mode = qpwgraph_item::Input;
204 			else
205 			if (spa_streq(str, "out"))
206 				port_mode = qpwgraph_item::Output;
207 		}
208 		uint port_flags = qpwgraph_pipewire::Port::None;
209 		if (n && (n->node_mode != qpwgraph_item::Duplex))
210 			port_flags |= qpwgraph_pipewire::Port::Terminal;
211 		str = spa_dict_lookup(props, PW_KEY_PORT_PHYSICAL);
212 		if (str && pw_properties_parse_bool(str))
213 			port_flags |= qpwgraph_pipewire::Port::Physical;
214 		str = spa_dict_lookup(props, PW_KEY_PORT_TERMINAL);
215 		if (str && pw_properties_parse_bool(str))
216 			port_flags |= qpwgraph_pipewire::Port::Terminal;
217 		str = spa_dict_lookup(props, PW_KEY_PORT_MONITOR);
218 		if (str && pw_properties_parse_bool(str))
219 			port_flags |= qpwgraph_pipewire::Port::Monitor;
220 		str = spa_dict_lookup(props, PW_KEY_PORT_CONTROL);
221 		if (str && pw_properties_parse_bool(str))
222 			port_flags |= qpwgraph_pipewire::Port::Control;
223 		if (pw->createPort(id, node_id, port_name, port_mode, port_type, port_flags))
224 			++nchanged;
225 	}
226 	else
227 	if (spa_streq(type, PW_TYPE_INTERFACE_Link)) {
228 		const char *str = spa_dict_lookup(props, PW_KEY_LINK_OUTPUT_PORT);
229 		const uint port1_id = (str ? uint(pw_properties_parse_int(str)) : 0);
230 		str = spa_dict_lookup(props, PW_KEY_LINK_INPUT_PORT);
231 		const uint port2_id = (str ? uint(pw_properties_parse_int(str)) : 0);
232 		if (pw->createLink(id, port1_id, port2_id))
233 			++nchanged;
234 	}
235 
236 	if (nchanged > 0)
237 		pw->changedNotify();
238 }
239 
240 static
qpwgraph_registry_event_global_remove(void * data,uint32_t id)241 void qpwgraph_registry_event_global_remove ( void *data, uint32_t id )
242 {
243 	qpwgraph_pipewire *pw = static_cast<qpwgraph_pipewire *> (data);
244 #ifdef CONFIG_DEBUG
245 	qDebug("qpwgraph_registry_event_global_remove[%p]: id:%u", pw, id);
246 #endif
247 
248 	pw->removeObject(id);
249 	pw->changedNotify();
250 }
251 
252 static
253 const struct pw_registry_events qpwgraph_registry_events = {
254 	.version = PW_VERSION_REGISTRY_EVENTS,
255 	.global = qpwgraph_registry_event_global,
256 	.global_remove = qpwgraph_registry_event_global_remove,
257 };
258 // registry-events.
259 
260 
261 // core-events...
262 static
qpwgraph_core_event_done(void * data,uint32_t id,int seq)263 void qpwgraph_core_event_done ( void *data, uint32_t id, int seq )
264 {
265 	qpwgraph_pipewire *pw = static_cast<qpwgraph_pipewire *> (data);
266 	qpwgraph_pipewire::Data *pd = pw->data();
267 #ifdef CONFIG_DEBUG
268 	qDebug("qpwgraph_core_event_done[%p]: id:%u seq:%d", pd, id, seq);
269 #endif
270 
271 	if (id == PW_ID_CORE) {
272 		pd->last_seq = seq;
273 		if (pd->pending_seq == seq)
274 			pw_thread_loop_signal(pd->loop, false);
275 	}
276 }
277 
278 static
qpwgraph_core_event_error(void * data,uint32_t id,int seq,int res,const char * message)279 void qpwgraph_core_event_error (
280 	void *data, uint32_t id, int seq, int res, const char *message )
281 {
282 	qpwgraph_pipewire *pw = static_cast<qpwgraph_pipewire *> (data);
283 	qpwgraph_pipewire::Data *pd = pw->data();
284 #ifdef CONFIG_DEBUG
285 	qDebug("qpwgraph_core_event_error[%p]: id:%u seq:%d res:%d : %s", pd, id, seq, res, message);
286 #endif
287 
288 	if (id == PW_ID_CORE) {
289 		pd->error = true;
290 		pd->last_res = res;
291 	}
292 
293 	pw_thread_loop_signal(pd->loop, false);
294 }
295 
296 static
297 const struct pw_core_events qpwgraph_core_events = {
298 	.version = PW_VERSION_CORE_EVENTS,
299 	.info = nullptr,
300 	.done = qpwgraph_core_event_done,
301 	.error = qpwgraph_core_event_error,
302 };
303 // core-events.
304 
305 
306 // link-events...
307 static
qpwgraph_link_proxy_sync(qpwgraph_pipewire * pw)308 int qpwgraph_link_proxy_sync ( qpwgraph_pipewire *pw )
309 {
310 	qpwgraph_pipewire::Data *pd = pw->data();
311 
312 	if (pw_thread_loop_in_thread(pd->loop))
313 		return 0;
314 
315 	pd->pending_seq = pw_proxy_sync((struct pw_proxy *)pd->core, pd->pending_seq);
316 
317 	while (true) {
318 		pw_thread_loop_wait(pd->loop);
319 		if (pd->error)
320 			return pd->last_res;
321 		if (pd->pending_seq == pd->last_seq)
322 			break;
323 	}
324 
325 	return 0;
326 }
327 
328 static
qpwgraph_link_proxy_error(void * data,int seq,int res,const char * message)329 void qpwgraph_link_proxy_error ( void *data, int seq, int res, const char *message )
330 {
331 #ifdef CONFIG_DEBUG
332 	qDebug("qpwgraph_link_proxy_error: seq:%d res:%d : %s", seq, res, message);
333 #endif
334 
335 	int *link_res = (int *)data;
336 	*link_res = res;
337 }
338 
339 static
340 const struct pw_proxy_events qpwgraph_link_proxy_events = {
341 	.version = PW_VERSION_PROXY_EVENTS,
342 	.error = qpwgraph_link_proxy_error,
343 };
344 // link-events.
345 
346 
347 //----------------------------------------------------------------------------
348 // qpwgraph_pipewire -- PipeWire graph driver
349 
350 QMutex qpwgraph_pipewire::g_mutex;
351 
352 
353 // Constructor.
qpwgraph_pipewire(qpwgraph_canvas * canvas)354 qpwgraph_pipewire::qpwgraph_pipewire ( qpwgraph_canvas *canvas )
355 	: qpwgraph_sect(canvas), m_data(nullptr)
356 {
357 	resetPortTypeColors();
358 
359 	open();
360 }
361 
362 
363 // Destructor.
~qpwgraph_pipewire(void)364 qpwgraph_pipewire::~qpwgraph_pipewire (void)
365 {
366 	close();
367 }
368 
369 
370 // Client methods.
open(void)371 bool qpwgraph_pipewire::open (void)
372 {
373 	QMutexLocker locker(&g_mutex);
374 
375 	pw_init(nullptr, nullptr);
376 
377 	m_data = new Data;
378 	spa_zero(*m_data);
379 
380 	m_data->loop = pw_thread_loop_new("qpwgraph_thread_loop", nullptr);
381 	if (m_data->loop == nullptr) {
382 		qDebug("pw_thread_loop_new: Can't create thread loop.");
383 		delete m_data;
384 		pw_deinit();
385 		return false;
386 	}
387 
388 	struct pw_loop *loop = pw_thread_loop_get_loop(m_data->loop);
389 	m_data->context = pw_context_new(loop,
390 		nullptr /*properties*/, 0 /*user_data size*/);
391 	if (m_data->context == nullptr) {
392 		qDebug("pw_context_new: Can't create context.");
393 		pw_thread_loop_destroy(m_data->loop);
394 		delete m_data;
395 		m_data = nullptr;
396 		pw_deinit();
397 		return false;
398 	}
399 
400 	m_data->core = pw_context_connect(m_data->context,
401 		nullptr /*properties*/, 0 /*user_data size*/);
402 	if (m_data->core == nullptr) {
403 		qDebug("pw_context_connect: Can't connect context.");
404 		pw_context_destroy(m_data->context);
405 		pw_thread_loop_destroy(m_data->loop);
406 		delete m_data;
407 		m_data = nullptr;
408 		pw_deinit();
409 		return false;
410 	}
411 
412 	pw_core_add_listener(m_data->core,
413 		 &m_data->core_listener, &qpwgraph_core_events, this);
414 
415 	m_data->registry = pw_core_get_registry(m_data->core,
416 		PW_VERSION_REGISTRY, 0 /*user_data size*/);
417 
418 	pw_registry_add_listener(m_data->registry,
419 		&m_data->registry_listener, &qpwgraph_registry_events, this);
420 
421 	m_data->pending_seq = 0;
422 	m_data->last_seq = 0;
423 	m_data->error = false;
424 
425 	pw_thread_loop_start(m_data->loop);
426 
427 	return true;
428 }
429 
430 
close(void)431 void qpwgraph_pipewire::close (void)
432 {
433 	QMutexLocker locker(&g_mutex);
434 
435 	clearObjects();
436 
437 	if (m_data == nullptr)
438 		return;
439 
440 	if (m_data->loop)
441 		pw_thread_loop_stop(m_data->loop);
442 
443 	if (m_data->registry)
444 		pw_proxy_destroy((struct pw_proxy*)m_data->registry);
445 
446 	if (m_data->core)
447 		pw_core_disconnect(m_data->core);
448 
449 	if (m_data->context)
450 		pw_context_destroy(m_data->context);
451 
452 	if (m_data->loop)
453 		pw_thread_loop_destroy(m_data->loop);
454 
455 	delete m_data;
456 	m_data = nullptr;
457 
458 	pw_deinit();
459 }
460 
461 
462 // Callback notifiers.
changedNotify(void)463 void qpwgraph_pipewire::changedNotify (void)
464 {
465 	emit changed();
466 }
467 
468 
469 // PipeWire port (dis)connection.
connectPorts(qpwgraph_port * port1,qpwgraph_port * port2,bool connect)470 void qpwgraph_pipewire::connectPorts (
471 	qpwgraph_port *port1, qpwgraph_port *port2, bool connect )
472 {
473 	if (m_data == nullptr)
474 		return;
475 
476 	if (port1 == nullptr || port2 == nullptr)
477 		return;
478 
479 	const qpwgraph_node *node1 = port1->portNode();
480 	const qpwgraph_node *node2 = port2->portNode();
481 
482 	if (node1 == nullptr || node2 == nullptr)
483 		return;
484 
485 	QMutexLocker locker(&g_mutex);
486 
487 	pw_thread_loop_lock(m_data->loop);
488 
489 	Port *p1 = findPort(port1->portId());
490 	Port *p2 = findPort(port2->portId());
491 
492 	if ((p1 == nullptr || p2 == nullptr) ||
493 		(p1->port_mode & qpwgraph_item::Output) == 0 ||
494 		(p2->port_mode & qpwgraph_item::Input)  == 0 ||
495 		(p1->port_type != p2->port_type)) {
496 		pw_thread_loop_unlock(m_data->loop);
497 		return;
498 	}
499 
500 	if (!connect) {
501 		// Disconnect ports...
502 		foreach (Link *link, p1->port_links) {
503 			if ((link->port1_id == p1->id) &&
504 				(link->port2_id == p2->id)) {
505 				pw_registry_destroy(m_data->registry, link->id);
506 				qpwgraph_link_proxy_sync(this);
507 				break;
508 			}
509 		}
510 		pw_thread_loop_unlock(m_data->loop);
511 		return;
512 	}
513 
514 	// Connect ports...
515 	char val[4][16];
516 	::snprintf(val[0], sizeof(val[0]), "%u", p1->node_id);
517 	::snprintf(val[1], sizeof(val[1]), "%u", p1->id);
518 	::snprintf(val[2], sizeof(val[2]), "%u", p2->node_id);
519 	::snprintf(val[3], sizeof(val[3]), "%u", p2->id);
520 
521 	struct spa_dict props;
522 	struct spa_dict_item items[6];
523 	props = SPA_DICT_INIT(items, 0);
524 	items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_OUTPUT_NODE, val[0]);
525 	items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_OUTPUT_PORT, val[1]);
526 	items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_NODE,  val[2]);
527 	items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_PORT,  val[3]);
528 	items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_LINGER,    "true");
529 	const char *str = ::getenv("PIPEWIRE_LINK_PASSIVE");
530 	if (str && pw_properties_parse_bool(str))
531 		items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_PASSIVE, "true");
532 
533 	struct pw_proxy *proxy = (struct pw_proxy *)pw_core_create_object(m_data->core,
534 		"link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props, 0);
535 	if (proxy) {
536 		int link_res = 0;
537 		struct spa_hook listener;
538 		spa_zero(listener);
539 		pw_proxy_add_listener(proxy,
540 			&listener, &qpwgraph_link_proxy_events, &link_res);
541 		qpwgraph_link_proxy_sync(this);
542 		spa_hook_remove(&listener);
543 		pw_proxy_destroy(proxy);
544 	}
545 
546 	pw_thread_loop_unlock(m_data->loop);
547 }
548 
549 
550 // PipeWire node type inquirer. (static)
isNodeType(uint node_type)551 bool qpwgraph_pipewire::isNodeType ( uint node_type )
552 {
553 	return (node_type == qpwgraph_pipewire::nodeType());
554 }
555 
556 
557 // PipeWire node type.
nodeType(void)558 uint qpwgraph_pipewire::nodeType (void)
559 {
560 	static
561 	const uint PipeWireNodeType
562 		= qpwgraph_item::itemType("PIPEWIRE_NODE_TYPE");
563 
564 	return PipeWireNodeType;
565 }
566 
567 
568 // PipeWire port type(s) inquirer. (static)
isPortType(uint port_type)569 bool qpwgraph_pipewire::isPortType ( uint port_type )
570 {
571 	return port_type == audioPortType()
572 		|| port_type == midiPortType()
573 		|| port_type == videoPortType()
574 		|| port_type == otherPortType();
575 }
576 
audioPortType(void)577 uint qpwgraph_pipewire::audioPortType (void)
578 {
579 	return qpwgraph_item::itemType(DEFAULT_AUDIO_TYPE);
580 }
581 
midiPortType(void)582 uint qpwgraph_pipewire::midiPortType (void)
583 {
584 	return qpwgraph_item::itemType(DEFAULT_MIDI_TYPE);
585 }
586 
videoPortType(void)587 uint qpwgraph_pipewire::videoPortType (void)
588 {
589 	return qpwgraph_item::itemType(DEFAULT_VIDEO_TYPE);
590 }
591 
otherPortType(void)592 uint qpwgraph_pipewire::otherPortType (void)
593 {
594 	return qpwgraph_item::itemType("PIPEWIRE_PORT_TYPE");
595 }
596 
597 
598 // PipeWire node:port finder and creator if not existing.
findNodePort(uint32_t node_id,uint32_t port_id,qpwgraph_item::Mode port_mode,qpwgraph_node ** node,qpwgraph_port ** port,bool add_new)599 bool qpwgraph_pipewire::findNodePort (
600 	uint32_t node_id, uint32_t port_id,  qpwgraph_item::Mode port_mode,
601 	qpwgraph_node **node, qpwgraph_port **port, bool add_new )
602 {
603 	Node *n = findNode(node_id);
604 	if (n == nullptr)
605 		return false;
606 
607 	Port *p = findPort(port_id);
608 	if (p == nullptr)
609 		return false;
610 
611 	const uint node_type
612 		= qpwgraph_pipewire::nodeType();
613 	qpwgraph_item::Mode node_mode
614 		= port_mode;
615 	const uint port_type
616 		= p->port_type;
617 
618 	*node = qpwgraph_sect::findNode(node_id, node_mode, node_type);
619 	*port = nullptr;
620 
621 	if (*node == nullptr) {
622 		const uint port_flags = p->port_flags;
623 		const uint port_flags_mask
624 			= (Port::Physical | Port::Terminal);
625 		if ((port_flags & port_flags_mask) != port_flags_mask) {
626 			node_mode = qpwgraph_item::Duplex;
627 			*node = qpwgraph_sect::findNode(node_id, node_mode, node_type);
628 		}
629 	}
630 
631 	if (*node)
632 		*port = (*node)->findPort(port_id, port_mode, port_type);
633 
634 	if (add_new && *node == nullptr) {
635 		*node = new qpwgraph_node(node_id, n->node_name, node_mode, node_type);
636 		(*node)->setNodeIcon(QIcon(":/images/itemPipewire.png"));
637 		qpwgraph_sect::addItem(*node);
638 	}
639 
640 	if (add_new && *port == nullptr && *node) {
641 		*port = (*node)->addPort(port_id, p->port_name, port_mode, port_type);
642 		(*port)->updatePortTypeColors(canvas());
643 	}
644 
645 	return (*node && *port);
646 }
647 
648 
649 // PipeWire graph updaters.
updateItems(void)650 void qpwgraph_pipewire::updateItems (void)
651 {
652 	QMutexLocker locker(&g_mutex);
653 
654 	if (m_data == nullptr)
655 		return;
656 
657 #ifdef CONFIG_DEBUG
658 	qDebug("qpwgraph_pipewire::updateItems()");
659 #endif
660 
661 	// 1. Nodes/ports inventory...
662 	//
663 	QList<qpwgraph_port *> ports;
664 
665 	foreach (Object *object, m_objects) {
666 		if (object->type != Object::Node)
667 			continue;
668 		Node *n1 = static_cast<Node *> (object);
669 		foreach (const Port *p1, n1->node_ports) {
670 			const qpwgraph_item::Mode port_mode1
671 				= p1->port_mode;
672 			qpwgraph_node *node1 = nullptr;
673 			qpwgraph_port *port1 = nullptr;
674 			if (findNodePort(n1->id, p1->id,
675 					port_mode1, &node1, &port1, true)) {
676 				node1->setMarked(true);
677 				port1->setMarked(true);
678 			}
679 			if ((port_mode1 & qpwgraph_item::Output)
680 				&& (!p1->port_links.isEmpty()))
681 				ports.append(port1);
682 		}
683 	}
684 
685 	// 2. Links inventory...
686 	//
687 	foreach (qpwgraph_port *port1, ports) {
688 		Port *p1 = findPort(port1->portId());
689 		if (p1 == nullptr)
690 			continue;
691 		foreach (const Link *link, p1->port_links) {
692 			Port *p2 = findPort(link->port2_id);
693 			if (p2 == nullptr)
694 				continue;
695 			const qpwgraph_item::Mode port_mode2
696 				= qpwgraph_item::Input;
697 			qpwgraph_node *node2 = nullptr;
698 			qpwgraph_port *port2 = nullptr;
699 			if (findNodePort(p2->node_id, link->port2_id,
700 					port_mode2, &node2, &port2, false)) {
701 				qpwgraph_connect *connect = port1->findConnect(port2);
702 				if (connect == nullptr) {
703 					connect = new qpwgraph_connect();
704 					connect->setPort1(port1);
705 					connect->setPort2(port2);
706 					connect->updatePortTypeColors();
707 					connect->updatePath();
708 					qpwgraph_sect::addItem(connect);
709 				}
710 				if (connect)
711 					connect->setMarked(true);
712 			}
713 		}
714 	}
715 
716 	// 3. Clean-up all un-marked items...
717 	//
718 	qpwgraph_sect::resetItems(qpwgraph_pipewire::nodeType());
719 }
720 
721 
clearItems(void)722 void qpwgraph_pipewire::clearItems (void)
723 {
724 	QMutexLocker locker(&g_mutex);
725 
726 	if (m_data == nullptr)
727 		return;
728 
729 #ifdef CONFIG_DEBUG
730 	qDebug("qpwgraph_pipewire::clearItems()");
731 #endif
732 
733 	// Clean-up all items...
734 	//
735 	qpwgraph_sect::clearItems(qpwgraph_pipewire::nodeType());
736 }
737 
738 
739 // Special port-type colors defaults (virtual).
resetPortTypeColors(void)740 void qpwgraph_pipewire::resetPortTypeColors (void)
741 {
742 	qpwgraph_canvas *canvas = qpwgraph_sect::canvas();
743 	if (canvas) {
744 		canvas->setPortTypeColor(
745 			qpwgraph_pipewire::audioPortType(),
746 			QColor(Qt::darkGreen).darker(120));
747 		canvas->setPortTypeColor(
748 			qpwgraph_pipewire::midiPortType(),
749 			QColor(Qt::darkRed).darker(120));
750 		canvas->setPortTypeColor(
751 			qpwgraph_pipewire::videoPortType(),
752 			QColor(Qt::darkCyan).darker(120));
753 		canvas->setPortTypeColor(
754 			qpwgraph_pipewire::otherPortType(),
755 			QColor(Qt::darkYellow).darker(120));
756 	}
757 }
758 
759 
760 // Node/port renaming method (virtual override).
renameItem(qpwgraph_item * item,const QString & name)761 void qpwgraph_pipewire::renameItem (
762 	qpwgraph_item *item, const QString& name )
763 {
764 	// TODO: ?...
765 	//
766 
767 	qpwgraph_sect::renameItem(item, name);
768 }
769 
770 
771 // PipeWire client data struct access.
772 //
data(void) const773 qpwgraph_pipewire::Data *qpwgraph_pipewire::data (void) const
774 {
775 	return m_data;
776 }
777 
778 
779 // Object methods.
780 //
findObject(uint id) const781 qpwgraph_pipewire::Object *qpwgraph_pipewire::findObject ( uint id ) const
782 {
783 	return m_objectids.value(id, nullptr);
784 }
785 
786 
addObject(uint id,Object * object)787 void qpwgraph_pipewire::addObject ( uint id, Object *object )
788 {
789 	m_objectids.insert(id, object);
790 	m_objects.append(object);
791 }
792 
793 
removeObject(uint id)794 void qpwgraph_pipewire::removeObject ( uint id )
795 {
796 	Object *object = m_objectids.value(id, nullptr);
797 	if (object == nullptr)
798 		return;
799 
800 	m_objectids.remove(id);
801 	m_objects.removeAll(object);
802 
803 	if (object->type == Object::Node)
804 		destroyNode(static_cast<Node *> (object));
805 	else
806 	if (object->type == Object::Port)
807 		destroyPort(static_cast<Port *> (object));
808 	else
809 	if (object->type == Object::Link)
810 		destroyLink(static_cast<Link *> (object));
811 }
812 
813 
clearObjects(void)814 void qpwgraph_pipewire::clearObjects (void)
815 {
816 	qDeleteAll(m_objects);
817 	m_objects.clear();
818 	m_objectids.clear();
819 }
820 
821 
822 // Node methods.
823 //
findNode(uint node_id) const824 qpwgraph_pipewire::Node *qpwgraph_pipewire::findNode ( uint node_id ) const
825 {
826 	Node *node = static_cast<Node *> (findObject(node_id));
827 	return (node && node->type == Object::Node ? node : nullptr);
828 }
829 
830 
createNode(uint node_id,const QString & node_name,qpwgraph_item::Mode node_mode,uint node_types)831 qpwgraph_pipewire::Node *qpwgraph_pipewire::createNode (
832 	uint node_id,
833 	const QString& node_name,
834 	qpwgraph_item::Mode node_mode,
835 	uint node_types )
836 {
837 	Node *node = new Node(node_id);
838 	node->node_name = node_name;
839 	node->node_mode = node_mode;
840 	node->node_types = Node::Types(node_types);
841 
842 	addObject(node_id, node);
843 
844 	return node;
845 }
846 
847 
destroyNode(Node * node)848 void qpwgraph_pipewire::destroyNode ( Node *node )
849 {
850 	foreach (const Port *port, node->node_ports)
851 		removeObject(port->id);
852 
853 	node->node_ports.clear();
854 
855 	delete node;
856 }
857 
858 
859 // Port methods.
860 //
findPort(uint port_id) const861 qpwgraph_pipewire::Port *qpwgraph_pipewire::findPort ( uint port_id ) const
862 {
863 	Port *port = static_cast<Port *> (findObject(port_id));
864 	return (port && port->type == Object::Port ? port : nullptr);
865 }
866 
867 
createPort(uint port_id,uint node_id,const QString & port_name,qpwgraph_item::Mode port_mode,uint port_type,uint port_flags)868 qpwgraph_pipewire::Port *qpwgraph_pipewire::createPort (
869 	uint port_id,
870 	uint node_id,
871 	const QString& port_name,
872 	qpwgraph_item::Mode port_mode,
873 	uint port_type,
874 	uint port_flags )
875 {
876 	Node *node = findNode(node_id);
877 	if (node == nullptr)
878 		return nullptr;
879 
880 	Port *port = new Port(port_id);
881 	port->node_id = node_id;
882 	port->port_name = port_name;
883 	port->port_mode = port_mode;
884 	port->port_type = port_type;
885 	port->port_flags = Port::Flags(port_flags);
886 
887 	node->node_ports.append(port);
888 
889 	addObject(port_id, port);
890 
891 	return port;
892 }
893 
894 
destroyPort(Port * port)895 void qpwgraph_pipewire::destroyPort ( Port *port )
896 {
897 	Node *node = findNode(port->node_id);
898 	if (node == nullptr)
899 		return;
900 
901 	foreach (const Link *link, port->port_links)
902 		removeObject(link->id);
903 
904 	port->port_links.clear();
905 	node->node_ports.removeAll(port);
906 
907 	delete port;
908 }
909 
910 
911 // Link methods.
912 //
findLink(uint link_id) const913 qpwgraph_pipewire::Link *qpwgraph_pipewire::findLink ( uint link_id ) const
914 {
915 	Link *link = static_cast<Link *> (findObject(link_id));
916 	return (link && link->type == Object::Link ? link : nullptr);
917 }
918 
919 
createLink(uint link_id,uint port1_id,uint port2_id)920 qpwgraph_pipewire::Link *qpwgraph_pipewire::createLink (
921 	uint link_id, uint port1_id, uint port2_id )
922 {
923 	Port *port1 = findPort(port1_id);
924 	if (port1 == nullptr)
925 		return nullptr;
926 	if ((port1->port_mode & qpwgraph_item::Output) == 0)
927 		return nullptr;
928 
929 	Port *port2 = findPort(port2_id);
930 	if (port2 == nullptr)
931 		return nullptr;
932 	if ((port2->port_mode & qpwgraph_item::Input) == 0)
933 		return nullptr;
934 
935 	Link *link = new Link(link_id);
936 	link->port1_id = port1_id;
937 	link->port2_id = port2_id;
938 
939 	port1->port_links.append(link);
940 
941 	addObject(link_id, link);
942 
943 	return link;
944 }
945 
946 
destroyLink(Link * link)947 void qpwgraph_pipewire::destroyLink ( Link *link )
948 {
949 	Port *port = findPort(link->port1_id);
950 	if (port == nullptr)
951 		return;
952 
953 	port->port_links.removeAll(link);
954 
955 	delete link;
956 }
957 
958 
959 // end of qpwgraph_pipewire.cpp
960