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