1 /* PipeWire
2 *
3 * Copyright © 2018 Wim Taymans
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 */
24
25 #include "config.h"
26
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <sys/mman.h>
30
31 #include <spa/utils/string.h>
32
33 #include <jack/metadata.h>
34 #include <jack/uuid.h>
35
36 #include <pipewire/pipewire.h>
37 #include <pipewire/extensions/metadata.h>
38
find_description(jack_uuid_t subject)39 static jack_description_t *find_description(jack_uuid_t subject)
40 {
41 jack_description_t *desc;
42 pw_array_for_each(desc, &globals.descriptions) {
43 if (jack_uuid_compare(desc->subject, subject) == 0)
44 return desc;
45 }
46 return NULL;
47 }
48
set_property(jack_property_t * prop,const char * key,const char * value,const char * type)49 static void set_property(jack_property_t *prop, const char *key, const char *value, const char *type)
50 {
51 prop->key = strdup(key);
52 prop->data = strdup(value);
53 prop->type = strdup(type);
54 }
55
copy_properties(jack_property_t * src,uint32_t cnt)56 static jack_property_t *copy_properties(jack_property_t *src, uint32_t cnt)
57 {
58 jack_property_t *dst;
59 uint32_t i;
60 dst = malloc(sizeof(jack_property_t) * cnt);
61 if (dst != NULL) {
62 for (i = 0; i < cnt; i++)
63 set_property(&dst[i], src[i].key, src[i].data, src[i].type);
64 }
65 return dst;
66 }
67
copy_description(jack_description_t * dst,jack_description_t * src)68 static int copy_description(jack_description_t *dst, jack_description_t *src)
69 {
70 dst->properties = copy_properties(src->properties, src->property_cnt);
71 if (dst->properties == NULL)
72 return -errno;
73 jack_uuid_copy(&dst->subject, src->subject);
74 dst->property_cnt = src->property_cnt;
75 dst->property_size = src->property_size;
76 return dst->property_cnt;
77 }
78
add_description(jack_uuid_t subject)79 static jack_description_t *add_description(jack_uuid_t subject)
80 {
81 jack_description_t *desc;
82 desc = pw_array_add(&globals.descriptions, sizeof(*desc));
83 if (desc != NULL) {
84 spa_zero(*desc);
85 jack_uuid_copy(&desc->subject, subject);
86 }
87 return desc;
88 }
89
remove_description(jack_description_t * desc)90 static void remove_description(jack_description_t *desc)
91 {
92 jack_free_description(desc, false);
93 pw_array_remove(&globals.descriptions, desc);
94 }
95
find_property(jack_description_t * desc,const char * key)96 static jack_property_t *find_property(jack_description_t *desc, const char *key)
97 {
98 uint32_t i;
99 for (i = 0; i < desc->property_cnt; i++) {
100 jack_property_t *prop = &desc->properties[i];
101 if (spa_streq(prop->key, key))
102 return prop;
103 }
104 return NULL;
105 }
106
add_property(jack_description_t * desc,const char * key,const char * value,const char * type)107 static jack_property_t *add_property(jack_description_t *desc, const char *key,
108 const char *value, const char *type)
109 {
110 jack_property_t *prop;
111
112 if (desc->property_cnt == desc->property_size) {
113 desc->property_size = desc->property_size > 0 ? desc->property_size * 2 : 8;
114 desc->properties = realloc(desc->properties, sizeof(*prop) * desc->property_size);
115 }
116 prop = &desc->properties[desc->property_cnt++];
117 set_property(prop, key, value, type);
118 return prop;
119 }
120
clear_property(jack_property_t * prop)121 static void clear_property(jack_property_t *prop)
122 {
123 free((char*)prop->key);
124 free((char*)prop->data);
125 free((char*)prop->type);
126 }
127
remove_property(jack_description_t * desc,jack_property_t * prop)128 static void remove_property(jack_description_t *desc, jack_property_t *prop)
129 {
130 clear_property(prop);
131 desc->property_cnt--;
132 memmove(desc->properties, SPA_PTROFF(prop, sizeof(*prop), void),
133 SPA_PTRDIFF(SPA_PTROFF(desc->properties, sizeof(*prop) * desc->property_cnt, void),
134 prop));
135
136 if (desc->property_cnt == 0)
137 remove_description(desc);
138 }
139
change_property(jack_property_t * prop,const char * value,const char * type)140 static int change_property(jack_property_t *prop, const char *value, const char *type)
141 {
142 int changed = 0;
143 if (!spa_streq(prop->data, value)) {
144 free((char*)prop->data);
145 prop->data = strdup(value);
146 changed++;
147 }
148 if (!spa_streq(prop->type, type)) {
149 free((char*)prop->type);
150 prop->type = strdup(type);
151 changed++;
152 }
153 return changed;
154 }
155
update_property(struct client * c,jack_uuid_t subject,const char * key,const char * type,const char * value)156 static int update_property(struct client *c,
157 jack_uuid_t subject,
158 const char* key,
159 const char* type,
160 const char* value)
161 {
162 jack_property_change_t change;
163 jack_description_t *desc;
164 int changed = 0;
165
166 pthread_mutex_lock(&globals.lock);
167 desc = find_description(subject);
168
169 if (key == NULL) {
170 if (desc != NULL) {
171 remove_description(desc);
172 change = PropertyDeleted;
173 changed++;
174 }
175 } else {
176 jack_property_t *prop;
177
178 prop = desc ? find_property(desc, key) : NULL;
179
180 if (value == NULL || type == NULL) {
181 if (prop != NULL) {
182 remove_property(desc, prop);
183 change = PropertyDeleted;
184 changed++;
185 }
186 } else if (prop == NULL) {
187 if (desc == NULL)
188 desc = add_description(subject);
189 add_property(desc, key, value, type);
190 change = PropertyCreated;
191 changed++;
192 } else {
193 changed = change_property(prop, value, type);
194 change = PropertyChanged;
195 }
196 }
197 pthread_mutex_unlock(&globals.lock);
198
199 if (c->property_callback && changed) {
200 pw_log_info("emit %lu %s", subject, key);
201 c->property_callback(subject, key, change, c->property_arg);
202 }
203
204 return changed;
205 }
206
207
208 SPA_EXPORT
jack_set_property(jack_client_t * client,jack_uuid_t subject,const char * key,const char * value,const char * type)209 int jack_set_property(jack_client_t*client,
210 jack_uuid_t subject,
211 const char* key,
212 const char* value,
213 const char* type)
214 {
215 struct client *c = (struct client *) client;
216 uint32_t id;
217 int res = -1;
218
219 spa_return_val_if_fail(c != NULL, -EINVAL);
220 spa_return_val_if_fail(key != NULL, -EINVAL);
221 spa_return_val_if_fail(value != NULL, -EINVAL);
222
223 pw_thread_loop_lock(c->context.loop);
224 if (c->metadata == NULL)
225 goto done;
226
227 if (subject & (1<<30))
228 goto done;
229
230 id = jack_uuid_to_index(subject);
231
232 if (type == NULL)
233 type = "";
234
235 pw_log_info("set id:%u (%"PRIu64") '%s' to '%s@%s'", id, subject, key, value, type);
236 if (update_property(c, subject, key, type, value))
237 pw_metadata_set_property(c->metadata->proxy, id, key, type, value);
238 res = 0;
239 done:
240 pw_thread_loop_unlock(c->context.loop);
241
242 return res;
243 }
244
245 SPA_EXPORT
jack_get_property(jack_uuid_t subject,const char * key,char ** value,char ** type)246 int jack_get_property(jack_uuid_t subject,
247 const char* key,
248 char** value,
249 char** type)
250 {
251 jack_description_t *desc;
252 jack_property_t *prop;
253 int res = -1;
254
255 pthread_mutex_lock(&globals.lock);
256 desc = find_description(subject);
257 if (desc == NULL)
258 goto done;
259
260 prop = find_property(desc, key);
261 if (prop == NULL)
262 goto done;
263
264 *value = strdup(prop->data);
265 *type = strdup(prop->type);
266 res = 0;
267
268 pw_log_debug("subject:%"PRIu64" key:'%s' value:'%s' type:'%s'",
269 subject, key, *value, *type);
270 done:
271 pthread_mutex_unlock(&globals.lock);
272 return res;
273 }
274
275 SPA_EXPORT
jack_free_description(jack_description_t * desc,int free_description_itself)276 void jack_free_description (jack_description_t* desc, int free_description_itself)
277 {
278 uint32_t n;
279
280 for (n = 0; n < desc->property_cnt; ++n)
281 clear_property(&desc->properties[n]);
282 free(desc->properties);
283 if (free_description_itself)
284 free(desc);
285 }
286
287 SPA_EXPORT
jack_get_properties(jack_uuid_t subject,jack_description_t * desc)288 int jack_get_properties (jack_uuid_t subject,
289 jack_description_t* desc)
290 {
291 jack_description_t *d;
292 int res = -1;
293
294 spa_return_val_if_fail(desc != NULL, -EINVAL);
295
296 pthread_mutex_lock(&globals.lock);
297 d = find_description(subject);
298 if (d == NULL)
299 goto done;
300
301 res = copy_description(desc, d);
302 done:
303 pthread_mutex_unlock(&globals.lock);
304 return res;
305 }
306
307 SPA_EXPORT
jack_get_all_properties(jack_description_t ** result)308 int jack_get_all_properties (jack_description_t** result)
309 {
310 uint32_t i;
311 jack_description_t *dst, *src;
312 struct pw_array *descriptions;
313 uint32_t len;
314
315 pthread_mutex_lock(&globals.lock);
316 descriptions = &globals.descriptions;
317 len = pw_array_get_len(descriptions, jack_description_t);
318 src = descriptions->data;
319 dst = malloc(descriptions->size);
320 for (i = 0; i < len; i++)
321 copy_description(&dst[i], &src[i]);
322 *result = dst;
323 pthread_mutex_unlock(&globals.lock);
324
325 return len;
326 }
327
328 SPA_EXPORT
jack_remove_property(jack_client_t * client,jack_uuid_t subject,const char * key)329 int jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key)
330 {
331 struct client *c = (struct client *) client;
332 uint32_t id;
333 int res = -1;
334
335 spa_return_val_if_fail(c != NULL, -EINVAL);
336 spa_return_val_if_fail(key != NULL, -EINVAL);
337
338 pw_thread_loop_lock(c->context.loop);
339
340 if (c->metadata == NULL)
341 goto done;
342
343 id = jack_uuid_to_index(subject);
344
345 pw_log_info("remove id:%u (%"PRIu64") '%s'", id, subject, key);
346 pw_metadata_set_property(c->metadata->proxy,
347 id, key, NULL, NULL);
348 res = 0;
349 done:
350 pw_thread_loop_unlock(c->context.loop);
351
352 return res;
353 }
354
355 SPA_EXPORT
jack_remove_properties(jack_client_t * client,jack_uuid_t subject)356 int jack_remove_properties (jack_client_t* client, jack_uuid_t subject)
357 {
358 struct client *c = (struct client *) client;
359 uint32_t id;
360 int res = -1;
361
362 spa_return_val_if_fail(c != NULL, -EINVAL);
363
364 pw_thread_loop_lock(c->context.loop);
365 if (c->metadata == NULL)
366 goto done;
367
368 id = jack_uuid_to_index(subject);
369
370 pw_log_info("remove id:%u (%"PRIu64")", id, subject);
371 pw_metadata_set_property(c->metadata->proxy,
372 id, NULL, NULL, NULL);
373 res = 0;
374 done:
375 pw_thread_loop_unlock(c->context.loop);
376
377 return res;
378 }
379
380 SPA_EXPORT
jack_remove_all_properties(jack_client_t * client)381 int jack_remove_all_properties (jack_client_t* client)
382 {
383 struct client *c = (struct client *) client;
384
385 spa_return_val_if_fail(c != NULL, -EINVAL);
386
387 pw_thread_loop_lock(c->context.loop);
388 pw_metadata_clear(c->metadata->proxy);
389 pw_thread_loop_unlock(c->context.loop);
390
391 return 0;
392 }
393
394 SPA_EXPORT
jack_set_property_change_callback(jack_client_t * client,JackPropertyChangeCallback callback,void * arg)395 int jack_set_property_change_callback (jack_client_t* client,
396 JackPropertyChangeCallback callback,
397 void* arg)
398 {
399 struct client *c = (struct client *) client;
400
401 spa_return_val_if_fail(c != NULL, -EINVAL);
402
403 c->property_callback = callback;
404 c->property_arg = arg;
405 return 0;
406 }
407