#!/usr/bin/python3

from gi.repository import Gtk
import os
import re
import resource
import shutil
import subprocess

"""First, make sure user is root"""
if os.getuid() != 0:
    print("This application needs to run as root.")
    exit(1)

class SysInfo:
    """Get information about the system"""
    def list_users(self):
        """Lists "real" users and shows whether they are in audio group, or not"""
        # Get min and max UID values for users
        # This will later be used to determine which users are regular users
        with open("/etc/login.defs", "r") as login_defs_file:
            for line in login_defs_file:
                if re.match("^UID_MIN", line):
                    uid_min_expr = re.compile('\d+')
                    uid_min=int(uid_min_expr.findall(line)[0])
                if re.match("^UID_MAX", line):
                    uid_max_expr = re.compile('\d+')
                    uid_max=int(uid_max_expr.findall(line)[0])

        # create a list of real users based on UID limits and login shell
        real_users = []
        with open("/etc/passwd", "r") as passwd_file:
            for line in passwd_file:
                l = line.rstrip().split(':')
                uid = int(l[2])
                if uid >= uid_min and uid <= uid_max and l[6] != '/usr/sbin/nologin':
                    real_users.append(l)
        # 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(',')
        # create the list of users and their attributes that we are interested in
        # return in the format: user_name full_name in_audio_group(True/False)
        users = []
        for user in real_users:
            user_name = user[0]
            full_name = user[4].strip(",,,")
            if user_name in audio_users:
                users.append([user_name, full_name, True])
            else:
                users.append([user_name, full_name, False])
        return users

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

    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]

class RTSetup:
    """Adds or removes RT ability with jack"""
    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 enable_pam_file(self, action):
        if action == "enable":
            os.rename(self.disabled_path, self.enabled_path)
        elif action == "disable":
            os.rename(self.enabled_path, self.disabled_path)
        elif action == "create":
            shutil.copyfile(self.backup_file, self.enabled_path)

    def set_user_audio_group(self, users):
        for user in users:
            if user[1] == True:
                subprocess.call(["/usr/sbin/adduser", user[0], "audio"])
            elif user[1] == False:
                subprocess.call(["/usr/sbin/deluser", user[0], "audio"])

class UbuntuStudioControls:

    def __init__(self):
        '''Activate the SysInfo class'''
        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)
        '''Get buttons'''
        self.check_audio_group = builder.get_object('check_audio_group')
        self.check_realtime_audio = builder.get_object('check_realtime_audio')
        self.button_msg_ok = builder.get_object('button_msg_ok')
        '''Get liststore for combo-box'''
        self.user_list_store = builder.get_object('liststore1')
        '''Get the combobox'''
        self.combo_user = builder.get_object('combo_user')
        '''Get a list of users and append them to the combo'''
        self.listed_users = self.sysinfo.list_users()
        for user in self.listed_users:
            username = user[0]
            fullname = user[1]
            audio_group = user[2]
            user_label = fullname + " (" + username + ")"
            self.user_list_store.append([username, fullname, user_label, audio_group])

        '''Check if audio.conf and/or audio.conf.disabled exists, returns are true or false'''
        self.jack_file_exists, self.jack_file_disabled = self.sysinfo.check_pam_files()
        if self.jack_file_exists:
            self.check_realtime_audio.set_active(True)
        else:
            rtprio, memlock = self.sysinfo.check_rlimits()
            if rtprio != 0:
                self.check_realtime_audio.set_sensitive(False)
                self.message_dialog_rt_info.show()


        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_ok_clicked": self.on_main_button_ok_clicked,
            "on_main_button_cancel_clicked": self.on_main_button_cancel_clicked,
            "on_main_button_help_clicked": self.on_main_button_help_clicked,
            "on_check_audio_group_toggled": self.on_check_audio_group_toggled,
            "on_check_realtime_audio_toggled": self.on_check_realtime_audio_toggled,
            "on_user_combo_changed": self.on_user_combo_changed,
            "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
        }
        builder.connect_signals(handlers)
        self.combo_user.set_active(0) #Set the combo to show the first item in the list

        self.rtsetup = RTSetup()

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

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

    def on_check_audio_group_toggled(self, toggle):
        combo_index = int(self.combo_user.get_active())
        if toggle.get_active():
            self.user_list_store[combo_index][3] = True
        else:
            self.user_list_store[combo_index][3] = False

    def on_check_realtime_audio_toggled(self, button):
        if button.get_active():
            pass
        else:
            pass

    def on_user_combo_changed(self, combo):
        tree_iter = combo.get_active_iter()
        if tree_iter != None:
            model = combo.get_model()
            audio_group = model[tree_iter][:4][3]
            self.check_audio_group.set_active(audio_group)

    def on_main_button_ok_clicked(self, button):
        '''Let's hide the controls, so nothing goes wrong after this'''
        self.window_main.hide()
        change_happened = False
        '''First, check if rt_setting was changed'''
        rt_toggle = self.check_realtime_audio.get_active() # Returns true or false
        if rt_toggle != self.jack_file_exists:
            if rt_toggle == True and self.jack_file_disabled:
                self.rtsetup.enable_pam_file("enable")
            elif rt_toggle == True and self.jack_file_disabled == False:
                self.rtsetup.enable_pam_file("create")
            elif rt_toggle == False:
                self.rtsetup.enable_pam_file("disable")
            change_happened = True
        '''Then, whether we are changing user group memberships'''
        i = 0
        change_users = []
        for user in self.user_list_store:
            if user[3] != self.listed_users[i][2]:
                change_users.append([user[0], user[3]])
            i = i + 1
        if change_users:
            self.rtsetup.set_user_audio_group(change_users)
            change_happened = True
        if change_happened:
            self.message_dialog_changes_info.show()
        else:
            Gtk.main_quit()

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

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

Gtk.main()
