def input_logic(input_key):
    """
    Perform an action for a given input_key. If one through nine, attempt to play the audio file associated with that
    number. If zero, attempt to kill all audio processes. If ten (minus key) execute back_page. if eleven (plus key)
    execute forward_page.
    If any number is given other than the ones above, raise a not implemented error, because nothing else is
    implemented.
    :param input_key: Input key, already processed through Dictionaries.KEY_CODES.
    :return: None
    """
    if 1 <= input_key <= 9:
        audio_logic(global_variables.input.page, input_key - 1)
    elif input_key is 0:
        global_variables.audio.kill_audio = True
        sleep(global_config.audio.polling_rate)
        global_variables.audio.kill_audio = False
    elif input_key is 10:
        play_buttons.back_page()
    elif input_key is 11:
        play_buttons.forward_page()
    else:
        log(
            ERROR, "input_logic", "Input key \"" + input_key +
            "\" was pressed, there is no handler for that key!")
        raise NotImplementedError
def run_gui():
    """
    Easy way to get main to start running the mainloop while keeping it inside display_controller.
    Make sure all the files are read and in place before the user can do anything, then start running the window proper.
    :return: None
    """
    frame_top.refresh_files()
    window.mainloop()
    log(INFO, "create_gui", "GUI exited mainloop, shutting down!")
def display_terminal_output():
    """
    Function to display_terminal_outout. Because this is not implemented, log an error and raise an exception.
    :return: None
    """
    log(
        ERROR, "display_terminal_output",
        "NO TERMINAL OUTPUT SUPPORTED, SWITCH use_gui IN Config.ini FROM False TO "
        "True!")
    raise NotImplementedError
def key_detection():
    """
    Attempts to open the event file given by event_file_location. If there is a permission error, execute
    chown_event_file and try one more time. If the file isn't found, raise a not implemented error.
    Read the file, and unpack the information its giving. Use most of that information to determine if the user is
    pressing a key relevant to this program. (relevancy is determined using KEY_CODES in dictionaries)
    If the pressed key is relevant, forward that key to input_logic.
    If the pressed key is not relevant, ignore it.
    If the quit variable is True, shut down the thread.
    NOTE: The information on how to unpack things was taken from StackOverflow. If there are any suggestions on how to
    make this more efficient/better to read, that would be great.
    :return: None
    """
    try:
        keyboard_input = open(global_variables.input.event_file_location, "rb")
    except PermissionError:
        chown_event_file(global_variables.event_file_location)
        keyboard_input = open(global_variables.input.event_file_location, "rb")
    except FileNotFoundError:
        raise NotImplementedError

    global_variables.online.key_detector = True

    # These next 5 lines were taken from StackOverflow (kind of, made some modifications).
    jank_format = 'llHHI'
    event_size = struct.calcsize(jank_format)
    event = keyboard_input.read(event_size)
    while event:
        (tv_sec, tv_usec, key_type, code,
         value) = struct.unpack(jank_format, event)

        if (key_type != 0 or code != 0) and code != 4 and value == 1:
            # print("Event type %u, code %u, value %u at %d.%d" % (key_type, code, value, tv_sec, tv_usec))
            output_key = KEY_CODES.get(code, 666)
            if output_key != 666:
                input_logic(output_key)
            else:
                pass
        else:
            pass
        event = keyboard_input.read(event_size)

        if global_variables.misc.quit is True:
            log(INFO, "key_detection_linux", "Shutting down!")
            keyboard_input.close()
            global_variables.online.key_detector = False
            return 0
def chown_event_file(event_file_path):
    """
    Takes in the path to an event file that needs to be chown'd, and launches a gnome-terminal with the chown command
    running in it, and awaiting the sudo password of the user if the file exists.
    :param event_file_path: String containing the file path to the event file to be chown'd.
    :return: None
    """
    log(INFO, "chown_event_file",
        "Executing chown on \"" + event_file_path + "\".")
    command = sp.Popen("gnome-terminal --window --title=\"Fix Permission of " +
                       event_file_path +
                       "\" --wait -- sudo chown $USER:$USER " +
                       event_file_path,
                       stdout=sp.PIPE,
                       shell=True)
    while command.poll() is None:
        sleep(0.5)
Example #6
0
    def __init__(self):
        self.file_config = cp.ConfigParser()
        if isfile("config.ini") is False:
            # If config.ini is missing, create a new one.
            log(INFO, "GlobalConfigClass", "config.ini missing, creating a new one.")
            self.file_config["MAIN"] = {"use_gui": "True"}
            self.file_config["AUDIO"] = {"root_sound_folder": "Sound Files", "polling_rate": "1 / 20",
                                         "max_audio_threads": "10", "create_loopback": "True"}
            self.file_config["INPUT"] = {"event_file_location": "None"}
            with open("config.ini", "w") as configfile:
                self.file_config.write(configfile)
        else:
            log(INFO, "GlobalConfigClass", "Reading config.ini")
            self.file_config.read("config.ini")

        self.main = self.MainClass(self.file_config)
        self.audio = self.AudioClass(self.file_config)
        self.input = self.InputClass(self.file_config)
def start_audio_setup():
    """
    Create the null sink that the soundboard will output to, and export the ID to null_sink
    If the create_loopback config option is True, create a loopback with the source of the null sink previously created,
    and export the ID to loopback.
    :return: None
    """
    audio_start_command = """ pactl load-module module-null-sink sink_name=PythonSoundboardOutput 
    sink_properties=device.description="Python_Soundboard_Output" rate=48000 """
    loopback_start_command = """ pactl load-module module-loopback source=PythonSoundboardOutput.monitor 
    latency_msec=5 """
    log(INFO, "start_audio_setup", "Creating null sink.")
    global_variables.audio.null_sink = eval(
        subprocess.check_output(audio_start_command, shell=True))
    if global_config.audio.create_loopback is True:
        log(INFO, "start_audio_setup", "Creating loopback.")
        global_variables.audio.loopback = eval(
            subprocess.check_output(loopback_start_command, shell=True))
Example #8
0
def process_config_value(file_config, input_category, input_variable, input_fallback):
    """
    Attempt to read the config option specified with the variables and return it. If the config option is missing,
    return the fallback instead.
    :param file_config: The dictionary given by ConfigParser.
    :param input_category: The overall category that the config option is in, ex [MAIN]
    :param input_variable: The config option that is attempting to be read, ex use_gui
    :param input_fallback: The fallback option that will be used if the config option is missing, ex True
    :return: Either the value of the config option, or the value of input_fallback.
    """
    if input_category in file_config and input_variable in file_config[input_category]:
        output = file_config[input_category][input_variable]
        log(INFO, "process_config_value", "Config value [" + input_category + "](" + input_variable + ") read as \"" +
            str(output) + "\"")
    else:
        output = input_fallback
        log(INFO, "process_config_value", "Config value [" + input_category + "](" + input_variable +
            ") missing, defaulting to \"" + str(output) + "\"")
    return output
 def play_audio(self):
     """
     Used exclusively for the audio buttons.
     Get what number the button is.
     If the button is not 0, attempt to play an audio file determined by the current page and number of button
     pressed.
     Else if the button is 0, kill all running audio processes.
     Else, do nothing as nothing should hit the else.
     :return: None
     """
     if self.number is not 0:
         audio_logic(global_variables.input.page, self.number - 1)
     elif self.number is 0:
         global_variables.audio.kill_audio = True
         sleep(global_config.audio.polling_rate)
         global_variables.audio.kill_audio = False
     else:
         log(
             WARNING, "play_audio", "Invalid button \"" + self.number +
             "\" attempted to play a sound?")
         pass
def start_input_controller():
    """
    If there is something in the event_file_location, start up key_detection in a separate thread.

    Else, don't do anything.
    :return: None
    """
    if global_variables.input.event_file_location is not None and global_variables.online.key_detector is False:
        log(INFO, "start_input_controller", "Starting Linux key detection.")
        key_detector = threading.Thread(target=key_detection)
        key_detector.start()
    elif global_variables.online.key_detector is True:
        log(WARNING, "start_input_controller",
            "Key detection already started, not starting key detection.")
    else:
        log(INFO, "start_input_controller",
            "event_file_location set to None, not starting key detection.")
def refresh_files():
    """
    If locked is False, set locked to True and clear the lists to be modified. Next, get a list
    of folders. Run through the entire folder_list to add the name of the folder to folder_names and get a list of
    files in that folder. Go through the entire file_list and append the name and path to pre_file_names and
    pre_file_paths respectively. Once done with that, append pre_file_names to file_names, and pre_file_paths to
    file_paths. Then set locked to False.

    Else, log a warning and do nothing else.
    :return: None
    """
    log(INFO, "refresh_files", "Starting refresh.")
    if global_variables.file.locked is False:
        # First, lock and clear all folder names, file names, and file paths.
        global_variables.file.locked = True
        global_variables.file.folder_names.clear()
        global_variables.file.file_names.clear()
        global_variables.file.file_paths.clear()

        # Get a list of folders, and add the names to folder_names.
        folder_list = get_item_list(global_config.audio.root_sound_folder)
        for folder in folder_list:
            global_variables.file.folder_names.append(folder)

            # Get a list of files in the folder, add the name and path to separate lists, and add the name list and path
            # list to file_names and file_paths respectively.
            file_list = get_item_list(global_config.audio.root_sound_folder +
                                      "/" + folder)
            pre_file_names = []
            pre_file_paths = []
            for file in file_list:
                pre_file_names.append(file)
                pre_file_paths.append(global_config.audio.root_sound_folder +
                                      "/" + folder + "/" + file)
            global_variables.file.file_names.append(pre_file_names)
            global_variables.file.file_paths.append(pre_file_paths)

        # Since the folder/file names/paths are no longer being written to, unlock them.
        global_variables.file.locked = False
        log(INFO, "refresh_files", "Ended refresh.")
    else:
        log(WARNING, "refresh_files",
            "Files are locked, is a refresh already occurring?")
def end_audio_setup():
    """
    Kill all running audio processes by setting kill_audio to True, waiting the polling_rate, then setting kill_audio
    back to False.
    If the create_loopback config option is True, unload the loopback with the source as the soundboards null-sink using
    the ID from loopback.
    Unload the null-sink that the soundboard is outputting to using the ID from null-sink.
    :return: None
    """
    log(INFO, "end_audio_setup", "Killing all running audio threads.")
    global_variables.audio.kill_audio = True
    sleep(global_config.audio.polling_rate)
    global_variables.audio.kill_audio = False
    if global_config.audio.create_loopback is True:
        log(INFO, "end_audio_setup", "Removing loopback.")
        subprocess.call("pactl unload-module " +
                        str(global_variables.audio.loopback),
                        shell=True)
    log(INFO, "end_audio_setup", "Removing null sink.")
    subprocess.call("pactl unload-module " +
                    str(global_variables.audio.null_sink),
                    shell=True)
    Function to display_terminal_outout. Because this is not implemented, log an error and raise an exception.
    :return: None
    """
    log(
        ERROR, "display_terminal_output",
        "NO TERMINAL OUTPUT SUPPORTED, SWITCH use_gui IN Config.ini FROM False TO "
        "True!")
    raise NotImplementedError


if global_config.main.use_gui is True:
    """
    If the use_gui value is True, setup the GUI to let the main thread run it.
    """
    window = tk.Tk()
    log(INFO, "DisplayController", "Initial GUI created.")

    log(INFO, "DisplayController", "Populating GUI.")
    window.title("Python Linux Soundboard")

    # Create the top buttons
    frame_top = FrameTopClass()

    # Create the folder list at coordinates 0, 1 and allow stretching.
    folder_list = FolderListClass(0, 1, 15)
    window.columnconfigure(0, weight=1)
    window.rowconfigure(1, weight=1)

    # Create the play buttons at coordinates 1, 1.
    play_buttons = PlayButtonsClass(1, 1)