Jack2  1.9.13
JackMetadata.cpp
1 /*
2  Copyright (C) 2011 David Robillard
3  Copyright (C) 2013 Paul Davis
4  Copyright (C) 2019 Filipe Coelho
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU Lesser General Public License as published by
8  the Free Software Foundation; either version 2.1 of the License, or (at
9  your option) any later version.
10 
11  This program is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14  License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public License
17  along with this program; if not, write to the Free Software Foundation,
18  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20 
21 #include "JackMetadata.h"
22 
23 #include "JackClient.h"
24 
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <limits.h>
28 
29 
30 #define JACK_METADATA_PREFIX "http://jackaudio.org/metadata/"
31 LIB_EXPORT const char* JACK_METADATA_CONNECTED = JACK_METADATA_PREFIX "connected";
32 LIB_EXPORT const char* JACK_METADATA_EVENT_TYPES = JACK_METADATA_PREFIX "event-types";
33 LIB_EXPORT const char* JACK_METADATA_HARDWARE = JACK_METADATA_PREFIX "hardware";
34 LIB_EXPORT const char* JACK_METADATA_ICON_LARGE = JACK_METADATA_PREFIX "icon-large";
35 LIB_EXPORT const char* JACK_METADATA_ICON_NAME = JACK_METADATA_PREFIX "icon-name";
36 LIB_EXPORT const char* JACK_METADATA_ICON_SMALL = JACK_METADATA_PREFIX "icon-small";
37 LIB_EXPORT const char* JACK_METADATA_ORDER = JACK_METADATA_PREFIX "order";
38 LIB_EXPORT const char* JACK_METADATA_PORT_GROUP = JACK_METADATA_PREFIX "port-group";
39 LIB_EXPORT const char* JACK_METADATA_PRETTY_NAME = JACK_METADATA_PREFIX "pretty-name";
40 LIB_EXPORT const char* JACK_METADATA_SIGNAL_TYPE = JACK_METADATA_PREFIX "signal-type";
41 #undef JACK_METADATA_PREFIX
42 
43 namespace Jack
44 {
45 
46 JackMetadata::JackMetadata(bool isEngine)
47 #if HAVE_DB
48  : fDB(NULL), fDBenv(NULL), fIsEngine(isEngine)
49 #endif
50 {
51  PropertyInit();
52 }
53 
54 JackMetadata::~JackMetadata()
55 {
56 #if HAVE_DB
57  char dbpath[PATH_MAX + 1];
58 
59  if (fDB) {
60  fDB->close (fDB, 0);
61  fDB = NULL;
62  }
63  if (fDBenv) {
64  fDBenv->close (fDBenv, 0);
65  fDBenv = NULL;
66  }
67 
68  if (fIsEngine)
69  {
70  // cleanup after libdb, nasty!
71  snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", jack_server_dir);
72  remove (dbpath);
73 
74  snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.001", jack_server_dir);
75  remove (dbpath);
76 
77  snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.002", jack_server_dir);
78  remove (dbpath);
79 
80  snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.003", jack_server_dir);
81  remove (dbpath);
82 
83  // remove our custom dir
84  snprintf (dbpath, sizeof(dbpath), "%s/jack_db", jack_server_dir);
85  rmdir (dbpath);
86  }
87 #endif
88 }
89 
90 int JackMetadata::PropertyInit()
91 {
92 #if HAVE_DB
93 
94  int ret;
95  char dbpath[PATH_MAX + 1];
96 
97  /* idempotent */
98 
99  if (fDBenv) {
100  return 0;
101  }
102 
103  if ((ret = db_env_create (&fDBenv, 0)) != 0) {
104  jack_error ("cannot initialize DB environment: %s\n", db_strerror (ret));
105  return -1;
106  }
107 
108  snprintf (dbpath, sizeof(dbpath), "%s/jack_db", jack_server_dir);
109  mkdir (dbpath, S_IRWXU | S_IRWXG);
110 
111  if ((ret = fDBenv->open (fDBenv, dbpath, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) {
112  jack_error ("cannot open DB environment: %s", db_strerror (ret));
113  return -1;
114  }
115 
116  if ((ret = db_create (&fDB, fDBenv, 0)) != 0) {
117  jack_error ("Cannot initialize metadata DB (%s)", db_strerror (ret));
118  return -1;
119  }
120 
121  snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", jack_server_dir);
122  if ((ret = fDB->open (fDB, NULL, dbpath, NULL, DB_HASH, DB_CREATE | DB_THREAD, 0666)) != 0) {
123  jack_error ("Cannot open metadata DB at %s: %s", dbpath, db_strerror (ret));
124  fDB->close (fDB, 0);
125  fDB = NULL;
126  return -1;
127  }
128 
129  return 0;
130 
131 #else // !HAVE_DB
132  return -1;
133 #endif
134 }
135 
136 int JackMetadata::PropertyChangeNotify(JackClient* client, jack_uuid_t subject, const char* key, jack_property_change_t change)
137 {
138  /* the engine passes in a NULL client when it removes metadata during port or client removal
139  */
140 
141  if (client == NULL) {
142  return 0;
143  }
144 
145  return client->PropertyChangeNotify(subject, key, change);
146 }
147 
148 #if HAVE_DB
149 void JackMetadata::MakeKeyDbt(DBT* dbt, jack_uuid_t subject, const char* key)
150 {
151  char ustr[JACK_UUID_STRING_SIZE];
152  size_t len1, len2;
153 
154  memset (dbt, 0, sizeof(DBT));
155  memset (ustr, 0, JACK_UUID_STRING_SIZE);
156  jack_uuid_unparse (subject, ustr);
157  len1 = JACK_UUID_STRING_SIZE;
158  len2 = strlen (key) + 1;
159  dbt->size = len1 + len2;
160  dbt->data = malloc (dbt->size);
161  memcpy (dbt->data, ustr, len1); // copy subject+null
162  memcpy ((char *)dbt->data + len1, key, len2); // copy key+null
163 }
164 #endif
165 
166 int JackMetadata::SetProperty(JackClient* client, jack_uuid_t subject, const char* key, const char* value, const char* type)
167 {
168 #if HAVE_DB
169 
170  DBT d_key;
171  DBT data;
172  int ret;
173  size_t len1, len2;
174  jack_property_change_t change;
175 
176  if (!key || key[0] == '\0') {
177  jack_error ("empty key string for metadata not allowed");
178  return -1;
179  }
180 
181  if (!value || value[0] == '\0') {
182  jack_error ("empty value string for metadata not allowed");
183  return -1;
184  }
185 
186  if (PropertyInit()) {
187  return -1;
188  }
189 
190  /* build a key */
191 
192  MakeKeyDbt(&d_key, subject, key);
193 
194  /* build data */
195 
196  memset (&data, 0, sizeof(data));
197 
198  len1 = strlen (value) + 1;
199  if (type && type[0] != '\0') {
200  len2 = strlen (type) + 1;
201  } else {
202  len2 = 0;
203  }
204 
205  data.size = len1 + len2;
206  data.data = malloc (data.size);
207  memcpy (data.data, value, len1);
208 
209  if (len2) {
210  memcpy ((char *)data.data + len1, type, len2);
211  }
212 
213  if (fDB->exists (fDB, NULL, &d_key, 0) == DB_NOTFOUND) {
214  change = PropertyCreated;
215  } else {
216  change = PropertyChanged;
217  }
218 
219  if ((ret = fDB->put (fDB, NULL, &d_key, &data, 0)) != 0) {
220  char ustr[JACK_UUID_STRING_SIZE];
221  jack_uuid_unparse (subject, ustr);
222  jack_error ("Cannot store metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
223  if (d_key.size > 0) {
224  free (d_key.data);
225  }
226  if (data.size > 0) {
227  free (data.data);
228  }
229  return -1;
230  }
231 
232  PropertyChangeNotify(client, subject, key, change);
233 
234  if (d_key.size > 0) {
235  free (d_key.data);
236  }
237  if (data.size > 0) {
238  free (data.data);
239  }
240 
241  return 0;
242 
243 #else // !HAVE_DB
244  return -1;
245 #endif
246 }
247 
248 int JackMetadata::GetProperty(jack_uuid_t subject, const char* key, char** value, char** type)
249 {
250 #if HAVE_DB
251 
252  DBT d_key;
253  DBT data;
254  int ret;
255  size_t len1, len2;
256 
257  if (key == NULL || key[0] == '\0') {
258  return -1;
259  }
260 
261  if (PropertyInit()) {
262  return -1;
263  }
264 
265  /* build a key */
266 
267  MakeKeyDbt(&d_key, subject, key);
268 
269  /* setup data DBT */
270 
271  memset (&data, 0, sizeof(data));
272  data.flags = DB_DBT_MALLOC;
273 
274  if ((ret = fDB->get (fDB, NULL, &d_key, &data, 0)) != 0) {
275  if (ret != DB_NOTFOUND) {
276  char ustr[JACK_UUID_STRING_SIZE];
277  jack_uuid_unparse (subject, ustr);
278  jack_error ("Cannot retrieve metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
279  }
280  if (d_key.size > 0) {
281  free (d_key.data);
282  }
283  if (data.size > 0) {
284  free (data.data);
285  }
286  return -1;
287  }
288 
289  /* result must have at least 1 char plus 1 null to be valid
290  (old rule was:
291  result must have at least 2 chars plus 2 nulls to be valid
292  )
293  */
294 
295  if (data.size < 2) {
296  if (d_key.size > 0) {
297  free (d_key.data);
298  }
299  if (data.size > 0) {
300  free (data.data);
301  }
302  return -1;
303  }
304 
305  len1 = strlen ((const char*)data.data) + 1;
306  (*value) = (char*)malloc (len1);
307  memcpy (*value, data.data, len1);
308 
309  if (len1 < data.size) {
310  len2 = strlen ((const char*)data.data + len1) + 1;
311 
312  (*type) = (char*)malloc (len2);
313  memcpy (*type, (const char *)data.data + len1, len2);
314  } else {
315  /* no type specified, assume default */
316  *type = NULL;
317  }
318 
319  if (d_key.size > 0) {
320  free (d_key.data);
321  }
322  if (data.size > 0) {
323  free (data.data);
324  }
325 
326  return 0;
327 
328 #else // !HAVE_DB
329  return -1;
330 #endif
331 }
332 
333 int JackMetadata::GetProperties(jack_uuid_t subject, jack_description_t* desc)
334 {
335 #if HAVE_DB
336 
337  DBT key;
338  DBT data;
339  DBC* cursor;
340  int ret;
341  size_t len1, len2;
342  size_t cnt = 0;
343  char ustr[JACK_UUID_STRING_SIZE];
344  size_t props_size = 0;
345  jack_property_t* prop;
346 
347  desc->properties = NULL;
348  desc->property_cnt = 0;
349 
350  memset (ustr, 0, JACK_UUID_STRING_SIZE);
351  jack_uuid_unparse (subject, ustr);
352 
353  if (PropertyInit()) {
354  return -1;
355  }
356 
357 
358  if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
359  jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
360  return -1;
361  }
362 
363  memset (&key, 0, sizeof(key));
364  memset (&data, 0, sizeof(data));
365  data.flags = DB_DBT_MALLOC;
366 
367  while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
368 
369  /* require 2 extra chars (data+null) for key,
370  which is composed of UUID str plus a key name
371  */
372 
373  if (key.size < JACK_UUID_STRING_SIZE + 2) {
374  /* if (key.size > 0) free(key.data); */
375  if (data.size > 0) {
376  free (data.data);
377  }
378  continue;
379  }
380 
381  if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
382  /* not relevant */
383  /* if (key.size > 0) free(key.data); */
384  if (data.size > 0) {
385  free (data.data);
386  }
387  continue;
388  }
389 
390  /* result must have at least 1 char plus 1 null to be valid
391  (old rule was:
392  result must have at least 2 chars plus 2 nulls to be valid
393  )
394  */
395 
396  if (data.size < 2) {
397  /* if (key.size > 0) free(key.data); */
398  if (data.size > 0) {
399  free (data.data);
400  }
401  continue;
402  }
403 
404  /* realloc array if necessary */
405 
406  if (cnt == props_size) {
407  if (props_size == 0) {
408  props_size = 8; /* a rough guess at a likely upper bound for the number of properties */
409  } else {
410  props_size *= 2;
411  }
412 
413  desc->properties = (jack_property_t*)realloc (desc->properties, sizeof(jack_property_t) * props_size);
414  }
415 
416  prop = &desc->properties[cnt];
417 
418  /* store UUID/subject */
419 
420  jack_uuid_copy (&desc->subject, subject);
421 
422  /* copy key (without leading UUID as subject */
423 
424  len1 = key.size - JACK_UUID_STRING_SIZE;
425  prop->key = (char*)malloc (len1);
426  memcpy ((char*)prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1);
427 
428  /* copy data (which contains 1 or 2 null terminated strings, the value
429  and optionally a MIME type.
430  */
431 
432  len1 = strlen ((const char*)data.data) + 1;
433  prop->data = (char*)malloc (len1);
434  memcpy ((char*)prop->data, data.data, len1);
435 
436  if (len1 < data.size) {
437  len2 = strlen ((const char *)data.data + len1) + 1;
438 
439  prop->type = (char*)malloc (len2);
440  memcpy ((char*)prop->type, (const char *)data.data + len1, len2);
441  } else {
442  /* no type specified, assume default */
443  prop->type = NULL;
444  }
445 
446  /* if (key.size > 0) free(key.data); */
447  if (data.size > 0) {
448  free (data.data);
449  }
450 
451  ++cnt;
452  }
453 
454  cursor->close (cursor);
455  desc->property_cnt = cnt;
456 
457  return cnt;
458 
459 #else // !HAVE_DB
460  return -1;
461 #endif
462 }
463 
464 int JackMetadata::GetAllProperties(jack_description_t** descriptions)
465 {
466 #if HAVE_DB
467 
468  DBT key;
469  DBT data;
470  DBC* cursor;
471  int ret;
472  size_t dcnt = 0;
473  size_t dsize = 0;
474  size_t n = 0;
475  jack_description_t* desc = NULL;
476  jack_uuid_t uuid = JACK_UUID_EMPTY_INITIALIZER;
477  jack_description_t* current_desc = NULL;
478  jack_property_t* current_prop = NULL;
479  size_t len1, len2;
480 
481  if (PropertyInit()) {
482  return -1;
483  }
484 
485  if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
486  jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
487  return -1;
488  }
489 
490  memset (&key, 0, sizeof(key));
491  memset (&data, 0, sizeof(data));
492  data.flags = DB_DBT_MALLOC;
493 
494  dsize = 8; /* initial guess at number of descriptions we need */
495  dcnt = 0;
496  desc = (jack_description_t*)malloc (dsize * sizeof(jack_description_t));
497 
498  while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
499 
500  /* require 2 extra chars (data+null) for key,
501  which is composed of UUID str plus a key name
502  */
503 
504  if (key.size < JACK_UUID_STRING_SIZE + 2) {
505  /* if (key.size > 0) free(key.data); */
506  if (data.size > 0) {
507  free (data.data);
508  }
509  continue;
510  }
511 
512  if (jack_uuid_parse ((const char *)key.data, &uuid) != 0) {
513  continue;
514  }
515 
516  /* do we have an existing description for this UUID */
517 
518  for (n = 0; n < dcnt; ++n) {
519  if (jack_uuid_compare (uuid, desc[n].subject) == 0) {
520  break;
521  }
522  }
523 
524  if (n == dcnt) {
525  /* we do not have an existing description, so grow the array */
526 
527  if (dcnt == dsize) {
528  dsize *= 2;
529  desc = (jack_description_t*)realloc (desc, sizeof(jack_description_t) * dsize);
530  }
531 
532  /* initialize */
533 
534  desc[n].property_size = 0;
535  desc[n].property_cnt = 0;
536  desc[n].properties = NULL;
537 
538  /* set up UUID */
539 
540  jack_uuid_copy (&desc[n].subject, uuid);
541  dcnt++;
542  }
543 
544  current_desc = &desc[n];
545 
546  /* see if there is room for the new property or if we need to realloc
547  */
548 
549  if (current_desc->property_cnt == current_desc->property_size) {
550  if (current_desc->property_size == 0) {
551  current_desc->property_size = 8;
552  } else {
553  current_desc->property_size *= 2;
554  }
555 
556  current_desc->properties = (jack_property_t*)realloc (current_desc->properties, sizeof(jack_property_t) * current_desc->property_size);
557  }
558 
559  current_prop = &current_desc->properties[current_desc->property_cnt++];
560 
561  /* copy key (without leading UUID) */
562 
563  len1 = key.size - JACK_UUID_STRING_SIZE;
564  current_prop->key = (char*)malloc (len1);
565  memcpy ((char*)current_prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1);
566 
567  /* copy data (which contains 1 or 2 null terminated strings, the value
568  and optionally a MIME type.
569  */
570 
571  len1 = strlen ((const char *)data.data) + 1;
572  current_prop->data = (char*)malloc (len1);
573  memcpy ((char*)current_prop->data, data.data, len1);
574 
575  if (len1 < data.size) {
576  len2 = strlen ((const char *)data.data + len1) + 1;
577 
578  current_prop->type = (char*)malloc (len2);
579  memcpy ((char*)current_prop->type, (const char *)data.data + len1, len2);
580  } else {
581  /* no type specified, assume default */
582  current_prop->type = NULL;
583  }
584 
585  /* if (key.size > 0) free(key.data); */
586  if (data.size > 0) {
587  free (data.data);
588  }
589  }
590 
591  cursor->close (cursor);
592 
593  (*descriptions) = desc;
594 
595  return dcnt;
596 
597 #else // !HAVE_DB
598  return -1;
599 #endif
600 }
601 
602 int JackMetadata::GetDescription(jack_uuid_t subject, jack_description_t* desc)
603 {
604  return 0;
605 }
606 
607 int JackMetadata::GetAllDescriptions(jack_description_t** descs)
608 {
609  return 0;
610 }
611 
612 void JackMetadata::FreeDescription(jack_description_t* desc, int free_actual_description_too)
613 {
614  uint32_t n;
615 
616  for (n = 0; n < desc->property_cnt; ++n) {
617  free ((char*)desc->properties[n].key);
618  free ((char*)desc->properties[n].data);
619  if (desc->properties[n].type) {
620  free ((char*)desc->properties[n].type);
621  }
622  }
623 
624  free (desc->properties);
625 
626  if (free_actual_description_too) {
627  free (desc);
628  }
629 }
630 
631 int JackMetadata::RemoveProperty(JackClient* client, jack_uuid_t subject, const char* key)
632 {
633 #if HAVE_DB
634 
635  DBT d_key;
636  int ret;
637 
638  if (PropertyInit()) {
639  return -1;
640  }
641 
642  MakeKeyDbt(&d_key, subject, key);
643  if ((ret = fDB->del (fDB, NULL, &d_key, 0)) != 0) {
644  jack_error ("Cannot delete key %s (%s)", key, db_strerror (ret));
645  if (d_key.size > 0) {
646  free (d_key.data);
647  }
648  return -1;
649  }
650 
651  PropertyChangeNotify(client, subject, key, PropertyDeleted);
652 
653  if (d_key.size > 0) {
654  free (d_key.data);
655  }
656 
657  return 0;
658 
659 #else // !HAVE_DB
660  return -1;
661 #endif
662 }
663 
664 int JackMetadata::RemoveProperties(JackClient* client, jack_uuid_t subject)
665 {
666 #if HAVE_DB
667 
668  DBT key;
669  DBT data;
670  DBC* cursor;
671  int ret;
672  char ustr[JACK_UUID_STRING_SIZE];
673  int retval = 0;
674  uint32_t cnt = 0;
675 
676  memset (ustr, 0, JACK_UUID_STRING_SIZE);
677  jack_uuid_unparse (subject, ustr);
678 
679  if (PropertyInit() || fDB == NULL) {
680  return -1;
681  }
682 
683  if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
684  jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
685  return -1;
686  }
687 
688  memset (&key, 0, sizeof(key));
689  memset (&data, 0, sizeof(data));
690  data.flags = DB_DBT_MALLOC;
691 
692  while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
693 
694  /* require 2 extra chars (data+null) for key,
695  which is composed of UUID str plus a key name
696  */
697 
698  if (key.size < JACK_UUID_STRING_SIZE + 2) {
699  /* if (key.size > 0) free(key.data); */
700  if (data.size > 0) {
701  free (data.data);
702  }
703  continue;
704  }
705 
706  if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
707  /* not relevant */
708  /* if (key.size > 0) free(key.data); */
709  if (data.size > 0) {
710  free (data.data);
711  }
712  continue;
713  }
714 
715  if ((ret = cursor->del (cursor, 0)) != 0) {
716  jack_error ("cannot delete property (%s)", db_strerror (ret));
717  /* don't return -1 here since this would leave things
718  even more inconsistent. wait till the cursor is finished
719  */
720  retval = -1;
721  }
722  cnt++;
723 
724  /* if (key.size > 0) free(key.data); */
725  if (data.size > 0) {
726  free (data.data);
727  }
728  }
729 
730  cursor->close (cursor);
731 
732  if (cnt) {
733  PropertyChangeNotify(client, subject, NULL, PropertyDeleted);
734  }
735 
736  if (retval) {
737  return -1;
738  }
739 
740  return cnt;
741 
742 #else // !HAVE_DB
743  return -1;
744 #endif
745 }
746 
747 int JackMetadata::RemoveAllProperties(JackClient* client)
748 {
749 #if HAVE_DB
750 
751  int ret;
752  jack_uuid_t empty_uuid = JACK_UUID_EMPTY_INITIALIZER;
753 
754  if (PropertyInit()) {
755  return -1;
756  }
757 
758  if ((ret = fDB->truncate (fDB, NULL, NULL, 0)) != 0) {
759  jack_error ("Cannot clear properties (%s)", db_strerror (ret));
760  return -1;
761  }
762 
763  PropertyChangeNotify(client, empty_uuid, NULL, PropertyDeleted);
764 
765  return 0;
766 
767 #else // !HAVE_DB
768  return -1;
769 #endif
770 }
771 
772 } // end of namespace
773 
774 
775 
LIB_EXPORT const char * JACK_METADATA_PRETTY_NAME
LIB_EXPORT const char * JACK_METADATA_EVENT_TYPES
SERVER_EXPORT void jack_error(const char *fmt,...)
Definition: JackError.cpp:92
const char * type
Definition: JackMetadata.h:38
const char * data
Definition: JackMetadata.h:37
uint32_t property_size
Definition: JackMetadata.h:45
LIB_EXPORT const char * JACK_METADATA_SIGNAL_TYPE
const char * key
Definition: JackMetadata.h:36
LIB_EXPORT const char * JACK_METADATA_ICON_LARGE
LIB_EXPORT const char * JACK_METADATA_ICON_NAME
LIB_EXPORT const char * JACK_METADATA_ORDER
jack_uuid_t subject
Definition: JackMetadata.h:42
LIB_EXPORT const char * JACK_METADATA_HARDWARE
LIB_EXPORT const char * JACK_METADATA_CONNECTED
jack_property_t * properties
Definition: JackMetadata.h:44
LIB_EXPORT const char * JACK_METADATA_ICON_SMALL
uint32_t property_cnt
Definition: JackMetadata.h:43