#!/usr/bin/python3 -u

# autojack - Monitors dbus for added audio devices (hot plugged USB audio intefaces)
# on detect it does one of three things:
# makes it the jack device
# makes it a jack client (via zita-ajbridge)
# nothing
#
# Monitors acpi for headphone un/plug  and reroutes audio if needed
#
# autojack also monitors dbus for messages from studio-controls to:
# - stop jack
# - re/start jack
# - remove a USB device as jack master to allow safe device removal
# - reread ~/.config/autojackrc and apply any changes

import os
from os.path import expanduser
import re
import time
from gi.repository import GLib
import dbus
import dbus.mainloop.glib
import subprocess
import shlex
import signal
import configparser
import logging
import jack
import alsaaudio

def get_dev_info(x):
    '''uses audio device number to make a device structure with device
    info. The layout is:
        device[0] = device name as a string
        device[1] = bool True is USB device
        device[2] = bool True is internal device
        device[3] = bool True is firewire device
        device[4] = number of sub devices (0 means MIDI only)
        device[5 to n] = subdevice info as a list:
            device[n][0] = sub device number (some devices skip...)
            device[n][1] = bool has playback
            device[n][2] = PID of playback user
            device[n][3] = bool has capture
            device[n][4] = PID of capture user
    This structure is returned. If the device does not
    exist, a minimal structure is still returned ["", False, 0]'''
    device = []
    cname = ""
    l_usb = False
    l_internal = False
    l_fw = False
    sub = 0
    if os.path.exists(f"/proc/asound/card{str(x)}"):
        if os.path.isfile(f"/proc/asound/card{str(x)}/id"):
            with open(f"/proc/asound/card{str(x)}/id", "r") as card_file:
                for line in card_file:
                    # only need one line
                    cname = line.rstrip()
        else:
            cname = str(x)

    if os.path.exists(f"/proc/asound/card{str(x)}/usbbus"):
        l_usb = True
    if os.path.exists(f"/proc/asound/card{str(x)}/codec#0"):
        l_internal = True
    if os.path.exists(f"/proc/asound/card{str(x)}/firewire"):
        l_fw = True

    device.append(cname)
    device.append(l_usb)
    device.append(l_internal)
    device.append(l_fw)
    device.append(sub)

    for y in range(0, 20):
        subdevice = []
        cap = False
        cap_pid = 0
        play = False
        play_pid = 0
        if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}p"):
            play = True
            if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}p/sub0"):
                with open(f"/proc/asound/card{str(x)}/pcm{str(y)}p/sub0/status", "r") as info_file:
                    for line in info_file:
                        if re.match("^owner_pid", line.rstrip()):
                            play_pid = int(line.rstrip().split(": ", 1)[1])

        if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}c"):
            cap = True
            if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}c/sub0"):
                with open(f"/proc/asound/card{str(x)}/pcm{str(y)}c/sub0/status", "r") as info_file:
                    for line in info_file:
                        if re.match("^owner_pid", line.rstrip()):
                            cap_pid = int(line.rstrip().split(": ", 1)[1])

        if play or cap:
            device[4] = device[4] + 1
            subdevice.append(y)
            subdevice.append(play)
            subdevice.append(play_pid)
            subdevice.append(cap)
            subdevice.append(cap_pid)
            device.append(subdevice)
    # change this to create a list, USB cards may have devices and subdevices
    logging.log(7, f"Device list: {str(device)}")
    return device


def import_device_array():
    '''creates an array of device structures as per get_dev_info(), from
    device 0 to the highest number device currently on the system. Missing
    device numbers will still save a space in the case a USB device has
    been removed and there is still a higher number so that array index
    can be the same as the device number.'''
    global devices
    devices = []
    ndevs = 0

    if os.path.exists("/proc/asound/cards"):
        with open("/proc/asound/cards", "r") as cards_file:
            for line in cards_file:
                # need to find lines with:space/int/space[
                # ndevs = int from above
                # last one is highest dev number
                sub = line.rstrip()[1:]
                sub2 = sub.split(" ")
                if sub2[0].isdigit():
                    ndevs = int(sub2[0])
    ndevs += 1
    for x in range(0, ndevs):
        # card loop
        device = []
        device = get_dev_info(x)
        devices.append(device)
        del device


def device_by_name(name):
    for idx, device in enumerate(devices):
        if device[0] == name.split(',')[0]:
            return device, idx
    return "", -1

def import_config():
    ''' sets default parmeters, then reads values from configuration file'''
    global config
    global def_config
    global config_path
    global config_file
    global zdev
    global pulse_in
    global pulse_out
    global p_in_con
    global p_out_con
    global blacklist
    zdev = []
    pulse_in = []
    pulse_out = []
    p_in_con = []
    p_out_con = []

    config = configparser.ConfigParser()
    def_config = config['DEFAULT']
    config_path = "~/.config/autojack"
    config_file = "{path}/autojackrc".format(path=config_path)
    old_config_file = "~/.config/autojackrc"

    default_device = "0,0,0"
    for adir in glob.glob("/proc/asound/card*/codec*"):
        idfile = adir.rsplit('/', 1)[0]
        with open(f"{idfile}/id", "r") as card_file:
            for line in card_file:
                # only need one line
                tmpname = line.rstrip()
            if tmpname != "HDMI":
                default_device = f"{tmpname},0,0"


    # first set defaults
    config['DEFAULT'] = {
        'JACK': "False",
        'DRIVER': "alsa",
        'CHAN-IN': "0",
        'CHAN-OUT': "0",
        'RATE': "48000",
        'FRAME': "1024",
        'PERIOD': "2",
        'ZFRAME': "512",
        'XDEV': "",
        'PULSE-IN': "pulse_in",
        'PULSE-OUT': "pulse_out",
        'PJ-IN-CON': 'system:capture_1',
        'PJ-OUT-CON': 'monitor',
        'A2J': "True",
        'DEV': default_device,
        'USBAUTO': "True",
        'USB-SINGLE': "False",
        'USBDEV': "",
        "PULSE": "True",
        "LOG-LEVEL": "15",
        "BLACKLIST": "",
        "PHONE-ACTION": "switch",
        "PHONE-DEVICE": default_device,
        "MONITOR": 'system:playback_1'
    }

    # read in autojack config file
    c_file = expanduser(config_file)
    if os.path.isfile(c_file):
        # New config file exists, read it in
        config.read(c_file)
    else:
        # New file did not exist, let's check for a legacy file
        c_file = expanduser(old_config_file)
        if os.path.isfile(c_file):
            # Found the legacy config, let's load it
            with open(c_file, "r") as rc_file:
                for line in rc_file:
                    if re.match("^#", line):
                        continue
                    lsplit = line.rstrip().split("=", 1)
                    if lsplit[0] == "JACK":
                        def_config['JACK'] = lsplit[1]
                    elif lsplit[0] == "DRIVER":
                        def_config['DRIVER'] = lsplit[1]
                    elif lsplit[0] == "DEV":
                        def_config['DEV'] = lsplit[1]
                    elif lsplit[0] == "CHAN-IN":
                        def_config['CHAN-IN'] = lsplit[1]
                    elif lsplit[0] == "CHAN-OUT":
                        def_config['CHAN-OUT'] = lsplit[1]
                    elif lsplit[0] == "RATE":
                        def_config['RATE'] = lsplit[1]
                    elif lsplit[0] == "FRAME":
                        def_config['FRAME'] = lsplit[1]
                    elif lsplit[0] == "ZFRAME":
                        def_config['ZFRAME'] = lsplit[1]
                    elif lsplit[0] == "PERIOD":
                        def_config['PERIOD'] = lsplit[1]
                    elif lsplit[0] == "PULSE":
                        pulse = lsplit[1]
                        if pulse == 'True':
                            def_config['PULSE-IN'] = 'pulse_in'
                            def_config['PULSE-OUT'] = 'pulse_out'
                        else:
                            def_config['PULSE-IN'] = ''
                            def_config['PULSE-OUT'] = ''
                    elif lsplit[0] == "PULSE-IN":
                        pin = lsplit[1]
                        if pin:
                            def_config['PULSE-IN'] = 'pulse_in'
                    elif lsplit[0] == "PULSE-OUT":
                        pout = lsplit[1]
                        if pout:
                            def_config['PULSE-OUT'] = 'pulse_out'
                    elif lsplit[0] == "A2J":
                        def_config['A2J'] = lsplit[1]
                    elif lsplit[0] == "OUTPUT":
                        def_config['PJ-IN-CON'] = "1"
                        def_config['PJ-OUT-CON'] = "1"
                    elif lsplit[0] == "PORTS":
                        def_config['PJ-OUT-CON'] = f"{lsplit[1]}"
                    elif lsplit[0] == "XDEV":
                        def_config['XDEV'] = lsplit[1].strip('"')
                        zdev = def_config['XDEV'].strip('"').split()
                    elif lsplit[0] == "USBAUTO":
                        def_config['USBAUTO'] = lsplit[1]
                    elif lsplit[0] == "USB-SINGLE":
                        def_config['USB-SINGLE'] = lsplit[1]
                    elif lsplit[0] == "USBDEV":
                        def_config['USBDEV'] = lsplit[1]

    if def_config['DEV'] == "default":
        def_config['DEV'] = "PCH,0,0"
    zdev = def_config['XDEV'].strip('"').split()
    blacklist = def_config['BLACKLIST'].strip('"').split()
    pulse_in = def_config['PULSE-IN'].strip('"').split()
    pulse_out = def_config['PULSE-OUT'].strip('"').split()
    p_in_con = def_config['PJ-IN-CON'].strip('"').split()
    p_out_con = def_config['PJ-OUT-CON'].strip('"').split()
    for i, this_con in enumerate(p_out_con):
        if this_con == "monitor":
            p_out_con[i] = def_config['MONITOR']
    if pulse_in == [] and pulse_out == []:
        def_config['PULSE'] = "False"


def reconfig():
    '''reads values from configuration file and changes run to match. This tries
    to do this without stopping jack if not needed'''
    global devices
    global zdev
    global blacklist
    global pulse_in
    global pulse_out
    global config
    global def_config

    old_config = configparser.ConfigParser()
    old_def_config = old_config['DEFAULT']
    old_config.read_dict(config)
    oldzdev = zdev
    oldblacklist = blacklist
    oldpulse_in = pulse_in
    oldpulse_out = pulse_out
    import_config()
    if def_config['LOG-LEVEL'] != old_def_config['LOG-LEVEL']:
        logger = logging.getLogger()
        logger.setLevel(int(def_config['LOG-LEVEL']))
        logging.debug(f"log level: {def_config['LOG-LEVEL']}")

    newlist = [def_config['JACK'], def_config['DRIVER'], def_config['CHAN-IN'], def_config['CHAN-OUT'],
               def_config['RATE'], def_config['FRAME'], def_config['PERIOD'], def_config['DEV'], def_config['USBDEV']]
    oldlist = [old_def_config['JACK'], old_def_config['DRIVER'], old_def_config['CHAN-IN'], old_def_config['CHAN-OUT'],
               old_def_config['RATE'], old_def_config['FRAME'], old_def_config['PERIOD'], old_def_config['DEV'],
               old_def_config['USBDEV']]
    if newlist != oldlist:
        config_start()
        return
    # if we got this far all the above params have not changed
    if def_config['JACK'] == "False":
        return
        # no use checking anything else

    pulse_dirty = False
    if def_config['PULSE-IN'] != old_def_config['PULSE-IN']:
        pulse_dirty = True
        disconnect_pa(old_def_config)
        cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-jack-source"],
                            universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"remove jackd_source: {cp.stdout.strip()}")
        for piname in pulse_in:
            def_config['PULSE'] = "True"
            cp = subprocess.run(["/usr/bin/pactl", "load-module", "module-jack-source", f"client_name={piname}",
                                "channels=2", "connect=no"],
                                universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
            logging.debug(f"Load jackd_source: {cp.stdout.strip()}")

    if def_config['PULSE-OUT'] != old_def_config['PULSE-OUT']:
        if not pulse_dirty:
            pulse_dirty = True
            disconnect_pa(old_def_config)
        cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-jack-sink"],
                            universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"PA unload jack-sink: {cp.stdout.strip()}")
        for poname in pulse_out:
            def_config['PULSE'] = "True"
            cp = subprocess.run(["/usr/bin/pactl", "load-module", "module-jack-sink", f"client_name={poname}",
                            "channels=2", "connect=no"], universal_newlines=True, stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT, shell=False)
            logging.debug(f"PA load jack-sink: {cp.stdout.strip()}")

    if [def_config['PJ-IN-CON'], def_config['PJ-OUT-CON']] != [old_def_config['PJ-IN-CON'], old_def_config['PJ-OUT-CON']]:
        # need to pass old_def_config
        if not pulse_dirty:
            pulse_dirty = True
            disconnect_pa(old_def_config)

    if old_def_config['A2J'] != def_config['A2J']:
        cp = subprocess.run(["/usr/bin/killall", "-9", "a2jmidid"],
                            universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"kill old a2j: {cp.stdout.strip()}")
        if def_config['A2J'] == "True":
            # Can't add logging for background processes. Will show up in syslog
            subprocess.Popen(["/usr/bin/a2jmidid", "-e"], shell=False).pid

    # need to add blacklist to this part, jackmaster will already be ok
    if blacklist != oldblacklist:
        logging.debug("reconfig - blacklist changed")
        for bldev in blacklist:
            # whats in the old doesn't matter
            kill_slave(bldev)
    if zdev != oldzdev:
        logging.debug("reconfig - extra-dev")
        for ozd in oldzdev:
            if not ozd in zdev:
                kill_slave(ozd)
        for nzd in zdev:
            if not nzd in blacklist and (def_config['DRIVER'] != "alsa" or nzd != last_master):
                start_slave(nzd)

    if phones:
        if def_config['PHONE-DEVICE'] != last_master:
            start_slave(def_config['PHONE-DEVICE'])
            con_dirty = True
    elif def_config['Monitor'] != "system:playback_1":
        mon_dev = def_config['Monitor'].split('-')[0]
        if not mon_dev in zdev and (def_config['DRIVER'] != "alsa" or nzd != last_master):
            start_slave(mon_dev)
            con_dirty = True

    if ([def_config['USBAUTO'], def_config['USB-SINGLE']] != [old_def_config['USBAUTO'],
        old_def_config['USB-SINGLE']]):
        logging.debug("reconfig - usbauto/usb-single")

        import_device_array()
        for device in devices:
            if device[1] and device[4]:
                # USB Audio device
                for sub in range(5, 5 + int(device[4])):
                    subname = f"{device[0]},{str(device[sub][0])},0"
                    dev_has_pid = False
                    if device[sub][2] or device[sub][4]:
                        dev_has_pid = True
                    if subname != def_config['USBDEV']:
                        if def_config['USBAUTO'] == "True" and not subname in blacklist:
                            if dev_has_pid:
                                kill_slave(subname)
                            start_slave(subname)
                        else:
                            kill_slave(subname)

    if pulse_dirty:
        #do this last after all bridges are created
        connect_pa()


def config_start():
    ''' Pulls configuration and force restarts the world '''
    global last_master
    global zdev
    global pulse_in
    global pulse_out
    global def_config
    global phones

    logging.info("Running: config_start()")

    import_config()
    logger = logging.getLogger()
    logger.setLevel(int(def_config['LOG-LEVEL']))
    logging.debug(f"log level: {def_config['LOG-LEVEL']}")

    # if at session start we should wait a few seconds for pulse
    # to be fully running
    time.sleep(2)
    # Stop jack if running
    cp = subprocess.run(["/usr/bin/killall", "-q", "-9", "jackdbus", "jackd", "a2jmidid"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"Kill old Procs: {cp.stdout.strip()}")
    JD, ji = device_by_name(def_config['DEV'])
    if (def_config['DRIVER'] == "firewire") or JD[3]:
        time.sleep(3)
        # this is a firewire device, A busreset makes for stability
        cp = subprocess.run(["/usr/bin/ffado-test", "BusReset"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"reset firewire bus: {cp.stdout.strip()}")
        time.sleep(3)
        cp = subprocess.run(["/usr/bin/killall", "-q", "ffado-dbus-server", "ffado-mixer"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Kill ffado mixer because busreset needed: {cp.stdout.strip()}")
        

    if def_config['JACK'] == "False":
        # restart Pulse
        cp = subprocess.run(["/usr/bin/pulseaudio", "-k"],
                    universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"JACK == {def_config['JACK']} restart pulse: {cp.stdout.strip()}")
        return

    # Assume start of session where pulse may be fully loaded
    # get rid of anything that can automatically interfere
    cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-jackdbus-detect"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"remove jackdbus_detect: {cp.stdout.strip()}")
    cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-udev-detect"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"remove module-udev-detect: {cp.stdout.strip()}")
    cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-alsa-card"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"remove module-alsa-card: {cp.stdout.strip()}")
    if os.path.exists(f"/proc/asound/{def_config['USBDEV'].split(',')[0]}") and def_config['USBDEV'] != "":
        mdev = def_config['USBDEV']
    else:
        mdev = def_config['DEV']
    if os.path.isfile(expanduser('~/.config/jack/conf.xml')):
        logging.debug("Found previous jack config removing")
        os.remove(expanduser('~/.config/jack/conf.xml'))
    # Now start jackdbus with the configured device
    # the commands are all different so...

    if def_config['DRIVER'] == "alsa":
        cp = subprocess.run(["/usr/bin/jack_control", "ds", "alsa"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")
        cp = subprocess.run(["/usr/bin/jack_control", "dps", "device", f"hw:{mdev}", "dps", "rate", def_config['RATE']],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")
        cp = subprocess.run(["/usr/bin/jack_control", "dps", "period", def_config['FRAME'], "dps", "nperiods",
                        def_config['PERIOD'], "start"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")

    elif def_config['DRIVER'] == "firewire":
        cp = subprocess.run(["/usr/bin/ffado-test", "BusReset"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"reset firewire bus: {cp.stdout.strip()}")
        cp = subprocess.run(["/usr/bin/killall", "-q", "ffado-dbus-server", "ffado-mixer"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Kill ffado mixer because busreset needed: {cp.stdout.strip()}")
        time.sleep(3)
        cp = subprocess.run(["/usr/bin/jack_control", "ds", "firewire", "dps", "period", def_config['FRAME']],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")
        #cp = subprocess.run(["/usr/bin/jack_control", "dps", "device", "hw:0", "dps", "rate", def_config['RATE']],
        cp = subprocess.run(["/usr/bin/jack_control", "dps", "rate", def_config['RATE']],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")
        #cp = subprocess.run(["/usr/bin/jack_control", "dps", "nperiods", def_config['PERIOD'], "start"],
        cp = subprocess.run(["/usr/bin/jack_control", "dps", "nperiods", "3"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")
        cp = subprocess.run(["/usr/bin/jack_control", "start"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")

    elif def_config['DRIVER'] == "dummy":
        cp = subprocess.run(["/usr/bin/jack_control", "ds", "dummy"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")
        cp = subprocess.run(["/usr/bin/jack_control", "dps", "rate", def_config['RATE'], "dps" "period", def_config['FRAME']],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")
        cp = subprocess.run(["/usr/bin/jack_control", "dps", "capture", def_config['CHAN-IN'], "dps", "playback",
                        def_config['CHAN-OUT'], "start"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Jackdbus: {cp.stdout.strip()}")

    else:
        # we should never get here
        logging.error("programming error! if you find this in your log file please file" +
              "a bug report about: Driver not found")
    last_master = mdev
    # maybe check for jack up (need function?)
    time.sleep(2)

    start_jack_client()

    for piname in pulse_in:
        def_config['PULSE'] = "True"
        cp = subprocess.run(["/usr/bin/pactl", "load-module", "module-jack-source", f"client_name={piname}",
                        "channels=2", "connect=no"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Pulseaudio: {cp.stdout.strip()}")
    for poname in pulse_out:
        def_config['PULSE'] = "True"
        cp = subprocess.run(["/usr/bin/pactl", "load-module", "module-jack-sink", f"client_name={poname}", "channels=2",
                        "connect=no"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Pulseaudio: {cp.stdout.strip()}")

    for cname in zdev:
        if not cname in blacklist and (def_config['DRIVER'] != "alsa" or cname != last_master):
            logging.debug(f"Extra Dev: {cname}")
            start_slave(cname)

    if phones:
        if def_config['PHONE-DEVICE'] != last_master:
            start_slave(def_config['PHONE-DEVICE'])
            con_dirty = True
    elif def_config['Monitor'] != "system:playback_1":
        mon_dev = def_config['Monitor'].split('-')[0]
        if not mon_dev in zdev and (def_config['DRIVER'] != "alsa" or cname != last_master):
            start_slave(mon_dev)
            con_dirty = True

    if def_config['USBAUTO'] == "True":
        import_device_array()
        for device in devices:
            if device[1] and device[4]:
                # USB Audio device
                for sub in range(5, 5 + int(device[4])):
                    subname = f"{device[0]},{str(device[sub][0])},0"
                    dev_has_pid = False
                    if device[sub][2] or device[sub][4]:
                        dev_has_pid = True
                    if subname != def_config['USBDEV']:
                        if not subname in blacklist:
                            if dev_has_pid:
                                kill_slave(subname)
                            start_slave(subname)
                        else:
                            kill_slave(subname)

    # not sure all these delays need to be here. Was checking with old pulse.
    # this has to be last after all audio bridges are created
    time.sleep(2)
    connect_pa()

    if def_config['A2J'] == "True":
        # Can't set up logging
        subprocess.Popen(["/usr/bin/a2jmidid", "-e"], shell=False).pid


def start_jack_client():
    ''' Create a jack client for monitoring and changing port connections'''
    global jack_client
    global jack_died
    global jack_alive
    global con_dirty
    try:
        jack_client = jack.Client('AutoJack', False, True)
    except jack.JackError:
        logging.warning("Unable to create jack client")
        jack_died = True
        return
    jack_client.set_shutdown_callback(jackdied)
    jack_client.set_port_connect_callback(jack_con_det)
    jack_client.activate()
    
    jack_died = False
    jack_alive = True
    con_dirty = True
    logging.debug("jack client created")


def jackdied(state, why):
    '''gets called by jack if it is exiting, we can't clean up
    here... so tell the world jack died with a flag instead'''
    global jack_died
    jack_died = True
    logging.debug("jack died callback")


def jack_con_det(a, b, connect):
    ''' a port has had a connection check if one of the ports is monitor L/R
        if so and phones == True move to phones port'''
    global con_dirty
    global def_config
    if connect and (phones or (def_config['MONITOR'] != "system:playback_1")):
        con_dirty = True


def check_jack_status(user_data):
    '''Check if jack has died and the client needs to be
    closed.'''
    global jack_client
    global jack_alive
    global jack_died
    global jack_count
    global con_dirty
    global phone_port
    jack_count = jack_count + 1
    if jack_count == 200:
        logging.log(7, f"Jack_status: {str(jack_alive)}")
        jack_count = 0

        logfile = expanduser("~/.log/autojack.log")
        if os.path.isfile(logfile) and os.path.getsize(logfile) > 100000:
            logging.debug("Log File getting large: rotate")
            for old_idx in range(5, -1, -1):
                if old_idx:
                    if os.path.isfile(f"{logfile}.{str(old_idx)}"):
                        os.replace(f"{logfile}.{str(old_idx)}", f"{logfile}.{str(old_idx + 1)}")
                else:
                    logging.shutdown()
                    os.replace(logfile, f"{logfile}.1")
                    logging.basicConfig(filename=logfile, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                                        level=int(def_config['LOG-LEVEL']))
            logging.debug("Log File rotated")

    if con_dirty and jack_alive:
        logging.debug("Connection changed")
        if phones:
            new_l = phone_port
            logging.debug(f"change it to phones port: {new_l}")
        else:
            new_l = def_config['MONITOR']
        switch_outputs("system:playback_1", new_l)
        con_dirty = False

    # sent by jackdied() callback
    if jack_died:
        jack_client.deactivate()
        jack_client.close()
        jack_died = False
        jack_alive = False
        logging.debug("jack client closed and cleaned up")
    return True


def switch_outputs(left_old, left_new):
    '''finds any outputs connected to a_dev and moves them to b_dev.
    Devices are jack client names including the first port of the stereo
    pair. example: system:playback_1'''
    global def_config
    global jack_client
    global jack_alive
    logging.debug(f"moving connections from {str(left_old)} to {str(left_new)}")
    if not jack_alive:
        logging.debug("Jack not running")
        return
    if left_old == left_new:
        logging.debug("oops, both ports are the same, no move done")
        return
    right_old = get_right_port(left_old)
    right_new = get_right_port(left_new)
    l_old_con = jack_client.get_all_connections(left_old)
    logging.log(8, f"old left connections {str(l_old_con)}")
    # first connect all output to new device
    for raw_p in l_old_con:
        s_port = str(raw_p).split("'")[1]
        jack_client.connect(s_port, left_new)
    r_old_con = jack_client.get_all_connections(right_old)
    logging.log(8, f"old right connections {str(r_old_con)}")
    for raw_p in r_old_con:
        s_port = str(raw_p).split("'")[1]
        jack_client.connect(s_port, right_new)
    # then disconnect old ones
    for raw_p in l_old_con:
        s_port = str(raw_p).split("'")[1]
        jack_client.disconnect(s_port, left_old)
    for raw_p in r_old_con:
        s_port = str(raw_p).split("'")[1]
        jack_client.disconnect(s_port, right_old)


def get_right_port(left_port_ask):
    ''' given a port, this returns the jack port of the next port
    or for a mono device the same port so that it is connected to both
    left and write'''
    global jack_client
    global jack_alive
    if not jack_alive:
        return ""

    right = False
    ''' For firewire backend, the ports are labeled firewire_pcm:something<num>_in or _out
        but have an alias of system:<direction>_<num>. We have to find the real port name
        for left_port '''
    left_port = jack_client.get_port_by_name(left_port_ask)
    logging.debug(f"Real left port name: {str(left_port.name)}")
    right_port = left_port.name
    if left_port.is_input:
        ports = jack_client.get_ports(f"{left_port.name.split(':')[0]}*", is_audio=True, is_input=True)
    else:
        ports = jack_client.get_ports(f"{left_port.name.split(':')[0]}*", is_audio=True, is_output=True)
    for next_port in ports:
        if next_port.name == left_port.name:
            logging.log(8, f"{str(next_port.name)} == {str(left_port)}")
            right = True
        elif right:
            right_port = next_port.name
            logging.log(8, f"Right port: {str(right_port)}")
            break
    return right_port


def connect_pa():
    '''connects pulse ports to the correct device ports. May have to
    use zita-ajbridge to first make the correct device available.'''
    global def_config
    global pulse_in
    global pulse_out
    global p_in_con
    global p_out_con
    global jack_client
    global jack_alive
    if not jack_alive:
        logging.debug("Can't connect, jack not running")
        return

    if (p_in_con == []) and (p_out_con == []):
        return

    for i, name in enumerate(pulse_in):
        if p_in_con[i] != 'none':
            logging.debug(f"jack connect {p_in_con[i]} from {name}:front-left")
            jack_client.connect(p_in_con[i], f"{name}:front-left")
            con_left = p_in_con[i]
            con_right = get_right_port(con_left)
            logging.debug(f"jack connect {con_right} from {name}:front-right")
            jack_client.connect(con_right, f"{name}:front-right")

    for i, name in enumerate(pulse_out):
        if p_out_con[i] != 'none':
            if p_out_con[i] == "monitor":
                con_left = def_config['MONITOR']
            else:
                con_left = p_out_con[i]
            logging.debug(f"jack connect {name}:front-left from {con_left}")
            jack_client.connect(f"{name}:front-left", con_left)
            con_right = get_right_port(con_left)
            logging.debug(f"jack connect {name}:front-right from {con_right}")
            jack_client.connect(f"{name}:front-right", con_right)


def disconnect_pa(our_config):
    '''disconnect Pulse ports we know we have connected.
    The pa-jack bridge is left running.'''
    global jack_client
    global jack_alive
    if not jack_alive:
        logging.debug("Jack not running")
        return
    p_in_con = our_config['PJ-IN-CON'].strip('"').split()
    p_out_con = our_config['PJ-OUT-CON'].strip('"').split()
    for i, name in enumerate(our_config['PULSE-IN'].strip('"').split()):
        if p_in_con[i] != 'none':
            logging.debug(f"jack disconnect {p_in_con[i]} from {name}:front-left")
            jack_client.disconnect(p_in_con[i], f"{name}:front-left")
            con_left = p_in_con[i]
            con_right = get_right_port(con_left)
            logging.debug(f"jack disconnect {con_right} from {name}:front-right")
            jack_client.disconnect(con_right, f"{name}:front-right")

    for i, name in enumerate(our_config['PULSE-OUT'].strip('"').split()):
        if p_out_con[i] != 'none':
            if p_out_con[i] == "monitor":
                con_left = def_config['MONITOR']
            else:
                con_left = p_out_con[i]
            logging.debug(f"jack disconnect {name}:front-left from {con_left}")
            jack_client.disconnect(f"{name}:front-left", con_left)
            con_right = get_right_port(con_left)
            logging.debug(f"jack disconnect {name}:front-right from {con_right}")
            jack_client.disconnect(f"{name}:front-right", con_right)


def msg_cb_new(*args, **kwargs):
    '''call back for udev sensing new device. checks if device is audio.
    checks if device is USB. If both are true and configuration is to
    use this device, the device is either connected with zita-ajbridge
    or becomes jack's master device'''
    global devices
    global last_master
    global def_config

    if def_config['JACK'] == "False":
        # then we don't care
        ### XXX well we do till we find out if this is phones
        return

    # let things settle
    time.sleep(2)

    import_config()

    if args[0].find("sound-card") >= 0:
        # remake database
        import_device_array()
        a_if = args[0].split("sound-card", 1)
        audio_if = a_if[1].split(".", 1)[0]
        device = devices[int(audio_if)]
        # make sure device is USB and is not midi only
        if device[1] and device[4]:
            # tell gui devicelist has changed so it can up date device dropdowns
            subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.studio.control.event.usb_signal"],
                           shell=False)

            for subn in range(5, int(device[4]) + 5):
                subname = f"{device[0]},{str(device[subn][0])},0"
                if subname == last_master:
                    # spurious indication, ignore
                    logging.debug(f" We already have that device - spurious signal, ignoring")
                    return
                logging.debug(f" device = {subname} play:{str(device[subn][1])} capture:{str(device[subn][3])}")

                if (def_config['DRIVER'] == "alsa") and (def_config['USBDEV'] == subname):
                    # change_jack_master(cid, "sm")
                    logging.debug(f"Changing jack master to: {subname}")
                    config_start()
                    last_master = subname
                    time.sleep(1)
                    start_slave(def_config['DEV'])
                    time.sleep(1)
                    # connect_pa()
                elif def_config['USBAUTO'] == "True":
                    start_slave(subname)
                    if subname == def_config['PHONE-DEVICE']:
                        phones_switch(True)


def msg_cb_removed(*args, **kwargs):
    ''' dbus call back when a USB device removal has been detected by udev '''
    global devices
    global last_master
    global def_config

    if def_config['JACK'] == "False":
        # then we don't care
        ### XXX well we do till we find out if this is phones
        return

    saved_devices = devices
    # let things settle
    time.sleep(1)

    if args[0].find("sound-card") >= 0:
        # tell gui devicelist has changed
        subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.studio.control.event.usb_signal"],
                       shell=False)

        a_if = args[0].split("sound-card", 1)
        audio_if = a_if[1].split(".", 1)[0]
        logging.info(f"sound card: hw:{audio_if} removed")
        if os.path.exists(f"/proc/asound/card{str(audio_if)}"):
            # Hmm, I guess it hasn't really gone, false alarm
            logging.debug(f"Device is still here - signal ignored")
            return

        device = devices[int(audio_if)]
        if not device[1]:
            # not a usb device
            return
        for sn in range(5, 5 + int(device[4])):
            subd = f"{device[0]},{str(device[sn][0])},0"
            logging.debug(f"Subdevice {subd} unplugged")
            if def_config['USBDEV'] == subd:
                if last_master == def_config['USBDEV']:
                    kill_slave(def_config['DEV'])
                    time.sleep(1)
                    config_start()
            elif def_config['USBAUTO'] == "True":
                if subd == def_config['PHONE-DEVICE']:
                    phones_switch(False)
                kill_slave(subd)
        import_device_array()


def get_card_rate(rtdev):
    ''' find out closest SR jack master that device handles'''
    dname, l_dev, sub = rtdev.split(",", 2)
    backup_rate = ""
    if os.path.isfile(f"/proc/asound/{dname}/pcm{l_dev}p/sub{sub}/hw_params"):
        p_file = f"/proc/asound/{dname}/pcm{l_dev}p/sub{sub}/hw_params"
        dtype = 0
    elif os.path.isfile(f"/proc/asound/{dname}/pcm{l_dev}c/sub{sub}/hw_params"):
        p_file = f"/proc/asound/{dname}/pcm{l_dev}c/sub{sub}/hw_params"
        dtype = 1
    else:
        logging.warn(f"missing info file for: {ldev} adding client may fail.")
        return def_config['RATE']

    adev_h = alsaaudio.PCM(type=dtype, device=f"hw:{rtdev}")

    adev_h.setrate(int(def_config['RATE']))
    with open(p_file, "r") as card_file:
        for line in card_file:
            if 'rate:' in line:
                fnd_rate = line.split()[1]
                backup_rate = fnd_rate
    if fnd_rate == def_config['RATE']:
        logging.debug(f"Good supports jack rate: {def_config['RATE']}")
        adev_h.close()
        return fnd_rate
    for try_rate in ['48000', '44100', '88200', '96000', '32000']:
        adev_h.setrate(int(try_rate))
        with open(p_file, "r") as card_file:
            for line in card_file:
                if 'rate:' in line:
                    fnd_rate = line.split()[1]
                    if fnd_rate == try_rate:
                        logging.debug(f"Closest we could find: {fnd_rate}")
                        adev_h.close()
                        return fnd_rate

    if backup_rate:
        adev_h.close()
        return backup_rate
    logging.debug(f"Unable to confirm working rate, using: {def_config['RATE']}")
    adev_h.close()
    return def_config['RATE']


def start_slave(ldev):
    ''' takes the audio device as a parameter and starts a bridge
    from that device to jack '''
    global devices
    global def_config
    global procs
    global blacklist

    logging.debug(f"Start slave: {ldev}")
    buff_size = def_config['ZFRAME']
    import_device_array()
    #ldev should be what can compare to blacklist
    if ldev in blacklist:
        #don't start a slave blacklisted device
        logging.debug(f"Blacklisted: {ldev} don't start")
        return

    dname, l_dev, sub = ldev.split(",", 2)
    dsr = get_card_rate(ldev)
    if dname == "HDMI":
        logging.info("HDMI device, setting buffer to 4096")
        buff_size = "4096"
    if (dname == "PCH") and (int(buff_size) < 128):
        logging.info("PCH device, minimum buffer 128, using 128")
        buff_size = "128"
    device, idx = device_by_name(ldev)
    for index in range(5, 5 + int(device[4])):
        if device[index][0] == int(ldev):
            break
        # we found it and it seems to have this sub
        if device[index][1]:
            if not device[index][2]:
                if device[1] and (def_config['USB-SINGLE'] == "True"):
                    logging.info("USB device set input only, no playback bridge")
                else:
                    cmd = f"/usr/bin/zita-j2a -j {ldev}-out -d hw:{ldev} -r {dsr} -p {buff_size} -n {def_config['PERIOD']} -c 100"
                    logging.debug(f"device bridging comand line: {cmd}")
                    procout = subprocess.Popen(shlex.split(cmd), shell=False)
                    pidout = procout.pid
                    logging.debug(f" Device {ldev} out has pid: {pidout}")
                    procs.append(procout)
                    device[index][2] = pidout
            else:
                logging.debug(f" Device {ldev} playback already bridged")
        if device[index][3]:
            if not device[sdev][4]:
                cmd = f"/usr/bin/zita-a2j -j {ldev}-in -d hw:{ldev} -r {dsr} -p {buff_size} -n {def_config['PERIOD']} -c 100"
                logging.debug(f"Device input comand line: {cmd}")
                procin = subprocess.Popen(shlex.split(cmd), shell=False)
                pidin = procin.pid
                logging.debug(f" Device {ldev} in has pid: {pidin}")
                procs.append(procin)
                device[index][4] = pidin
            else:
                logging.debug(f" Device {ldev} capture already bridged")
    time.sleep(1)
    import_device_array()


def kill_slave(ldev):
    ''' takes the device as a parameter and if the device exists
    and is bridged to jack, stops the bridge '''
    global devices
    global procs
    dname, l_dev, sub = ldev.split(",", 2)
    logging.debug(f"{ldev} kill in progress")
    device, idx = device_by_name(ldev)
    for index in range(5, 5 + int(device[4])):
        if device[index][0] == int(l_dev):
            break
    logging.debug(f"Device is {device[0]}")
    if device[4]:
        logging.debug(f"{device[0]} has {device[4]} sub devices, want sub device {l_dev}")
        logging.debug(f"{device[0]} sub device {l_dev} has pid: {device[index][2]}")
        if device[index][2]:
            logging.debug(f"{ldev} found Playback bridge to kill")
            for i, pr in enumerate(procs):
                if pr.pid == device[index][2]:
                    logging.debug(f"kill {str(dname)} sub: {str(l_dev)} PID: {str(device[index][2])}")
                    pr.send_signal(signal.SIGINT)
                    try:
                        rt = pr.wait(timeout=15)
                    except TimeoutExpired:
                        logging.debug(f"kill PID: {str(device[index][2])} failed")
                        pr.terminate()
                        outs, errs = pr.communicate()
                    del procs[i]
        else:
            logging.debug(f"{ldev} no Playback bridge found")
        if device[index][4]:
            logging.debug(f"{ldev} found Capture bridge to kill")
            for i, pr in enumerate(procs):
                if pr.pid == device[index][4]:
                    logging.info(f"kill {str(dname)} sub: {str(l_dev)} PID: {str(device[index][4])}")
                    pr.send_signal(signal.SIGINT)
                    try:
                        rt = pr.wait(timeout=15)
                    except TimeoutExpired:
                        logging.debug(f"kill PID: {str(device[index][4])} failed")
                        pr.terminate()
                        outs, errs = pr.communicate()
                    del procs[i]
        else:
            logging.debug(f"{ldev} no Capture bridge found")


def ses_cb_quit(*args, **kwargs):
    ''' dbus call back when quit signal caught. This is for use in
    testing and the GUI never sends this. '''
    logging.warning("Got quit signal.")
    cp = subprocess.run(["/usr/bin/killall", "-9", "jackdbus", "jackd", "a2jmidid"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"Kill jack and friends: {cp.stdout.strip()}")
    cp = subprocess.run(["/usr/bin/pulseaudio", "-k"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"Restart PA: {cp.stdout.strip()}")
    os._exit(0)


def handler(signum, frame):
    ''' a handler for system signals that may be sent by the system.
        we want to trap sigint, sigkill and sigterm and do the same as
        above. '''
    logging.warning(f"Got signal number: {str(signum)} - Dying.")
    cp = subprocess.run(["/usr/bin/killall", "-9", "jackdbus", "jackd", "a2jmidid"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"Kill jack and friends: {cp.stdout.strip()}")
    cp = subprocess.run(["/usr/bin/pulseaudio", "-k"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"Restart PA: {cp.stdout.strip()}")
    os._exit(0)


def ses_cb_stop(*args, **kwargs):
    ''' dbus call back when stop signal caught. This stops jack. '''
    logging.info("Got stop signal.")
    cp = subprocess.run(["/usr/bin/killall", "-9", "jackdbus", "jackd", "a2jmidid"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"Kill jack and friends: {cp.stdout.strip()}")
    cp = subprocess.run(["/usr/bin/pulseaudio", "-k"],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
    logging.debug(f"Restart PA: {cp.stdout.strip()}")


def ses_cb_start(*args, **kwargs):
    ''' dbus call back when (re)start signal caught'''
    logging.info("Got start signal.")
    config_start()


def ses_cb_config(*args, **kwargs):
    ''' dbus call back when config signal caught '''
    logging.info("Got config signal.")
    reconfig()


def ses_cb_tablet(*args, **kwargs):
    ''' dbus call back when tablet signal caught
        This reads the config file and sets the tablet
        buttons, pen and size/position'''
    ''' not sure what we will do with this, if anything
        tablet plans changed/on hold for now; '''
    logging.info("Got tablet signal.")


def ses_cb_ping(*args, **kwargs):
    ''' dbus call back when config signal caught '''
    logging.info("Got ping signal.")
    time.sleep(3)
    subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.studio.control.event.V3_2_signal"],
                   shell=False)


def phones_switch(plugged):
    ''' Does the actual phones signal and level switching '''
    global def_config
    global zdev
    global phones
    global phone_port
    phones = plugged
    logging.debug(f"Changing Headphone to plugged in = {str(plugged)}")
    if def_config['JACK'] == "False":
        # we can't do anything up to pulse (I think)
        return
    out_L = "system:playback_1"
    out_R = "system:playback_2"
    logging.debug(f"Headphone PHONE-ACTION = {str(def_config['PHONE-ACTION'])}")
    device, idx = device_by_name(def_config['PHONE-DEVICE'])
    if def_config['PHONE-ACTION'] == "switch":
        logging.debug("Switching outputs")
        if device[2]:
            spkr = ""
            logging.debug("Headphone device is internal change it's mixer settings")
            my_mixers = alsaaudio.mixers(device=f"hw:{device[0]}")
            if "Front" in my_mixers:
                spkr = "Front"
            elif "Speakers" in my_mixers:
                spkr = "Speakers"
            spkr_mix = alsaaudio.Mixer(control=spkr, device=f'hw:{device[0]}')
            hp_mix = alsaaudio.Mixer(control='Headphone', device=f'hw:{device[0]}')
            if plugged:
                spkr_mix.setvolume(0)
                spkr_mix.setmute(1)
                hp_mix.setvolume(100)
                hp_mix.setmute(0)
            else:
                spkr_mix.setvolume(100)
                spkr_mix.setmute(0)
                hp_mix.setvolume(0)
                hp_mix.setmute(1)
        if def_config['PHONE-DEVICE'] != def_config['DEV']:
            logging.debug("Headphone device is not system")
            # set jack port names
            phone_port = f"{def_config['PHONE-DEVICE']}-out:playback_1"
            logging.debug(f"Headphone jack is: {str(phone_port)}")
            hp_is_usb = False
            if os.path.exists(f"/proc/asound/{def_config['PHONE-DEVICE'].split(',')[0]}/usbbus"):
                hp_is_usb = True
            # USB device?
            if not plugged:
                switch_outputs(phone_port, "system:playback_1")
                # find out if this is already an zdev
                if (not def_config['PHONE-DEVICE'] in zdev) and (not hp_is_usb):
                    logging.debug("Headphone device had no jack port ... kill unneeded bridge")
                    kill_slave(def_config['PHONE-DEVICE'])
            else:
                if not def_config['PHONE-DEVICE'] in zdev and (not hp_is_usb):
                    logging.debug("Headphone device has no jack port ... create bridge")
                    # we have to start a zita proc
                    start_slave(def_config['PHONE-DEVICE'])
                    time.sleep(1)
                switch_outputs("system:playback_1", phone_port)

    elif def_config['PHONE-ACTION'] == "script":
        # run a script to perform this instead
        logging.debug("Use script")
        fpath = expanduser('~/.config/autojack/headphone')
        if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
            cp = subprocess.run([fpath, str(plugged)],
                        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
            logging.debug(f"Running script: {fpath} {str(plugged)}")
    else:
        logging.debug("No action chosen")


def phones_check():
    ''' check to see if there are phones plugged in. We only do this 
    on start up'''
    logging.debug("Checking for phones plugged in")
    global phones
    global devices
    global def_config
    global internal
    phones = False

    import_config()
    import_device_array()

    dname, ddev, dsub = def_config['PHONE-DEVICE'].split(',')
    device, idx = device_by_name(def_config['PHONE-DEVICE'])
    if device == []:
        logging.warniing(f"Phones device: {def_config['PHONE-DEVICE']} not found")
        return
    for index in range(5, 5 + int(device[4])):
        if device[index][0] == int(ddev):
            break
    logging.debug(f"Checking phones device: {str(device[0])}")
    if device[1]:
        # phones device is USB
        if device[index][1]:
            # If USB make sure it has audio playback
            phones_switch(True)
            return
        else:
            logging.warning(f"Headphone device {device[0]} appears to have no outputs")
    elif device[2]:
        # this is internal we can use amixer
        logging.debug("Checking for phones with internal device")
        my_mixers = alsaaudio.mixers(device=f"hw:{device[0]}")
        hp_sw = ""
        if "Front" in my_mixers:
            hp_sw = "Front "
        cmd = f"/usr/bin/amixer -D hw:PCH cget iface=CARD,name='{hp_sw}Headphone Jack'"
        logging.debug(f"amixer command: {cmd}")
        cp = subprocess.run(shlex.split(cmd), universal_newlines=True, 
                stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        for line in cp.stdout.split(":"):
            logging.debug(f"amixer line: {line.strip()}")
            if line.strip() == "values=on":
                phones_switch(True)
                return
            elif line.strip() == "values=off":
                phones_switch(False)
                return
        logging.debug(f"headphone detect string: {cp.stdout.strip()}")


def ses_cb_phones(*args, **kwargs):
    ''' User has manually asked to toggle phones (un)plugged'''
    global phones
    if phones:
        phones_switch(False)
        phones = False
    else:
        phones_switch(True)
        phones = True


def phones_plug(*args, **kwargs):
    ''' callback means headphones have been plugged in lets make sure
        phones are unmuted and have a level higher than -inf.
        We also may want to mute speakers '''
    logging.info("Got phones plugged in signal.")
    phones_switch(True)


def phones_unplug(*args, **kwargs):
    ''' callback means headphones have been unplugged in lets make sure
        phones are muted and the speakers  are unmuted and have a
        level higher than -inf.'''
    logging.info("Got phones unplugged signal.")
    phones_switch(False)


def main():
    ''' Autojack runs at session start and manages audio for the session.
    this is the daemon for studio-controls'''
    global last_master
    global procs
    global jack_alive
    global jack_died
    global jack_count
    global phones
    global con_dirty
    phones = False
    jack_count = 0
    procs = []
    last_master = ""
    jack_alive = False
    jack_died = False
    con_dirty = False
    # set up logging
    logpath = expanduser("~/.log")
    # make sure the logfile directory exists
    if not os.path.exists(logpath):
        os.makedirs(logpath)
    logfile = expanduser("~/.log/autojack.log")
    logging.basicConfig(filename=logfile, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
    logging.info('logging online')
    phones_check()
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    signal.signal(signal.SIGHUP, handler)
    signal.signal(signal.SIGINT, handler)
    signal.signal(signal.SIGQUIT, handler)
    signal.signal(signal.SIGILL, handler)
    signal.signal(signal.SIGTRAP, handler)
    signal.signal(signal.SIGABRT, handler)
    signal.signal(signal.SIGBUS, handler)
    signal.signal(signal.SIGFPE, handler)
    #signal.signal(signal.SIGKILL, handler)
    signal.signal(signal.SIGUSR1, handler)
    signal.signal(signal.SIGSEGV, handler)
    signal.signal(signal.SIGUSR2, handler)
    signal.signal(signal.SIGPIPE, handler)
    signal.signal(signal.SIGALRM, handler)
    signal.signal(signal.SIGTERM, handler)
    subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/",
                    "org.studio.control.event.quit_signal"], shell=False)
    logging.info('kill other autojack intances')
    time.sleep(3)

    config_start()
    import_device_array()
    system_bus = dbus.SystemBus()
    system_bus.add_signal_receiver(msg_cb_new, dbus_interface='org.freedesktop.systemd1.Manager', signal_name='UnitNew')
    system_bus.add_signal_receiver(msg_cb_removed, dbus_interface='org.freedesktop.systemd1.Manager',
                                   signal_name='UnitRemoved')
    system_bus.add_signal_receiver(phones_plug, dbus_interface='org.studio.control.event',
                                signal_name='plug_signal')
    system_bus.add_signal_receiver(phones_unplug, dbus_interface='org.studio.control.event',
                                signal_name='unplug_signal')

    user_bus = dbus.SessionBus()
    user_bus.add_signal_receiver(ses_cb_stop, dbus_interface='org.studio.control.event',
                                 signal_name='stop_signal')
    user_bus.add_signal_receiver(ses_cb_start, dbus_interface='org.studio.control.event',
                                 signal_name='start_signal')
    user_bus.add_signal_receiver(ses_cb_config, dbus_interface='org.studio.control.event',
                                 signal_name='config_signal')
    user_bus.add_signal_receiver(ses_cb_ping, dbus_interface='org.studio.control.event',
                                 signal_name='ping_signal')
    user_bus.add_signal_receiver(ses_cb_tablet, dbus_interface='org.studio.control.event',
                                 signal_name='tablet_signal')
    user_bus.add_signal_receiver(ses_cb_quit, dbus_interface='org.studio.control.event',
                                 signal_name='quit_signal')
    user_bus.add_signal_receiver(ses_cb_phones, dbus_interface='org.studio.control.event',
                                 signal_name='phones_signal')

    subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/",
                    "org.studio.control.event.V3_2_signal"], shell=False)

    timeout_id = GLib.timeout_add(500, check_jack_status, None)

    loop = GLib.MainLoop()
    loop.run()


if __name__ == '__main__':
    main()
