~charles/gmixerctl

11d0057483b54f7700bd8cd906622459c90da9ff — Charles Daniels 5 years ago ba253c8 0.1.1
0.1.1 release, fixed crash on unhandled input formats
4 files changed, 65 insertions(+), 10 deletions(-)

M CHANGELOG
M gmixerctl/constants.py
M gmixerctl/gui.py
M gmixerctl/mixerctl.py
M CHANGELOG => CHANGELOG +8 -0
@@ 8,3 8,11 @@

	* Added support for switching mixer devices via the "basic" tab by
	  re-starting the application in-place.

0.1.1

	* Revised parsing logic to be regex based, removing the possibility
	  for a number of possible crashes.

	* If the basic tab fails to build, an error dialog is displayed before
	  crashing.

M gmixerctl/constants.py => gmixerctl/constants.py +14 -2
@@ 10,6 10,8 @@ log_level = logging.DEBUG

mixer_device = "/dev/mixer"

project_homepage = "https://github.com/charlesdaniels/gmixerctl"

# control names to appear in the basic tab
basic_controls = [
    "outputs.master",


@@ 21,10 23,10 @@ basic_controls = [
    "record.slaves"
]

version = "0.1.0"
version = "0.1.1"

license = """
Copyright (c) 2018, Charles Daniels (except where otherwise noted) 
Copyright (c) 2018, Charles Daniels (except where otherwise noted)
All rights reserved.

Redistribution and use in source and binary forms, with or without


@@ 53,3 55,13 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""

basic_error = """
Something is wrong.

If you see this message, one of the assumptions gmixerctl makes about your
audio setup is so fundamentally wrong that it can't generate a "basic"
tab for you. Either this is a bug in gmixerctl, or a problem with your system.

Please report this issue on the gmixerctl homepage: {}
""".format(project_homepage)

M gmixerctl/gui.py => gmixerctl/gui.py +4 -0
@@ 231,6 231,7 @@ def main():

    tabs = {}
    tkvars = {}
    tab_name = "basic"

    # custom-build "basic" tab
    row_counter = 0


@@ 252,6 253,9 @@ def main():
        render_control(tabs[tab_name], control, tabs, tkvars, row_counter)
        row_counter += 1

    if "basic" not in tabs:
        tkinter.messagebox.showerror("Error", constants.basic_error)

    # add mixer device selector to basic tab
    dev_selector_label = tkinter.Label(tabs[tab_name],
            text = "select mixer device")

M gmixerctl/mixerctl.py => gmixerctl/mixerctl.py +39 -8
@@ 1,6 1,7 @@
import os
import logging
import subprocess
import re

from . import util
from . import constants


@@ 13,13 14,19 @@ def parse_line(line):
    :param line:
    """

    if len(line.split("=")) != 2:
        logging.warning(
                "don't know what to do with line '{}', no '='".format(line))
        return (line, {})

    name = line.split("=")[0]
    rest = line.split("=")[1]
    rest = line.split("=")[1].strip()

    state = {}
    state["name"] = name

    if "[" in rest:
    # enum case, i.e. off [ off on ]
    if re.match("^[a-zA-Z0-9:-]+\s+\[[a-zA-Z0-9 :-]+\]$", rest):
        state["type"] = "enum"
        state["current"] = rest.split("[")[0].strip()



@@ 30,7 37,8 @@ def parse_line(line):
            else:
                state["possible"].append(val)

    elif "{" in rest:
    # set case, i.e. hp  { spkr hp }
    elif re.match("((^[a-zA-Z0-9,:-]+\s+)|^)\{[a-zA-Z0-9 :-]+\}$", rest):
        state["type"] = "set"
        rest = rest.replace("}", "")
        state["current"] = tuple(rest.split("{")[0].strip().split(","))


@@ 42,13 50,30 @@ def parse_line(line):
            else:
                state["possible"].append(val)

    else:
    # value case, single int value
    elif re.match("^[0-9]+$", rest):
        state["type"] = "value"
        state["current"] = int(rest)

    # value case, pair of int values
    elif re.match("^[0-9]+[,][0-9]+$", rest):
        state["type"] = "value"
        state["current"] = tuple((int(x) for x in rest.split(",")))

    # value case, single int value, with annotation
    elif re.match("^[0-9]+\s+[a-zA-Z]+$", rest):
        state["type"] = "value"
        if "," in rest:
            state["current"] = tuple((int(x) for x in rest.split(",")))
        else:
            state["current"] = int(rest)
        state["current"] = int(rest.split(' ')[0])

    # value case, pair of int values, with annotation
    elif re.match("^[0-9]+[,][0-9]+\s+[a-zA-Z]+$", rest):
        state["type"] = "value"
        rest = rest.split(' ')[0]
        state["current"] = tuple((int(x) for x in rest.split(",")))

    else:
        logging.warning("unhanded format for '{}', giving up".format(rest))
        return (line, {})

    return name, state



@@ 70,6 95,12 @@ def get_state():
            continue

        key, val = parse_line(line)

        if len(val.keys()) == 0:
            logging.warning(
                "discarding key '{}' due to empty value".format(key))
            continue

        control[key] = val

    return control