From 0bbdbd0b1a7e98dd89112a74110dbc6ee8112021 Mon Sep 17 00:00:00 2001
From: Phillip Berndt <phillip.berndt@googlemail.com>
Date: Sat, 11 Apr 2020 14:11:31 +0200
Subject: [PATCH] Sort approximate matches in detected profiles by quality of
 match

See #189
---
 README.md    |  1 +
 autorandr.py | 42 +++++++++++++++++++++++++++++++++++-------
 2 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index 9f92164..ab3d23d 100644
--- a/README.md
+++ b/README.md
@@ -215,6 +215,7 @@ profiles matching multiple (or any) monitors.
 ## Changelog
 
 **autorandr 1.10 (dev)**
+* *2020-04-11* Sort approximate matches in detected profiles by quality of match
 * *2019-12-31* Fix output positioning if the top-left output is not the first
 * *2019-12-31* Accept negative gamma values (and interpret them as 0)
 * *2019-12-31* Prefer the X11 launcher over systemd/udev configuration
diff --git a/autorandr.py b/autorandr.py
index 275fd15..295d01a 100755
--- a/autorandr.py
+++ b/autorandr.py
@@ -26,7 +26,6 @@ from __future__ import print_function
 
 import binascii
 import copy
-import fnmatch
 import getopt
 import hashlib
 import os
@@ -428,9 +427,9 @@ class XrandrOutput(object):
             if len(self.edid) != 32 and len(other.edid) == 32 and not self.edid.startswith(XrandrOutput.EDID_UNAVAILABLE):
                 return hashlib.md5(binascii.unhexlify(self.edid)).hexdigest() == other.edid
             if "*" in self.edid:
-                return fnmatch.fnmatch(other.edid, self.edid)
+                return match_asterisk(self.edid, other.edid) > 0
             elif "*" in other.edid:
-                return fnmatch.fnmatch(self.edid, other.edid)
+                return match_asterisk(other.edid, self.edid) > 0
         return self.edid == other.edid
 
     def __ne__(self, other):
@@ -573,8 +572,29 @@ def get_symlinks(profile_path):
     return symlinks
 
 
+def match_asterisk(pattern, data):
+    """Match data against a pattern
+
+    The difference to fnmatch is that this function only accepts patterns with a single
+    asterisk and that it returns a "closeness" number, which is larger the better the match.
+    Zero indicates no match at all.
+    """
+    if "*" not in pattern:
+        return 1 if pattern == data else 0
+    parts = pattern.split("*")
+    if len(parts) > 2:
+        raise ValueError("Only patterns with a single asterisk are supported, %s is invalid" % pattern)
+    if not data.startswith(parts[0]):
+        return 0
+    if not data.endswith(parts[1]):
+        return 0
+    matched = len(pattern)
+    total = len(data) + 1
+    return matched * 1. / total
+
+
 def find_profiles(current_config, profiles):
-    "Find profiles matching the currently connected outputs"
+    "Find profiles matching the currently connected outputs, sorting asterisk matches to the back"
     detected_profiles = []
     for profile_name, profile in profiles.items():
         config = profile["config"]
@@ -588,7 +608,9 @@ def find_profiles(current_config, profiles):
         if not matches or any((name not in config.keys() for name in current_config.keys() if current_config[name].edid)):
             continue
         if matches:
-            detected_profiles.append(profile_name)
+            closeness = max(match_asterisk(output.edid, current_config[name].edid), match_asterisk(current_config[name].edid, output.edid))
+            detected_profiles.append((closeness, profile_name))
+    detected_profiles = [o[1] for o in sorted(detected_profiles, key=lambda x: -x[0])]
     return detected_profiles
 
 
@@ -1337,6 +1359,7 @@ def main(argv):
             "CURRENT_PROFILES": ":".join(current_profiles)
         }
 
+        best_index = 9999
         for profile_name in profiles.keys():
             if profile_blocked(os.path.join(profile_path, profile_name), block_script_metadata):
                 if "--current" not in options and "--detected" not in options:
@@ -1344,9 +1367,14 @@ def main(argv):
                 continue
             props = []
             if profile_name in detected_profiles:
-                props.append("(detected)")
-                if ("-c" in options or "--change" in options) and not load_profile:
+                if len(detected_profiles) == 1:
+                    props.append("(detected)")
+                else:
+                    index = detected_profiles.index(profile_name) + 1
+                    props.append("(detected) (%d%s match)" % (index, ["st", "nd", "rd"][index - 1] if index < 4 else "th"))
+                if ("-c" in options or "--change" in options) and index < best_index:
                     load_profile = profile_name
+                    best_index = index
             elif "--detected" in options:
                 continue
             if profile_name in current_profiles:
-- 
2.39.5