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