diff --git a/README.md b/README.md
index f3862d7..9d6ccfa 100644
--- a/README.md
+++ b/README.md
@@ -11,11 +11,17 @@ It has the following features:
 - filter passband can be set from GUI
 - it extensively uses HTML5 features like WebSocket, Web Audio API, and Canvas
 - it works in Google Chrome, Chromium and Mozilla Firefox
-- currently supports RTL-SDR, HackRF, SDRplay, AirSpy, LimeSDR, PlutoSDR
+- supports a wide range of [SDR hardware](https://github.com/jketterl/openwebrx/wiki/Supported-Hardware#sdr-devices)
 - Multiple SDR devices can be used simultaneously
 - [digiham](https://github.com/jketterl/digiham) based demodularors (DMR, YSF, Pocsag)
 - [dsd](https://github.com/f4exb/dsdcc) based demodulators (D-Star, NXDN)
-- [wsjt-x](https://physics.princeton.edu/pulsar/k1jt/wsjtx.html) based demodulators (FT8, FT4, WSPR, JT65, JT9)
+- [wsjt-x](https://physics.princeton.edu/pulsar/k1jt/wsjtx.html) based demodulators (FT8, FT4, WSPR, JT65, JT9, FST4,
+  FST4W)
+- [direwolf](https://github.com/wb2osz/direwolf) based demodulation of APRS packets
+- [JS8Call](http://js8call.com/) support
+- [DRM](https://github.com/jketterl/openwebrx/wiki/DRM-demodulator-notes) support
+- [FreeDV](https://github.com/jketterl/openwebrx/wiki/FreeDV-demodulator-notes) support
+- M17 support based on [m17-cxx-demod](https://github.com/mobilinkd/m17-cxx-demod)
 
 ## Setup
 
@@ -35,6 +41,9 @@ If you have trouble setting up or configuring your receiver, you have some great
 you just generally want to have some OpenWebRX-related chat, come visit us over on
 [our groups.io group](https://groups.io/g/openwebrx).
 
+If you want to hang out, chat, or get in touch directly with the developers, receiver operators or users, feel free to
+drop by in [our Discord server](https://discord.gg/gnE9hPz).
+
 ## Usage tips
 
 You can zoom the waterfall display by the mouse wheel. You can also drag the waterfall to pan across it.
diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css
index 5ede36a..3195997 100644
--- a/htdocs/css/openwebrx.css
+++ b/htdocs/css/openwebrx.css
@@ -927,6 +927,7 @@ img.openwebrx-mirror-img
 
     display: flex;
     flex-direction: column;
+    position: relative;
 }
 
 .openwebrx-meta-slot > * {
@@ -944,8 +945,6 @@ img.openwebrx-mirror-img
     display: block;
     content: "";
     background-image: url("../gfx/openwebrx-mute.png");
-    width:100%;
-    height:133px;
     background-position: center;
     background-repeat: no-repeat;
     cursor: pointer;
diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js
index 597475b..dc8995e 100644
--- a/htdocs/lib/MessagePanel.js
+++ b/htdocs/lib/MessagePanel.js
@@ -186,7 +186,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
         'style="' + stylesToString(styles) + '"'
     ].join(' ');
     if (msg.lat && msg.lon) {
-        link = '' + overlay + '';
+        link = '' + overlay + '';
     } else {
         link = '
' + overlay + '
'
     }
diff --git a/htdocs/lib/MetaPanel.js b/htdocs/lib/MetaPanel.js
index 849cb21..8a7c85c 100644
--- a/htdocs/lib/MetaPanel.js
+++ b/htdocs/lib/MetaPanel.js
@@ -162,7 +162,7 @@ YsfMetaPanel.prototype.setLocation = function(lat, lon, callsign) {
     this.hasLocation = hasLocation; this.callsign = callsign;
     var html = '';
     if (hasLocation) {
-        html = '';
+        html = '';
     }
     this.el.find('.openwebrx-ysf-source .location').html(html);
 };
diff --git a/htdocs/map.js b/htdocs/map.js
index fd3e740..afc3448 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -9,7 +9,7 @@
     });
 
     var expectedCallsign;
-    if (query.callsign) expectedCallsign = query.callsign;
+    if (query.callsign) expectedCallsign = decodeURIComponent(query.callsign);
     var expectedLocator;
     if (query.locator) expectedLocator = query.locator;
 
@@ -137,14 +137,13 @@
                     marker.band = update.band;
                     marker.comment = update.location.comment;
 
-                    // TODO the trim should happen on the server side
-                    if (expectedCallsign && expectedCallsign == update.callsign.trim()) {
+                    if (expectedCallsign && expectedCallsign == update.callsign) {
                         map.panTo(pos);
                         showMarkerInfoWindow(update.callsign, pos);
                         expectedCallsign = false;
                     }
 
-                    if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign.trim()) {
+                    if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
                         showMarkerInfoWindow(infowindow.callsign, pos);
                     }
                 break;
@@ -319,6 +318,8 @@
                 delete infowindow.callsign;
             });
         }
+        delete infowindow.locator;
+        delete infowindow.callsign;
         return infowindow;
     }
 
diff --git a/owrx/meta.py b/owrx/meta.py
index c7e9c1d..85de1ba 100644
--- a/owrx/meta.py
+++ b/owrx/meta.py
@@ -24,7 +24,7 @@ class DmrCache(object):
         self.cacheTimeout = timedelta(seconds=86400)
 
     def isValid(self, key):
-        if not key in self.cache:
+        if key not in self.cache:
             return False
         entry = self.cache[key]
         return entry["timestamp"] + self.cacheTimeout > datetime.now()
@@ -55,20 +55,20 @@ class DmrMetaEnricher(object):
 
     def enrich(self, meta):
         if not Config.get()["digital_voice_dmr_id_lookup"]:
-            return None
-        if not "source" in meta:
-            return None
+            return meta
+        if "source" not in meta:
+            return meta
         id = meta["source"]
         cache = DmrCache.getSharedInstance()
         if not cache.isValid(id):
-            if not id in self.threads:
+            if id not in self.threads:
                 self.threads[id] = threading.Thread(target=self.downloadRadioIdData, args=[id], daemon=True)
                 self.threads[id].start()
-            return None
+            return meta
         data = cache.get(id)
         if "count" in data and data["count"] > 0 and "results" in data:
-            return data["results"][0]
-        return None
+            meta["additional"] = data["results"][0]
+        return meta
 
 
 class YsfMetaEnricher(object):
@@ -76,11 +76,17 @@ class YsfMetaEnricher(object):
         self.parser = parser
 
     def enrich(self, meta):
+        for key in ["source", "up", "down", "target"]:
+            if key in meta:
+                meta[key] = meta[key].strip()
+        for key in ["lat", "lon"]:
+            if key in meta:
+                meta[key] = float(meta[key])
         if "source" in meta and "lat" in meta and "lon" in meta:
             # TODO parsing the float values should probably happen earlier
-            loc = LatLngLocation(float(meta["lat"]), float(meta["lon"]))
+            loc = LatLngLocation(meta["lat"], meta["lon"])
             Map.getSharedInstance().updateLocation(meta["source"], loc, "YSF", self.parser.getBand())
-        return None
+        return meta
 
 
 class MetaParser(Parser):
@@ -95,7 +101,5 @@ class MetaParser(Parser):
         if "protocol" in meta:
             protocol = meta["protocol"]
             if protocol in self.enrichers:
-                additional_data = self.enrichers[protocol].enrich(meta)
-                if additional_data is not None:
-                    meta["additional"] = additional_data
+                meta = self.enrichers[protocol].enrich(meta)
         self.handler.write_metadata(meta)