Skip to content

Commit 93e5266

Browse files
committed
More useful error messages
1 parent 89bf1ac commit 93e5266

File tree

1 file changed

+55
-28
lines changed

1 file changed

+55
-28
lines changed

autorandr.py

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,36 @@
7474
The following virtual configurations are available:
7575
""".strip()
7676

77+
class AutorandrException(Exception):
78+
def __init__(self, message, original_exception=None, report_bug=False):
79+
self.message = message
80+
self.report_bug = report_bug
81+
if original_exception:
82+
self.original_exception = original_exception
83+
trace = sys.exc_info()[2]
84+
while trace.tb_next:
85+
trace = trace.tb_next
86+
self.line = trace.tb_lineno
87+
else:
88+
try:
89+
import inspect
90+
self.line = inspect.currentframe().f_back.f_lineno
91+
except:
92+
self.line = None
93+
self.original_exception = None
94+
95+
def __str__(self):
96+
retval = [ self.message ]
97+
if self.line:
98+
retval.append(" (line %d)" % self.line)
99+
if self.original_exception:
100+
retval.append(":\n " % self.line)
101+
retval.append(str(self.original_exception).replace("\n", "\n "))
102+
if self.report_bug:
103+
retval.append("\nThis appears to be a bug. Please help improving autorandr by reporting it upstream."
104+
"\nPlease attach the output of `xrandr --verbose` to your bug report if appropriate.")
105+
return "".join(retval)
106+
77107
class XrandrOutput(object):
78108
"Represents an XRandR output"
79109

@@ -199,22 +229,22 @@ def from_xrandr_output(cls, xrandr_output):
199229
xrandr_output = xrandr_output.replace("\r\n", "\n")
200230
match_object = re.search(XrandrOutput.XRANDR_OUTPUT_REGEXP, xrandr_output)
201231
except:
202-
raise RuntimeError("Parsing XRandR output failed, there is an error in the regular expression.")
232+
raise AutorandrException("Parsing XRandR output failed, there is an error in the regular expression.", report_bug = True)
203233
if not match_object:
204234
debug = debug_regexp(XrandrOutput.XRANDR_OUTPUT_REGEXP, xrandr_output)
205-
raise RuntimeError("Parsing XRandR output failed, the regular expression did not match: %s" % debug)
235+
raise AutorandrException("Parsing XRandR output failed, the regular expression did not match: %s" % debug, report_bug = True)
206236
remainder = xrandr_output[len(match_object.group(0)):]
207237
if remainder:
208-
raise RuntimeError(("Parsing XRandR output failed, %d bytes left unmatched after regular expression, "
209-
"starting at byte %d with ..'%s'.") % (len(remainder), len(match_object.group(0)), remainder[:10]))
238+
raise AutorandrException(("Parsing XRandR output failed, %d bytes left unmatched after regular expression, "
239+
"starting at byte %d with ..'%s'.") % (len(remainder), len(match_object.group(0)), remainder[:10]), report_bug=True)
210240

211241
match = match_object.groupdict()
212242

213243
modes = []
214244
if match["modes"]:
215245
modes = [ x.groupdict() for x in re.finditer(XrandrOutput.XRANDR_OUTPUT_MODES_REGEXP, match["modes"]) ]
216246
if not modes:
217-
raise RuntimeError("Parsing XRandR output failed, couldn't find any display modes")
247+
raise AutorandrException("Parsing XRandR output failed, couldn't find any display modes", report_bug=True)
218248

219249
options = {}
220250
if not match["connected"]:
@@ -285,8 +315,7 @@ def from_config_file(cls, edid_map, configuration):
285315
if fuzzy_output in fuzzy_edid_map:
286316
edid = edid_map[list(edid_map.keys())[fuzzy_edid_map.index(fuzzy_output)]]
287317
elif "off" not in options:
288-
raise RuntimeError("Failed to find an EDID for output `%s' in setup file, required as `%s' is not off in config file."
289-
% (options["output"], options["output"]))
318+
raise AutorandrException("Failed to find an EDID for output `%s' in setup file, required as `%s' is not off in config file." % (options["output"], options["output"]))
290319
output = options["output"]
291320
del options["output"]
292321

@@ -332,21 +361,21 @@ def debug_regexp(pattern, string):
332361
string[partial_length:partial_length+10]))
333362
except ImportError:
334363
pass
335-
return "Debug information available if `regex' module is installed."
364+
return "Debug information would be available if the `regex' module was installed."
336365

337366
def parse_xrandr_output():
338367
"Parse the output of `xrandr --verbose' into a list of outputs"
339368
xrandr_output = os.popen("xrandr -q --verbose").read()
340369
if not xrandr_output:
341-
raise RuntimeError("Failed to run xrandr")
370+
raise AutorandrException("Failed to run xrandr")
342371

343372
# We are not interested in screens
344373
xrandr_output = re.sub("(?m)^Screen [0-9].+", "", xrandr_output).strip()
345374

346375
# Split at output boundaries and instanciate an XrandrOutput per output
347376
split_xrandr_output = re.split("(?m)^([^ ]+ (?:(?:dis)?connected|unknown connection).*)$", xrandr_output)
348377
if len(split_xrandr_output) < 2:
349-
raise RuntimeError("No output boundaries found")
378+
raise AutorandrException("No output boundaries found", report_bug=True)
350379
outputs = OrderedDict()
351380
modes = OrderedDict()
352381
for i in range(1, len(split_xrandr_output), 2):
@@ -483,7 +512,7 @@ def apply_configuration(new_configuration, current_configuration, dry_run=False)
483512
if auxiliary_changes_pre:
484513
argv = base_argv + list(chain.from_iterable(auxiliary_changes_pre))
485514
if subprocess.call(argv) != 0:
486-
raise RuntimeError("Command failed: %s" % " ".join(argv))
515+
raise AutorandrException("Command failed: %s" % " ".join(argv))
487516

488517
# Disable unused outputs, but make sure that there always is at least one active screen
489518
disable_keep = 0 if remain_active_count else 1
@@ -508,7 +537,7 @@ def apply_configuration(new_configuration, current_configuration, dry_run=False)
508537
for index in range(0, len(operations), 2):
509538
argv = base_argv + list(chain.from_iterable(operations[index:index+2]))
510539
if subprocess.call(argv) != 0:
511-
raise RuntimeError("Command failed: %s" % " ".join(argv))
540+
raise AutorandrException("Command failed: %s" % " ".join(argv))
512541

513542
def add_unused_outputs(source_configuration, target_configuration):
514543
"Add outputs that are missing in target to target, in 'off' state"
@@ -596,14 +625,9 @@ def main(argv):
596625
# Sort by descending mtime
597626
profiles = OrderedDict(sorted(profiles.items(), key=lambda x: -x[1]["config-mtime"]))
598627
except Exception as e:
599-
print("Failed to load profiles:\n%s" % str(e), file=sys.stderr)
600-
sys.exit(1)
628+
raise AutorandrException("Failed to load profiles", e)
601629

602-
try:
603-
config, modes = parse_xrandr_output()
604-
except Exception as e:
605-
print("Failed to parse current configuration from XRandR:\n%s" % str(e), file=sys.stderr)
606-
sys.exit(1)
630+
config, modes = parse_xrandr_output()
607631

608632
if "--fingerprint" in options:
609633
output_setup(config, sys.stdout)
@@ -617,13 +641,11 @@ def main(argv):
617641
options["--save"] = options["-s"]
618642
if "--save" in options:
619643
if options["--save"] in ( x[0] for x in virtual_profiles ):
620-
print("Cannot save current configuration as profile '%s':\nThis configuration name is a reserved virtual configuration." % options["--save"])
621-
sys.exit(1)
644+
raise AutorandrException("Cannot save current configuration as profile '%s':\nThis configuration name is a reserved virtual configuration." % options["--save"])
622645
try:
623646
save_configuration(os.path.join(profile_path, options["--save"]), config)
624647
except Exception as e:
625-
print("Failed to save current configuration as profile '%s':\n%s" % (options["--save"], str(e)), file=sys.stderr)
626-
sys.exit(1)
648+
raise AutorandrException("Failed to save current configuration as profile '%s'" % (options["--save"],), e)
627649
print("Saved current configuration as profile '%s'" % options["--save"])
628650
sys.exit(0)
629651

@@ -664,8 +686,7 @@ def main(argv):
664686
load_config = profile["config"]
665687
scripts_path = profile["path"]
666688
except KeyError:
667-
print("Failed to load profile '%s':\nProfile not found" % load_profile, file=sys.stderr)
668-
sys.exit(1)
689+
raise AutorandrException("Failed to load profile '%s': Profile not found" % load_profile)
669690
if load_profile in detected_profiles and detected_profiles[0] != load_profile:
670691
update_mtime(os.path.join(scripts_path, "config"))
671692
add_unused_outputs(config, load_config)
@@ -682,14 +703,20 @@ def main(argv):
682703
apply_configuration(load_config, config, False)
683704
exec_scripts(scripts_path, "postswitch")
684705
except Exception as e:
685-
print("Failed to apply profile '%s':\n%s" % (load_profile, str(e)), file=sys.stderr)
686-
sys.exit(1)
706+
raise AutorandrException("Failed to apply profile '%s'" % load_profile, e, True)
687707

688708
sys.exit(0)
689709

690710
if __name__ == '__main__':
691711
try:
692712
main(sys.argv)
713+
except AutorandrException as e:
714+
print(file=sys.stderr)
715+
print(e, file=sys.stderr)
716+
sys.exit(1)
693717
except Exception as e:
694-
print("General failure. Please report this as a bug:\n%s" % (str(e),), file=sys.stderr)
718+
trace = sys.exc_info()[2]
719+
while trace.tb_next:
720+
trace = trace.tb_next
721+
print("\nUnhandled exception in line %d. Please report this as a bug:\n %s" % (trace.tb_lineno, "\n ".join(str(e).split("\n")),), file=sys.stderr)
695722
sys.exit(1)

0 commit comments

Comments
 (0)