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