#!/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 alsaaudio
import copy
import dbus
import dbus.service
import dbus.exceptions
import dbus.mainloop.glib
import glob
import jack
import json
import logging
import os
import re
import shlex
import shutil
import signal
import subprocess
import sys
import time
from gi.repository import GLib
from os.path import expanduser


global name_base
global control_interface_name
global configure_interface_name
global service_name
global install_path
install_path = os.path.abspath(f"{sys.path[0]}/..")
sys.path.insert(1, f"{install_path}/lib/python3/dist-packages")
import auto_jack

name_base = 'org.jackaudio'
control_interface_name = name_base + '.JackControl'
configure_interface_name = name_base + '.Configure'
service_name = name_base + '.service'


class sendbus(dbus.service.Object):
    def __init__(self):
        dbus.service.Object.__init__(self, dbus.SessionBus(), "/")

    @dbus.service.signal(dbus_interface="org.studio.control.command",
                         signature="s")
    def signal(self, sg):
        logging.log(7, f"sent signal {sg}")
        print(f"sent it {sg}")
        pass

    @dbus.service.signal(dbus_interface="org.studio.control.state",
                         signature="ss")
    def state(self, vr, st):
        logging.log(7, f"sent state ver: {vr} status: {st}")
        pass


def get_raw(device):
    ''' given a USB*,*,* device name return a
    true (raw) dev,*,* string'''
    global conf_db
    if device == 'none':
        return 'none'
    un, ud, us = device.split(',')
    if un in conf_db['devices']:
        rn = conf_db['devices'][un]['raw']
        return f"{rn},{ud},{us}"
    else:
        return 'none'


def get_db(device):
    ''' give a device string (raw or db nameed)
    return the data base for that device'''
    global conf_db
    name = device.split(',')[0]
    if name in conf_db['devices']:
        return conf_db['devices'][name]
    else:
        for dev in conf_db['devices']:
            if conf_db['devices'][dev]['raw'] == name:
                return conf_db['devices'][dev]


def extra_devices():
    ''' set up all extra device as per configuration '''
    global conf_db
    global last_master
    global phones
    global jack_alive
    logging.debug("updating extra devices")

    if not jack_alive:
        logging.debug("Jack is not running, why are we adding extra devices?")
        return
        # no use checking anything else
    import_config("extra_devices")
    # a few loops
    for dev in conf_db['devices']:
        time.sleep(.5)
        rawdev = conf_db['devices'][dev]['raw']
        numst = conf_db['devices'][dev]['number']
        usb = bool(conf_db['devices'][dev]['usb']
                   and conf_db['extra']['usbauto'])
        for sub in conf_db['devices'][dev]['sub']:
            fullname = f"{dev},{sub},0"
            sub_db = conf_db['devices'][dev]['sub'][sub]
            logging.debug(
                f"checking: {fullname} channel counts: "
                f"{sub_db['play-chan']} "
                f"{sub_db['cap-chan']} card: {numst}")
            if sub_db['hide'] or int(conf_db['devices'][dev]['number']) == -1:
                logging.debug(f"{fullname} is hidden or unplugged, no bridge")
                if sub_db['cap-pid'] or sub_db['play-pid']:
                    kill_slave(fullname, conf_db['devices'])
                break
            if get_raw(fullname) == last_master:
                logging.debug(f"{fullname} is master, no bridge")
                break
            if fullname == conf_db['extra']['phone-device']:
                if phones:
                    if not sub_db['play-pid']:
                        start_slave(fullname)
                        break
            if sub_db['play-chan'] or sub_db['cap-chan']:
                if not sub_db['cap-pid'] or not sub_db['play-pid']:
                    start_slave(fullname)
                    break
            else:
                if sub_db['cap-pid'] or sub_db['play-pid']:
                    kill_slave(fullname, conf_db['devices'])
    logging.debug("updating extra devices complete")


def net_bridge():
    ''' set up all audio network bridges as per configuration '''
    global conf_db
    logging.debug("updating Audio network bridges")

    if not conf_db['jack']['on']:
        return
        # no use checking anything else
    kla = shutil.which("killall")
    if kla is None:
        logging.warning("unable to kill old bridges missing killall")
    else:
        cp = subprocess.run([kla, "zita-n2j"],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            shell=False)
        cp1 = subprocess.run([kla, "zita-j2n"],
                             universal_newlines=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT,
                             shell=False)
        logging.debug(
            f"kill old zita-njbridge: {cp.stdout.strip()}{cp1.stdout.strip()}")

    for bridge in conf_db['znet']:
        br_db = conf_db['znet'][bridge]
        cmd = ""
        cnt_str = "--chan "
        late_bits = ""
        if br_db['direction'] == "out":
            cmd = "zita-j2n "
            cnt_str = f"{cnt_str}{str(br_db['count'])}"
            late_bits = f"--{br_db['bits']}"
        elif br_db['direction'] == "in":
            cmd = "zita-n2j"
            late_bits = f"--buff {str(br_db['latency'])}"
            for i in range(1, (br_db['count'] + 1)):
                if i == br_db['count']:
                    cnt_str = f"{cnt_str}{str(i)}"
                else:
                    cnt_str = f"{cnt_str}{str(i)},"
        else:
            logging.warning(f"audio network bridge {bridge} invalid")
            continue
        fcmd = shutil.which(cmd)
        if fcmd is None:
            logging.warning(
                f"command {cmd} not found, bridge: "
                f"{bridge} can not be started")
            continue
        cmd = f"{fcmd} --jname {bridge} {cnt_str} "
        f"{late_bits} {br_db['ip']} {str(br_db['port'])}"
        subprocess.Popen(shlex.split(cmd), shell=False).pid

    logging.debug("updating network audio bridges complete")


def import_config(caller):
    ''' sets default parmeters, then reads values from configuration file'''
    global config_path
    global config_file
    global conf_db
    global fw_exists
    global install_path
    global phone_port
    global startup
    phone_port = 'none'

    logging.log(7, f"import_config called by: {caller}")

    if startup:
        conf_db = auto_jack.convert(False)
        startup = False
    else:
        conf_db = auto_jack.convert()
    # logging.log(7, json.dumps(conf_db, indent = 4))
    # print(json.dumps(conf_db, indent = 4))
    devices = conf_db['devices']
    if os.path.exists("/etc/modprobe.d/blacklist-studio.conf"):
        fw_exists = True
    else:
        for this_dev in devices:
            if devices[this_dev]['firewire']:
                fw_exists = True
    for pout in conf_db['pulse']['outputs']:
        if conf_db['pulse']['outputs'][pout]['connection'] == "monitor":
            conf_db['pulse']['outputs'][
                pout]['connection'] = conf_db['extra']['monitor']

    if conf_db['jack']['driver'] == 'firewire':
        # we don't save this, but treat usbdev as none
        conf_db['jack']['usbdev'] = 'none'
    if 'phone-device' in conf_db['extra']:
        if 'phone-left' not in conf_db['extra']:
            conf_db['extra']['phone-left'] = "1"
        hp_lft = conf_db['extra']['phone-left']
        hp_d_l = conf_db['extra']['phone-device'].split(',', 2)
        if len(hp_d_l) == 3:
            dnm, dnum, sub = hp_d_l
            if dnm in conf_db['devices']:
                hp_db = conf_db['devices'][dnm]
                if dnum in hp_db['sub']:
                    hp_sub_db = hp_db['sub'][dnum]
                    phone_port = f"{hp_sub_db['name']}-out:"
                    phone_port = f"{phone_port}playback_{hp_lft}"
        elif conf_db['extra']['phone-device'] == 'system':
            phone_port = f"system:playback_{hp_lft}"
    logger = logging.getLogger()
    logger.setLevel(int(conf_db['log-level']))
    if caller == "config_start" or caller == "reconfig":
        logging.log(7, f"log level: {str(conf_db['log-level'])}")
        logging.log(7, json.dumps(conf_db, indent=4))
        logging.debug(f"phone port set to: {phone_port}")


def reconfig():
    '''reads values from configuration file and changes run
    to match. This tries to do this without stopping jack
    if not needed'''
    global phones
    global conf_db
    global last_master
    global midiproc
    logging.debug("reconfigure autojack")

    old_conf_db = copy.deepcopy(conf_db)
    import_config("reconfig")
    ol_jkdb = old_conf_db['jack']
    jackdb = conf_db['jack']

    old_jack = [ol_jkdb['on'], ol_jkdb['driver'],
                ol_jkdb['dev'],
                ol_jkdb['rate'], ol_jkdb['frame'],
                ol_jkdb['period'],
                ol_jkdb['connect-mode'], ol_jkdb['chan-in'],
                ol_jkdb['chan-out'],
                ol_jkdb['usbdev'], ol_jkdb['cap-latency'],
                ol_jkdb['play-latency']]
    new_jack = [jackdb['on'], jackdb['driver'], jackdb['dev'],
                jackdb['rate'], jackdb['frame'],
                jackdb['period'],
                jackdb['connect-mode'], jackdb['chan-in'],
                jackdb['chan-out'],
                jackdb['usbdev'], jackdb['cap-latency'],
                jackdb['play-latency']]
    if old_jack != new_jack:
        logging.debug("change requires restart")
        config_start()
        return
    if not conf_db['jack']['on']:
        return
        # no use checking anything else

    pulse_dirty = False
    if [old_conf_db['pulse']['inputs'],
        old_conf_db[
            'pulse']['outputs']] != [
                conf_db['pulse']['inputs'],
                conf_db['pulse']['outputs']]:
        pc = shutil.which("pactl")
        if pc is None:
            logging.warning(
                "pactl is missing... unable to control"
                "pulseaudio\n reconfigure failed")
            return

        pulse_dirty = True
        logging.debug("reconfiguring pulse bridges")

        if len(conf_db[
                'pulse']['inputs']) or len(conf_db['pulse']['outputs']):
            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()}")
        else:
            cp = subprocess.run(["/usr/bin/pactl",
                                 "load-module",
                                 "module-udev-detect"],
                                universal_newlines=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
                                shell=False)
            logging.debug("load module-udev-detect: "
                          f"{cp.stdout.strip()}")

        if old_conf_db['pulse']['inputs'] != conf_db['pulse']['inputs']:
            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 bridge in conf_db['pulse']['inputs']:
                this_count = str(
                    conf_db['pulse']['inputs'][bridge]['count'])
                cp = subprocess.run(["/usr/bin/pactl",
                                     "load-module",
                                     "module-jack-source",
                                     f"client_name={bridge}",
                                     f"channels={this_count}",
                                     "connect=no"],
                                    universal_newlines=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT,
                                    shell=False)
                logging.debug(f"Load jackd_source: {cp.stdout.strip()}")

        if old_conf_db['pulse']['outputs'] != conf_db['pulse']['outputs']:
            logging.debug("reconfiguring pulse bridges")
            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 bridge in conf_db['pulse']['outputs']:
                this_count = str(
                    conf_db['pulse']['outputs'][bridge]['count'])
                cp = subprocess.run(["/usr/bin/pactl",
                                     "load-module",
                                     "module-jack-sink",
                                     f"client_name={bridge}",
                                     f"channels={this_count}",
                                     "connect=no"],
                                    universal_newlines=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT,
                                    shell=False)
                logging.debug(f"PA load jack-sink: {cp.stdout.strip()}")

    if old_conf_db['extra']['a2j'] != conf_db['extra']['a2j']:
        logging.debug("reconfiguring MIDI bridge")
        if 'midiproc' in globals():
            midiproc.send_signal(signal.SIGINT)
            try:
                rt = midiproc.wait(timeout=15)
            except subprocess.TimeoutExpired:
                logging.debug(f"kill a2jmidid failed")
                try:
                    os.kill(int(sub_db['cap-pid']), 9)
                except Exception:
                    print("")

        if conf_db['extra']['a2j']:
            # Can't add logging for background processes.
            # Will show up in syslog
            ajc = shutil.which("a2jmidid")
            if ajc is None:
                logging.Warning(
                    "a2jmidid not found, is it installed? "
                    "Starting MIDI-JACK bridge failed.")
            else:
                up = ""
                if 'a2j_u' in conf_db['extra']:
                    if conf_db['extra']['a2j_u']:
                        up = "-u"
                    cmd = f"{ajc} -e {up}"
                    midiproc = subprocess.Popen(
                        shlex.split(cmd),
                        shell=False)

    if [old_conf_db['mnet']['count'],
        old_conf_db['mnet']['type']] != [conf_db['mnet']['count'],
                                         conf_db['mnet']['type']]:
        logging.debug("reconfiguring MIDI network")
        cp = subprocess.run(["/usr/bin/killall",
                             "qmidinet"],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            shell=False)
        logging.debug(f"kill old qmidinet: {cp.stdout.strip()}")
        if conf_db['mnet']['count']:
            mint = shutil.which("qmidinet")
            if mint is None:
                logging.Warning(
                    "qmidinet not found, is it installed?"
                    "Starting networked midi failed.")
            else:
                mtype = ""
                if conf_db['mnet']['type'] == 'jack':
                    mtype = "--alsa-midi=no --jack-midi=yes"
                cmd = f"{mint} --num-ports={str(conf_db['mnet']['count'])}"
                f"--no-gui {mtype}"
                subprocess.Popen(shlex.split(cmd), shell=False).pid

    # extra devices compares what is to config and fixes things
    logging.debug("Check extra devices")
    extra_devices()
    net_bridge()

    if conf_db['extra'][
        'phone-device'] != old_conf_db[
            'extra']['phone-device']:
        logging.debug("reconfigure Headphones setup")
        phones = False
        phones_check()
    if conf_db['extra']['monitor'][:7] != "system:" and not phones:
        logging.debug("Check main outputs")
        mon_dev, ldev, temp = conf_db['extra']['monitor'].split('-')
        if not conf_db['devices'][
            mon_dev]['sub'][ldev] and conf_db[
                'jack']['driver'] != "alsa" or mon_dev != last_master:
            start_slave(mon_dev)
            con_dirty = True

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


def config_start():
    ''' Pulls configuration and force restarts the world '''
    global conf_db
    global configure_interface_name
    global control_interface_name
    global fw_exists
    global jack_alive
    global jack_died
    global last_master
    global midiproc
    global name_base
    global phones
    global service_name

    if jack_alive:
        kill_jack_client()

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

    import_config("config_start")
    # if at session start we should wait a few seconds for pulse
    # to be fully running
    time.sleep(2)
    logging.debug("Running: config_start jack stat")
    jack_stat("Stopping...")
    logging.info("Running: config_start after jack_stat")
    fpath = expanduser('~/.config/autojack/prestop')
    if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
        logging.debug("Found prestop script")
        cp = subprocess.run([fpath],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            shell=True)
        logging.debug(f"Running script: {fpath}")
    if 'midiproc' in globals():
        midiproc.send_signal(signal.SIGINT)
        try:
            rt = midiproc.wait(timeout=15)
        except subprocess.TimeoutExpired:
            logging.debug(f"kill a2jmidid failed")
            try:
                os.kill(int(sub_db['cap-pid']), 9)
            except Exception:
                print("")
    pc = shutil.which("pactl")
    if pc is None:
        logging.warning(
            "pactl is missing... unable to control"
            "pulseaudio\n configure failed")
    else:
        cp = subprocess.run([pc, "unload-module", "module-jack-source"],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Pulseaudio: {cp.stdout.strip()}")
        time.sleep(1)
        cp = subprocess.run([pc, "unload-module", "module-jack-sink"],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT, shell=False)
        logging.debug(f"Pulseaudio: {cp.stdout.strip()}")
        time.sleep(1)

    bus = dbus.SessionBus()
    controller = bus.get_object(service_name, "/org/jackaudio/Controller")
    control_iface = dbus.Interface(controller, control_interface_name)
    # configure_iface = dbus.Interface(controller, configure_interface_name)

    try:
        control_iface.StopServer()
    except Exception:
        print("stop failed")
    time.sleep(2)
    cp = subprocess.run(["alsactl", "init"], universal_newlines=True,
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                        shell=False)

    # Stop jack if running
    kla = shutil.which("killall")
    if kla is None:
        logging.warning("killall not found, audio controls may fail to work")
    else:
        cp = subprocess.run([kla, "-q", "-9", "jackdbus", "jackd",
                            "a2jmidid", "pulseaudio"],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            shell=False)
        logging.debug(f"Kill old Procs: {cp.stdout.strip()}")
    cp = subprocess.run(["alsactl", "init"], universal_newlines=True,
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                        shell=False)

    jack_stat("Stopped")
    last_master = 'none'

    fpath = expanduser('~/.config/autojack/poststop')
    if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
        logging.debug("Found poststop script")
        cp = subprocess.run([fpath],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            shell=True)
        logging.debug(f"Running script: {fpath}")

    if (conf_db['jack']['driver'] == "firewire") or fw_exists:
        time.sleep(3)
        # this is a firewire device, A busreset makes for stability
        ftst = shutil.which("ffado-test")
        if ftst is None:
            logging.warning(
                "ffado-test not found, fire wire device may"
                "lock up. ffado-tools not installed?")
        else:
            cp = subprocess.run([ftst, "BusReset"],
                                universal_newlines=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
                                shell=False)
            logging.debug(f"reset firewire bus: {cp.stdout.strip()}")
            time.sleep(3)

        if kla is None:
            logging.warning(
                "killall not found, audio controls may fail to work")
        else:
            cp = subprocess.run([kla,
                                 "-q", "ffado-dbus-server",
                                 "ffado-mixer"],
                                universal_newlines=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
                                shell=False)
            logging.debug(
                "Kill ffado mixer because busreset needed:"
                f"{cp.stdout.strip()}")

    # restart Pulse to reload default modules which we may have removed
    # bus = dbus.SessionBus() # bus already set above
    systemd1 = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')
    manager = dbus.Interface(systemd1, 'org.freedesktop.systemd1.Manager')
    job = manager.RestartUnit('pulseaudio.service', 'fail')
    logging.debug(f"JACK is {str(conf_db['jack']['on'])} restart pulse: {job.strip()}")
    if not conf_db['jack']['on']:
        return
    logging.debug(f"JACK is {str(conf_db['jack']['on'])} start up jack")
    jack_stat("Config Pulse")

    # 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()}")

    fpath = expanduser('~/.config/autojack/prestart')
    if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
        logging.debug("Found prestart script")
        jack_stat("Prestart running")
        cp = subprocess.run([fpath],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            shell=True)
        logging.debug(f"Running script: {fpath}")

    jack_stat("Configuring...")
    rn = 'none'
    if conf_db['jack']['usbdev'] != "none":
        rn = get_db(conf_db['jack']['usbdev'])['raw']
    if os.path.exists(f"/proc/asound/{rn}"):
        mdev = get_raw(conf_db['jack']['usbdev'])
    else:
        mdev = conf_db['jack']['dev']
        if mdev.split(',')[0] == "HDMI" or mdev.split(',')[0] == "NVidia":
            logging.info("HDMI device, setting buffer to 4096")
            conf_db['jack']['frame'] = "4096"
    mdev_db = get_db(mdev)

    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
    if conf_db['jack']['connect-mode'] != 'n':
        bus = dbus.SessionBus()
        controller = bus.get_object(service_name, "/org/jackaudio/Controller")
        # control_iface = dbus.Interface(controller, control_interface_name)
        configure_iface = dbus.Interface(controller, configure_interface_name)
        configure_iface.SetParameterValue(
            ['engine',
             'self-connect-mode'],
            dbus.Byte(ord(conf_db['jack']['connect-mode'])))

    if conf_db['jack']['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)

    bus = dbus.SessionBus()
    controller = bus.get_object(service_name, "/org/jackaudio/Controller")
    control_iface = dbus.Interface(controller, control_interface_name)
    configure_iface = dbus.Interface(controller, configure_interface_name)

    configure_iface.SetParameterValue(
        ['engine', 'driver'], str(conf_db['jack']['driver']))
    logging.debug(f"JACK driver set to: {conf_db['jack']['driver']}")
    time.sleep(.4)
    if conf_db['jack']['driver'] == "alsa":
        # we use default device for FW right now
        configure_iface.SetParameterValue(['driver', 'device'], f"hw:{mdev}")
        logging.debug(f"JACK device set to: {mdev}")
        if mdev_db['firewire'] and conf_db['jack']['frame'] < 256:
            conf_db['jack']['frame'] = 256
            logging.info(
                f"Firewire device using ALSA drivers, JACK buffer set to: 256")

    time.sleep(.2)
    configure_iface.SetParameterValue(
        ['driver', 'rate'], dbus.UInt32(conf_db['jack']['rate']))
    time.sleep(.2)
    configure_iface.SetParameterValue(
        ['driver', 'period'], dbus.UInt32(conf_db['jack']['frame']))
    time.sleep(.2)
    if conf_db['jack']['driver'] != "dummy":
        configure_iface.SetParameterValue(['driver',
                                           'nperiods'],
                                          dbus.UInt32(conf_db[
                                              'jack']['period']))
        time.sleep(.2)
        configure_iface.SetParameterValue(
            ['driver', 'input-latency'],
            dbus.UInt32(conf_db['jack']['cap-latency']))
        time.sleep(.2)
        configure_iface.SetParameterValue(['driver',
                                           'output-latency'],
                                          dbus.UInt32(conf_db['jack'][
                                              'play-latency']))

    else:
        configure_iface.SetParameterValue(['driver', 'capture'],
                                          dbus.UInt32(conf_db[
                                              'jack']['chan-in']))
        time.sleep(.2)
        configure_iface.SetParameterValue([
            'driver', 'playback'],
            dbus.UInt32(conf_db['jack']['chan_out']))
        time.sleep(.2)
        configure_iface.SetParameterValue(['driver', 'monitor'], True)
    logging.debug(
        "JACK rate/period/nperiods set to: "
        f"{conf_db['jack']['rate']}/"
        f"{conf_db['jack']['frame']}/{conf_db['jack']['period']}")
    time.sleep(3)
    jack_stat("Starting...")

    try:
        control_iface.StartServer()
    except Exception:
        logging.debug("JACK start failed")
        jack_stat("Start Failed")
        return
    logging.debug("JACK started")

    if conf_db['jack']['driver'] == "alsa":
        last_master = mdev
    # maybe check for jack up (need function?)
    time.sleep(3)

    jack_stat("Poststart")
    fpath = expanduser('~/.config/autojack/poststart')
    if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
        logging.debug("Found poststart script")
        cp = subprocess.run([fpath],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            shell=True)
        logging.debug(f"Running script: {fpath}")

    start_jack_client()
    time.sleep(1)

    jack_stat("Adding Bridges")
    pc = shutil.which("pactl")
    if pc is None:
        logging.warning(
            "pactl is missing... unable to control"
            "pulseaudio\n configure failed")
    else:
        for bridge in conf_db['pulse']['inputs']:
            this_count = str(conf_db['pulse']['inputs'][bridge]['count'])
            cp = subprocess.run([pc, "load-module", "module-jack-source",
                                 f"client_name={bridge}",
                                 f"channels={this_count}", "connect=no"],
                                universal_newlines=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT, shell=False)
            logging.debug(f"Pulseaudio: {cp.stdout.strip()}")
            time.sleep(1)
        for bridge in conf_db['pulse']['outputs']:
            this_count = str(conf_db['pulse']['outputs'][bridge]['count'])
            cp = subprocess.run([pc, "load-module", "module-jack-sink",
                                 f"client_name={bridge}",
                                 f"channels={this_count}", "connect=no"],
                                universal_newlines=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT, shell=False)
            logging.debug(f"Pulseaudio: {cp.stdout.strip()}")
            time.sleep(1)

        if len(conf_db['pulse']['inputs']) or len(conf_db['pulse']['outputs']):
            cp = subprocess.run([pc, "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()}")
            time.sleep(1)
            cp = subprocess.run([pc, "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()}")
            time.sleep(1)

    extra_devices()
    net_bridge()

    # check if phones device exists
    ph_dev = conf_db['extra']['phone-device'].split(',')[0]
    if ph_dev not in conf_db['devices']:
        phones = False
    if phones:
        if conf_db['extra']['phone-device'] != last_master:
            start_slave(conf_db['extra']['phone-device'])
            con_dirty = True
    elif conf_db['extra']['monitor'][:7] != "system:":
        mon_dev = conf_db['extra']['monitor'].split('-')[0]
        start_slave(mon_dev)
        con_dirty = True

    # 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 conf_db['extra']['a2j']:
        ajc = shutil.which("a2jmidid")
        if ajc is None:
            logging.Warning(
                "a2jmidid not found, is it installed?"
                " Starting MIDI-JACK bridge failed.")
        else:
            up = ""
            if 'a2j_u' in conf_db['extra']:
                if conf_db['extra']['a2j_u']:
                    up = "-u"
                cmd = f"{ajc} -e {up}"
                midiproc = subprocess.Popen(shlex.split(cmd), shell=False)
                # logging not possible without extra thread

    if conf_db['mnet']['count']:
        mint = shutil.which("qmidinet")
        if mint is None:
            logging.Warning(
                "qmidinet not found, is it installed?"
                " Starting networked midi failed.")
        else:
            mtype = ""
            if conf_db['mnet']['type'] == 'jack':
                mtype = "--alsa-midi=no --jack-midi=yes"
            cmd = f"{mint} --num-ports={str(conf_db['mnet']['count'])} "
            f"--no-gui {mtype}"
            subprocess.Popen(shlex.split(cmd), shell=False).pid

    jack_stat("Postbridge")
    fpath = expanduser('~/.config/autojack/postbridge')
    if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
        logging.debug("Found poststart script")
        cp = subprocess.run([fpath],
                            universal_newlines=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT, shell=True)
        logging.debug(f"Running script: {fpath}")
    jack_stat("Running")


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
    logging.debug("create jack client")
    jack.set_error_function(callback=jack_error)
    jack.set_info_function(callback=jack_info)
    # use dbus call to find if jack is running
    bus = dbus.SessionBus()
    controller = bus.get_object(service_name, "/org/jackaudio/Controller")
    control_iface = dbus.Interface(controller, control_interface_name)
    if int(control_iface.IsStarted()):
        try:
            jack_client = jack.Client(
                'AutoJack', use_exact_name=False, no_start_server=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 kill_jack_client():
    global jack_client
    global jack_alive
    global jack_died

    if jack_client in globals():
        jack_client.deactivate()
        jack_client.close()
    jack_died = False
    jack_alive = False
    jack_stat("Stopped")
    logging.debug("jack client closed and cleaned up")


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
    jack_stat("Stopping")
    logging.debug("jack died callback")


def jack_stat(status):
    global last_status
    last_status = status
    sendbs.state(version, status)


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 conf_db
    if connect and (phones or (conf_db['extra']['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. At least that is what it started as. Now it is a loop
    that is run on a timer that checks for various things to do.
    '''
    global con_dirty
    global conf_db
    global control_signal
    global device_signal
    global jack_alive
    global jack_count
    global jack_client
    global jack_died
    global last_status
    global phone_port
    global phone_signal

    if control_signal != []:
        command_run()
        control_signal = []
    if device_signal < 0:
        device_removed()
        device_signal = 0
    if device_signal > 0:
        device_new()
        device_signal = 0
    if phone_signal < 0:
        phones_switch(False)
        phone_signal = 0
    if phone_signal > 0:
        phones_switch(True)
        phone_signal = 0

    # use dbus call to find if jack is running
    bus = dbus.SessionBus()
    controller = bus.get_object(service_name, "/org/jackaudio/Controller")
    control_iface = dbus.Interface(controller, control_interface_name)
    # configure_iface = dbus.Interface(controller, configure_interface_name)
    if jack_alive and not int(control_iface.IsStarted()):
        jack_died = True

    # sent by jackdied() callback
    if jack_died:
        kill_jack_client()

    jack_count = jack_count + 1
    if jack_count == 200:
        # maintain logfiles and other things that don't need to
        # be looked at so often
        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) > 200000:
            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=conf_db['log-level'])
            logging.debug("Log File rotated")
        logfile = expanduser("~/.log/jack/jackdbus.log")
        if os.path.isfile(logfile) and os.path.getsize(logfile) > 200000:
            logging.debug("JACK 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:
                    os.replace(logfile, f"{logfile}.1")
            logging.debug("JACK Log File rotated")

    rgov, rboost = auto_jack.get_gvnr_hw()
    pk = shutil.which("pkexec")
    if pk is None:
        logging.warning(
            "pexec is missing... unable to change"
            " cpu-governor or boost")
    else:
        if conf_db['cpu-governor'] != rgov:
            if conf_db['cpu-governor']:
                gov = "performance"
            else:
                if os.path.exists("/sys/devices/system/cpu/intel_pstate"):
                    gov = "powersave"
                else:
                    gov = "ondemand"
            logging.info(f"Setting CPU Governor to {gov}")
            subprocess.run(
                [pk, f"{install_path}/sbin/studio-system", gov], shell=False)

        if conf_db['boost'] != rboost:
            if conf_db['boost']:
                boost = "noboost"
                logging.info("Setting Intel Boost to disable")
            else:
                boost = 'boost'
                logging.info("Setting Intel Boost to enable")
            subprocess.run(
                [pk, f"{install_path}/sbin/studio-system", boost], shell=False)

    if con_dirty and jack_alive:
        logging.debug("Connection changed")
        if phones and 'phone_port' in globals() and phone_port != 'none':
            new_l = phone_port
            logging.debug(f"change it to phones port: {new_l}")
        else:
            new_l = conf_db['extra']['monitor']
        switch_outputs("system:playback_1", new_l)
        con_dirty = False

    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 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("both ports are the same, no move done")
        return
    try:
        jack_client.get_port_by_name(str(left_old))
    except jack.JackError:
        logging.debug(f"Jack port: {left_old} not found")
        return
    try:
        jack_client.get_port_by_name(str(left_new))
    except jack.JackError:
        logging.debug(f"Jack port: {left_new} not found")
        return

    right_old = get_next_port(left_old)
    right_new = get_next_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]
        try:
            jack_client.connect(s_port, left_new)
        except Exception:
            logging.debug(f"Jack port {s_port} could not be connected to {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]
        try:
            jack_client.connect(s_port, right_new)
        except Exception:
            logging.debug(f"Jack port {s_port} could not be connected to {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_next_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.name)}")
            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 conf_db
    global jack_client
    global jack_alive
    if not jack_alive:
        logging.debug("Can't connect pulse, jack not running")
        return
    logging.debug("connect pulse bridges")

    for bridge in conf_db['pulse']['inputs']:
        connection = conf_db['pulse']['inputs'][bridge]['connection']
        if connection != 'none':
            prevport = ""
            nextport = connection
            pulselist = jack_client.get_ports(
                f"{bridge}:*", is_audio=True, is_input=True)
            for pport in pulselist:
                # apparently reg ex with : doesn't find : so sort it here
                if pport.name.split(':')[0] != bridge:
                    continue
                if nextport != prevport:
                    logging.debug(f"jack connect {nextport} to {pport.name}")
                    try:
                        jack_client.connect(nextport, pport)
                    except Exception:
                        logging.log(8, "already connected skipping")
                    prevport = nextport
                    nextport = get_next_port(prevport)

    for bridge in conf_db['pulse']['outputs']:
        connection = conf_db['pulse']['outputs'][bridge]['connection']
        if connection != 'none':
            prevport = ""
            nextport = connection
            if connection == "monitor":
                nextport = conf_db['extra']['MONITOR']
            pulselist = jack_client.get_ports(
                f"{bridge}:*", is_audio=True, is_output=True)
            for pport in pulselist:
                if pport.name.split(':')[0] != bridge:
                    continue
                if nextport != prevport:
                    logging.debug(f"jack connect {pport.name} to {nextport}")
                    try:
                        jack_client.connect(pport, nextport)
                    except Exception:
                        logging.log(8, "already connected skipping")
                    prevport = nextport
                    nextport = get_next_port(prevport)


def disconnect_pa(our_config):
    '''disconnect Pulse ports we know we have connected.
    The pa-jack bridge is left running. Leave other connections
    alone.'''
    global jack_client
    global jack_alive
    if not jack_alive:
        logging.debug("Jack not running")
        return
    logging.debug("disconnect pulse bridges")
    p_in_db = our_config['pulse']['inputs']
    p_out_db = our_config['pulse']['outputs']
    for bridge in p_in_db:
        if p_in_db[bridge]['connection'] != 'none':
            prevport = ""
            nextport = p_in_db[bridge]['connection']
            pulselist = jack_client.get_ports(
                f"{bridge}:*", is_audio=True, is_input=True)
            for pport in pulselist:
                if nextport != prevport:
                    logging.debug(
                        f"jack disconnect {nextport} from {pport.name}")
                    try:
                        jack_client.disconnect(nextport, pport)
                    except Exception:
                        logging.log(8, "already connected skipping")
                    prevport = nextport
                    nextport = get_next_port(prevport)

    for bridge in p_out_db:
        if p_out_db[bridge]['connection'] != 'none':
            prevport = ""
            if p_out_db[bridge]['connection'] == "monitor":
                nextport = our_config['extra']['monitor']
            else:
                nextport = p_out_db[bridge]['connection']
            pulselist = jack_client.get_ports(
                f"{bridge}:*", is_audio=True, is_output=True)
            for pport in pulselist:
                if nextport != prevport:
                    logging.debug(
                        f"jack disconnect {pport.name} from {nextport}")
                    try:
                        jack_client.disconnect(pport, nextport)
                    except Exception:
                        logging.log(8, "already connected skipping")
                    prevport = nextport
                    nextport = get_next_port(prevport)


def msg_cb_new(*args, **kwargs):
    '''call back for udev sensing new device. checks if device is audio.
        If it is, set a global variable to run in timer loop.
    '''
    global last_master
    global conf_db
    global device_signal
    global version
    global cards_f

    if not conf_db['jack']['on']:
        # then we don't care
        return

    # let things settle
    time.sleep(3)

    if args[0].find("sound-card") >= 0:
        device_signal = 1
        return


def device_new():
    '''
        call back for udev sensing new device has set variable so
        this 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 last_master
    global conf_db
    global version
    global cards_f

    # check to see if any new cards
    with open('/proc/asound/cards', "r") as card_file:
        my_cards_f = card_file.read()
        if my_cards_f == cards_f:
            logging.debug("card file not changed, skip")
            return
        else:
            cards_f = my_cards_f
    # remake database
    old_devs_db = copy.deepcopy(conf_db['devices'])
    import_config("msg_cb_new")
    dev_db = {}
    for dev_name in conf_db['devices']:
        dev_db = conf_db['devices'][dev_name]
        # we only care about usb devices that are plugged in and have audio
        if dev_db['usb'] and dev_db['number'] != -1 and len(dev_db['sub']):
            # check if it was not in db before or "card number" has changed
            if dev_name not in old_devs_db or dev_db[
                    'number'] != old_devs_db[dev_name]['number']:
                # tell gui devicelist has changed so it can up date
                # device dropdowns
                sendbs.state(version, 'usb')
                logging.debug(f"USB device {dev_name} has been plugged in")

                for subn in dev_db['sub']:
                    subname = f"{dev_name},{str(subn)},0"
                    if subname == last_master:
                        # this device is current master
                        # maybe jack did a reset?
                        continue
                    sub_db = dev_db['sub'][subn]
                    if (conf_db['jack']['driver'] == "alsa") and (
                            conf_db['jack']['usbdev'] == subname):
                        logging.debug(
                            f"Changing jack master to: {subname}")
                        config_start()
                        return
                    if sub_db['play-pid'] or sub_db['cap-pid']:
                        # device in use already
                        continue
                    if conf_db['extra']['usbauto']:
                        # extra_devices()
                        start_slave(subname)
                        if subname == conf_db['extra']['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 conf_db
    global cards_f
    global device_signal
    global jack_alive
    global last_master

    if not jack_alive:
        # then we don't care
        return

    # let things settle
    time.sleep(.5)
    if args[0].find("sound-card") >= 0:
        device_signal = -1


def device_removed():
    ''' dbus call back when a USB device removal has been detected by udev '''
    global conf_db
    global cards_f
    global jack_alive
    global last_master

    if not jack_alive:
        # then we don't care
        return

    # check to see if any cards have vanished
    with open('/proc/asound/cards', "r") as card_file:
        my_cards_f = card_file.read()
        # logging.debug(f"card file: {str(my_cards_f)}")
        if my_cards_f == cards_f:
            logging.debug("card file not changed, skip")
            return
        else:
            cards_f = my_cards_f

    # remake database
    old_devs_db = copy.deepcopy(conf_db['devices'])
    import_config("msg_cb_removed")
    dev_db = {}
    for dev_name in conf_db['devices']:
        dev_db = conf_db['devices'][dev_name]
        logging.debug(f"check device {dev_name} for unplug")
        # we only care about usb devices that are unplugged and have audio
        logging.debug(f"USB {str(dev_db['usb'])} Num: {str(dev_db['number'])}")
        if dev_db['usb'] and (dev_db['number'] == -1) and len(dev_db['sub']):
            # check if it was not in db before or "card number" has changed
            if dev_name in old_devs_db or dev_db[
                    'number'] != old_devs_db[dev_name]['number']:
                # tell gui devicelist has changed
                # so it can up date device dropdowns
                sendbs.state(version, 'usb')
                logging.debug(f"USB device {dev_name} has been unplugged")

                for subn in dev_db['sub']:
                    subname = f"{dev_name},{str(subn)},0"
                    if conf_db['jack']['usbdev'] == subname:
                        if last_master == get_raw(
                                conf_db['jack']['usbdev']):
                            kill_slave(conf_db['jack']['dev'], old_devs_db)
                            time.sleep(1)
                            config_start()
                    elif conf_db['extra']['usbauto']:
                        if subname == conf_db['extra']['phone-device']:
                            phones_switch(False)
                        kill_slave(subname, old_devs_db)


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

    logging.debug(f"Start slave: {ldev}")
    if len(ldev.split(",", 2)) != 3:
        logging.debug(
            f"Device {ldev} missing device number or sub device")
        return

    import_config("start_slave")
    dname, l_dev, sub = ldev.split(",", 2)
    dev_db = conf_db['devices'][dname]
    sub_db = dev_db['sub'][l_dev]

    if sub_db['hide']:
        # don't start a slave blacklisted device
        logging.debug(f"Blacklisted: {ldev} don't start")
        return

    raw_xp = f"{dev_db['raw']},{l_dev},{sub}"
    buff_size = sub_db['frame']
    # should do play and capture separately
    if sub_db['play-pid'] or sub_db['cap-pid']:
        logging.debug(
            f" Device {ldev} already bridged or in use by other application")
        return

    if dev_db['hdmi']:
        logging.info("HDMI device, setting buffer to 4096")
        buff_size = "4096"
    elif dev_db['internal'] and (buff_size < 128):
        logging.info("Internal device, minimum buffer 128, using 128")
        buff_size = "128"
    dsr = str(sub_db['rate'])
    if dsr not in dev_db['rates']:
        logging.info(f"sample rate {dsr} for {ldev} not valid")
        if "48000" in dev_db['rates']:
            dsr = "48000"
        elif "44100" in dev_db['rates']:
            dsr = "44100"
        elif len(dev_db['rates']):
            dsr = dev_db['rates'][0]
        else:
            logging.info(f"{ldev} has no sample rates, no bridge")
            return
        logging.info(f"Using {dsr} instead")
    # we found it and it seems to have this sub
    if sub_db['playback']:
        if not sub_db['play-pid']:
            # this should detect if the user has manually set
            # either in or out numbers
            channels = 0
            if sub_db['play-chan']:
                channels = sub_db['play-chan']
            elif dev_db['usb'] and not conf_db['extra']['usb-single']:
                channels = 100
            elif ldev == conf_db['extra']['phone-device']:
                channels = 100
            if channels:
                cmd = f"/usr/bin/zita-j2a -j {sub_db['name']}-out"
                cmd = f"{cmd} -d hw:{raw_xp} -r {dsr} -p {buff_size}"
                cmd = f"{cmd} -n {str(sub_db['nperiods'])} -c {str(channels)}"
                cmd = f"{cmd} -O {str(sub_db['play-latency'])}"
                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)
                sub_db['play-pid'] = pidout
        else:
            logging.debug(
                f" Device {ldev} playback already bridged or "
                "in use by other application")
    if sub_db['capture']:
        if not sub_db['cap-pid']:
            chanels = 0
            if sub_db['cap-chan']:
                channels = sub_db['cap-chan']
            elif dev_db['usb']:
                channels = 100
            if channels:
                cmd = f"/usr/bin/zita-a2j -j {sub_db['name']}-in"
                cmd = f"{cmd} -d hw:{raw_xp} -r {dsr} -p {buff_size}"
                cmd = f"{cmd} -n {str(sub_db['nperiods'])} -c {str(channels)}"
                cmd = f"{cmd} -I {str(sub_db['cap-latency'])}"
                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)
                sub_db['cap-pid'] = pidin
        else:
            logging.debug(
                f" Device {ldev} capture already bridged or "
                "in use by other application")
    time.sleep(1)
    import_config("start_slave")


def kill_slave(ldev, devs_db):
    ''' takes the device as a parameter and if the device exists
    and is bridged to jack, stops the bridge '''
    global conf_db
    global procs
    dname, l_dev, sub = ldev.split(",", 2)
    logging.debug(f"{ldev} kill in progress")
    # dev_db = conf_db['devices'][dname]
    dev_db = devs_db[dname]
    if len(dev_db['sub']):
        sub_db = dev_db['sub'][l_dev]
        logging.debug(
            f"{dname} has {str(len(dev_db['sub']))} sub devices, "
            f"want sub device {l_dev}")
        if sub_db['play-pid']:
            logging.debug(
                f"{ldev} found Playback bridge to kill. "
                f"PID: {str(sub_db['play-pid'])}")
            for i, pr in enumerate(procs):
                if pr.pid == sub_db['play-pid']:
                    logging.debug(
                        f"kill {str(dname)} sub: {str(l_dev)} "
                        f"PID: {str(sub_db['play-pid'])}")
                    pr.send_signal(signal.SIGINT)
                    try:
                        rt = pr.wait(timeout=15)
                    except subprocess.TimeoutExpired:
                        logging.debug(
                            f"kill PID: {str(sub_db['play-pid'])} failed")
                        pr.terminate()
                        outs, errs = pr.communicate()
                    del procs[i]
        else:
            logging.debug(f"{ldev} no Playback bridge found")
        if sub_db['cap-pid']:
            logging.debug(
                f"{ldev} found Capture bridge to kill. "
                f"PID: {str(sub_db['cap-pid'])}")
            for i, pr in enumerate(procs):
                if pr.pid == sub_db['cap-pid']:
                    logging.info(
                        f"kill {str(dname)} sub: {str(l_dev)} "
                        f"PID: {str(sub_db['cap-pid'])}")
                    pr.send_signal(signal.SIGINT)
                    try:
                        rt = pr.wait(timeout=15)
                    except subprocess.TimeoutExpired:
                        logging.debug(
                            f"kill PID: {str(sub_db['cap-pid'])} failed")
                        try:
                            os.kill(int(sub_db['cap-pid']), 9)
                        except Exception:
                            print("")
                    del procs[i]
        else:
            logging.debug(f"{ldev} no Capture bridge found")
    # get rid of pid entries
    import_config("kill_slave")


def we_die():
    global lock_file
    global midiproc
    if 'midiproc' in globals():
        midiproc.send_signal(signal.SIGINT)
        try:
            rt = midiproc.wait(timeout=15)
        except subprocess.TimeoutExpired:
            logging.debug(f"kill a2jmidid failed")
            try:
                os.kill(int(sub_db['cap-pid']), 9)
            except Exception:
                print("")
    cp = subprocess.run(["/usr/bin/killall",
                        "-9", "jackdbus", "jackd", "a2jmidid"],
                        universal_newlines=True,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, shell=False)
    jack_stat("Stopped")
    logging.debug(f"Kill jack and friends: {cp.stdout.strip()}")
    bus = dbus.SessionBus()
    systemd1 = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')
    manager = dbus.Interface(systemd1, 'org.freedesktop.systemd1.Manager')
    job = manager.RestartUnit('pulseaudio.service', 'fail')
    logging.debug(f"Restart PA: {job.strip()}")
    if os.path.isfile(lock_file):
        new_pid = str(os.getpid())
        if os.path.isfile(lock_file):
            with open(lock_file, "r") as lk_file:
                for line in lk_file:
                    # only need one line
                    old_pid = line.rstrip()
            if new_pid == old_pid:
                os.remove(lock_file)
    os._exit(0)


def phones_switch(plugged):
    ''' Does the actual phones signal and level switching '''
    global conf_db
    global phones
    global phone_port
    global jack_client
    global jack_alive
    phones = plugged
    logging.debug(f"Changing Headphone to plugged in = {str(plugged)}")
    logging.debug(f"Headphone port: {str(phone_port)}")
    if (not conf_db['jack']['on']) or not jack_alive:
        # we can't do anything up to pulse (I think)
        return
    if  'phone_port' not in globals() or (phone_port == 'none'):
        logging.debug("phone device and port not set: setting phones to False")
        phones = False
        return
    logging.debug(
        f"Headphone phone action = {str(conf_db['extra']['phone-action'])}")
    pdev_list = []
    if conf_db['extra']['phone-device'] == 'system' and conf_db['jack']['driver'] == 'alsa':
        if conf_db['jack']['usbdev'] != 'none':
            pdev_list = conf_db['jack']['usbdev'].split(",", 2)
        else:
            pdev_list = conf_db['jack']['dev'].split(",", 2)
    else:
        pdev_list = conf_db['extra']['phone-device'].split(",", 2)
    alsadev = False
    if len(pdev_list) == 3:
        dname, l_dev, sub = pdev_list
        dev_db = conf_db['devices'][dname]
        sub_db = dev_db['sub'][l_dev]
        alsadev = True

    if conf_db['extra']['phone-action'] == "switch":
        logging.debug("Switching outputs")
        if alsadev and dev_db['internal']:
            spkr = ""
            logging.debug(
                "Headphone device is internal change it's mixer settings")
            my_mixers = alsaaudio.mixers(device=f"hw:{dname}")
            if "Front" in my_mixers:
                spkr = "Front"
            elif "Speakers" in my_mixers:
                spkr = "Speakers"
            spkr_mix = alsaaudio.Mixer(control=spkr, device=f'hw:{dname}')
            hp_mix = alsaaudio.Mixer(control='Headphone', device=f'hw:{dname}')
            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)
        logging.debug(f"Headphone jack is: {phone_port}")
        hp_search = f"{phone_port.split(':', 1)[0]}*"
        port_list = jack_client.get_ports(
            name_pattern=hp_search, is_audio=True,
            is_input=True, is_physical=True)
        if not plugged: # switch to monitor
            switch_outputs(phone_port, conf_db['extra']['monitor'])
            # BUG sub_db may not exist if not alsadev
            if alsadev and not sub_db['play-chan']:
                logging.debug(
                    "Headphone device had no jack port ... "
                    "kill unneeded bridge")
                kill_slave(conf_db['extra']['phone-device'], conf_db['devices'])
        else: # switch to phone port
            if alsadev and port_list == []:
                logging.debug(
                    "Headphone device has no jack port ... create bridge")
                start_slave(conf_db['extra']['phone-device'])
                time.sleep(1)
            mn_search = f"{conf_db['extra']['monitor'].split(':', 1)[0]}*"
            port_list = jack_client.get_ports(
                name_pattern=mn_search, is_audio=True,
                is_input=True, is_physical=True)
            switch_outputs(conf_db['extra']['monitor'], phone_port)

    elif conf_db['extra']['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.info("headphone script not found or not executable")
    else:
        logging.debug("No headphone 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 conf_db
    phones = False

    import_config("phones_check")
    # need to check if jack is running

    if len(conf_db['extra']['phone-device'].split(',')) < 3:
        logging.info(f"Phones device: {conf_db['extra']['phone-device']} invalid")
        return
    dname, ddev, dsub = conf_db['extra']['phone-device'].split(',')
    if dname not in conf_db['devices']:
        logging.info(f"Phones device: {conf_db['extra']['phone-device']} invalid")
        return
    dev_db = conf_db['devices'][dname]
    sub_db = dev_db['sub'][ddev]
    logging.log(7, f"phonecheck: got device")
    if int(dev_db['number']) < 0:
        logging.info(f"Phones device: {dname} not present")
        return
    logging.debug(f"Checking phones device: {dname}")
    if dev_db['usb']:
        # phones device is USB
        if sub_db['playback']:
            # If USB make sure it has audio playback
            phones_switch(True)
            return
        else:
            logging.warning(
                f"Headphone device {conf_db['extra']['phone-device']} "
                "appears to have no outputs")
    elif dev_db['internal']:
        # this is internal we can use amixer
        logging.debug("Checking for phones with internal device")
        try:
            my_mixers = alsaaudio.mixers(device=f"hw:{dname}")
        except Exception:
            logging.info(f"Phones device: {dname} mixer not accessable")
            return
        hp_sw = ""
        if "Front" in my_mixers:
            hp_sw = "Front "
        cmd = f"/usr/bin/amixer -D hw:{dname} cget"
        cmd = f"{cmd} 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 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 '''
    global conf_db
    global phone_signal
    logging.info("Got phones plugged in signal.")
    dname, ddev, dsub = conf_db['extra']['phone-device'].split(',')
    dev_db = conf_db['devices'][dname]
    if int(dev_db['number']) > -1 and dev_db['internal']:
        # changed to:
        phone_signal = 1
        # the line below to be moved to check_jack_status
        # phones_switch(True)
    else:
        logging.info("Ignored phone plug: not correct device")


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.'''
    global conf_db
    global phone_signal
    logging.info("Got phones unplugged signal.")
    dname, ddev, dsub = conf_db['extra']['phone-device'].split(',')
    dev_db = conf_db['devices'][dname]
    if int(dev_db['number']) > -1 and dev_db['internal']:
        # changed to:
        phone_signal = -1
        # the line below to be moved to check_jack_status
        # phones_switch(False)
    else:
        logging.info("Ignored phone unplug: not correct device")


def jack_error(mesg):
    ''' jack call back to deal with jack errors '''
    global jack_alive
    if jack_alive:
        if "not running" in mesg:
            logging.warning(f"Jack Message: {mesg}")


def jack_info(mesg):
    ''' jack call back to deal with jack info '''
    logging.info(f"Jack Message: {mesg}")


def ses_cb_command(*args, **kwargs):
    ''' Generic signal receiver '''
    global control_signal
    control_signal = args


def command_run():
    ''' Generic signal receiver '''
    global version
    global last_status
    global phones
    global control_signal
    args = control_signal
    rec_string = f"args: {str(args[0])}"
    logging.debug(f"got signal - {rec_string}")
    if 'start' in args:
        logging.info("Got start signal.")
        config_start()
        return
    if 'config' in args:
        logging.info("Got config signal.")
        reconfig()
        return
    if 'stop' in args:
        logging.info("Got stop signal.")
        config_start()
        return
    if 'ping' in args:
        logging.info("Got ping signal.")
        time.sleep(3)
        sendbs.state(version, last_status)
        return
    if 'quit' in args:
        logging.warning("Got quit signal.")
        we_die()
        return
    if 'phones' in args:
        logging.info("Got phones signal.")
        phones_switch(True)
        phones = True
        logging.debug(f"Manual phones switch to: {str(phones)}")
        return
    if 'monitor' in args:
        logging.info("Got monitor signal.")
        phones_switch(False)
        phones = False
        logging.debug(f"Manual phones switch to: {str(phones)}")
        return


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.")
    we_die()


def main():
    ''' Autojack runs at session start and manages audio for the session.
    this is the daemon for studio-controls'''
    global cards_f
    global config_path
    global con_dirty
    global fw_exists
    global install_path
    global jack_alive
    global jack_client
    global jack_count
    global jack_died
    global last_master
    global lock_file
    global phones
    global procs
    global sendbs
    global startup
    global version
    global control_signal
    global device_signal
    global phone_signal
    control_signal = []
    device_signal = 0
    phone_signal = 0
    auto_jack.check_user()  # don't run as system or root
    cards_f = "none"
    con_dirty = False
    fw_exists = False
    phones = False
    jack_alive = False
    jack_client = 0
    jack_count = 0
    jack_died = False
    last_master = ""
    procs = []
    startup = True
    print("starting up")
    print(f"install path: {install_path}")
    version = auto_jack.version()
    print(f"version: {version}")

    # 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 - AutoJack - %(levelname)s - %(message)s',
        level=logging.DEBUG)
    logging.info('Autojack started: logging started')
    print("logging started")

    # Try and kill any other running instance
    config_path = expanduser("~/.config/autojack")
    if not os.path.isdir(config_path):
        os.makedirs(config_path)
    lock_file = f"{config_path}/autojack.lock"
    print("sending quit to any old autojack")
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    sendbs = sendbus()
    sendbs.signal('quit')
    time.sleep(3)
    new_pid = str(os.getpid())
    old_pid = new_pid
    if os.path.isfile(lock_file):
        file_stats = os.stat(lock_file)
        if int(file_stats.st_size):
            # other instance still hasn't gone maybe hung
            with open(lock_file, "r") as lk_file:
                for line in lk_file:
                    # only need one line
                    old_pid = line.rstrip()
            if new_pid != old_pid:
                print("old lock file found, killing old pid")
                try:
                    os.kill(int(old_pid), 9)
                except Exception:
                    print("")
                time.sleep(1)
    with open(lock_file, "w") as lk_file:
        lk_file.write(new_pid)
        print("Lock file created")

    phones_check()
    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) unhandlable  :)
    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)

    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_command, dbus_interface='org.studio.control.command',
        signal_name='signal')

    sendbs.signal('start')

    jack_stat("Undetermined")

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

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


if __name__ == '__main__':
    main()
