Einstellungen-Dialog: Verzoegerung fuer das Nach-vorn-Holen von Firefox
Bei einem eingehenden Ruf wartet SwyxTray jetzt eine konfigurierbare Zeit (Standard 1000 ms), bevor der Firefox-Tab aktiviert und das Fenster in den Vordergrund geholt wird. Der Wert steht als IncomingCallFocusDelayMs in der websocket.json und laesst sich ueber den neuen Menuepunkt Einstellungen im Tray-Menue aendern; die Aenderung wirkt sofort und wird zurueckgeschrieben. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
namespace SwyxTray;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kleiner modaler Dialog hinter dem Menuepunkt "Einstellungen": stellt die
|
||||||
|
/// Wartezeit ein, bis Firefox bei einem eingehenden Ruf nach vorn geholt wird
|
||||||
|
/// (<see cref="Web.WebSocketConfig.IncomingCallFocusDelayMs"/>). Der Aufrufer
|
||||||
|
/// liest nach OK den Wert aus <see cref="FocusDelayMs"/> und speichert selbst.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SettingsDialog : Form
|
||||||
|
{
|
||||||
|
private const int MaxDelayMs = 60_000;
|
||||||
|
|
||||||
|
private readonly NumericUpDown _delayInput;
|
||||||
|
|
||||||
|
/// <summary>Die eingestellte Verzoegerung in Millisekunden.</summary>
|
||||||
|
public int FocusDelayMs => (int)_delayInput.Value;
|
||||||
|
|
||||||
|
public SettingsDialog(int focusDelayMs)
|
||||||
|
{
|
||||||
|
Text = "SwyxTray — Einstellungen";
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
MaximizeBox = false;
|
||||||
|
MinimizeBox = false;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(570, 174);
|
||||||
|
|
||||||
|
var label = new Label
|
||||||
|
{
|
||||||
|
Text = "Verzoegerung, bis Firefox bei einem eingehenden Ruf\n" +
|
||||||
|
"nach vorn geholt wird (Millisekunden):",
|
||||||
|
Location = new Point(18, 18),
|
||||||
|
AutoSize = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_delayInput = new NumericUpDown
|
||||||
|
{
|
||||||
|
Minimum = 0,
|
||||||
|
Maximum = MaxDelayMs,
|
||||||
|
Increment = 100,
|
||||||
|
ThousandsSeparator = true,
|
||||||
|
Location = new Point(18, 81),
|
||||||
|
Width = 150,
|
||||||
|
Value = Math.Clamp(focusDelayMs, 0, MaxDelayMs)
|
||||||
|
};
|
||||||
|
|
||||||
|
var ok = new Button
|
||||||
|
{
|
||||||
|
Text = "OK",
|
||||||
|
DialogResult = DialogResult.OK,
|
||||||
|
Location = new Point(273, 123),
|
||||||
|
Size = new Size(135, 35)
|
||||||
|
};
|
||||||
|
|
||||||
|
var cancel = new Button
|
||||||
|
{
|
||||||
|
Text = "Abbrechen",
|
||||||
|
DialogResult = DialogResult.Cancel,
|
||||||
|
Location = new Point(417, 123),
|
||||||
|
Size = new Size(135, 35)
|
||||||
|
};
|
||||||
|
|
||||||
|
AcceptButton = ok;
|
||||||
|
CancelButton = cancel;
|
||||||
|
Controls.AddRange(new Control[] { label, _delayInput, ok, cancel });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,8 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
|||||||
private readonly ToolStripMenuItem _copyEndpointItem;
|
private readonly ToolStripMenuItem _copyEndpointItem;
|
||||||
|
|
||||||
private LocalWebSocketServer? _server;
|
private LocalWebSocketServer? _server;
|
||||||
|
private WebSocketConfig? _webSocketConfig;
|
||||||
|
private bool _settingsDialogOpen;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public TrayApplicationContext()
|
public TrayApplicationContext()
|
||||||
@@ -69,6 +71,7 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
|||||||
new ToolStripSeparator(),
|
new ToolStripSeparator(),
|
||||||
new ToolStripMenuItem("Neu verbinden", null, (_, _) => Reconnect()),
|
new ToolStripMenuItem("Neu verbinden", null, (_, _) => Reconnect()),
|
||||||
new ToolStripMenuItem("Protokoll oeffnen", null, (_, _) => OpenLog()),
|
new ToolStripMenuItem("Protokoll oeffnen", null, (_, _) => OpenLog()),
|
||||||
|
new ToolStripMenuItem("Einstellungen", null, (_, _) => OpenSettings()),
|
||||||
new ToolStripSeparator(),
|
new ToolStripSeparator(),
|
||||||
new ToolStripMenuItem("Beenden", null, (_, _) => ExitApplication())
|
new ToolStripMenuItem("Beenden", null, (_, _) => ExitApplication())
|
||||||
});
|
});
|
||||||
@@ -129,6 +132,8 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_webSocketConfig = config;
|
||||||
|
|
||||||
if (UiDispatcher.Capture() is not { } dispatcher)
|
if (UiDispatcher.Capture() is not { } dispatcher)
|
||||||
{
|
{
|
||||||
Log.Error("Kein UI-Kontext vorhanden — der WebSocket-Zugang bleibt aus.");
|
Log.Error("Kein UI-Kontext vorhanden — der WebSocket-Zugang bleibt aus.");
|
||||||
@@ -308,6 +313,54 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
|||||||
_notifyIcon.ShowBalloonTip(5_000, s.StatusText, body, ToolTipIcon.Info);
|
_notifyIcon.ShowBalloonTip(5_000, s.StatusText, body, ToolTipIcon.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeigt den Einstellungen-Dialog. Der laufende Server benutzt dieselbe
|
||||||
|
/// Konfigurations-Instanz, deshalb wirkt eine Aenderung sofort — ohne
|
||||||
|
/// Neustart. Gespeichert wird in die websocket.json, damit der Wert den
|
||||||
|
/// naechsten Start ueberlebt.
|
||||||
|
/// </summary>
|
||||||
|
private void OpenSettings()
|
||||||
|
{
|
||||||
|
// ShowDialog laesst das Tray-Menue weiter bedienbar — ein zweiter
|
||||||
|
// Klick soll keinen zweiten Dialog oeffnen.
|
||||||
|
if (_settingsDialogOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = _webSocketConfig ?? WebSocketConfig.Load();
|
||||||
|
if (config is null)
|
||||||
|
{
|
||||||
|
_notifyIcon.ShowBalloonTip(5_000, "Einstellungen",
|
||||||
|
"Die Konfiguration konnte nicht geladen werden (siehe Protokoll).",
|
||||||
|
ToolTipIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_webSocketConfig = config;
|
||||||
|
_settingsDialogOpen = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var dialog = new SettingsDialog(config.IncomingCallFocusDelayMs);
|
||||||
|
if (dialog.ShowDialog() != DialogResult.OK
|
||||||
|
|| dialog.FocusDelayMs == config.IncomingCallFocusDelayMs)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.IncomingCallFocusDelayMs = dialog.FocusDelayMs;
|
||||||
|
if (config.Save())
|
||||||
|
{
|
||||||
|
Log.Info("Einstellungen gespeichert: Verzoegerung fuer das Nach-vorn-Holen " +
|
||||||
|
$"von Firefox jetzt {config.IncomingCallFocusDelayMs} ms.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_settingsDialogOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Reconnect()
|
private void Reconnect()
|
||||||
{
|
{
|
||||||
Log.Info("Neuverbindung durch Benutzer angefordert.");
|
Log.Info("Neuverbindung durch Benutzer angefordert.");
|
||||||
|
|||||||
@@ -317,7 +317,9 @@ internal sealed class LocalWebSocketServer : IDisposable
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holt bei einem eingehenden Ruf den konfigurierten Firefox-Tab nach
|
/// Holt bei einem eingehenden Ruf den konfigurierten Firefox-Tab nach
|
||||||
/// vorn (<see cref="WebSocketConfig.IncomingCallTabTitle"/>): Tabs ueber
|
/// vorn (<see cref="WebSocketConfig.IncomingCallTabTitle"/>): die
|
||||||
|
/// konfigurierte Zeit warten (<see
|
||||||
|
/// cref="WebSocketConfig.IncomingCallFocusDelayMs"/>), dann Tabs ueber
|
||||||
/// das Plugin auslesen, den ersten mit passendem Titel aktivieren. Laeuft
|
/// das Plugin auslesen, den ersten mit passendem Titel aktivieren. Laeuft
|
||||||
/// im Hintergrund, damit der UI-Thread nicht auf das Plugin wartet;
|
/// im Hintergrund, damit der UI-Thread nicht auf das Plugin wartet;
|
||||||
/// scheitert still bis auf das Protokoll — ein fehlender Tab oder ein
|
/// scheitert still bis auf das Protokoll — ein fehlender Tab oder ein
|
||||||
@@ -337,6 +339,14 @@ internal sealed class LocalWebSocketServer : IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Kurz warten, damit der Ruf erst sichtbar klingelt, bevor
|
||||||
|
// Firefox dem Nutzer den Fokus wegnimmt. Die Dauer kommt aus der
|
||||||
|
// Konfiguration (Einstellungen-Dialog im Tray-Menue).
|
||||||
|
if (_config.IncomingCallFocusDelayMs > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(_config.IncomingCallFocusDelayMs, _shutdown.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (await _tabChannel.TryListTabsAsync(_shutdown.Token).ConfigureAwait(false) is not { } tabs)
|
if (await _tabChannel.TryListTabsAsync(_shutdown.Token).ConfigureAwait(false) is not { } tabs)
|
||||||
{
|
{
|
||||||
Log.Debug($"Eingehender Ruf: kein Firefox-Plugin verbunden — Tab \"{title}\" bleibt, wo er ist.");
|
Log.Debug($"Eingehender Ruf: kein Firefox-Plugin verbunden — Tab \"{title}\" bleibt, wo er ist.");
|
||||||
|
|||||||
@@ -116,6 +116,14 @@ internal sealed class WebSocketConfig
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string IncomingCallTabTitle { get; set; } = "SwyxWeb";
|
public string IncomingCallTabTitle { get; set; } = "SwyxWeb";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wartezeit in Millisekunden zwischen Klingelbeginn und dem Nach-vorn-
|
||||||
|
/// Holen des Firefox-Fensters — der Ruf soll erst sichtbar klingeln,
|
||||||
|
/// bevor der Fokus wechselt. 0 = sofort. Einstellbar auch ueber den
|
||||||
|
/// Menuepunkt "Einstellungen" im Tray-Menue.
|
||||||
|
/// </summary>
|
||||||
|
public int IncomingCallFocusDelayMs { get; set; } = 1000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Voreingestellt hoert die App nur auf 127.0.0.1 und ::1. Auf <c>true</c>
|
/// Voreingestellt hoert die App nur auf 127.0.0.1 und ::1. Auf <c>true</c>
|
||||||
/// bindet sie an alle Schnittstellen und ist damit aus dem lokalen Netz
|
/// bindet sie an alle Schnittstellen und ist damit aus dem lokalen Netz
|
||||||
@@ -165,6 +173,26 @@ internal sealed class WebSocketConfig
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schreibt die Konfiguration zurueck in die Datei — etwa nach einer
|
||||||
|
/// Aenderung ueber den Einstellungen-Dialog. Kommentare, die jemand von
|
||||||
|
/// Hand in die JSON-Datei geschrieben hat, gehen dabei verloren.
|
||||||
|
/// </summary>
|
||||||
|
public bool Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Log.Directory);
|
||||||
|
File.WriteAllText(FilePath, JsonSerializer.Serialize(this, WriteOptions), Encoding.UTF8);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
Log.Error($"WebSocket-Konfiguration {FilePath} konnte nicht gespeichert werden.", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool Validate()
|
private bool Validate()
|
||||||
{
|
{
|
||||||
if (Port is < 1 or > 65535)
|
if (Port is < 1 or > 65535)
|
||||||
@@ -185,6 +213,11 @@ internal sealed class WebSocketConfig
|
|||||||
MaxConnections = 1;
|
MaxConnections = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IncomingCallFocusDelayMs < 0)
|
||||||
|
{
|
||||||
|
IncomingCallFocusDelayMs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Solange die Pruefung abgeschaltet ist, darf ein fehlendes oder kurzes
|
// Solange die Pruefung abgeschaltet ist, darf ein fehlendes oder kurzes
|
||||||
// Token den Start nicht verhindern.
|
// Token den Start nicht verhindern.
|
||||||
if (Token.Length < 16 && !SecurityDisabled)
|
if (Token.Length < 16 && !SecurityDisabled)
|
||||||
|
|||||||
Reference in New Issue
Block a user