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