#!/usr/bin/python3

import gi

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gdk
import os
from os.path import expanduser
import getpass
import pwd
import re
import resource
import shutil
import subprocess
import glob
import dbus
from dbus.mainloop.glib import DBusGMainLoop
import time
import jack
import configparser

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"""
        # create a list of users who are members of audio group:
        with open("/etc/group", "r") as groups_file:
            for line in groups_file:
                if re.match("^audio:", line):
                    audio_users = line.split(':')[3].rstrip().split(',')

        user = getpass.getuser()
        audio_group = False
        if user in audio_users:
            audio_group = True

        return audio_group

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

    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'''
        in_performance = False
        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()):
                        in_performance = True
        return in_performance

    def get_boosted(self):
        '''Checks for Intel boost state'''
        boosted = False
        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()):
                        boosted = True
        return boosted

    # 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 = "/usr/share/ubuntustudio-controls/audio.conf"

    def set_governor(self, enable):
        if enable == True:
            gov = "performance"
        else:
            if os.path.exists("/sys/devices/system/cpu/intel_pstate"):
                gov = "powersave"
            else:
                gov = "ondemand"
        subprocess.run(["/usr/bin/pkexec", "/usr/sbin/ubuntustudio-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", "/usr/sbin/ubuntustudio-system", "boost"], shell=False)
            else:
                subprocess.run(["/usr/bin/pkexec", "/usr/sbin/ubuntustudio-system", "noboost"], shell=False)


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

    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
        self.sysinfo = SysInfo()
        '''Create the GUI'''
        builder = Gtk.Builder()
        builder.add_from_file("/usr/share/ubuntustudio-controls/ubuntustudio-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.button_msg_ok = builder.get_object('button_msg_ok')
        '''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')
        '''audio tab stuff'''
        '''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.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.usb_single_ck = builder.get_object('usb_single_ck')
        self.combo_zita_add = builder.get_object('combo_zita_add')
        self.combo_zita_remove = builder.get_object('combo_zita_remove')

        '''pulse tab'''
        self.pj_in_combo = builder.get_object('pj_in_combo')
        self.pj_out_combo = builder.get_object('pj_out_combo')
        self.pj_in_name = builder.get_object('pj_in_name')
        self.pj_out_name = builder.get_object('pj_out_name')
        self.pj_in_con = builder.get_object('pj_in_con')
        self.pj_out_con = builder.get_object('pj_out_con')

        ''' tablet '''
        ''' pad '''
        self.pad_device_c = builder.get_object('pad_device')
        self.tab_but_num = builder.get_object('tab_but_num')
        self.tab_but_mod1 = builder.get_object('tab_but_mod1')
        self.tab_but_mod2 = builder.get_object('tab_but_mod2')
        self.tab_but_key = builder.get_object('tab_but_key')
        # need 12 buttons too
        ''' pen '''
        self.pen_top_x = builder.get_object('pen_top_x')
        self.pen_top_y = builder.get_object('pen_top_y')
        self.pen_bot_x = builder.get_object('pen_bot_x')
        self.pen_bot_y = builder.get_object('pen_bot_y')
        #self.pen_device_c = builder.get_object('pen_device')

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

        '''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("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")
            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)

        # Audio stuff

        # first set defaults
        self.config['DEFAULT'] = {
            'JACK': 'False',
            'DRIVER': "alsa",
            'CHAN-IN': '0',
            'CHAN-OUT': '0',
            'RATE': "48000",
            'FRAME': "1024",
            'PERIOD': "2",
            'ZFRAME': "512",
            'XDEV': "",
            'PULSE-IN': 'pulse_in',
            'PULSE-OUT': 'pulse_out',
            'PJ-IN-CON': '1',
            'PJ-OUT-CON': '1',
            'A2J': "True",
            'DEV': "0,0,0",
            'USBAUTO': "True",
            'USB-SINGLE': "False",
            'USBDEV': "",
            'LOG-LEVEL': "19",
        }

        # This is going to bite us sometime...
        # People who have more than one tablet will want to save
        # settings for all of them not just one of them.
        # but for now we assume one tablet
        w = Gtk.Window()
        screen = w.get_screen()
        m = screen.get_monitor_at_window(screen.get_active_window())
        curmon = screen.get_monitor_geometry(m)
        pos = f"{str(curmon.x)} {str(curmon.y)} {str(curmon.width + curmon.x)} {str(curmon.height + curmon.y)}"
        self.config['TABLET'] = {
            'DEVICE': "none",
            'BUTTON1': "none none none",
            'BUTTON2': "none none none",
            'BUTTON3': "none none none",
            'BUTTON8': "none none none",
            'BUTTON9': "none none none",
            'BUTTON10': "none none none",
            'BUTTON11': "none none none",
            'BUTTON12': "none none none",
            'BUTTON13': "none none none",
            'BUTTON14': "none none none",
            'BUTTON15': "none none none",
            'BUTTON16': "none none none",
            'POSITION': pos,
            'PEN': "none",
        }
        self.tab_conf = self.config['TABLET']

        global autojack
        global newusb
        global jack_alive
        global jack_died
        global xrun_count
        global max_in
        global max_out
        max_in = 18
        max_out = 18
        xrun_count = 0
        autojack = False
        newusb = False
        jack_alive = False
        jack_died = False
        self.dirty = False

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

        # Find out if jack is running
        try:
            client = jack.Client('controls', False, True)
            self.def_config['JACK'] = "True"
            client.close()
        except jack.JackError:
            self.def_config['JACK'] = "False"

        self.logging_comb.set_active_id(self.def_config['LOG-LEVEL'])
        # fill in Jack master widgets
        self.jack_rate_combo.set_active_id(self.def_config['RATE'])
        self.combobox_late.set_active_id(self.def_config['FRAME'])
        self.combo_periods.set_active_id(self.def_config['PERIOD'])
        self.combo_backend.set_active_id(self.def_config['DRIVER'])
        self.chan_in_spin.set_range(0, 128)
        self.chan_out_spin.set_range(0, 128)
        self.chan_in_spin.set_value(self.def_config.getint('CHAN-IN'))
        self.chan_out_spin.set_value(self.def_config.getint('CHAN-OUT'))
        self.jack_midi_check.set_active(self.def_config['A2J'] == "True")
        # Fill Extra devices widgets
        self.usb_plug_check.set_active(self.def_config['USBAUTO'] == "True")
        if self.def_config['USBAUTO'] == "True":
            self.usb_single_ck.set_sensitive(True)
            self.usb_single_ck.set_active(self.def_config['USB-SINGLE'] == "True")
        else:
            self.usb_single_ck.set_sensitive(False)
            self.usb_single_ck.set_active(self.def_config['USB-SINGLE'] == "True")


        self.refresh_dropdowns()
        self.i_in = 0
        self.i_out = 0
        self.refresh_pulse_tab(self.i_in, self.i_out)
        self.refresh_tablet()

        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,
            "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,

            "cb_zita_add": self.cb_zita_add,
            "cb_zita_remove": self.cb_zita_remove,
            "usb_plug_cb": self.usb_plug_cb,

            "pj_in_combo_cb": self.pj_in_combo_cb,
            "pj_out_combo_cb": self.pj_out_combo_cb,
            "pj_in_add_cb": self.pj_in_add_cb,
            "pj_out_add_cb": self.pj_out_add_cb,
            "pj_in_rem_cb": self.pj_in_rem_cb,
            "pj_out_rem_cb": self.pj_out_rem_cb,
            "pj_in_name_cb": self.pj_in_name_cb,
            "pj_out_name_cb": self.pj_out_name_cb,

            "tab_device_cb": self.tab_device_cb,
            "tab_but_num_cb": self.tab_but_num_cb,
            "tab_but_mod_cb": self.tab_but_mod_cb,
            "tab_apply_cb": self.tab_apply_cb,
        }
        builder.connect_signals(handlers)

        self.rtsetup = RTSetup()
        self.timeout_id = GLib.timeout_add(500, self.check_jack_status, None)

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

    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 xrun_count
        global max_in
        global max_out

        # sent by jackdied() callback
        if jack_died:
            jack_client.deactivate()
            jack_client.close()
            max_in = 18
            max_out = 18
            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_alive:
            load = jack_client.cpu_load()
            self.dsp_label.set_text(f"DSP: {str(load)[0:4]}%")
            self.xrun_lab.set_text(f" {str(xrun_count)}")
        else:
            self.dsp_label.set_text("DSP: 0%")
            self.xrun_lab.set_text(" 0")
            xrun_count = 0
            try:
                jack_client = jack.Client('controls', False, True)
            except jack.JackError:
                self.jack_state.set_text(" Stopped")
                self.jack_ind.set_from_icon_name("gtk-stop", 0)
                self.xrun_lab.set_text(" 0")
                xrun_count = 0
                return True
            self.jack_state.set_text(" Running")
            self.jack_ind.set_from_icon_name("gtk-apply", 0)
            jack_client.set_shutdown_callback(self.jackdied)
            jack_client.set_xrun_callback(self.jackxrun)
            jack_client.activate()
            jack_alive = True
            max_in = len(jack_client.get_ports('system.*', is_audio=True, is_output=True))
            max_out = len(jack_client.get_ports('system.*', is_audio=True, is_input=True))
            self.refresh_pulse_tab(self.i_in, self.i_out)
        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 jackxrun(xruntime, extra):
        global xrun_count
        xrun_count = xrun_count + 1

    def xrun_reset(self, button):
        global xrun_count
        xrun_count = 0

    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.'''

        if self.def_config['DRIVER'] == "alsa":
            self.jack_device_combo.set_sensitive(True)
            self.jack_usb_dev_combo.set_sensitive(True)
            self.chan_in_spin.set_sensitive(False)
            self.chan_out_spin.set_sensitive(False)
            self.combo_periods.set_sensitive(True)
        elif self.def_config['DRIVER'] == "firewire":
            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.combo_periods.set_sensitive(True)
        elif self.def_config['DRIVER'] == "dummy":
            self.jack_device_combo.set_sensitive(False)
            self.jack_usb_dev_combo.set_sensitive(False)
            self.chan_in_spin.set_sensitive(True)
            self.chan_out_spin.set_sensitive(True)
            self.combo_periods.set_sensitive(False)
        self.jack_device_combo.get_model().clear()
        self.jack_device_combo.append("0,0,0", "default")
        if self.def_config['DEV'] == "0,0,0":
            self.jack_device_combo.set_active_id("0,0,0")

        self.jack_usb_dev_combo.get_model().clear()
        self.jack_usb_dev_combo.append("", "No USB Master")

        if self.def_config['DRIVER'] == "alsa":
            if self.def_config['USBDEV'] == "":
                self.jack_usb_dev_combo.set_active_id("")
            else:
                self.jack_usb_dev_combo.append(self.def_config['USBDEV'], self.def_config['USBDEV'])
                self.jack_usb_dev_combo.set_active_id(self.def_config['USBDEV'])
                self.jack_device_combo.append("200,0,0", "USB Jack Master")
                self.jack_device_combo.set_active_id("200,0,0")

        self.combo_zita_remove.get_model().clear()
        self.combo_zita_remove.append("label", "Remove (connected)")
        self.combo_zita_remove.set_active_id("label")

        self.combo_zita_add.get_model().clear()
        self.combo_zita_add.append("label", "Add (available)")
        self.combo_zita_add.set_active_id("label")

        usb_save = ""
        pci_save = ""
        for x in range(0, 10):
            # card loop
            cname = ""
            next_id = ""
            next_d = ""
            is_usb = False
            if os.path.exists(f"/proc/asound/card{str(x)}"):
                if os.path.isfile(f"/proc/asound/card{str(x)}/id"):
                    with open(f"/proc/asound/card{str(x)}/id", "r") as card_file:
                        for line in card_file:
                            # only need one line
                            cname = line.rstrip()
                else:
                    cname = str(x)
            if os.path.exists(f"/proc/asound/card{str(x)}/usbbus"):
                is_usb = True
            for y in range(0, 10):
                d_type = ""
                d_desc = ""
                if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}p"):
                    d_type = "playback"
                if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}c"):
                    if d_type == "":
                        d_type = "capture"
                    else:
                        d_type = f"{d_type} and capture"
                if d_type != "":
                    for z in range(0, 10):
                        if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}{d_type[0]}/sub{str(z)}"):
                            with open(f"/proc/asound/card{str(x)}/pcm{str(y)}{d_type[0]}/sub{str(z)}/info",
                                      "r") as info_file:
                                for line in info_file:
                                    if re.match("^name:", line.rstrip()):
                                        dname = line.rstrip().split(": ", 1)[1]
                                        next_id = f"{cname},{str(y)},{str(z)}"
                                        next_d = f"{next_id} {d_type} ({dname})"
                            #   Have everything we need now

                            # this block will fit where ever we know sub-ids and description
                            if is_usb:
                                if usb_save == "":
                                    usb_save = next_id
                                if next_id != self.def_config['USBDEV'] and self.def_config['DRIVER'] == "alsa":
                                    self.jack_usb_dev_combo.append(next_id, next_d)

                            else:
                                if pci_save == "":
                                    pci_save = next_id
                                if self.def_config['DRIVER'] == "alsa":
                                    self.jack_device_combo.append(next_id, next_d)
                                    if self.def_config['USBDEV'] == "" and self.def_config['DEV'] == next_id:
                                        self.jack_device_combo.set_active_id(next_id)
                                        self.dev_desc = next_d
                                if self.def_config['DEV'] != next_id and self.zdev.count(next_id) > 0:
                                    self.combo_zita_remove.append(next_id, next_d)
                                else:
                                    if self.def_config['DEV'] != next_id:
                                        self.combo_zita_add.append(next_id, next_d)

        if pci_save == "" and self.def_config['USBDEV'] == "":
            self.def_config['USBDEV'] = usb_save
            self.jack_usb_dev_combo.set_active_id(usb_save)
            self.jack_device_combo.append("200,0,0", "USB device below")
            self.jack_device_combo.set_active_id("200,0,0")

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

    def on_window_main_delete_event(self, *args):
        Gtk.main_quit(*args)

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

    def on_main_button_cancel_clicked(self, button):
        global jack_alive
        global jack_client
        if jack_alive:
            jack_client.close()
        Gtk.main_quit()

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

    def rt_button_hit(self, button):
        subprocess.run(["/usr/bin/pkexec", "/usr/sbin/ubuntustudio-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.def_config['LOG-LEVEL'] != newval:
            self.def_config['LOG-LEVEL'] = newval
            self.cb_audio_apply(widget)


    # Audio setup call backs
    def cb_zita_add(self, button):
        a_id = str(button.get_active_id())
        if a_id != "None" and a_id != "label":
            self.zdev.append(str(a_id))
            if not self.dirty:
                self.dirty = True

    def cb_zita_remove(self, button):
        a_id = str(button.get_active_id())
        if a_id != "None" and a_id != "label":
            self.zdev.remove(str(a_id))
            if not self.dirty:
                self.dirty = True

    def jack_device_changed(self, button):
        a_id = str(button.get_active_id())
        a_desc = str(button.get_active_text())
        if a_id != "None" and a_id != "200,0,0":
            self.def_config['DEV'] = a_id
            self.dev_desc = a_desc
            if not self.dirty:
                self.dirty = True

    def jack_driver_changed(self, button):
        a_driver = str(button.get_active_text())
        self.def_config['DRIVER'] = a_driver
        if not self.dirty:
            self.dirty = True

    def usb_master_changed(self, button):
        a_id = str(button.get_active_id())
        if a_id != "None":
            self.def_config['USBDEV'] = a_id
        if not self.dirty:
            self.dirty = True

    def usb_plug_cb(self, widget):
        a_id = widget.get_active()
        self.usb_single_ck.set_sensitive(a_id)


    def refresh_pulse_tab(self, i_in, i_out):
        ''' Fill in all pulse related widgets '''
        '''self.combo_out_ports.set_active_id(self.def_config['PORTS'])'''
        global jack_alive
        global max_in
        global max_out
        self.pj_in_con.set_sensitive(False)
        self.pj_in_combo.set_sensitive(False)
        self.pj_in_name.set_sensitive(False)
        self.pj_out_con.set_sensitive(False)
        self.pj_out_combo.set_sensitive(False)
        self.pj_out_name.set_sensitive(False)

        self.pj_in_con.get_model().clear()
        self.pj_in_con.append("0", "no connection")
        self.pj_in_con.set_active(0)
        for i in range(1, max_in, 2):
            self.pj_in_con.append(f"{str(i)}", f"{str(i)} and {str(i + 1)}")
        self.pj_in_combo.get_model().clear()
        for i, pj_name in enumerate(self.pulse_in):
            self.pj_in_combo.append(str(i), pj_name)
        if len(self.pulse_in):
            self.pj_in_combo.set_active(i_in)
            self.pj_in_name.set_text(self.pulse_in[i_in])
            if (int(self.p_in_con[i_in]) > max_in) and (jack_alive):
                self.pj_in_con.append(self.p_in_con[i_in],
                    f"{self.p_in_con[i_in]} and {str(int(self.p_in_con[i_in]) + 1)} (invalid)")
            self.pj_in_con.set_active_id(self.p_in_con[i_in])
        else:
            self.pj_in_name.set_text("")

        self.pj_out_con.get_model().clear()
        self.pj_out_con.append("0", "no connection")
        self.pj_out_con.set_active(0)
        for i in range(1, max_out, 2):
            self.pj_out_con.append(f"{str(i)}", f"{str(i)} and {str(i + 1)}")
        self.pj_out_combo.get_model().clear()
        for i, pj_name in enumerate(self.pulse_out):
            self.pj_out_combo.append(str(i), pj_name)
        if len(self.pulse_out):
            self.pj_out_combo.set_active(i_out)
            self.pj_out_name.set_text(self.pulse_out[i_out])
            if (int(self.p_out_con[i_out]) > max_out) and (jack_alive):
                self.pj_out_con.append(self.p_out_con[i_out],
                    f"{self.p_out_con[i_out]} and {str(int(self.p_out_con[i_out]) + 1)} (invalid)")
            self.pj_out_con.set_active_id(self.p_out_con[i_out])
        else:
            self.pj_out_name.set_text("")

        self.pj_in_con.set_sensitive(True)
        self.pj_in_combo.set_sensitive(True)
        self.pj_in_name.set_sensitive(True)
        self.pj_out_con.set_sensitive(True)
        self.pj_out_combo.set_sensitive(True)
        self.pj_out_name.set_sensitive(True)


    def pj_in_name_cb(self, widget):
        ''' call back for any pulse bridge input name or connect change
        to current values '''
        if not widget.get_sensitive():
            return
        if len(self.pulse_in):
            self.pulse_in[self.i_in] = "".join(str(self.pj_in_name.get_text()).split())
            self.pj_in_name.set_text(self.pulse_in[self.i_in])
            self.p_in_con[self.i_in] = str(self.pj_in_con.get_active_id())
            self.refresh_pulse_tab(self.i_in, self.i_out)


    def pj_out_name_cb(self, widget):
        ''' call back for any pulse bridge output name or connect change
        to current values '''
        if not widget.get_sensitive():
            return
        if len(self.pulse_out):
            self.pulse_out[self.i_out] = "".join(str(self.pj_out_name.get_text()).split())
            self.pj_out_name.set_text(self.pulse_out[self.i_out])
            self.p_out_con[self.i_out] = str(self.pj_out_con.get_active_id())
            self.refresh_pulse_tab(self.i_in, self.i_out)


    def pj_in_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.i_in = self.pj_in_combo.get_active()
        self.refresh_pulse_tab(self.i_in, self.i_out)


    def pj_out_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.i_out = self.pj_out_combo.get_active()
        self.refresh_pulse_tab(self.i_in, self.i_out)


    def pj_in_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 '''
        self.i_in = len(self.pulse_in)
        self.i_out = 0
        if len(self.pulse_out):
            self.i_out = int(self.pj_out_combo.get_active_id())
        self.pulse_in.append(f"pulse_in-{str(self.i_in + 1)}")
        self.p_in_con.append("0")
        self.refresh_pulse_tab(self.i_in, self.i_out)


    def pj_out_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 '''
        self.i_out = len(self.pulse_out)
        self.i_in = 0
        if len(self.pulse_in):
            self.i_in = int(self.pj_in_combo.get_active_id())
        self.pulse_out.append(f"pulse_out-{str(self.i_out + 1)}")
        self.p_out_con.append("0")
        self.refresh_pulse_tab(self.i_in, self.i_out)


    def pj_in_rem_cb(self, widget):
        ''' get index of current bridge
        remove name from list by index
        remove connection from list by index '''
        index = self.pj_in_combo.get_active()
        del self.pulse_in[index]
        del self.p_in_con[index]
        self.i_in = 0
        self.i_out = 0
        if len(self.pulse_out):
            self.i_out = int(self.pj_out_combo.get_active_id())
        self.refresh_pulse_tab(self.i_in, self.i_out)


    def pj_out_rem_cb(self, widget):
        ''' get index of current bridge
        remove name from list by index
        remove connection from list by index '''
        index = self.pj_out_combo.get_active()
        del self.pulse_out[index]
        del self.p_out_con[index]
        self.i_out = 0
        self.i_in = 0
        if len(self.pulse_in):
            self.i_in = int(self.pj_in_combo.get_active_id())
        self.refresh_pulse_tab(self.i_in, self.i_out)


    def mixer_cb(self, button):
        '''callback for mixer button. This starts QASMixer
        with the device set to whatever is jack master'''
        if self.def_config['USBDEV'] != "":
            # must be hw:device not hw:device,0,0
            mixdevice = self.def_config['USBDEV'].split(',', 1)[0]
        else:
            mixdevice = self.def_config['DEV'].split(',', 1)[0]
        subprocess.Popen(["/usr/bin/qasmixer", "-n", f"--device=hw:{str(mixdevice)}"], shell=False).pid

    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")

    def cb_jack_start(self, button):
        ''' call back for Jack (re)start button'''
        global autojack
        self.def_config['JACK'] = "True"
        self.config_save()
        if autojack:
            subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.ubuntustudio.control.event.start_signal"],
                           shell=False)
        else:
            time.sleep(5)
            if autojack:
                subprocess.run(
                    ["/usr/bin/dbus-send", "--type=signal", "/", "org.ubuntustudio.control.event.start_signal"],
                    shell=False)
            else:
                self.start_autojack()

    def cb_jack_stop(self, button):
        global autojack
        self.def_config['JACK'] = "False"
        self.config_save()
        if autojack:
            subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.ubuntustudio.control.event.stop_signal"],
                           shell=False)
        else:
            time.sleep(5)
            if autojack:
                subprocess.run(
                    ["/usr/bin/dbus-send", "--type=signal", "/", "org.ubuntustudio.control.event.stop_signal"],
                    shell=False)
            else:
                self.start_autojack()

    def cb_audio_apply(self, button):
        '''callback for audio tab apply button'''
        global autojack
        self.config_save()
        if autojack:
            subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.ubuntustudio.control.event.config_signal"],
                           shell=False)
        else:
            time.sleep(5)
            if autojack:
                subprocess.run(
                    ["/usr/bin/dbus-send", "--type=signal", "/", "org.ubuntustudio.control.event.config_signal"],
                    shell=False)
            else:
                self.start_autojack()

    def config_save(self):
        ''' Write audio setting to ~/.config/autojack/autojackrc'''
        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(expanduser(self.config_file), "w") as rc_file:
            self.def_config['DRIVER'] = str(self.combo_backend.get_active_id())
            self.def_config['CHAN-IN'] = str(int(self.chan_in_spin.get_value()))
            self.def_config['CHAN-OUT'] = str(int(self.chan_out_spin.get_value()))
            self.def_config['RATE'] = str(self.jack_rate_combo.get_active_id())
            self.def_config['FRAME'] = str(self.combobox_late.get_active_id())
            zframe = str(int(int(self.combobox_late.get_active_id()) / 2))
            if int(zframe) < 128:
                zframe = str(64)
            self.def_config['ZFRAME'] = zframe
            self.def_config['PERIOD'] = str(self.combo_periods.get_active_id())
            self.def_config['A2J'] = str(self.jack_midi_check.get_active())
            self.def_config['PULSE-IN'] = ' '.join(self.pulse_in)
            self.def_config['PULSE-OUT'] = ' '.join(self.pulse_out)
            self.def_config['PJ-IN-CON'] = ' '.join(self.p_in_con)
            self.def_config['PJ-OUT-CON'] = ' '.join(self.p_out_con)
            self.def_config['USBAUTO'] = str(self.usb_plug_check.get_active())
            self.def_config['USB-SINGLE'] = str(self.usb_single_ck.get_active())
            self.def_config['XDEV'] = ' '.join(self.zdev)
            self.config.write(rc_file)

    def start_autojack(self):
        '''start autojack as it has been detected as not running.
        This should only happen directly after a fresh install of
        -controls'''
        print("Starting Autojack...")
        # first tell any old autojack to die
        subprocess.run(["/usr/bin/dbus-send", "--type=signal", "/", "org.ubuntustudio.control.event.quit_signal"],
                       shell=False)
        # do it
        subprocess.Popen(["/usr/bin/autojack"], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
                         stderr=subprocess.STDOUT, shell=False).pid

    def refresh_tablet(self):
        ''' refresh tablet tab in gui. By the time we open -controls
            The tablet should be set with whatever the config file has
            so we read the device values.'''
        return
        ''' not used yet '''
        d_lines = []
        self.pad_device_c.append(self.tab_conf['DEVICE'], self.tab_conf['DEVICE'])
        self.pad_device_c.set_active(0)

        device_list = subprocess.check_output(["/usr/bin/xsetwacom", "list"], shell=False)
        d_lines = device_list.decode()
        for line in d_lines.split('\n'):
            if len(line):
                # we have a line
                device = line.rstrip().split("id:", 1)[0].strip()
                print (f"tablet device = {device}")
                tablet = line.rstrip().split("type:", 1)[1].strip()
                print (f"tablet type = {tablet}")
                if tablet == "PAD":
                    # this is is a pad device for the buttons tab
                    #self.pad_device_c = builder.get_object('pad_device')
                    print(f"have a pad, device = {device}")
                    if device != self.tab_conf['DEVICE']:
                        self.pad_device_c.append(device, device)


        pad_dev = ""
        if d_lines != []:
            pad_dev = self.pad_device_c.get_active_id()


    def tab_device_cb(self, widget):
        ''' device is changed may have been none before so
            refill all tablet spots '''
        self.refresh_tablet()

    def tab_but_num_cb(self, widget):
        ''' button number changed, refill related widgets '''

    def tab_but_mod_cb(self, widget):
        ''' one of the button variables has changed, save the three '''

    def tab_apply_cb(self, widget):
        ''' wrap it all up and send it to the device '''
            


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

    def on_button_msg_ok_clicked(self, button):
        Gtk.main_quit()

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

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


us = UbuntuStudioControls()
us.window_main.show_all()

Gtk.main()
