diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec7b56..1485e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). --- +## [Unreleased] + +--- + +## [1.6.2.4] - 2026-08-22 + +### 🐛 Behoben + +#### 🔢 Kanal-Anzeige: Backlog zeigte „Kanal N" statt Kanalname +- Nachrichten aus dem Backlog (DB) zeigten teils „Kanal 0"/„Kanal 1" statt „Mesh Hessen" o.ä., während Live-Nachrichten den Namen zeigten. Ursache: der Backlog nutzt den **gespeicherten** Kanalnamen — der stand als „Kanal N" fest, wenn die Nachricht empfangen/gespeichert wurde, **bevor** die Kanäle vom Gerät kamen. Jetzt wird `ChannelName` einheitlich aus dem **aktuellen Kanal-Index** aufgelöst (observable) und beim Eintreffen von Kanälen sowie nach dem Backlog-Laden neu berechnet. + +#### ↩️ Reply wechselt jetzt auf den Kanal der Nachricht +- „Antworten" auf eine Kanalnachricht schaltete den aktiven Kanal nicht mehr um — die Antwort ging auf dem gerade gewählten Kanal raus. Jetzt wird beim Reply der aktive Kanal auf den umgestellt, auf dem die Nachricht empfangen wurde (Live **und** Backlog, da Live-Nachrichten jetzt auch den `ChannelIndex` tragen). + +#### 🖱️ Debug-Log Auto-Scroll sprang nach oben +- Bei aktivem Auto-Scroll sprang das Debug-Log nach oben statt dem Ende zu folgen: Ab 10000 Zeilen wurde `DebugLogTextBox.Text` neu gesetzt (Trim), was die Scroll-Position auf den Anfang zurücksetzt — und das passierte **nach** `ScrollToEnd()`. Bei viel Traffic (ständiges Trimmen) wurde die Ansicht so bei jeder Zeile nach oben gerissen. Jetzt wird erst getrimmt, dann als letzte Aktion ans Ende gescrollt. + +--- + ## [1.6.2.3] - 2026-08-16 ### ✨ Hinzugefügt / 🔧 Geändert diff --git a/MeshhessenClient/MainWindow.Kiosk.cs b/MeshhessenClient/MainWindow.Kiosk.cs index aa596aa..886b0f7 100644 --- a/MeshhessenClient/MainWindow.Kiosk.cs +++ b/MeshhessenClient/MainWindow.Kiosk.cs @@ -508,7 +508,8 @@ private void LoadMessagesFromDbAsync() _dbOldestTimestamp = entry.Timestamp; } - // Rebuild visible list and scroll to newest + // Resolve stored "Kanal N" names against the current channel list, then rebuild. + RefreshChannelNames(); RebuildVisibleMessages(); if (_messages.Count > 0) MessageListView.ScrollIntoView(_messages[^1]); @@ -560,6 +561,7 @@ private void LazyLoadOlderMessages() _dbOldestTimestamp = entry.Timestamp; } + RefreshChannelNames(); RebuildVisibleMessages(); // Restore scroll position to the item that was previously first diff --git a/MeshhessenClient/MainWindow.xaml.cs b/MeshhessenClient/MainWindow.xaml.cs index d38ef29..b47ee0d 100644 --- a/MeshhessenClient/MainWindow.xaml.cs +++ b/MeshhessenClient/MainWindow.xaml.cs @@ -589,14 +589,16 @@ private void OnLogMessageReceived(object? sender, string logMessage) // on, so the user can scroll up and read without being yanked back down. if (AutoScrollLogCheckBox?.IsChecked != false) { - DebugLogTextBox.ScrollToEnd(); - - // Begrenze auf maximal 10000 Zeilen + // Trim FIRST: reassigning .Text resets the scroll position to the top, so it + // must happen before ScrollToEnd — otherwise every append past the limit yanks + // the view up instead of following the tail. var lines = DebugLogTextBox.Text.Split('\n'); if (lines.Length > 10000) { DebugLogTextBox.Text = string.Join('\n', lines.Skip(lines.Length - 10000)); } + + DebugLogTextBox.ScrollToEnd(); } }); } @@ -1839,11 +1841,12 @@ private void OnMessageReceived(object? sender, MessageItem message) return; // Nicht anzeigen } - // Setze ChannelName basierend auf Channel Index + // Setze ChannelName + ChannelIndex basierend auf Channel Index (einheitlich + // aufgelöst wie beim Backlog, damit die Anzeige konsistent ist). if (uint.TryParse(message.Channel, out uint channelIndex)) { - var channel = _channels.FirstOrDefault(c => c.Index == channelIndex); - message.ChannelName = channel?.Name ?? $"Kanal {channelIndex}"; + message.ChannelIndex = channelIndex; + message.ChannelName = ResolveChannelName(channelIndex); } else { @@ -2046,6 +2049,9 @@ private void OnChannelInfoReceived(object? sender, ChannelInfo channel) } _channels.Insert(insertAt, channel); + // A newly-arrived channel lets messages shown as "Kanal N" resolve to the real name. + RefreshChannelNames(); + // Aktiviere Kanal-Auswahl wenn Kanäle vorhanden sind if (_channels.Count > 0 && !ActiveChannelComboBox.IsEnabled) { @@ -4556,10 +4562,37 @@ private void DmMessageContextMenu_React_Click(object sender, RoutedEventArgs e) // Forwarded from DirectMessagesWindow - handled there via EmojiPickerRequested } + /// Resolve a channel index to its display name from the current channel list, + /// falling back to "Kanal N" when the channel isn't (yet) known. + private string ResolveChannelName(uint index) + { + var ch = _channels.FirstOrDefault(c => c.Index == index); + return !string.IsNullOrEmpty(ch?.Name) ? ch!.Name : $"Kanal {index}"; + } + + /// Re-resolve channel display names for all messages from the current channel + /// list. Backlog (DB) messages carry the name stored at receive time — often "Kanal N" + /// if the channels hadn't arrived yet — and channels can arrive after messages are shown. + private void RefreshChannelNames() + { + foreach (var m in _allMessages) + if (uint.TryParse(m.Channel, out var idx)) + m.ChannelName = ResolveChannelName(idx); + } + private void MessageContextMenu_Reply_Click(object sender, RoutedEventArgs e) { if (MessageListView.SelectedItem is not MessageItem msg) return; _replyToMessage = msg; + + // Switch the active channel to the one the message arrived on, so the reply goes out + // on the right channel (channel messages carry an index 0-7 in msg.Channel). + if (uint.TryParse(msg.Channel, out var replyChanIdx)) + { + var ch = _channels.FirstOrDefault(c => c.Index == replyChanIdx); + if (ch != null) ActiveChannelComboBox.SelectedItem = ch; + } + var preview = msg.Message?.Length > 60 ? msg.Message[..60] + "…" : msg.Message ?? string.Empty; ReplyIndicatorText.Text = string.Format(Loc("StrReplyingTo"), msg.From, preview); ReplyIndicatorPanel.Visibility = Visibility.Visible; diff --git a/MeshhessenClient/MeshhessenClient.csproj b/MeshhessenClient/MeshhessenClient.csproj index ae16904..6ac6af5 100644 --- a/MeshhessenClient/MeshhessenClient.csproj +++ b/MeshhessenClient/MeshhessenClient.csproj @@ -18,9 +18,9 @@ ..\public\ MeshhessenClient - 1.6.2.3 - 1.6.2.3 - 1.6.2.3 + 1.6.2.4 + 1.6.2.4 + 1.6.2.4 Meshtastic Community Meshhessen Client Offline-fähiger Windows Client für Meshtastic Geräte diff --git a/MeshhessenClient/Models/MessageItem.cs b/MeshhessenClient/Models/MessageItem.cs index b74ecdd..5b68c73 100644 --- a/MeshhessenClient/Models/MessageItem.cs +++ b/MeshhessenClient/Models/MessageItem.cs @@ -42,7 +42,15 @@ public string Message public string Channel { get; set; } = string.Empty; // Channel Index (legacy, display string) public uint ChannelIndex { get; set; } // raw channel index the packet arrived on - public string ChannelName { get; set; } = string.Empty; // Channel Name for display + + // Observable: resolved from the current channel list, so a message shown before the + // channels arrived (e.g. DB backlog on connect) updates from "Kanal N" to the real name. + private string _channelName = string.Empty; + public string ChannelName + { + get => _channelName; + set { _channelName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChannelName))); } + } public uint FromId { get; set; } public uint ToId { get; set; } public uint Id { get; set; } // Packet ID (for reactions) diff --git a/README.md b/README.md index 408c50e..b2fa558 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,16 @@ dotnet publish MeshhessenClient/MeshhessenClient.csproj -c Release -r win-x64 -- EXE liegt danach unter `public\MeshhessenClient.exe`. Alternativ: `build.bat` ausführen. +### Forks & Weiterverwendung + +Der Quellcode darf im Rahmen der Lizenz geforkt und angepasst werden — wir freuen uns über abgeleitete Projekte. + +> ⚠️ **Der Meshhessen-Tile-Server ist davon ausgenommen.** Die Karten-Infrastruktur (`tile.meshhessenclient.de` und die zugehörigen Vektor-/Raster-Endpunkte) wird **ausschließlich für den offiziellen Meshhessen Client** bereitgestellt und aus Spenden der Community finanziert. Forks, abgeleitete oder umgebaute Clients (auch für andere Mesh-Protokolle wie MeshCore) dürfen diese Server **nicht** nutzen und müssen **eigene Tile-Infrastruktur betreiben**. +> +> Der Client bringt dafür alles mit: der Karten-Modus **„Online – eigener Tile-Server"** in den Einstellungen erlaubt beliebige eigene Tile-URLs. Ein einfacher Tile-Cache (z. B. eigener OSM/OpenTopo-Proxy) ist schnell und günstig aufgesetzt. +> +> Unautorisierte Zugriffe auf die Meshhessen-Server werden technisch unterbunden. + ## 🙏 Credits @@ -703,6 +713,16 @@ dotnet publish MeshhessenClient/MeshhessenClient.csproj -c Release -r win-x64 -- The EXE will be at `public\MeshhessenClient.exe`. Alternatively, run `build.bat`. +### Forks & Reuse + +You are welcome to fork and adapt the source code within the terms of the license — we're happy to see derivative projects. + +> ⚠️ **The Meshhessen tile server is not part of that.** The map infrastructure (`tile.meshhessenclient.de` and the associated vector/raster endpoints) is provided **exclusively for the official Meshhessen Client** and is funded by community donations. Forks, derivative or repurposed clients (including ports to other mesh protocols such as MeshCore) **may not** use these servers and must **run their own tile infrastructure**. +> +> The client ships with everything you need for that: the **"Online – custom tile server"** map mode in settings accepts any tile URLs you like. A simple tile cache (e.g. your own OSM/OpenTopo proxy) is quick and cheap to set up. +> +> Unauthorized access to the Meshhessen servers is blocked at the technical level. + ## 🙏 Credits