#!/usr/bin/python3

import gi

import os
import dbus
import getpass
import grp
import jack
import json
import re
import resource
import shlex
import shutil
import signal
import socket
import subprocess
import sys
import time

from dbus.mainloop.glib import DBusGMainLoop
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
from os.path import expanduser

DBusGMainLoop(set_as_default=True)


class SysInfo:
    """Get information about the system"""

    # get info about if rtaccess is setup right
    def user_audio(self):
        """Checks if current user is in the audio group, or not"""
        audio_users = []
        audio_users = grp.getgrnam("audio")[3]
        user = getpass.getuser()
        if user in audio_users:
            return True

        return False

    def check_pam_files(self):
        '''Checks for the existence of two files'''
        if os.path.isfile("/etc/security/limits.d/audio.conf"):
            return True
        return False

    def check_rlimits(self):
        """returns hard rlimit values for RTPRIO and MEMLOCK"""
        return resource.getrlimit(resource.RLIMIT_RTPRIO)[1], resource.getrlimit(resource.RLIMIT_MEMLOCK)[1]

    # System tweaks
    def get_performance(self):
        '''Checks for current cpu governor'''
        if os.path.isfile("/sys/devices/system/cpu/cpufreq/policy0/scaling_governor"):
            with open("/sys/devices/system/cpu/cpufreq/policy0/scaling_governor", "r") as perform_file_test:
                for line in perform_file_test:
                    if re.match("performance", line.rstrip()):
                        return True
        return False

    def get_boosted(self):
        '''Checks for Intel boost state'''
        boost_path = "/sys/devices/system/cpu/intel_pstate/no_turbo"
        if os.path.exists(boost_path):
            with open(boost_path, "r") as boost_test:
                for line in boost_test:
                    if re.match("0", line.rstrip()):
                        return True
        return False

    # Audio stuff


class RTSetup:
    # defs for doing things
    def __init__(self):
        self.enabled_path = "/etc/security/limits.d/audio.conf"
        self.disabled_path = "/etc/security/limits.d/audio.conf.disabled"
        self.backup_file = f"{install_path}/share/studio-controls/audio.conf"

    def set_governor(self, enable):
        if enable:
            gov = "performance"
        else:
            if os.path.exists("/sys/devices/system/cpu/intel_pstate"):
                gov = "powersave"
            else:
                gov = "ondemand"
        subprocess.run(["/usr/bin/pkexec", f"{install_path}/sbin/studio-system", gov], shell=False)

    def set_boost(self, enable):
        boost_path = "/sys/devices/system/cpu/intel_pstate/no_turbo"
        if os.path.exists(boost_path):
            if enable == True:
                subprocess.run(["/usr/bin/pkexec", f"{install_path}/sbin/studio-system", "boost"], shell=False)
            else:
                subprocess.run(["/usr/bin/pkexec", f"{install_path}/sbin/studio-system", "noboost"], shell=False)


class DevDB:
    ''' class for adding system devices to config data base:
    '''
    #def __init__(self):



    def scan_dev(self, conf):
        ''' scan devices and create device db, scan config and add data'''
        ndevs = 0
        # first reset device number to -1
        for dev in conf['devices']:
            conf['devices'][dev]['number'] = -1
            if not 'raw' in conf['devices'][dev]:
                conf['devices'][dev]['raw'] = dev

        # now find the real number
        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(" ")
                    first_el = line.rstrip()[1:].split(" ")[0]
                    if first_el.isdigit():
                        #if sub2[0].isdigit():
                        #ndevs = int(sub2[0])
                        ndevs = int(first_el)
        ndevs += 1
        for x in range(0, ndevs):
            # card loop
            cname = ""
            usb = False
            bus = 'none'
            d_id = 'none'
            # first get device raw name
            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)
            #print(f"cname: {cname} card: {str(x)}")
            # now check for USB device remembering to go by id/bus not raw name
            if os.path.exists(f"/proc/asound/card{str(x)}/usbbus"):
                usb = True
                with open(f"/proc/asound/card{str(x)}/usbbus", "r") as bus_file:
                    for line in bus_file:
                        bus = line.strip().split('/')[0]
                        #print(f"found usb bus: {bus}")
                if os.path.isfile(f"/proc/asound/card{str(x)}/usbid"):
                    with open(f"/proc/asound/card{str(x)}/usbid", "r") as id_file:
                        for line in id_file:
                            d_id  = line.strip()
            found_name = ""
            for dev in conf['devices']:
                if usb and [ conf['devices'][dev]['id'], conf['devices'][dev]['bus'] ] == [ d_id, bus ]:
                    #this is the device... well maybe check if it already has a number
                    #if
                    #print(f"found usb id/bus match for {cname}")
                    conf['devices'][dev]['number'] = x
                    conf['devices'][dev]['raw'] = cname
                    found_name = dev
                    break
                elif usb and [ conf['devices'][dev]['id'], conf['devices'][dev]['bus'] ] == [ 'none', 'none' ]:
                    #print(f"found usb with none/none {cname}")
                    if conf['devices'][dev]['raw'] == cname:
                        #print(f"found usb none/none match for {cname} == {conf['devices'][dev]['raw']}")
                        found_name = dev
                        conf['devices'][dev]['number'] = x
                        conf['devices'][dev]['id'] = d_id
                        conf['devices'][dev]['bus'] = bus
                        break
                elif dev == cname:
                    conf['devices'][dev]['raw'] = cname
                    conf['devices'][dev]['number'] = x
                    found_name = dev
                    break
            if found_name == "":
                #print ("device not found")
                if usb:
                    found_name = f"USB{conf['extra']['usbnext']}"
                    conf['devices'][found_name] = {'number': x, 'usb': True, "internal": False,
                    'hdmi': False, 'firewire': False,'min_latency': 64,
                    'rates': ['32000', '44100', '48000', '88200', '96000', '192000'],
                    'raw': cname, 'id': d_id, 'bus': bus, 'sub': {}}
                    conf['extra']['usbnext'] += 1
                else:
                    found_name = cname
                    conf['devices'][cname] = {'number': x, 'usb': False, "internal": False,
                    'hdmi': False, 'firewire': False,'min_latency': 16,
                    'rates': ['32000', '44100', '48000', '88200', '96000', '192000'],
                    'raw': cname, 'id': 'none', 'bus': 'none', 'sub': {}}

            #print(f"{cname}")
            self.get_dev_info(conf['devices'][found_name])
        #print(json.dumps(conf['devices'], indent = 4))
        dev_list = list(conf['devices'])
        '''for device in dev_list:
            if conf['devices'][device]'''



    def  get_dev_info(self, config):
        ''' get device info from system files '''
        cname = ""
        config['usb'] = False
        config['internal'] = False
        config['firewire'] = False
        config['min_latency'] = 16
        if not 'id' in config:
            config['id'] = 'none'
        if not 'bus' in config:
            config['bus'] = 'none'
        sub = 0
        rates = ['32000', '44100', '48000', '88200', '96000', '192000']
        x = int(config['number'])
        if os.path.exists(f"/proc/asound/card{str(x)}/usbbus"):
            config['usb'] = True
            config['min_latency'] = 32
            with open(f"/proc/asound/card{str(x)}/usbbus", "r") as bus_file:
                for line in bus_file:
                    config['bus'] = line.strip().split('/')[0]
            if os.path.isfile(f"/proc/asound/card{str(x)}/usbid"):
                with open(f"/proc/asound/card{str(x)}/usbid", "r") as id_file:
                    for line in id_file:
                        config['id']  = line.strip()
            # can get supported rates from /proc/asound/card{str(x)}/stream0
            if os.path.exists(f"/proc/asound/card{str(x)}/stream0"):
                with open(f"/proc/asound/card{str(x)}/stream0", "r") as card_file:
                    rates = []
                    for line in card_file:
                        if 'Rates:' in line:
                            fnd_rates = line.split()[1:]
                            for rate in fnd_rates:
                                rate = rate.split(',')[0]
                                if not rate in rates:
                                    rates.append(rate)
        elif os.path.exists(f"/proc/asound/card{str(x)}/codec#0"):
            config['internal'] = True
            config['min_latency'] = 128
            # can get supported rates from file above
            with open(f"/proc/asound/card{str(x)}/codec#0", "r") as card_file:
                node = False
                rates = []
                for line in card_file:
                    if 'Node' in line:
                        node = True
                        if 'Digital' in line:
                            node = False
                    if node and 'rates' in line:
                        fnd_rates = line.split()[2:]
                        for rate in fnd_rates:
                            if not rate in rates:
                                rates.append(rate)
        elif os.path.exists(f"/proc/asound/card{str(x)}/firewire"):
            config['firewire'] = True
            config['min_latency'] = 256

        config['hdmi'] = bool(cname in ['HDMI', 'NVidia'])
        if config['hdmi']:
            config['min_latency'] = 4096

        config['rates'] = rates

        for y in range(0, 20):
            cap = False
            play = False
            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)}c"):
                cap = True

            if cap or play:
                if not str(y) in config['sub']:
                    config['sub'][str(y)] = {'playback': play, 'capture': cap}

                sub_db = config['sub'][str(y)]
                if not 'playback' in sub_db:
                    sub_db['playback'] = play
                if not 'capture' in sub_db:
                    sub_db['capture'] = cap
                if not "play-chan" in sub_db:
                    if config['usb']:
                        sub_db['play-chan'] = 100
                    else:
                        sub_db['play-chan'] = 0
                if not 'cap-chan' in sub_db:
                    if config['usb']:
                        sub_db['cap-chan'] = 100
                    else:
                        sub_db['cap-chan'] = 0
                if not "rate" in sub_db:
                    sub_db['rate'] = 48000
                    if (not '48000' in rates) and len(rates):
                        sub_db['rate'] = int(rates[0])
                if not 'frame' in sub_db:
                    sub_db['frame'] = 1024
                    if 1024 < config['min_latency']:
                        sub_db['frame'] = config['min_latency']
                if not 'nperiods' in sub_db:
                    sub_db['nperiods'] = 2
                if not 'hide' in sub_db:
                    sub_db['hide'] = False
                if not 'name' in sub_db:
                    sub_db['name'] = 'none'
                if not "cap-latency" in sub_db:
                    sub_db['cap-latency'] = 0
                if not "play-latency" in sub_db:
                    sub_db['play-latency'] = 0

        #print(f"Device list: {str(config)}")
        #print(json.dumps(config, indent = 4))


class StudioControls:
    global lock_file
    config_path = "~/.config/autojack/"
    old_config_file = f"{config_path}autojackrc"
    config_file = f"{config_path}autojack.json"


    def __init__(self):
        '''Activate the SysInfo class'''
        # this is a long chunk of code that initializes every thing
        # it should probably be split into tabs at least
        global lock_file
        global install_path
        install_path = os.path.abspath(f"{os.path.dirname(sys.argv[0])}/..")
        print(f"install path: {install_path}")
        self.sysinfo = SysInfo()
        c_dir = expanduser(self.config_path)
        if not os.path.isdir(c_dir):
            os.makedirs(c_dir)
        lock_file = expanduser(f"{self.config_path}/studio-controls.lock")
        new_pid = str(os.getpid())
        if os.path.isfile(lock_file):
            old_pid = new_pid
            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:
                try:
                    os.kill(int(old_pid), 9)
                except Exception:
                    pass
                time.sleep(1)
        with open(lock_file, "w") as lk_file:
            lk_file.write(new_pid)
        self.version = ""
        vfile = f"{install_path}/share/studio-controls/version"
        if os.path.isfile(vfile):
            with open(vfile, "r") as version_file:
                for line in version_file:
                    self.version = line.strip()

        signal.signal(signal.SIGHUP, self.sig_handler)
        signal.signal(signal.SIGINT, self.sig_handler)
        signal.signal(signal.SIGQUIT, self.sig_handler)
        signal.signal(signal.SIGILL, self.sig_handler)
        signal.signal(signal.SIGTRAP, self.sig_handler)
        signal.signal(signal.SIGABRT, self.sig_handler)
        signal.signal(signal.SIGBUS, self.sig_handler)
        signal.signal(signal.SIGFPE, self.sig_handler)
        #signal.signal(signal.SIGKILL, self.sig_handler)
        signal.signal(signal.SIGUSR1, self.sig_handler)
        signal.signal(signal.SIGSEGV, self.sig_handler)
        signal.signal(signal.SIGUSR2, self.sig_handler)
        signal.signal(signal.SIGPIPE, self.sig_handler)
        signal.signal(signal.SIGALRM, self.sig_handler)
        signal.signal(signal.SIGTERM, self.sig_handler)



        # Create the GUI
        builder = Gtk.Builder()
        builder.add_from_file(f"{install_path}/share/studio-controls/studio-controls.glade")
        # Get windows
        self.window_main = builder.get_object('window_main')
        self.window_help = builder.get_object('window_help')
        self.message_dialog_changes_info = builder.get_object('message_dialog_changes_info')
        self.message_dialog_rt_info = builder.get_object('message_dialog_rt_info')
        self.message_dialog_changes_info.set_transient_for(self.window_main)
        self.message_dialog_rt_info.set_transient_for(self.window_main)
        self.title_label = builder.get_object('label_main_top')
        self.button_msg_ok = builder.get_object('button_msg_ok')
        self.title_label.set_text(f"Studio Set Up Utility (version: {self.version})")
        # Get buttons for system tab
        self.rt_button = builder.get_object('rt_button')
        self.rt_warning = builder.get_object('rt_warning')
        self.combo_governor = builder.get_object('combo_governor')
        self.combo_boost = builder.get_object('combo_boost')
        self.logging_comb = builder.get_object('logging_comb')
        self.combo_fw = builder.get_object('combo_fw')
        # audio tab stuff
        # right side menu
        self.mixer_start = builder.get_object('mixer_start')
        # master tab
        self.jack_device_combo = builder.get_object('jack_device_combo')
        self.jack_usb_dev_combo = builder.get_object('jack_usb_dev_combo')
        self.chan_in_spin = builder.get_object('chan_in_spin')
        self.chan_out_spin = builder.get_object('chan_out_spin')
        self.jack_rate_combo = builder.get_object('jack_rate_combo')
        self.combobox_late = builder.get_object('combobox_late')
        self.combo_periods = builder.get_object('combo_periods')
        self.combo_backend = builder.get_object('combo_backend')
        self.monitor_combo = builder.get_object('monitor_combo')
        self.cap_lat_spin = builder.get_object('cap_lat_spin')
        self.play_lat_spin = builder.get_object('play_lat_spin')
        self.jack_midi_check = builder.get_object('jack_midi_check')
        self.jack_ind = builder.get_object('jack_ind')
        self.jack_state = builder.get_object('jack_state')
        self.dsp_label = builder.get_object('dsp_label')
        self.xrun_lab = builder.get_object('xrun_lab')

        # extra tab
        self.usb_plug_check = builder.get_object('usb_plug_check')
        self.xdev_select = builder.get_object('xdev_select')
        self.cap_chan_spin = builder.get_object('cap_chan_spin')
        self.play_chan_spin = builder.get_object('play_chan_spin')
        self.hide_check = builder.get_object('hide_check')
        self.xdev_name = builder.get_object('xdev_name')
        self.xdev_rate_drop = builder.get_object('xdev_rate_drop')
        self.xdev_buff_drop = builder.get_object('xdev_buff_drop')
        self.xdev_nperiods_drop = builder.get_object('xdev_nperiods_drop')
        self.xdev_cap_lat = builder.get_object('xdev_cap_lat')
        self.xdev_play_lat = builder.get_object('xdev_play_lat')
        # phones
        self.hp_action = builder.get_object('hp_action')
        self.hp_device = builder.get_object('hp_device')
        self.hp_switch = builder.get_object('hp_switch')

        # pulse tab
        self.pj_combo = builder.get_object('pj_combo')
        self.pj_direction = builder.get_object('pj_direction')
        self.pj_name = builder.get_object('pj_name')
        self.pj_count = builder.get_object('pj_count')
        self.pj_con = builder.get_object('pj_con')

        # Session Manager
        self.jk_connect_mode = builder.get_object('jk_connect_mode')

        # network tab
        # audio
        self.znet_bridge = builder.get_object('znet_bridge')
        self.znet_direction = builder.get_object('znet_direction')
        self.znet_count = builder.get_object('znet_count')
        self.znet_ip = builder.get_object('znet_ip')
        self.znet_port = builder.get_object('znet_port')
        self.znet_name = builder.get_object('znet_name')
        self.znet_bits = builder.get_object('znet_bits')
        self.znet_late = builder.get_object('znet_late')
        self.znet_warn = builder.get_object('znet_warn')
        # midi
        self.mnet_warn = builder.get_object('mnet_warn')
        self.mnet_count = builder.get_object('mnet_count')
        self.mnet_type = builder.get_object('mnet_type')

        # Dbus monitoring
        user_bus = dbus.SessionBus()
        user_bus.add_signal_receiver(self.db_ses_cb, dbus_interface='org.studio.control.event',
                                     signal_name='V3_5_signal')
        user_bus.add_signal_receiver(self.db_new_usb, dbus_interface='org.studio.control.event',
                                     signal_name='usb_signal')

        # Set default window icon for window managers
        self.window_main.set_default_icon_name('studio-controls')

        # Check if audio.conf and/or audio.conf.disabled exists, returns are true or false
        #self.rt_file = False
        self.jack_file_exists = self.sysinfo.check_pam_files()
        if self.jack_file_exists and self.sysinfo.user_audio():
            rtprio, memlock = self.sysinfo.check_rlimits()
            if rtprio == 0:
                self.rt_button.set_label("Reboot required")
                self.rt_button.set_sensitive(False)
                self.message_dialog_rt_info.show()
                self.rt_warning.set_text("Session restart required for Real Time Permissions")
            else:
                # turn off warning text, check on, deactivate
                self.rt_warning.set_text("")
                self.rt_button.set_label("Real Time Permissions Enabled")
                self.rt_button.set_sensitive(False)

        # show current CPU Governor
        self.combo_governor.append_text("Performance")
        if os.path.exists("/sys/devices/system/cpu/intel_pstate/"):
            self.combo_governor.append_text("Powersave")
        else:
            self.combo_governor.append_text("Ondemand")
        self.in_performance = self.sysinfo.get_performance()
        if self.in_performance:
            self.combo_governor.set_active(0)
        else:
            self.combo_governor.set_active(1)

        # show boost state
        if os.path.exists("/sys/devices/system/cpu/intel_pstate/no_turbo"):
            self.boosted = self.sysinfo.get_boosted()
            if self.boosted:
                self.combo_boost.set_active(1)
            else:
                self.combo_boost.set_active(0)
        else:
            self.combo_boost.set_sensitive(False)

        if os.path.exists("/etc/modprobe.d/blacklist-studio.conf"):
            self.combo_fw.set_active_id("ffado")
            self.combo_backend.append("firewire", "firewire")

        # Audio stuff

        global autojack
        global newusb
        global jack_alive
        global jack_died
        global jack_ports_changed
        global jack_time
        global jackstate
        global jackstring
        jackstring = "unknown"
        jackstate = False
        jack_time = 3
        autojack = True
        newusb = False
        jack_alive = False
        jack_died = False
        jack_ports_changed = True
        self.jack_error_mesg = ""
        self.jack_info_mesg = ""
        self.dirty = False
        self.not_applied = False
        jack.set_error_function(callback=self.jack_error)
        jack.set_info_function(callback=self.jack_info)

        # read in autojack config file
        c_file = expanduser(self.config_file)
        cp = subprocess.run([f"{install_path}/bin/convert-studio-controls"],
        universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
        print(f"Config file: {c_file}")
        with open(c_file) as f:
            self.conf_db = json.load(f)

        self.devdb = DevDB()
        self.devdb.scan_dev(self.conf_db)
        self.jackdb = self.conf_db['jack']
        self.extra = self.conf_db['extra']

        self.logging_comb.set_active_id(str(self.conf_db['log-level']))
        # fill in Jack master widgets
        self.combo_periods.set_sensitive(False)
        self.combo_backend.set_sensitive(False)
        self.chan_in_spin.set_sensitive(False)
        self.chan_out_spin.set_sensitive(False)
        self.cap_lat_spin.set_sensitive(False)
        self.play_lat_spin.set_sensitive(False)
        self.jack_midi_check.set_sensitive(False)
        self.usb_plug_check.set_sensitive(False)
        self.jk_connect_mode.set_sensitive(False)

        self.combo_periods.set_active_id(str(self.jackdb['period']))
        self.combo_backend.set_active_id(self.jackdb['driver'])
        self.chan_in_spin.set_range(1, 128)
        self.chan_out_spin.set_range(1, 128)
        self.chan_in_spin.set_value(self.jackdb['chan-in'])
        self.chan_out_spin.set_value(self.jackdb['chan-out'])
        self.cap_lat_spin.set_range(0, 1000)
        self.play_lat_spin.set_range(0, 1000)
        self.cap_lat_spin.set_value(self.jackdb['cap-latency'])
        self.play_lat_spin.set_value(self.jackdb['play-latency'])
        self.jack_midi_check.set_active(self.extra['a2j'])
        # Fill Extra devices widgets
        self.usb_plug_check.set_active(self.extra['usbauto'])

        # pulse bridge defaults
        self.pj_direction.set_sensitive(False)
        self.pj_count.set_range(1, 99)
        # Session Manger settings
        self.jk_connect_mode.set_active_id(self.jackdb['connect-mode'])
        # net settings
        self.znet_direction.set_sensitive(False)

        self.combo_periods.set_sensitive(True)
        self.combo_backend.set_sensitive(True)
        self.chan_in_spin.set_sensitive(True)
        self.chan_out_spin.set_sensitive(True)
        self.cap_lat_spin.set_sensitive(True)
        self.play_lat_spin.set_sensitive(True)
        self.jack_midi_check.set_sensitive(True)
        self.usb_plug_check.set_sensitive(True)
        self.jk_connect_mode.set_sensitive(True)

        self.refresh_dropdowns()
        self.pj_bridge = ""
        self.refresh_pulse_tab(self.pj_bridge)
        self.znetbridge = ""
        self.refresh_net(self.znetbridge)

        handlers = {
            "on_window_main_delete_event": self.on_window_main_delete_event,
            "on_window_help_delete_event": self.on_window_help_delete_event,
            "on_main_button_cancel_clicked": self.on_main_button_cancel_clicked,
            "on_main_button_help_clicked": self.on_main_button_help_clicked,
            "combo_governor_changed_cb": self.combo_governor_changed_cb,
            "combo_boost_changed_cb": self.combo_boost_changed_cb,
            "logging_change": self.logging_changed,
            "firewire_cb": self.firewire_cb,
            "rt_button_hit": self.rt_button_hit,
            "on_button_msg_ok_clicked": self.on_button_msg_ok_clicked,
            "on_button_rt_info_ok_clicked": self.on_button_rt_info_ok_clicked,
            "on_button_help_ok_clicked": self.on_button_help_ok_clicked,

            "jack_device_changed": self.jack_device_changed,
            "usb_master_changed": self.usb_master_changed,
            "jack_driver_changed": self.jack_driver_changed,
            "xrun_reset": self.xrun_reset,
            "cb_jack_start": self.cb_jack_start,
            "cb_jack_stop": self.cb_jack_stop,
            "cb_audio_apply": self.cb_audio_apply,
            "mixer_cb": self.mixer_cb,
            "pavucontrol_cb": self.pavucontrol_cb,
            "carla_cb": self.carla_cb,
            "generic_cb": self.generic_cb,

            "xdev_select_cb": self.xdev_select_cb,
            "xdev_changed": self.xdev_changed,
            "xdev_cap_all_cb": self.xdev_cap_all_cb,
            "xdev_play_all_cb": self.xdev_play_all_cb,
            "usb_plug_cb": self.usb_plug_cb,
            "hp_action_cb": self.hp_action_cb,
            "hp_switch_cb": self.hp_switch_cb,

            "pj_combo_cb": self.pj_combo_cb,
            "pj_add_cb": self.pj_add_cb,
            "pj_rem_cb": self.pj_rem_cb,
            "pj_name_cb": self.pj_name_cb,

            "znet_bridge_cb": self.znet_bridge_cb,
            "znet_changed_cb": self.znet_changed_cb,
            "znet_add_cb": self.znet_add_cb,
            "znet_rem_cb": self.znet_rem_cb,

            "mnet_count_cb": self.mnet_count_cb,
            "mnet_type_cb": self.mnet_type_cb,

            "ray_cb": self.ray_cb,
            "nsm_cb": self.nsm_cb,
            "agordejo_cb": self.agordejo_cb,
        }
        builder.connect_signals(handlers)

        self.rtsetup = RTSetup()
        self.timeout_id = GLib.timeout_add(500, self.check_jack_status, None)
        self.signal_autojack("ping")
        autojack = False
        print("initialization complete")

    def jack_error(self, mesg):
        if self.jackdb['on']:
            if "not running" in mesg:
                print(f"jack message received: {mesg}")

    def generic_cb(self, widget):
        ''' changes have been made but not yet applied '''
        if not widget.get_sensitive():
            return
        self.not_applied = True

    def jack_info(self, mesg):
        print(f"jack_info received: {mesg}")

    def db_ses_cb(*args, **kwargs):
        ''' this means we got a responce from autojack, Good'''
        global autojack
        global jackstate
        if not autojack:
            autojack = True
            print("autojack is running")
        jackstate = True

    def db_new_usb(*args, **kwargs):
        ''' Autojack tells us a USB audio device has been plugged in or
        unplugged. '''
        global newusb
        newusb = True
        print("autojack sees usb change")


    def check_jack_status(self, user_data):
        '''Check if jack has died and the client needs to be
        closed. Check if jack is running then set jack status indicator.
        Check to see if the device lists have changed
        and update the gui if so. Updating GUI prevents race with
        secondary updates caused by updating'''
        # these variables need to be global as they are used by callbacks
        global newusb
        global jack_client
        global jack_alive
        global jack_died
        global jack_ports_changed
        global jack_time
        global jackstate
        global jackstring

        if jackstate:
            stat_file = expanduser(f"{self.config_path}/autojack.state")
            with open(stat_file, "r") as st_file:
                jackstring = st_file.readline().rstrip()
                self.jack_state.set_text(f" {jackstring}")
            if jack_alive:
                if jackstring in ["Stopping...", "Stopped", "Config Pulse", "Configuring...", "Starting...", "Start Failed"]:
                    jack_died = True
            jackstate = False

        # sent by jackdied() callback
        if jack_died:
            jack_client.deactivate()
            jack_client.close()
            self.refresh_pulse_io()
            self.dirty = True
            jack_died = False
            jack_alive = False

        # device changed, update GUI
        if self.dirty or newusb:
            self.refresh_dropdowns()
            self.dirty = False
            newusb = False

        if jack_ports_changed:
            jack_ports_changed = False
            self.refresh_pulse_io()

        if jack_alive:
            bus = dbus.SessionBus()
            controller = bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller")
            control_iface = dbus.Interface(controller, "org.jackaudio.JackControl")
            xrun_count = control_iface.GetXruns()
            self.xrun_lab.set_text(f" {str(xrun_count)}")
            load = control_iface.GetLoad()
            self.dsp_label.set_text(f"DSP: {str(load)[0:4]}%")
        else:
            self.dsp_label.set_text("DSP: 0%")
            self.xrun_lab.set_text(" 0")
            if jackstring in ["Running", "Poststart", "Adding Bridges", "Postbridge"]:
                try:
                    jack_client = jack.Client('controls', use_exact_name=False, no_start_server=True)
                except jack.JackError:
                    self.xrun_lab.set_text(" 0")
                    return True
                # if jack is shutting down we need to know
                jack_client.set_shutdown_callback(self.jackdied)
                # get notified if a new port is created
                jack_client.set_port_registration_callback(self.new_jack_port)
                jack_client.activate()
                jack_alive = True
                jack_ports_changed = True
                self.refresh_pulse_tab(self.pj_bridge)
        return True


    def jackdied(state, why, extra):
        '''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_alive
        global jack_died
        jack_died = True
        jack_alive = False


    def new_jack_port(port_id, new, extra):
        ''' jack has added a port tell someone '''
        global jack_ports_changed
        jack_ports_changed = True


    def xrun_reset(self, button):
        ''' user asked for an xrun reset so do it '''
        bus = dbus.SessionBus()
        controller = bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller")
        control_iface = dbus.Interface(controller, "org.jackaudio.JackControl")
        control_iface.ResetXruns()

    def refresh_dropdowns(self):
        '''this call refreshes the device lists for all drop downs
        that use devices. If backend is not "alsa" then the jack master
        and USB master are set to default and no usb master. However,
        all all alsa devices will still be available for bridging and
        as output device.'''
        self.devdb.scan_dev(self.conf_db)
        xdev_current = self.xdev_select.get_active_id()
        self.current_ex_dev = xdev_current

        # driver is probably ok to leave above
        self.combo_backend.set_active_id(self.jackdb['driver'])

        self.cap_lat_spin.set_value(self.jackdb['cap-latency'])
        self.play_lat_spin.set_value(self.jackdb['play-latency'])


        self.jack_device_combo.set_sensitive(False)
        self.jack_usb_dev_combo.set_sensitive(False)
        self.chan_in_spin.set_sensitive(False)
        self.chan_out_spin.set_sensitive(False)
        self.combobox_late.set_sensitive(False)
        self.combo_periods.set_sensitive(False)
        self.jack_rate_combo.set_sensitive(False)
        self.xdev_select.set_sensitive(False)
        self.mixer_start.set_sensitive(False)
        ### popdown any combo boxes before changing
        self.jack_device_combo.popdown()
        self.xdev_select.popdown()
        self.jack_usb_dev_combo.popdown()
        self.jack_rate_combo.popdown()
        self.combobox_late.popdown()
        self.mixer_start.popdown()

        self.jack_device_combo.get_model().clear()
        self.xdev_select.get_model().clear()
        self.jack_usb_dev_combo.get_model().clear()
        self.jack_rate_combo.get_model().clear()
        self.combobox_late.get_model().clear()
        self.mixer_start.get_model().clear()
        self.mixer_start.append("none", "Open Device Mixer")
        self.mixer_start.set_active_id("none")
        if self.combo_fw.get_active_id() == "ffado":
            self.mixer_start.append("ffado", "FFADO Device Mixer")
        rates =[]
        def_rates = ['32000', '44100', '48000', '88200', '96000', '192000']
        frames = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096]

        self.jack_usb_dev_combo.append("none", "No USB Master")
        if self.jackdb['usbdev'] == "" or self.jackdb['usbdev'] == "none":
            self.jack_usb_dev_combo.set_active_id("none")

        self.hp_device.popdown()
        self.hp_device.get_model().clear()
        self.hp_device.append(self.extra['phone-device'], self.extra['phone-device'])
        self.hp_device.set_active_id(self.extra['phone-device'])

        for this_dev in self.conf_db['devices']:
            dev_db = self.conf_db['devices'][this_dev]
            if dev_db['firewire']:
                self.mixer_start.append("ffado", "FFADO Device Mixer")
            if dev_db['number'] != -1:
                # we only want plugged devices
                self.mixer_start.append(dev_db['raw'], dev_db['raw'])

            for this_sub in dev_db['sub']:
                sub_db = dev_db['sub'][this_sub]
                d_type = ""
                if sub_db['capture']:
                    d_type = "capture "
                if sub_db['playback']:
                    if d_type == "":
                        d_type = "playback"
                    else:
                        d_type = f"{d_type} and playback"
                next_id = ""
                dname = ""
                next_d = ""
                if d_type != "":
                    # we have an audio device (not MIDI)
                    subpath = f"/proc/asound/{str(this_dev)}/pcm{str(this_sub)}{str(d_type[0])}/sub0/info"
                    if os.path.exists(subpath):
                        with open(subpath, "r") as info_file:
                            for line in info_file:
                                clean_line = line.rstrip()
                                if re.match("^name:", clean_line):
                                    line_list = clean_line.split(": ", 1)
                                    if len(line_list) > 1:
                                        dname = line_list[1]
                next_id = f"{this_dev},{this_sub},0"
                desc_base = ""
                if dev_db['usb']:
                    desc_base = f"{this_dev} {dev_db['id']} {dev_db['bus']} {dev_db['raw']},{this_sub},0"
                else:
                    desc_base = next_id
                if dev_db['number'] == -1:
                    dname = "unplugged"
                #next_d = f"{next_id} {d_type} ({dname})"
                next_d = f"{desc_base} {d_type} ({dname})"
                if "Loopback" in [this_dev]:
                    self.xdev_select.append(next_id, next_d)
                else:
                    self.xdev_select.insert(0, next_id, next_d)
                if not sub_db['hide']:
                    # not hidden so we can add it
                    if not "Loopback" in [this_dev]:
                        self.hp_device.append(next_id, next_d)
                        if dev_db['usb']:
                            self.jack_usb_dev_combo.append(next_id, next_d)
                        else:
                            self.jack_device_combo.append(next_id, next_d)
                    if next_id == self.extra['phone-device']:
                        self.hp_device.set_active_id(next_id)
                    if next_id == self.jackdb['dev']:
                        self.jack_device_combo.set_active_id(next_id)
                        if self.jackdb['usbdev'] == "none":
                            #this is jack master get rates
                            rates = dev_db['rates']
                    if next_id == self.jackdb['usbdev']:
                        self.jack_usb_dev_combo.set_active_id(next_id)
                        #this is jack master get rates
                        rates = dev_db['rates']

        if self.jackdb['driver'] != "alsa" or rates == []:
            rates = def_rates

        for rate in rates:
            self.jack_rate_combo.append(rate, rate)
            if str(self.jackdb['rate']) in rates:
                self.jack_rate_combo.set_active_id(str(self.jackdb['rate']))
            else:
                self.jack_rate_combo.set_active(0)
        minlat = 16
        fw = False
        if self.jackdb['driver'] == "alsa":
            if self.jackdb['usbdev'] != 'none':
                minlat = self.conf_db['devices'][self.jackdb['usbdev'].split(',')[0]]['min_latency']
            else:
                minlat = self.conf_db['devices'][self.jackdb['dev'].split(',')[0]]['min_latency']
                fw = self.conf_db['devices'][self.jackdb['dev'].split(',')[0]]['firewire']
        for frame in frames:
            tplt = ""
            if frame == minlat and fw:
                tplt = " For lower latency Use the FFADO kernal modules."
            if frame >= minlat:
                self.combobox_late.append(str(frame), f"{str(frame)}{tplt}")
        self.combobox_late.set_active_id(str(self.jackdb['frame']))


        self.mixer_start.set_sensitive(True)
        self.jack_rate_combo.set_sensitive(True)
        self.combobox_late.set_sensitive(True)
        if self.jackdb['driver'] == "alsa":
            self.jack_device_combo.set_sensitive(True)
            self.jack_usb_dev_combo.set_sensitive(True)
            self.combo_periods.set_sensitive(True)
        elif self.jackdb['driver'] == "firewire":
            self.combo_periods.set_sensitive(True)
        elif self.jackdb['driver'] == "dummy":
            self.chan_in_spin.set_sensitive(True)
            self.chan_out_spin.set_sensitive(True)

        self.redraw_extra(xdev_current)


    '''Functions for all the gui controls'''

    def on_window_help_delete_event(self, window, event):
        self.window_help.hide_on_delete()
        return True


    def on_main_button_help_clicked(self, button):
        self.window_help.show()


    def rt_button_hit(self, button):
        subprocess.run(["/usr/bin/pkexec", f"{install_path}/sbin/studio-system", "fix"], shell=False)
        self.rt_button.set_label("Logout required")
        self.rt_button.set_sensitive(False)
        self.message_dialog_rt_info.show()
        self.rt_warning.set_text("Session restart required for Real Time Permissions")


    # system tweaks
    def combo_governor_changed_cb(self, button):
        if button.get_active_text() == "Performance":
            self.rtsetup.set_governor(True)
        else:
            self.rtsetup.set_governor(False)


    def combo_boost_changed_cb(self, button):
        if button.get_active_text() == "on":
            self.rtsetup.set_boost(True)
        else:
            self.rtsetup.set_boost(False)
        self.boosted = self.sysinfo.get_boosted()
        if self.boosted:
            self.combo_boost.set_active(1)
        else:
            self.combo_boost.set_active(0)


    def logging_changed(self, widget):
        newval = widget.get_active_id()
        if self.conf_db['log-level'] != newval:
            self.conf_db['log-level'] = newval
            self.cb_audio_apply(widget)


    def firewire_cb(self, widget):
        newval = widget.get_active_id()
        if newval == "alsa" or newval == "ffado":
            subprocess.run(["/usr/bin/pkexec", f"{install_path}/sbin/studio-system", newval], shell=False)


    # Audio setup call backs
    def xdev_select_cb(self, widget):
        a_id = str(widget.get_active_id())
        if a_id != "None" and a_id != self.current_ex_dev:
            self.redraw_extra(a_id)

    def xdev_changed(self, widget):
        #self.xdev_select.set_sensitive(False)
        if not self.xdev_select.get_sensitive():
            return
        self.cap_chan_spin.set_sensitive(False)
        self.play_chan_spin.set_sensitive(False)
        self.hide_check.set_sensitive(False)
        self.xdev_rate_drop.set_sensitive(False)
        self.xdev_buff_drop.set_sensitive(False)
        self.xdev_nperiods_drop.set_sensitive(False)
        self.xdev_cap_lat.set_sensitive(False)
        self.xdev_play_lat.set_sensitive(False)
        self.xdev_name.set_sensitive(False)
        this_dev = str(self.xdev_select.get_active_id())
        dev_db = self.conf_db['devices'][this_dev.split(',')[0]]
        sub_db = dev_db['sub'][str(this_dev.split(',')[1])]

        sub_db['cap-chan'] = self.cap_chan_spin.get_value_as_int()
        sub_db['play-chan'] = self.play_chan_spin.get_value_as_int()
        sub_db['hide'] = self.hide_check.get_active()
        sub_db['rate'] = int(self.xdev_rate_drop.get_active_id())
        sub_db['frame'] = int(self.xdev_buff_drop.get_active_id())
        sub_db['nperiods'] = int(self.xdev_nperiods_drop.get_active_id())
        sub_db['name'] = self.xdev_name.get_text().split(' ')[0]
        #get_value_as_int
        self.hide_check.set_sensitive(True)
        if not sub_db['hide']:
            self.cap_chan_spin.set_sensitive(True)
            self.play_chan_spin.set_sensitive(True)
            self.xdev_rate_drop.set_sensitive(True)
            self.xdev_buff_drop.set_sensitive(True)
            self.xdev_nperiods_drop.set_sensitive(True)
            self.xdev_cap_lat.set_sensitive(True)
            self.xdev_play_lat.set_sensitive(True)
            self.xdev_name.set_sensitive(True)


    def xdev_cap_all_cb(self, widget):
        if not widget.get_sensitive():
            return
        self.not_applied = True

        widget.set_sensitive(False)
        #self.cap_chan_spin.set_sensitive(False)
        #widget.set_active(False)
        if widget.get_label() == ' All ':
            self.cap_chan_spin.set_value(100)
            widget.set_label(' Off')
        else:
            widget.set_label(' All ')
            self.cap_chan_spin.set_value(0)
        widget.set_sensitive(True)
        self.cap_chan_spin.set_sensitive(True)


    def xdev_play_all_cb(self, widget):
        if not widget.get_sensitive():
            return
        self.not_applied = True
        widget.set_sensitive(False)
        #self.play_chan_spin.set_sensitive(False)
        #widget.set_active(False)
        if widget.get_label() == ' All ':
            self.play_chan_spin.set_value(100)
            widget.set_label(' Off')
        else:
            widget.set_label(' All ')
            self.play_chan_spin.set_value(0)
        widget.set_sensitive(True)
        self.play_chan_spin.set_sensitive(True)


    def redraw_extra(self, next_device):
        ''' change all widgets to reflect current values of the
        selected device '''
        frames = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096]
        self.xdev_select.set_sensitive(False)
        self.cap_chan_spin.set_sensitive(False)
        self.play_chan_spin.set_sensitive(False)
        self.hide_check.set_sensitive(False)
        self.xdev_rate_drop.set_sensitive(False)
        self.xdev_buff_drop.set_sensitive(False)
        self.xdev_nperiods_drop.set_sensitive(False)
        self.xdev_cap_lat.set_sensitive(False)
        self.xdev_play_lat.set_sensitive(False)
        self.xdev_name.set_sensitive(False)
        self.xdev_rate_drop.popdown()
        self.xdev_buff_drop.popdown()
        self.xdev_rate_drop.get_model().clear()
        self.xdev_buff_drop.get_model().clear()

        if next_device == 'none':
            for def_dev in self.conf_db['devices']:
                next_device = f"{def_dev},0,0"
                break
        self.xdev_select.set_active_id(next_device)
        dev_db = self.conf_db['devices'][next_device.split(',')[0]]
        sub_db = dev_db['sub'][str(next_device.split(',')[1])]
        if dev_db['number'] == -1:
            # device is unplugged, some info may be missing
            if not 'rates' in dev_db:
                # the device may not handle all these rates but missing some would be bad
                dev_db['rates'] = ["32000", "44100", "48000", "88200", "96000", "192000"]

        self.cap_chan_spin.set_value(sub_db['cap-chan'])
        self.play_chan_spin.set_value(sub_db['play-chan'])
        self.hide_check.set_active(sub_db['hide'])
        for rate in dev_db['rates']:
            self.xdev_rate_drop.append(str(rate), str(rate))
        self.xdev_rate_drop.set_active_id(str(sub_db['rate']))
        for frame in frames:
            if dev_db['min_latency'] <= frame:
                self.xdev_buff_drop.append(str(frame), str(frame))
        self.xdev_buff_drop.set_active_id(str(sub_db['frame']))
        self.xdev_nperiods_drop.set_active_id(str(sub_db['nperiods']))
        if sub_db['name'] == 'none':
            sub_db['name'] = next_device
        self.xdev_name.set_text(sub_db['name'])
        self.xdev_cap_lat.set_value(sub_db['cap-latency'])
        self.xdev_play_lat.set_value(sub_db['cap-latency'])

        self.xdev_select.set_sensitive(True)
        self.hide_check.set_sensitive(True)
        if not sub_db['hide']:
            self.cap_chan_spin.set_sensitive(True)
            self.play_chan_spin.set_sensitive(True)
            self.xdev_rate_drop.set_sensitive(True)
            self.xdev_buff_drop.set_sensitive(True)
            self.xdev_nperiods_drop.set_sensitive(True)
            self.xdev_cap_lat.set_sensitive(True)
            self.xdev_play_lat.set_sensitive(True)
            self.xdev_name.set_sensitive(True)


    def hp_action_cb(self, widget):
        if not widget.get_sensitive():
            return
        self.not_applied = True
        a_id = str(widget.get_active_id())
        if a_id == "script":
            print("Script is called ~/.config/autojack/phones.sh")
            # this needs to be shown as a dialog

    def hp_switch_cb(self, button):
        self.signal_autojack("phones")

    def jack_device_changed(self, button):
        if not button.get_sensitive():
            return
        self.not_applied = True
        a_id = str(button.get_active_id())
        a_desc = str(button.get_active_text())
        if a_id != "None":
            self.conf_db['jack']['dev'] = a_id
            self.dev_desc = a_desc
            if not self.dirty:
                self.dirty = True

    def jack_driver_changed(self, button):
        if not button.get_sensitive():
            return
        self.not_applied = True
        a_driver = str(button.get_active_text())
        self.conf_db['jack']['driver'] = a_driver
        if not self.dirty:
            self.dirty = True

    def usb_master_changed(self, button):
        if not button.get_sensitive():
            return
        self.not_applied = True
        a_id = str(button.get_active_id())
        if a_id != "None":
            self.conf_db['jack']['usbdev'] = a_id
        if not self.dirty:
            self.dirty = True

    def usb_plug_cb(self, widget):
        if not widget.get_sensitive():
            return
        self.not_applied = True
        a_id = widget.get_active()


    # Pulse bridge calls

    def refresh_pulse_io(self):
        ''' the ports that can be connected to pulse ports varies
        with what jack offers. This refreshes the two drop downs '''
        global jack_client
        global jack_alive
        # need to know what the current direction is
        self.pj_con.set_sensitive(False)
        self.monitor_combo.set_sensitive(False)
        direction = self.pj_direction.get_active_id()
        our_db = self.conf_db['pulse']['outputs']
        if direction == 'in':
            our_db = self.conf_db['pulse']['inputs']
        br_exists = False
        if our_db != {}:
            br_exists = True
            if self.pj_bridge == "":
                for br in in_db:
                   self.pj_bridge = br
                   break
        self.pj_con.popdown()
        self.pj_con.get_model().clear()
        self.pj_con.append("none", "no connection")
        if direction == 'out':
            self.pj_con.append("monitor", "Main Output Ports")
        self.pj_con.set_active_id('none')
        if br_exists:
            our_con = our_db[self.pj_bridge]['connection']
            if our_con != 'none':
                self.pj_con.append(our_con, our_con)
                self.pj_con.set_active_id(our_con)

        self.monitor_combo.popdown()
        self.monitor_combo.get_model().clear()
        monitor = self.conf_db['extra']["monitor"]
        self.monitor_combo.append(monitor, monitor)
        self.monitor_combo.set_active_id(monitor)

        if jack_alive:
            if direction == 'in':
                # get capture ports with audio and hardware
                jack_cap = jack_client.get_ports("", is_audio=True, is_output=True, is_physical=True)
                extra = ""
                last_dev = ""
                for jport in jack_cap:
                    port = jport.name
                    dev = port.split(':', 1)[0]
                    paliases = jport.aliases
                    for palias in paliases:
                        adev = palias.split(':', 1)[0]
                        if adev == "system":
                            extra = f" <{dev}>"
                            dev = adev
                            port = palias
                    self.pj_con.append(port, f"{port} {extra}")
                    if br_exists and port == our_db[self.pj_bridge]['connection']:
                        self.pj_con.remove(self.pj_con.get_active())
                        self.pj_con.set_active_id(port)

            jack_play = jack_client.get_ports("", is_audio=True, is_input=True, is_physical=True)
            extra = ""
            last_dev = ""
            for jport in jack_play:
                port = jport.name
                dev = port.split(':', 1)[0]
                paliases = jport.aliases
                for palias in paliases:
                    adev = palias.split(':', 1)[0]
                    if adev == "system":
                        extra = f" <{dev}>"
                        dev = adev
                        port = palias
                if direction == 'out':
                    self.pj_con.append(port, f"{port} {extra}")
                    if br_exists and port == our_db[self.pj_bridge]['connection']:
                        self.pj_con.remove(self.pj_con.get_active())
                        self.pj_con.set_active_id(port)
                self.monitor_combo.append(port, f"{port} {extra}")
                if port == monitor:
                    self.monitor_combo.remove(self.monitor_combo.get_active())
                    self.monitor_combo.set_active_id(monitor)

        else:
            self.pj_con.append("none", "Ports cannot be displayed unless JACK is running.")
            self.monitor_combo.append("none", "Ports cannot be displayed unless JACK is running.")

        self.pj_con.set_sensitive(True)
        self.monitor_combo.set_sensitive(True)


    def refresh_pulse_tab(self, new_bridge):
        ''' Fill in all pulse related widgets '''
        global jack_ports_changed
        out_db = self.conf_db['pulse']['outputs']
        in_db = self.conf_db['pulse']['inputs']

        self.pj_combo.set_sensitive(False)
        self.pj_direction.set_sensitive(False)
        self.pj_name.set_sensitive(False)
        self.pj_count.set_sensitive(False)
        self.pj_con.set_sensitive(False)
        self.pj_combo.popdown()
        self.pj_combo.get_model().clear()
        if out_db == {} and in_db == {}:
            # no bridges
            self.pj_combo.set_active(0)
            self.pj_name.set_text("")
            self.pj_count.set_value(1)
            self.pj_con.set_active_id('none')
            self.pj_direction.set_active_id('out')
            self.pj_direct.set_text('none')
            self.pj_bridge = ''
        else:
            for bridge in out_db:
                self.pj_combo.append(bridge, bridge)
                if not new_bridge:
                    new_bridge = bridge
            for bridge in in_db:
                self.pj_combo.append(bridge, bridge)
                if not new_bridge:
                    new_bridge = bridge
            our_db = {}
            if new_bridge in out_db:
                our_db = out_db
                self.pj_direction.set_active_id('out')
                #self.pj_direct.set_text('Output (Pulse to JACK)')
            if new_bridge in in_db:
                our_db = in_db
                self.pj_direction.set_active_id('in')
                #self.pj_direct.set_text('Input (JACK to Pulse)')
            self.pj_combo.set_active_id(new_bridge)
            self.pj_name.set_text(new_bridge)
            self.pj_count.set_value(our_db[new_bridge]['count'])
            self.pj_con.set_active_id(our_db[new_bridge]['connection'])
            self.pj_bridge = new_bridge

        self.pj_combo.set_sensitive(True)
        #self.pj_direction.set_sensitive(True)
        self.pj_name.set_sensitive(True)
        self.pj_count.set_sensitive(True)
        self.pj_con.set_sensitive(True)
        # rebuild the the connection dropdowns
        jack_ports_changed = True


    def pj_name_cb(self, widget):
        ''' call back for any pulse bridge input name or connect change
        to current values '''
        if not widget.get_sensitive():
            return
        self.not_applied = True
        if self.pj_direction.get_active_id() == 'in':
            temp_db = self.conf_db['pulse']['inputs']
        if self.pj_direction.get_active_id() == 'out':
            temp_db = self.conf_db['pulse']['outputs']
        if temp_db != {}:
            old_name = self.pj_combo.get_active_id()
            new_name = self.pj_name.get_text().split()[0]
            if new_name != old_name:
                if old_name in temp_db:
                    temp_db[new_name] = temp_db.pop(old_name)
            else:
                temp_db[new_name]['connection'] = f"{self.pj_con.get_active_id()}"
                temp_db[new_name]['count'] = self.pj_count.get_value_as_int()

            self.refresh_pulse_tab(new_name)


    def pj_combo_cb(self, widget):
        ''' callback to look at different pa bridge.
        need to save name and connection, then
        refresh name and connections to match '''
        if not widget.get_sensitive():
            return
        if widget.get_active() < 0:
            return
        self.pj_bridge = self.pj_combo.get_active_id()
        self.refresh_pulse_tab(self.pj_bridge)


    def pj_add_cb(self, widget):
        ''' need to create a name for the bridge and
        assign connect as "none". Before switching
        to display the new bridge we need to save the
        current bridge info '''
        if not widget.get_sensitive():
            return
        direct = 'out'
        temp_db = {}
        if  widget.get_active_id() == 'in':
            direct = 'in'
            temp_db =self.conf_db['pulse']['inputs']
        elif widget.get_active_id() == 'out':
            direct = 'out'
            temp_db =self.conf_db['pulse']['outputs']
        else:
            widget.set_active_id('label')
            return
        widget.set_sensitive(False)
        self.not_applied = True
        indx = 1
        done = False
        new_name = ""
        while not done:
            new_name = f"pulse{str(indx)}-{direct}"
            if not new_name in temp_db:
                done = True
            else:
                indx = indx + 1
        temp_db[new_name] = {
                            'connection': "none",
                            'count': 2
                            }
        self.refresh_pulse_tab(new_name)
        widget.set_active_id('label')
        widget.set_sensitive(True)


    def pj_rem_cb(self, widget):
        ''' get index of current bridge
        remove name from list by index
        remove connection from list by index '''
        if not widget.get_sensitive():
            return
        self.not_applied = True
        name = self.pj_combo.get_active_id()
        if name in self.conf_db['pulse']['inputs']:
            del self.conf_db['pulse']['inputs'][name]
        elif name in self.conf_db['pulse']['outputs']:
            del self.conf_db['pulse']['outputs'][name]
        self.pj_bridge = ""
        self.refresh_pulse_tab(self.pj_bridge)


    # network callbacks and refresh
    def refresh_net(self, new_bridge):
        ''' set up values for first bridge and midi '''
        # midi
        self.mnet_count.set_sensitive(False)
        self.mnet_type.set_sensitive(False)
        if not (os.path.isfile("/usr/bin/qmidinet") and os.access("/usr/bin/qmidinet", os.X_OK)):
            self.mnet_warn.set_text("Please Install qmidinet First")
            self.mnet_count.set_value(0)
        else:
            self.mnet_warn.set_text("")
            self.mnet_count.set_value(self.conf_db['mnet']['count'])
            self.mnet_type.set_active_id(self.conf_db['mnet']['type'])
            self.mnet_count.set_sensitive(True)
            self.mnet_type.set_sensitive(True)

        # zita-njbridge
        znet_db = self.conf_db['znet']
        self.znet_bridge.set_sensitive(False)
        self.znet_direction.set_sensitive(False)
        self.znet_count.set_sensitive(False)
        self.znet_ip.set_sensitive(False)
        self.znet_port.set_sensitive(False)
        self.znet_bits.set_sensitive(False)
        self.znet_name.set_sensitive(False)
        self.znet_late.set_sensitive(False)
        if not (os.path.isfile("/usr/bin/zita-n2j") and os.access("/usr/bin/zita-n2j", os.X_OK)):
            self.znet_warn.set_text("Please Install zita-njbridge First")
        else:
            self.znet_warn.set_text("")

            self.znet_bridge.get_model().clear()
            our_ip = "0.0.0.0"
            if znet_db == {}:
                # no bridges
                self.znet_bridge.set_active(0)
                self.znet_direction.set_active_id('out')
                self.znet_count.set_value(1)
                self.znet_ip.set_text(our_ip)
                self.znet_port.set_value(8300)
                self.znet_bits.set_active_id('none')
                self.znet_late.set_value(0)
                self.znet_name.set_text("")
                self.znetbridge = ""
                return
            else:
                for bridge in znet_db:
                    self.znet_bridge.append(bridge, bridge)
                    if not new_bridge:
                        new_bridge = bridge
                self.znet_direction.set_active_id(znet_db[new_bridge]['direction'])
                self.znet_bridge.set_active_id(new_bridge)
                self.znet_name.set_text(new_bridge)
                self.znet_count.set_value(znet_db[new_bridge]['count'])
                self.znet_ip.set_text(znet_db[new_bridge]['ip'])
                self.znet_port.set_value(znet_db[new_bridge]['port'])
                if znet_db[new_bridge]['direction'] == 'out':
                    self.znet_bits.set_active_id(znet_db[new_bridge]['bits'])
                    self.znet_late.set_value(0)
                else:
                    self.znet_bits.set_active_id('none')
                    self.znet_late.set_value(znet_db[new_bridge]['latency'])
                self.znetbridge = new_bridge

            if shutil.which("zita-n2j") == None:
                self.znet_warn.set_text("Please install zita-njbridge")
            else:
                self.znet_warn.set_text("")

                if znet_db[new_bridge]['direction'] == 'in':
                    self.znet_late.set_sensitive(True)
                if znet_db[new_bridge]['direction'] == 'out':
                    self.znet_bits.set_sensitive(True)
                self.znet_bridge.set_sensitive(True)
                #self.znet_direction.set_sensitive(True) # should never be true
                self.znet_count.set_sensitive(True)
                self.znet_ip.set_sensitive(True)
                self.znet_port.set_sensitive(True)
                self.znet_name.set_sensitive(True)


    # audio
    def znet_bridge_cb(self, widget):
        ''' callback to look at different znet bridge.
        need to save name and connection, then
        refresh name and connections to match '''
        if not widget.get_sensitive():
            return
        if widget.get_active() < 0:
            return
        self.znetbridge = self.znet_bridge.get_active_id()
        self.refresh_net(self.znetbridge)

    def znet_changed_cb(self, widget):
        if not widget.get_sensitive():
            return
        this_db = self.conf_db['znet']
        if this_db != {}:
            old_name = self.znet_bridge.get_active_id()
            new_name = self.znet_name.get_text().split()[0]
            if new_name != old_name:
                if old_name in this_db:
                    this_db[new_name] = this_db.pop(old_name)
                else:
                    # probably a "" string
                    return
            this_db[new_name]['count'] = self.znet_count.get_value_as_int()
            this_db[new_name]['ip'] = self.znet_ip.get_text()
            this_db[new_name]['port'] = self.znet_port.get_value_as_int()
            if this_db[new_name]['direction'] == 'out':
                if self.znet_bits.get_active_id() != 'none':
                    this_db[new_name]['bits'] = self.znet_bits.get_active_id()
            else:
                this_db[new_name]['bits'] = 'none'
            if this_db[new_name]['direction'] == 'in':
                this_db[new_name]['latency'] = self.znet_late.get_value_as_int()
            else:
                this_db[new_name]['latency'] = 0

            self.not_applied = True
            self.refresh_net(new_name)

    def znet_add_cb(self, widget):
        ''' need to create a name for the bridge and default values
        before switching to display the new bridge
        '''
        if not widget.get_sensitive():
            return
        direct = 'out'
        if not widget.get_active_id() in ['in', 'out']:
            widget.set_active_id('label')
            return
        widget.set_sensitive(False)
        self.not_applied = True
        indx = 1
        done = False
        new_name = ""
        temp_db = self.conf_db['znet']
        while not done:
            new_name = f"net{str(indx)}-{widget.get_active_id()}"
            if not new_name in temp_db:
                done = True
            else:
                indx = indx + 1
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        ip = s.getsockname()[0]
        s.close()
        late = 10
        bits = "none"
        if widget.get_active_id() == 'out':
            ipl = ip.split('.')
            ip = f"{ipl[0]}.{ipl[1]}.{ipl[2]}."
            late = 0
            bits = "16bit"
            dialog = Gtk.MessageDialog(
                transient_for=self.window_main,
                flags=0,
                message_type=Gtk.MessageType.WARNING,
                buttons=Gtk.ButtonsType.OK,
                text="Warning, The IP is not complete",
            )
            response = dialog.run()
            dialog.destroy()
        temp_db[new_name] = {
                            'direction': widget.get_active_id(),
                            'ip': ip,
                            'port': 8300,
                            'bits': bits,
                            'latency': late,
                            'count': 2
                            }
        self.refresh_net(new_name)
        widget.set_active_id('label')
        widget.set_sensitive(True)

    def znet_rem_cb(self, widget):
        ''' Remove the currently displayed bridge
        and refresh display (which defaults to the first in the list)'''
        if not widget.get_sensitive():
            return
        self.not_applied = True
        name = self.znet_bridge.get_active_id()
        if name in self.conf_db['znet']:
            del self.conf_db['znet'][name]
        self.znetbridge = ""
        self.refresh_net(self.znetbridge)


    # midi
    def mnet_count_cb(self, widget):
        if not widget.get_sensitive():
            return
        self.conf_db['mnet']['count'] = widget.get_value_as_int()
        self.not_applied = True


    def mnet_type_cb(self, widget):
        if not widget.get_sensitive():
            return
        self.conf_db['mnet']['type'] = widget.get_active_id()
        self.not_applied = True


    # External applications calls

    def mixer_cb(self, widget):
        '''callback for mixer button. This starts QASMixer
        with the device set to whatever is jack master'''
        if not widget.get_sensitive():
            return
        widget.set_sensitive(False)
        widget.popdown()
        if widget.get_active == -1:
            widget.set_active_id("none")
            return
        if widget.get_active_id() == "none":
            return
        if widget.get_active_id() == "ffado":
            try:
                subprocess.Popen(["/usr/bin/ffado-mixer"], shell=False).pid
            except:
                print("ffado-mixer errors are normal :P")
        else:
            mixdevice = widget.get_active_id()
            subprocess.Popen(["/usr/bin/qasmixer", "-n", f"--device=hw:{str(mixdevice)}"], shell=False).pid
        widget.set_active_id("none")
        widget.set_sensitive(True)


    def pavucontrol_cb(self, button):
        '''callback for pulse control button, opens pavucontrol'''
        subprocess.Popen(["/usr/bin/pavucontrol"], shell=False).pid

    def carla_cb(self, button):
        '''callback for carla button, opens carla'''
        if os.path.isfile("/usr/bin/carla") and os.access("/usr/bin/carla", os.X_OK):
            subprocess.Popen(["/usr/bin/carla"], shell=False).pid
        else:
            button.set_label("Please Install Carla First")
            button.set_sensitive(False)

    def ray_cb(self, button):
        '''callback for raysession button, opens raysession'''
        if os.path.isfile("/usr/bin/raysession") and os.access("/usr/bin/raysession", os.X_OK):
            subprocess.Popen(["/usr/bin/raysession"], shell=False).pid
        else:
            button.set_label("Please Install RaySession First")
            button.set_sensitive(False)

    def nsm_cb(self, button):
        '''callback for nsm button, opens New Session Manager'''
        if os.path.isfile("/usr/bin/nsm-legacy-gui") and os.access("/usr/bin/nsm-legacy-gui", os.X_OK):
            subprocess.Popen(["/usr/bin/nsm-legacy-gui"], shell=False).pid
        else:
            button.set_label("Please Install New Session Manager First")
            button.set_sensitive(False)

    def agordejo_cb(self, button):
        '''callback for agordejo button, opens agordejo'''
        if os.path.isfile("/usr/bin/agordejo") and os.access("/usr/bin/agordejo", os.X_OK):
            subprocess.Popen(["/usr/bin/agordejo"], shell=False).pid
        else:
            button.set_label("Please Install Agordejo First")
            button.set_sensitive(False)

    # Autojack signalling calls

    def cb_jack_start(self, button):
        ''' call back for Jack (re)start button'''
        global autojack
        self.jackdb['on'] = True
        self.config_save()
        self.not_applied = False
        self.signal_autojack("start")

    def cb_jack_stop(self, button):
        global autojack
        self.jackdb['on'] = False
        self.config_save()
        self.not_applied = False
        self.signal_autojack("stop")

    def cb_audio_apply(self, button):
        '''callback for audio tab apply button'''
        global autojack
        self.config_save()
        self.not_applied = False
        self.signal_autojack("config")

    def config_save(self):
        ''' Write audio setting to ~/.config/autojack/autojackrc'''

        self.jackdb['chan-in'] = int(self.chan_in_spin.get_value_as_int())
        self.jackdb['chan-out'] = int(self.chan_out_spin.get_value_as_int())
        self.jackdb['rate'] = int(self.jack_rate_combo.get_active_id())
        self.jackdb['frame'] = int(self.combobox_late.get_active_id())
        self.jackdb['period'] = int(self.combo_periods.get_active_id())
        self.jackdb['connect-mode'] = str(self.jk_connect_mode.get_active_id())
        self.jackdb['cap-latency'] = int(self.cap_lat_spin.get_value_as_int())
        self.jackdb['play-latency'] = int(self.play_lat_spin.get_value_as_int())

        self.extra['a2j'] = self.jack_midi_check.get_active()
        self.extra['usbauto'] = self.usb_plug_check.get_active()
        self.extra['monitor'] = str(self.monitor_combo.get_active_id())
        self.extra['phone-action'] = str(self.hp_action.get_active_id())
        self.extra['phone-device'] = str(self.hp_device.get_active_id())

        c_file = expanduser(self.config_file)
        if not os.path.isfile(c_file):
            # either first run or old version
            c_dir = expanduser(self.config_path)
            if not os.path.isdir(c_dir):
                os.makedirs(c_dir)
            if os.path.isfile(expanduser(self.old_config_file)):
                os.remove(expanduser(self.old_config_file))
        with open(c_file, 'w') as json_file:
            json.dump(self.conf_db, json_file, indent = 4)
            json_file.write("\n")
            return


    def signal_autojack(self, signal):
        global autojack
        cmd = f"/usr/bin/dbus-send --type=signal / org.studio.control.event.{signal}_signal"
        if autojack:
            subprocess.run(shlex.split(cmd), shell=False)
        else:
            time.sleep(5)
            if autojack:
                subprocess.run(shlex.split(cmd), shell=False)
            else:
                print("Starting Autojack...")
                # first tell any old autojack to die
                subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.studio.control.event.quit_signal"],
                               shell=False)
                # do it
                subprocess.Popen([f"{install_path}/bin/autojack"], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
                                 stderr=subprocess.STDOUT, shell=False).pid


    def on_button_help_ok_clicked(self, button):
        self.window_help.hide()

    def on_button_rt_info_ok_clicked(self, button):
        self.message_dialog_rt_info.hide()

    # All the ways we can die
    def on_button_msg_ok_clicked(self, button):
        self.we_die()

    def on_main_button_cancel_clicked(self, button):
        self.we_die()

    def on_window_main_delete_event(self, *args):
        self.we_die()

    def sig_handler(self, 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. '''
        self.we_die()

    def we_die(self):
        global jack_alive
        global jack_client
        global lock_file
        if jack_alive:
            jack_client.close()
        #self.window_main = builder.get_object('window_main')
        if self.not_applied:
            dialog = Gtk.MessageDialog(
                transient_for=self.window_main,
                flags=0,
                message_type=Gtk.MessageType.WARNING,
                buttons=Gtk.ButtonsType.NONE,
                text="Warning, New Settings have not been Applied",
            )
            dialog.add_buttons("Apply Settings", 10, "Exit", 11)
            response = dialog.run()
            if response == 10:
                self.config_save()
                self.not_applied = False
                self.signal_autojack("config")

            dialog.destroy()

        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)
        Gtk.main_quit()

us = StudioControls()
us.window_main.show_all()

Gtk.main()
