1 // qjackctlAlsaGraph.cpp
2 //
3 /****************************************************************************
4    Copyright (C) 2003-2020, 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 "qjackctlAlsaGraph.h"
23 
24 #include "qjackctlMainForm.h"
25 
26 
27 #ifdef CONFIG_ALSA_SEQ
28 
29 #include <QMutexLocker>
30 
31 
32 //----------------------------------------------------------------------------
33 // qjackctlAlsaGraph -- ALSA graph driver
34 
35 QMutex qjackctlAlsaGraph::g_mutex;
36 
37 
38 // Constructor.
qjackctlAlsaGraph(qjackctlGraphCanvas * canvas)39 qjackctlAlsaGraph::qjackctlAlsaGraph ( qjackctlGraphCanvas *canvas )
40 	: qjackctlGraphSect(canvas)
41 {
42 	resetPortTypeColors();
43 }
44 
45 
46 // ALSA port (dis)connection.
connectPorts(qjackctlGraphPort * port1,qjackctlGraphPort * port2,bool connect)47 void qjackctlAlsaGraph::connectPorts (
48 	qjackctlGraphPort *port1, qjackctlGraphPort *port2, bool connect )
49 {
50 	qjackctlMainForm *pMainForm = qjackctlMainForm::getInstance();
51 	if (pMainForm == nullptr)
52 		return;
53 
54 	snd_seq_t *seq = pMainForm->alsaSeq();
55 	if (seq == nullptr)
56 		return;
57 
58 	if (port1 == nullptr || port2 == nullptr)
59 		return;
60 
61 	const qjackctlGraphNode *node1 = port1->portNode();
62 	const qjackctlGraphNode *node2 = port2->portNode();
63 
64 	if (node1 == nullptr || node2 == nullptr)
65 		return;
66 
67 	QMutexLocker locker(&g_mutex);
68 
69 	const int client_id1
70 		= node1->nodeName().section(':', 0, 0).toInt();
71 	const int port_id1
72 		= port1->portName().section(':', 0, 0).toInt();
73 
74 	const int client_id2
75 		= node2->nodeName().section(':', 0, 0).toInt();
76 	const int port_id2
77 		= port2->portName().section(':', 0, 0).toInt();
78 
79 #ifdef CONFIG_DEBUG
80 	qDebug("qjackctlAlsaGraph::connectPorts(%d:%d, %d:%d, %d)",
81 		client_id1, port_id1, client_id2, port_id2, connect);
82 #endif
83 
84 	snd_seq_port_subscribe_t *seq_subs;
85 	snd_seq_addr_t seq_addr;
86 
87 	snd_seq_port_subscribe_alloca(&seq_subs);
88 
89 	seq_addr.client = client_id1;
90 	seq_addr.port = port_id1;
91 	snd_seq_port_subscribe_set_sender(seq_subs, &seq_addr);
92 
93 	seq_addr.client = client_id2;
94 	seq_addr.port = port_id2;
95 	snd_seq_port_subscribe_set_dest(seq_subs, &seq_addr);
96 
97 	if (connect) {
98 		snd_seq_subscribe_port(seq, seq_subs);
99 	} else {
100 		snd_seq_unsubscribe_port(seq, seq_subs);
101 	}
102 }
103 
104 
105 // ALSA node type inquirer. (static)
isNodeType(uint node_type)106 bool qjackctlAlsaGraph::isNodeType ( uint node_type )
107 {
108 	return (node_type == qjackctlAlsaGraph::nodeType());
109 }
110 
111 
112 // ALSA node type.
nodeType(void)113 uint qjackctlAlsaGraph::nodeType (void)
114 {
115 	static
116 	const uint AlsaNodeType
117 		= qjackctlGraphItem::itemType("ALSA_NODE_TYPE");
118 
119 	return AlsaNodeType;
120 }
121 
122 
123 // ALSA port type inquirer. (static)
isPortType(uint port_type)124 bool qjackctlAlsaGraph::isPortType ( uint port_type )
125 {
126 	return (port_type == qjackctlAlsaGraph::midiPortType());
127 }
128 
129 
130 // ALSA port type.
midiPortType(void)131 uint qjackctlAlsaGraph::midiPortType (void)
132 {
133 	static
134 	const uint AlsaMidiPortType
135 		= qjackctlGraphItem::itemType("ALSA_PORT_TYPE");
136 
137 	return AlsaMidiPortType;
138 }
139 
140 
141 // ALSA client:port finder and creator if not existing.
findClientPort(snd_seq_client_info_t * client_info,snd_seq_port_info_t * port_info,qjackctlGraphItem::Mode port_mode,qjackctlGraphNode ** node,qjackctlGraphPort ** port,bool add_new)142 bool qjackctlAlsaGraph::findClientPort (
143 	snd_seq_client_info_t *client_info,
144 	snd_seq_port_info_t *port_info,
145 	qjackctlGraphItem::Mode port_mode,
146 	qjackctlGraphNode **node,
147 	qjackctlGraphPort **port,
148 	bool add_new )
149 {
150 	const int client_id
151 		= snd_seq_client_info_get_client(client_info);
152 	const int port_id
153 		= snd_seq_port_info_get_port(port_info);
154 
155 	const QString& client_name
156 		= QString::number(client_id) + ':'
157 		+ QString::fromUtf8(snd_seq_client_info_get_name(client_info));
158 	const QString& port_name
159 		= QString::number(port_id) + ':'
160 		+ QString::fromUtf8(snd_seq_port_info_get_name(port_info));
161 
162 	const uint node_type
163 		= qjackctlAlsaGraph::nodeType();
164 	const uint port_type
165 		= qjackctlAlsaGraph::midiPortType();
166 
167 	qjackctlGraphItem::Mode node_mode = port_mode;
168 
169 	*node = qjackctlGraphSect::findNode(client_name, node_mode, node_type);
170 	*port = nullptr;
171 
172 	if (*node == nullptr && client_id >= 128) {
173 		node_mode = qjackctlGraphItem::Duplex;
174 		*node = qjackctlGraphSect::findNode(client_name, node_mode, node_type);
175 	}
176 
177 	if (*node)
178 		*port = (*node)->findPort(port_name, port_mode, port_type);
179 
180 	if (add_new && *node == nullptr) {
181 		*node = new qjackctlGraphNode(client_name, node_mode, node_type);
182 		(*node)->setNodeIcon(QIcon(":/images/graphAlsa.png"));
183 		qjackctlGraphSect::addItem(*node);
184 	}
185 
186 	if (add_new && *port == nullptr && *node) {
187 		*port = (*node)->addPort(port_name, port_mode, port_type);
188 		(*port)->updatePortTypeColors(canvas());
189 	}
190 
191 	if (add_new && *node) {
192 		int nchanged = 0;
193 		QString node_title = (*node)->nodeTitle();
194 		foreach (qjackctlAliasList *node_aliases, item_aliases(*node))
195 			node_title = node_aliases->clientAlias(client_name);
196 		if ((*node)->nodeTitle() != node_title) {
197 			(*node)->setNodeTitle(node_title);
198 			++nchanged;
199 		}
200 		if (*port) {
201 			QString port_title = (*port)->portTitle();
202 			foreach (qjackctlAliasList *port_aliases, item_aliases(*port))
203 				port_title = port_aliases->portAlias(client_name, port_name);
204 			if ((*port)->portTitle() != port_title) {
205 				(*port)->setPortTitle(port_title);
206 				++nchanged;
207 			}
208 		}
209 		if (nchanged > 0)
210 			(*node)->updatePath();
211 	}
212 
213 	return (*node && *port);
214 }
215 
216 
217 // ALSA graph updater.
updateItems(void)218 void qjackctlAlsaGraph::updateItems (void)
219 {
220 	QMutexLocker locker(&g_mutex);
221 
222 	qjackctlMainForm *pMainForm = qjackctlMainForm::getInstance();
223 	if (pMainForm == nullptr)
224 		return;
225 
226 	snd_seq_t *seq = pMainForm->alsaSeq();
227 	if (seq == nullptr)
228 		return;
229 
230 #ifdef CONFIG_DEBUG_0
231 	qDebug("qjackctlAlsaGraph::updateItems()");
232 #endif
233 
234 	// 1. Client/ports inventory...
235 	//
236 	snd_seq_client_info_t *client_info1;
237 	snd_seq_port_info_t *port_info1;
238 
239 	snd_seq_client_info_alloca(&client_info1);
240 	snd_seq_port_info_alloca(&port_info1);
241 
242 	snd_seq_client_info_set_client(client_info1, -1);
243 
244 	while (snd_seq_query_next_client(seq, client_info1) >= 0) {
245 		const int client_id
246 			= snd_seq_client_info_get_client(client_info1);
247 		if (0 >= client_id)	// Skip 0:System client...
248 			continue;
249 		snd_seq_port_info_set_client(port_info1, client_id);
250 		snd_seq_port_info_set_port(port_info1, -1);
251 		while (snd_seq_query_next_port(seq, port_info1) >= 0) {
252 			const unsigned int port_caps1
253 				= snd_seq_port_info_get_capability(port_info1);
254 			if (port_caps1 & SND_SEQ_PORT_CAP_NO_EXPORT)
255 				continue;
256 			qjackctlGraphItem::Mode port_mode1 = qjackctlGraphItem::None;
257 			const unsigned int port_is_input
258 				= (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
259 			if ((port_caps1 & port_is_input) == port_is_input) {
260 				port_mode1 = qjackctlGraphItem::Input;
261 				qjackctlGraphNode *node1 = nullptr;
262 				qjackctlGraphPort *port1 = nullptr;
263 				if (findClientPort(client_info1, port_info1,
264 						port_mode1, &node1, &port1, true)) {
265 					node1->setMarked(true);
266 					port1->setMarked(true);
267 				}
268 			}
269 			const unsigned int port_is_output
270 				= (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);
271 			if ((port_caps1 & port_is_output) == port_is_output) {
272 				port_mode1 = qjackctlGraphItem::Output;
273 				qjackctlGraphNode *node1 = nullptr;
274 				qjackctlGraphPort *port1 = nullptr;
275 				if (findClientPort(client_info1, port_info1,
276 						port_mode1, &node1, &port1, true)) {
277 					node1->setMarked(true);
278 					port1->setMarked(true);
279 				}
280 			}
281 		}
282 	}
283 
284 	// 2. Connections inventory...
285 	//
286 	snd_seq_client_info_t *client_info2;
287 	snd_seq_port_info_t *port_info2;
288 
289 	snd_seq_client_info_alloca(&client_info2);
290 	snd_seq_port_info_alloca(&port_info2);
291 
292 	snd_seq_query_subscribe_t *seq_subs;
293 	snd_seq_addr_t seq_addr;
294 
295 	snd_seq_query_subscribe_alloca(&seq_subs);
296 
297 	snd_seq_client_info_set_client(client_info1, -1);
298 
299 	while (snd_seq_query_next_client(seq, client_info1) >= 0) {
300 		const int client_id
301 			= snd_seq_client_info_get_client(client_info1);
302 		if (0 >= client_id)	// Skip 0:system client...
303 			continue;
304 		snd_seq_port_info_set_client(port_info1, client_id);
305 		snd_seq_port_info_set_port(port_info1, -1);
306 		while (snd_seq_query_next_port(seq, port_info1) >= 0) {
307 			const unsigned int port_caps1
308 				= snd_seq_port_info_get_capability(port_info1);
309 			if (port_caps1 & SND_SEQ_PORT_CAP_NO_EXPORT)
310 				continue;
311 			if (port_caps1 & (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ)) {
312 				const qjackctlGraphItem::Mode port_mode1
313 					= qjackctlGraphItem::Output;
314 				qjackctlGraphNode *node1 = nullptr;
315 				qjackctlGraphPort *port1 = nullptr;
316 				if (!findClientPort(client_info1, port_info1,
317 						port_mode1, &node1, &port1, false))
318 					continue;
319 				snd_seq_query_subscribe_set_type(seq_subs, SND_SEQ_QUERY_SUBS_READ);
320 				snd_seq_query_subscribe_set_index(seq_subs, 0);
321 				seq_addr.client = client_id;
322 				seq_addr.port = snd_seq_port_info_get_port(port_info1);
323 				snd_seq_query_subscribe_set_root(seq_subs, &seq_addr);
324 				while (snd_seq_query_port_subscribers(seq, seq_subs) >= 0) {
325 					seq_addr = *snd_seq_query_subscribe_get_addr(seq_subs);
326 					if (snd_seq_get_any_client_info(seq,
327 							seq_addr.client, client_info2) >= 0 &&
328 						snd_seq_get_any_port_info(seq,
329 							seq_addr.client, seq_addr.port, port_info2) >= 0) {
330 						const qjackctlGraphItem::Mode port_mode2
331 							= qjackctlGraphItem::Input;
332 						qjackctlGraphNode *node2 = nullptr;
333 						qjackctlGraphPort *port2 = nullptr;
334 						if (findClientPort(client_info2, port_info2,
335 								port_mode2, &node2, &port2, false)) {
336 							qjackctlGraphConnect *connect = port1->findConnect(port2);
337 							if (connect == nullptr) {
338 								connect = new qjackctlGraphConnect();
339 								connect->setPort1(port1);
340 								connect->setPort2(port2);
341 								connect->updatePortTypeColors();
342 								connect->updatePath();
343 								qjackctlGraphSect::addItem(connect);
344 							}
345 							if (connect)
346 								connect->setMarked(true);
347 						}
348 					}
349 					snd_seq_query_subscribe_set_index(seq_subs,
350 						snd_seq_query_subscribe_get_index(seq_subs) + 1);
351 				}
352 			}
353 		}
354 	}
355 
356 	// 3. Clean-up all un-marked items...
357 	//
358 	qjackctlGraphSect::resetItems(qjackctlAlsaGraph::nodeType());
359 }
360 
361 
clearItems(void)362 void qjackctlAlsaGraph::clearItems (void)
363 {
364 	QMutexLocker locker(&g_mutex);
365 
366 #ifdef CONFIG_DEBUG_0
367 	qDebug("qjackctlAlsaGraph::clearItems()");
368 #endif
369 
370 	qjackctlGraphSect::clearItems(qjackctlAlsaGraph::nodeType());
371 }
372 
373 
374 // Special port-type colors defaults (virtual).
resetPortTypeColors(void)375 void qjackctlAlsaGraph::resetPortTypeColors (void)
376 {
377 	qjackctlGraphCanvas *canvas = qjackctlGraphSect::canvas();
378 	if (canvas) {
379 		canvas->setPortTypeColor(
380 			qjackctlAlsaGraph::midiPortType(),
381 			QColor(Qt::darkMagenta).darker(120));
382 	}
383 }
384 
385 
386 // Client/port item aliases accessor.
item_aliases(qjackctlGraphItem * item) const387 QList<qjackctlAliasList *> qjackctlAlsaGraph::item_aliases (
388 	qjackctlGraphItem *item ) const
389 {
390 	QList<qjackctlAliasList *> alist;
391 
392 	qjackctlAliases *aliases = nullptr;
393 	qjackctlGraphCanvas *canvas = qjackctlGraphSect::canvas();
394 	if (canvas)
395 		aliases = canvas->aliases();
396 	if (aliases == nullptr)
397 		return alist; // empty!
398 
399 	uint item_type = 0;
400 	qjackctlGraphItem::Mode item_mode = qjackctlGraphItem::None;
401 	bool is_node = false;
402 
403 	if (item->type() == qjackctlGraphNode::Type) {
404 		qjackctlGraphNode *node = static_cast<qjackctlGraphNode *> (item);
405 		if (node && qjackctlAlsaGraph::isNodeType(node->nodeType())) {
406 			item_type = node->nodeType();
407 			item_mode = node->nodeMode();
408 			is_node = true;
409 		}
410 	}
411 	else
412 	if (item->type() == qjackctlGraphPort::Type) {
413 		qjackctlGraphPort *port = static_cast<qjackctlGraphPort *> (item);
414 		if (port) {
415 			item_type = port->portType();
416 			item_mode = port->portMode();
417 		}
418 	}
419 
420 	if (!item_type || !item_mode)
421 		return alist; // empty again!
422 
423 	if (is_node || item_type == qjackctlAlsaGraph::midiPortType()) {
424 		// ALSA MIDI type...
425 		if (item_mode & qjackctlGraphItem::Input)
426 			alist.append(&(aliases->alsaInputs));
427 		if (item_mode & qjackctlGraphItem::Output)
428 			alist.append(&(aliases->alsaOutputs));
429 	}
430 
431 	return alist; // hopefully non empty!
432 }
433 
434 
435 #endif	// CONFIG_ALSA_SEQ
436 
437 
438 // end of qjackctlAlsaGraph.cpp
439