support for resource.set.appid property for streams
[profile/ivi/pulseaudio-module-murphy-ivi.git] / murphy / classify.c
1 /*
2  * module-murphy-ivi -- PulseAudio module for providing audio routing support
3  * Copyright (c) 2012, Intel Corporation.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms and conditions of the GNU Lesser General Public License,
7  * version 2.1, as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.
12  * See the GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15 Requires(postun): /sbin/ldconfig
16 ot, write to the
17  * Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston,
18  * MA 02110-1301 USA.
19  *
20  */
21 #define _GNU_SOURCE
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <stdio.h>
28 #include <string.h>
29
30 #include <pulsecore/pulsecore-config.h>
31
32 #include <pulsecore/sink.h>
33 #include <pulsecore/card.h>
34 #include <pulsecore/device-port.h>
35 #include <pulsecore/core-util.h>
36
37 #include <aul.h>
38 #include <bundle.h>
39
40 #include "classify.h"
41 #include "node.h"
42 #include "utils.h"
43
44
45 static int pid2exe(pid_t, char *, size_t);
46 static char *pid2appid(pid_t, char *, size_t);
47
48 void pa_classify_node_by_card(mir_node        *node,
49                               pa_card         *card,
50                               pa_card_profile *prof,
51                               pa_device_port  *port)
52 {
53     const char *bus;
54     const char *form;
55     /*
56     const char *desc;
57     */
58
59     pa_assert(node);
60     pa_assert(card);
61
62     bus  = pa_utils_get_card_bus(card);
63     form = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_FORM_FACTOR);
64     /*
65     desc = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION);
66     */
67
68     node->type = mir_node_type_unknown;
69
70     if (form) {
71         if (!strcasecmp(form, "internal")) {
72             node->location = mir_external;
73             if (port && !strcasecmp(bus, "pci")) {
74                 pa_classify_guess_device_node_type_and_name(node, port->name,
75                                                             port->description);
76             }
77         }
78         else if (!strcasecmp(form, "speaker") || !strcasecmp(form, "car")) {
79             if (node->direction == mir_output) {
80                 node->location = mir_internal;
81                 node->type = mir_speakers;
82             }
83         }
84         else if (!strcasecmp(form, "handset")) {
85             node->location = mir_external;
86             node->type = mir_phone;
87             node->privacy = mir_private;
88         }
89         else if (!strcasecmp(form, "headset")) {
90             node->location = mir_external;
91             if (bus) {
92                 if (!strcasecmp(bus,"usb")) {
93                     node->type = mir_usb_headset;
94                 }
95                 else if (!strcasecmp(bus,"bluetooth")) {
96                     if (prof && !strcmp(prof->name, "a2dp"))
97                         node->type = mir_bluetooth_a2dp;
98                     else
99                         node->type = mir_bluetooth_sco;
100                 }
101                 else {
102                     node->type = mir_wired_headset;
103                 }
104             }
105         }
106         else if (!strcasecmp(form, "headphone")) {
107             if (node->direction == mir_output) {
108                 node->location = mir_external;
109                 if (bus) {
110                     if (!strcasecmp(bus,"usb"))
111                         node->type = mir_usb_headphone;
112                     else if (strcasecmp(bus,"bluetooth"))
113                         node->type = mir_wired_headphone;
114                 }
115             }
116         }
117         else if (!strcasecmp(form, "microphone")) {
118             if (node->direction == mir_input) {
119                 node->location = mir_external;
120                 node->type = mir_microphone;
121             }
122         }
123         else if (!strcasecmp(form, "phone")) {
124             if (bus && !strcasecmp(bus,"bluetooth") && prof) {
125                 if (!strcmp(prof->name, "a2dp"))
126                     node->type = mir_bluetooth_a2dp;
127                 else if (!strcmp(prof->name, "hsp"))
128                     node->type = mir_bluetooth_sco;
129                 else if (!strcmp(prof->name, "hfgw"))
130                     node->type = mir_bluetooth_carkit;
131                 else if (!strcmp(prof->name, "a2dp_source"))
132                     node->type = mir_bluetooth_source;
133                 else if (!strcmp(prof->name, "a2dp_sink"))
134                     node->type = mir_bluetooth_sink;
135
136                 if (node->type != mir_node_type_unknown)
137                     node->location = mir_external;
138             }
139         }
140     }
141     else {
142         if (port && !strcasecmp(bus, "pci")) {
143             pa_classify_guess_device_node_type_and_name(node, port->name,
144                                                         port->description);
145         }
146         else if (prof && !strcasecmp(bus, "bluetooth")) {
147             if (!strcmp(prof->name, "a2dp"))
148                 node->type = mir_bluetooth_a2dp;
149             else if (!strcmp(prof->name, "hsp"))
150                 node->type = mir_bluetooth_sco;
151             else if (!strcmp(prof->name, "hfgw"))
152                 node->type = mir_bluetooth_carkit;
153             else if (!strcmp(prof->name, "a2dp_source"))
154                 node->type = mir_bluetooth_source;
155             else if (!strcmp(prof->name, "a2dp_sink"))
156                 node->type = mir_bluetooth_sink;
157         }
158     }
159
160     if (!node->amname[0]) {
161        if (node->type != mir_node_type_unknown)
162             node->amname = (char *)mir_node_type_str(node->type);
163         else if (port && port->description)
164             node->amname = port->description;
165         else if (port && port->name)
166             node->amname = port->name;
167         else
168             node->amname = node->paname;
169     }
170
171
172     if (node->direction == mir_input)
173         node->privacy = mir_privacy_unknown;
174     else {
175         switch (node->type) {
176             /* public */
177         default:
178         case mir_speakers:
179         case mir_front_speakers:
180             node->privacy = mir_public;
181             break;
182             
183             /* private */
184         case mir_phone:
185         case mir_wired_headset:
186         case mir_wired_headphone:
187         case mir_usb_headset:
188         case mir_usb_headphone:
189         case mir_bluetooth_sco:
190         case mir_bluetooth_a2dp:
191             node->privacy = mir_private;
192             break;
193             
194             /* unknown */
195         case mir_null:
196         case mir_jack:
197         case mir_spdif:
198         case mir_hdmi:
199         case mir_bluetooth_sink:
200             node->privacy = mir_privacy_unknown;
201             break;
202         } /* switch */
203     }
204 }
205
206 pa_bool_t pa_classify_node_by_property(mir_node *node, pa_proplist *pl)
207 {
208     typedef struct {
209         const char *name;
210         mir_node_type value;
211     } type_mapping_t;
212
213     static type_mapping_t  map[] = {
214         {"speakers"      , mir_speakers         },
215         {"front-speakers", mir_front_speakers   },
216         {"rear-speakers" , mir_rear_speakers    },
217         {"microphone"    , mir_microphone       },
218         {"jack"          , mir_jack             },
219         {"hdmi"          , mir_hdmi             },
220         {"spdif"         , mir_spdif            },
221         { NULL           , mir_node_type_unknown}
222     };
223
224     const char *type;
225     int i;
226
227     pa_assert(node);
228     pa_assert(pl);
229
230     if ((type = pa_proplist_gets(pl, PA_PROP_NODE_TYPE))) {
231         for (i = 0; map[i].name;  i++) {
232             if (pa_streq(type, map[i].name)) {
233                 node->type = map[i].value;
234                 return TRUE;
235             }
236         }
237     }
238
239     return FALSE;
240 }
241
242 /* data->direction must be set */
243 void pa_classify_guess_device_node_type_and_name(mir_node   *node,
244                                                  const char *name,
245                                                  const char *desc)
246 {
247     pa_assert(node);
248     pa_assert(name);
249     pa_assert(desc);
250
251     if (node->direction == mir_output && strcasestr(name, "headphone")) {
252         node->type = mir_wired_headphone;
253         node->amname = (char *)desc;
254     }
255     else if (strcasestr(name, "headset")) {
256         node->type = mir_wired_headset;
257         node->amname = (char *)desc;
258     }
259     else if (strcasestr(name, "line")) {
260         node->type = mir_jack;
261         node->amname = (char *)desc;
262     }
263     else if (strcasestr(name, "spdif")) {
264         node->type = mir_spdif;
265         node->amname = (char *)desc;
266     }
267     else if (strcasestr(name, "hdmi")) {
268         node->type = mir_hdmi;
269         node->amname = (char *)desc;
270     }
271     else if (node->direction == mir_input &&
272              (strcasestr(name, "microphone") || strcasestr(desc, "microphone")))
273     {
274         node->type = mir_microphone;
275         node->amname = (char *)desc;
276     }
277     else if (node->direction == mir_output && strcasestr(name,"analog-output"))
278         node->type = mir_speakers;
279     else if (node->direction == mir_input && strcasestr(name, "analog-input"))
280         node->type = mir_jack;
281     else {
282         node->type = mir_node_type_unknown;
283     }
284 }
285
286
287 mir_node_type pa_classify_guess_stream_node_type(struct userdata *u,
288                                                  pa_proplist *pl,
289                                                  pa_nodeset_resdef **resdef)
290 {
291     pa_nodeset_map *map = NULL;
292     const char     *role;
293     const char     *bin;
294     char            buf[4096];
295     char            appid[PATH_MAX];
296     const char     *pidstr;
297     const char     *name;
298     int             pid;
299
300     pa_assert(u);
301     pa_assert(pl);
302
303
304     do {
305         if (!(pidstr = pa_proplist_gets(pl, PA_PROP_APPLICATION_PROCESS_ID)) ||
306             (pid = strtol(pidstr, NULL, 10)) < 2)
307         {
308             pid = 0;
309         }
310
311         if ((bin = pa_proplist_gets(pl, PA_PROP_APPLICATION_PROCESS_BINARY))) {
312             if (!strcmp(bin, "threaded-ml") || !strcmp(bin, "WebProcess")) {
313                 if (!pid)
314                     break;
315
316                 if (aul_app_get_appid_bypid(pid, buf, sizeof(buf)) < 0 &&
317                     pid2exe(pid, buf, sizeof(buf)) < 0)
318                 {
319                     pa_log("can't obtain real application name for wrt '%s' "
320                            "(pid %d)", bin, pid);
321                     break;
322                 }
323
324                 if ((name = strrchr(buf, '.')))
325                     name++;
326                 else
327                     name = buf;
328
329                 pa_proplist_sets(pl, PA_PROP_APPLICATION_NAME, name);
330                 pa_proplist_sets(pl, PA_PROP_APPLICATION_PROCESS_BINARY, buf);
331
332                 bin = buf;
333             }
334
335             if ((map = pa_nodeset_get_map_by_binary(u, bin))) {
336                 if (map->role)
337                     pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, map->role);
338                 break;
339             }
340         }
341
342         if ((role = pa_proplist_gets(pl, PA_PROP_MEDIA_ROLE)) &&
343             (map = pa_nodeset_get_map_by_role(u, role)))
344             break;
345
346         if (resdef)
347             *resdef = NULL;
348
349         return role ? mir_node_type_unknown : mir_player;
350
351     } while (0);
352
353     if (pid2appid(pid, appid, sizeof(appid)))
354         pa_proplist_sets(pl, PA_PROP_RESOURCE_SET_APPID, appid);
355
356     if (resdef)
357         *resdef = map ? map->resdef : NULL;
358
359     return map ? map->type : mir_player;
360 }
361
362 static char *get_tag(pid_t pid, char *tag, char *buf, size_t size)
363 {
364     char path[PATH_MAX];
365     char data[8192], *p, *q;
366     int  fd, n, tlen;
367
368     fd = -1;
369     snprintf(path, sizeof(path), "/proc/%u/status", pid);
370
371     if ((fd = open(path, O_RDONLY)) < 0) {
372     fail:
373         if (fd >= 0)
374             close(fd);
375         return NULL;
376     }
377
378     if ((n = read(fd, data, sizeof(data) - 1)) <= 0)
379         goto fail;
380     else
381         data[sizeof(data)-1] = '\0';
382
383     close(fd);
384     fd = -1;
385     tlen = strlen(tag);
386
387     p = data;
388     while (*p) {
389         if (*p != '\n' && p != data) {
390             while (*p && *p != '\n')
391                 p++;
392         }
393
394         if (*p == '\n')
395             p++;
396         else
397             if (p != data)
398                 goto fail;
399
400         if (!strncmp(p, tag, tlen) && p[tlen] == ':') {
401             p += tlen + 1;
402             while (*p == ' ' || *p == '\t')
403                 p++;
404
405             q = buf;
406             while (*p != '\n' && *p && size > 1)
407                 *q++ = *p++;
408             *q = '\0';
409
410             return buf;
411         }
412         else
413             p++;
414     }
415
416     goto fail;
417 }
418
419
420 static pid_t get_ppid(pid_t pid)
421 {
422     char  buf[32], *end;
423     pid_t ppid;
424
425     if (get_tag(pid, "PPid", buf, sizeof(buf)) != NULL) {
426         ppid = strtoul(buf, &end, 10);
427
428         if (end && !*end)
429             return ppid;
430     }
431
432     return 0;
433 }
434
435
436 static int pid2exe(pid_t pid, char *buf, size_t len)
437 {
438     pid_t ppid;
439     FILE *f;
440     char path[PATH_MAX];
441     char *p, *q;
442     int st = -1;
443
444     if (buf && len > 0) {
445         ppid = get_ppid(pid);
446
447         snprintf(path, sizeof(path), "/proc/%u/cmdline", ppid);
448
449         if ((f = fopen(path, "r"))) {
450             if (fgets(buf, len-1, f)) {
451                 if ((p = strchr(buf, ' ')))
452                     *p = '\0';
453                 else if ((p = strchr(buf, '\n')))
454                     *p = '\0';
455                 else
456                     p = buf + strlen(buf);
457
458                 if ((q = strrchr(buf, '/')))
459                     memmove(buf, q+1, p-q); 
460
461                 st = 0;
462             }
463             fclose(f);
464         }
465     }
466
467     if (st < 0)
468         pa_log("pid2exe(%u) failed", pid);
469     else
470         pa_log_debug("pid2exe(%u) => exe %s", pid, buf);
471
472     return st;
473 }
474
475
476 static char *get_binary(pid_t pid, char *buf, size_t size)
477 {
478     char    path[128];
479     ssize_t len;
480
481     snprintf(path, sizeof(path), "/proc/%u/exe", pid);
482     if ((len = readlink(path, buf, size - 1)) > 0) {
483         buf[len] = '\0';
484         return buf;
485     }
486     else
487         return NULL;
488 }
489
490
491 static char *strprev(char *point, char c, char *base)
492 {
493     while (point > base && *point != c)
494         point--;
495
496     if (*point == c)
497         return point;
498     else
499         return NULL;
500 }
501
502
503 static char *pid2appid(pid_t pid, char *buf, size_t size)
504 {
505     char binary[PATH_MAX];
506     char path[PATH_MAX], *dir, *p, *base;
507     int  len;
508
509     if (!pid || !get_binary(pid, binary, sizeof(binary)))
510         return NULL;
511
512     strncpy(path, binary, sizeof(path) - 1);
513     path[sizeof(path) - 1] = '\0';
514
515     /* fetch basename */
516     if ((p = strrchr(path, '/')) == NULL || p == path) {
517         strncpy(buf, binary, size - 1);
518         buf[size - 1] = '\0';
519         return buf;
520     }
521
522     base = p-- + 1;
523
524     /* fetch ../bin/<basename> */
525     if ((p = strprev(p, '/', path)) == NULL || p == path)
526         goto return_base;
527
528     if (strncmp(p + 1, "bin/", 4) != 0)
529         goto return_base;
530     else
531         p--;
532
533     /* fetch dir name above bin */
534     if ((dir = strprev(p, '/', path)) == NULL || dir == path)
535         goto return_base;
536
537     len = p - dir;
538
539     /* fetch 'apps' dir */
540     p = dir - 1;
541
542     if ((p = strprev(p, '/', path)) == NULL)
543         goto return_base;
544
545     if (strncmp(p + 1, "apps/", 5) != 0)
546         goto return_base;
547
548     if (len + 1 <= size) {
549         strncpy(buf, dir + 1, len);
550         buf[len] = '\0';
551
552         return buf;
553     }
554
555  return_base:
556     strncpy(buf, base, size - 1);
557     buf[size - 1] = '\0';
558     return buf;
559 }
560
561
562 mir_node_type pa_classify_guess_application_class(mir_node *node)
563 {
564     mir_node_type class;
565
566     pa_assert(node);
567
568     if (node->implement == mir_stream)
569         class = node->type;
570     else {
571         if (node->direction == mir_output)
572             class = mir_node_type_unknown;
573         else {
574             switch (node->type) {
575             default:                    class = mir_node_type_unknown;   break;
576             case mir_bluetooth_carkit:  class = mir_phone;               break;
577             case mir_bluetooth_source:  class = mir_player;              break;
578             }
579         }
580     }
581
582     return class;
583 }
584
585
586 pa_bool_t pa_classify_multiplex_stream(mir_node *node)
587 {
588     static pa_bool_t multiplex[mir_application_class_end] = {
589         [ mir_player  ] = TRUE,
590         [ mir_game    ] = TRUE,
591     };
592
593     mir_node_type class;
594
595     pa_assert(node);
596
597     if (node->implement == mir_stream && node->direction == mir_input) {
598         class = node->type;
599
600         if (class > mir_application_class_begin &&
601             class < mir_application_class_end)
602         {
603             return multiplex[class];
604         }
605     }
606
607     return FALSE;
608 }
609
610 const char *pa_classify_loopback_stream(mir_node *node)
611 {
612     const char *role[mir_device_class_end - mir_device_class_begin] = {
613         [ mir_bluetooth_carkit - mir_device_class_begin ] = "phone",
614         [ mir_bluetooth_source - mir_device_class_begin ] = "music" ,
615     };
616
617     int class;
618
619     pa_assert(node);
620
621     if (node->implement == mir_device) {
622         class = node->type;
623
624         if (class >= mir_device_class_begin && class < mir_device_class_end) {
625             return role[class - mir_device_class_begin];
626         }
627     }
628
629     return NULL;
630 }
631
632 /*
633  * Local Variables:
634  * c-basic-offset: 4
635  * indent-tabs-mode: nil
636  * End:
637  *
638  */