xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #849: palib.patch

File palib.patch, 13.0 KB (added by Antoine Martin, 6 years ago)

support for using palib instead of execing pactl and parsing the output

  • xpra/sound/pulseaudio_pactl_util.py

     
    132132    return get_x11_property("PULSE_ID")
    133133
    134134
    135 def add_audio_tagging_env(icon_path=None):
    136     """
    137         This is called audio-tagging in PulseAudio, see:
    138         http://pulseaudio.org/wiki/ApplicationProperties
    139         http://0pointer.de/blog/projects/tagging-audio.html
    140     """
    141     os.environ["PULSE_PROP_application.name"] = "xpra"
    142     os.environ["PULSE_PROP_media.role"] = "music"
    143     if icon_path and os.path.exists(icon_path):
    144         os.environ["PULSE_PROP_application.icon_name"] = icon_path
    145 
    146 
    147135def get_pa_device_options(monitors=False, input_or_output=None, ignored_devices=["bell-window-system"], log_errors=True):
    148136    """
    149137    Finds the list of devices, monitors=False allows us to filter out monitors
     
    194182            device_description = line[len("device.description = "):].strip('"')
    195183    return devices
    196184
     185
    197186def get_info():
    198187    info = {
     188            "pulseaudio.wrapper": "pactl",
     189            "pulseaudio.found"  : has_pa(),
    199190            "pulseaudio.id"     : get_pulse_id(),
    200191            "pulseaudio.server" : get_pulse_server(False),
    201192           }
  • xpra/sound/pulseaudio_palib_util.py

     
     1#!/usr/bin/env python
     2# This file is part of Xpra.
     3# Copyright (C) 2010-2014 Antoine Martin <antoine@devloop.org.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7import sys
     8
     9from xpra.log import Logger
     10log = Logger("sound")
     11log.enable_debug()
     12
     13import palib
     14
     15"""
     16#context = palib.PulseObj("Xpra", None, True)
     17class PALibContext(object):
     18    def __init__(self):
     19        pass
     20
     21    def __enter__(self):
     22        log("PALibContext.__enter__()")
     23        self.context = context
     24
     25    def __exit__(self, e_typ, e_val, trcbak):
     26        log("PALibContext.__exit__(%s) context=%s", (e_typ, e_val, trcbak), self.context)
     27"""
     28
     29class PALibContext(object):
     30    def __init__(self):
     31        pass
     32
     33    def __enter__(self):
     34        log("PALibContext.__enter__()")
     35        self.context = palib.PulseObj("Xpra", None, True)
     36
     37    def __exit__(self, e_typ, e_val, trcbak):
     38        log("PALibContext.__exit__(%s) context=%s", (e_typ, e_val, trcbak), self.context)
     39        if self.context:
     40            self.context.pulse_disconnect()
     41            self.context = None
     42
     43def has_pa():
     44    try:
     45        c = PALibContext()
     46        with c:
     47            pac = c.context
     48            log("has_pa() context=%s", pac)
     49            log("has_pa() connected=%s", pac.connected)
     50            log("has_pa()=%s", pac.action_done)
     51            return pac.action_done
     52    except Exception as e:
     53        log("has_pa() %s", e)
     54        return False
     55
     56def set_source_mute(device, mute=False):
     57    pass
     58
     59def get_default_sink():
     60    pass
     61    #return get_pactl_stat_line("Default Sink:")
     62
     63def get_pactl_server():
     64    pass
     65    #return get_pactl_stat_line("Server String:")
     66
     67
     68def get_pulse_server(may_start_it=True):
     69    return None
     70
     71def get_pulse_id():
     72    return None
     73    #return get_x11_property("PULSE_ID")
     74
     75
     76
     77def get_pa_device_options(monitors=False, input_or_output=None, ignored_devices=["bell-window-system"], log_errors=True):
     78    c = PALibContext()
     79    with c:
     80        pac = c.context
     81        if input_or_output:
     82            #input:
     83            if monitors:
     84                fn = pac.pulse_sink_input_list
     85            else:
     86                fn = pac.pulse_sink_list
     87        else:
     88            #output:
     89            fn = pac.pulse_source_list
     90        log("get_pa_device_options(%s, %s, %s, %s) calling %s", monitors, input_or_output, ignored_devices, log_errors, fn)
     91        v = fn()
     92        log("%s()=%s", fn, v)
     93        assert pac.action_done, "action not done"
     94    return {}
     95
     96def get_info():
     97    info = {
     98            "pulseaudio.wrapper": "palib",
     99            "pulseaudio.found"  : has_pa(),
     100            "pulseaudio.id"     : get_pulse_id(),
     101            "pulseaudio.server" : get_pulse_server(False),
     102           }
     103    i = 0
     104    for monitors in (True, False):
     105        for io in (True, False):
     106            devices = get_pa_device_options(monitors, io, log_errors=False)
     107            for d,name in devices.items():
     108                info["device.%s" % d] = name
     109            i += 1
     110    info["devices"] = i
     111    return info
     112
     113
     114def main():
     115    if "-v" in sys.argv:
     116        log.enable_debug()
     117    for k,v in get_info().items():
     118        log.info("%s : %s", k.ljust(64), v)
     119
     120if __name__ == "__main__":
     121    main()
  • xpra/sound/pulseaudio_util.py

    Property changes on: xpra/sound/pulseaudio_palib_util.py
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
    11#!/usr/bin/env python
    22# This file is part of Xpra.
    3 # Copyright (C) 2010-2014 Antoine Martin <antoine@devloop.org.uk>
     3# Copyright (C) 2010-2015 Antoine Martin <antoine@devloop.org.uk>
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
     
    77import sys
    88import os.path
    99
    10 from xpra.scripts.exec_util import safe_exec
    11 
    1210from xpra.log import Logger
    1311log = Logger("sound")
    1412
    1513
    16 def which(name):
    17     if sys.platform.startswith("win"):
    18         return    ""
    19     cmd = ["which", name]
    20     try:
    21         returncode, out, _ = safe_exec(cmd, log_errors=False)
    22         log("safe_exec(%s)=%s", cmd, (returncode, out))
    23         if returncode!=0 or not out:
    24             return ""
    25         c = out.decode("utf8").replace("\n", "").replace("\r", "")
    26         if os.path.exists(c) and os.path.isfile(c):
    27             if os.name=="posix" and not os.access(c, os.X_OK):
    28                 #odd, it's there but we can't run it!?
    29                 return ""
    30             return c
    31         return ""
    32     except:
    33         log.error("which(%s) error", name, exc_info=True)
    34     return ""
    35 
    36 pactl_bin = None
    37 has_pulseaudio = None
    38 
    39 def get_pactl_bin():
    40     global pactl_bin
    41     if pactl_bin is None:
    42         if sys.platform.startswith("win") or sys.platform.startswith("darwin"):
    43             pactl_bin = ""
    44         else:
    45             pactl_bin = which("pactl")
    46     return pactl_bin
    47 
    48 def pactl_output(log_errors=True, *pactl_args):
    49     pactl_bin = get_pactl_bin()
    50     if not pactl_bin:
    51         return -1, None
    52     #ie: "pactl list"
    53     cmd = [pactl_bin] + list(pactl_args)
    54     try:
    55         code, out, _ = safe_exec(cmd, log_errors=log_errors)
    56         log("pactl_output%s returned %s", pactl_args, code)
    57         return  code, out
    58     except Exception as e:
    59         if log_errors:
    60             log.error("failed to execute %s: %s", cmd, e)
    61         else:
    62             log("failed to execute %s: %s", cmd, e)
    63         return  -1, None
    64 
    65 def get_x11_property(atom_name):
    66     if sys.platform.startswith("darwin") or sys.platform.startswith("win"):
    67         return ""
    68     try:
    69         from gtk import gdk
    70         root = gdk.get_default_root_window()
    71         atom = gdk.atom_intern(atom_name)
    72         p = root.property_get(atom)
    73         if p is None:
    74             return ""
    75         v = p[2]
    76         log("get_x11_property(%s)=%s", atom_name, v)
    77         return v
    78     except:
    79         return ""
    80 
    81 def has_pa_x11_property():
    82     #try root window property (faster)
    83     ps = get_x11_property(b"PULSE_SERVER")
    84     log("has_pa_x11_property: get_x11_property(PULSE_SERVER)=%s", ps)
    85     return len(ps)>0
    86 
    87 def is_pa_installed():
    88     pactl_bin = get_pactl_bin()
    89     log("is_pa_installed() pactl_bin=%s", pactl_bin)
    90     return len(pactl_bin)>0
    91 
    92 def has_pa():
    93     global has_pulseaudio
    94     if has_pulseaudio is None:
    95         has_pulseaudio = has_pa_x11_property() or is_pa_installed()
    96     return has_pulseaudio
    97 
    98 
    99 def set_source_mute(device, mute=False):
    100     code, out = pactl_output(True, "set-source-mute", device, str(int(mute)))
    101     log("set_source_mute: output=%s", out)
    102     return code==0
    103 
    104 def get_pactl_stat_line(prefix):
    105     if not has_pa():
    106         return ""
    107     code, out = pactl_output(True, "stat")
    108     if code!=0:
    109         return    ""
    110     stat = ""
    111     for line in out.splitlines():
    112         if line.startswith(prefix):
    113             stat = line[len(prefix):].strip()
    114             break
    115     log("get_pactl_stat_line(%s)=%s", prefix, stat)
    116     return stat
    117 
    118 def get_default_sink():
    119     return get_pactl_stat_line("Default Sink:")
    120 
    121 def get_pactl_server():
    122     return get_pactl_stat_line("Server String:")
    123 
    124 
    125 def get_pulse_server(may_start_it=True):
    126     xp = get_x11_property("PULSE_SERVER")
    127     if xp or not may_start_it:
    128         return xp
    129     return get_pactl_server()
    130 
    131 def get_pulse_id():
    132     return get_x11_property("PULSE_ID")
    133 
    134 
    13514def add_audio_tagging_env(icon_path=None):
    13615    """
    13716        This is called audio-tagging in PulseAudio, see:
     
    14423        os.environ["PULSE_PROP_application.icon_name"] = icon_path
    14524
    14625
    147 def get_pa_device_options(monitors=False, input_or_output=None, ignored_devices=["bell-window-system"], log_errors=True):
    148     """
    149     Finds the list of devices, monitors=False allows us to filter out monitors
    150     (which could create sound loops if we use them)
    151     set input_or_output=True to get inputs only
    152     set input_or_output=False to get outputs only
    153     set input_or_output=None to get both
    154     Same goes for monitors (False|True|None)
    155     Returns the a dict() with the PulseAudio name as key and a description as value
    156     """
    157     if sys.platform.startswith("win") or sys.platform.startswith("darwin"):
    158         return {}
    159     status, out = pactl_output(False, "list")
    160     if status!=0 or not out:
    161         return  {}
    162     device_class = None
    163     device_description = None
    164     name = None
    165     devices = {}
    166     for line in out.splitlines():
    167         if not line.startswith(" ") and not line.startswith("\t"):        #clear vars when we encounter a new section
    168             if name and device_class:
    169                 if name in ignored_devices:
    170                     continue
    171                 #Verify against monitor flag if set:
    172                 if monitors is not None:
    173                     is_monitor = device_class=='"monitor"'
    174                     if is_monitor!=monitors:
    175                         continue
    176                 #Verify against input flag (if set):
    177                 if input_or_output is not None:
    178                     is_input = name.find("input")>=0
    179                     if is_input is True and input_or_output is False:
    180                         continue
    181                     is_output = name.find("output")>=0
    182                     if is_output is True and input_or_output is True:
    183                         continue
    184                 if not device_description:
    185                     device_description = name
    186                 devices[name] = device_description
    187             name = None; device_class = None
    188         line = line.strip()
    189         if line.startswith("Name: "):
    190             name = line[len("Name: "):]
    191         if line.startswith("device.class = "):
    192             device_class = line[len("device-class = "):]
    193         if line.startswith("device.description = "):
    194             device_description = line[len("device.description = "):].strip('"')
    195     return devices
     26#prefer the palib option which does everything in process:
     27try:
     28    if os.environ.get("XPRA_USE_PACTL", "0")=="1":
     29        raise ImportError("environment override: not using palib")
     30    from xpra.sound import pulseaudio_palib_util as _pulseaudio_util
     31except ImportError as e:
     32    #fallback forks a process and parses the output:
     33    log.warn("palib not available, using legacy pactl fallback")
     34    from xpra.sound import pulseaudio_pactl_util as  _pulseaudio_util       #@Reimport
    19635
    197 def get_info():
    198     info = {
    199             "pulseaudio.id"     : get_pulse_id(),
    200             "pulseaudio.server" : get_pulse_server(False),
    201            }
    202     i = 0
    203     for monitors in (True, False):
    204         for io in (True, False):
    205             devices = get_pa_device_options(monitors, io, log_errors=False)
    206             for d,name in devices.items():
    207                 info["device.%s" % d] = name
    208             i += 1
    209     info["devices"] = i
    210     return info
     36get_info                = _pulseaudio_util.get_info
     37has_pa                  = _pulseaudio_util.has_pa
     38get_pa_device_options   = _pulseaudio_util.get_pa_device_options
     39get_default_sink        = _pulseaudio_util.get_default_sink
     40get_pulse_server        = _pulseaudio_util.get_pulse_server
     41get_pulse_id            = _pulseaudio_util.get_pulse_id
     42set_source_mute         = _pulseaudio_util.set_source_mute
     43get_pa_device_options   = _pulseaudio_util.get_pa_device_options
    21144
    21245
    21346def main():