using System.Runtime.InteropServices; using MusicBridge.Agent.Media; using MusicBridge.Agent.Network; namespace MusicBridge.Agent.Desktop; internal sealed class DesktopHost(Func read, IMediaService media, PairingService pairing, Func network, Action showQr, Action exit, bool visible = true, StartupSettings? startup = null, bool minimized = false) : IDisposable { private readonly object gate = new(); private readonly TaskCompletionSource ready = new(TaskCreationOptions.RunContinuationsAsynchronously); private Thread? thread; private AgentWindow? window; private bool disposed; public Task StartAsync() { thread = new Thread(Run) { IsBackground = true, Name = "MusicBridge Desktop" }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return ready.Task; } private void Run() { try { Application.EnableVisualStyles(); using var view = new AgentWindow(read, media, pairing, network, showQr); view.Opacity = visible ? 1 : 0; view.ShowInTaskbar = visible; using var menu = new ContextMenuStrip(); void ShowWindow() { view.Show(); view.WindowState = FormWindowState.Normal; view.Activate(); } menu.Items.Add("Открыть MusicBridge", null, (_, _) => ShowWindow()); var toggle = menu.Items.Add("Play / Pause", null, async (_, _) => await view.SendAsync(new("toggle"))); menu.Items.Add("Подключить iPhone", null, (_, _) => { pairing.Open(); showQr(); }); if (startup is not null) { var autoStart = new ToolStripMenuItem("Запускать вместе с Windows"); try { autoStart.Checked = startup.Enabled; } catch (Exception) { autoStart.Enabled = false; } autoStart.Click += (_, _) => { try { startup.SetEnabled(!startup.Enabled); autoStart.Checked = startup.Enabled; } catch (Exception ex) { MessageBox.Show(view, ex.Message, "Автозапуск MusicBridge", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }; menu.Items.Add(autoStart); } menu.Items.Add(new ToolStripSeparator()); menu.Items.Add("Завершить MusicBridge", null, (_, _) => { view.AllowClose = true; view.Close(); }); using var icon = new NotifyIcon { Icon = SystemIcons.Application, Text = "MusicBridge", ContextMenuStrip = menu, Visible = visible }; icon.DoubleClick += (_, _) => ShowWindow(); using var timer = new System.Windows.Forms.Timer { Interval = 1000 }; timer.Tick += (_, _) => { var state = read(); toggle.Enabled = state.Capabilities.Toggle; var text = state.HasSession ? $"MusicBridge — {state.Title}" : "MusicBridge — нет плеера"; icon.Text = text.Length > 63 ? text[..63] : text; }; view.Shown += (_, _) => { if (minimized) view.Hide(); timer.Start(); ready.TrySetResult(); }; lock (gate) { if (disposed) { ready.TrySetCanceled(); return; } window = view; } Application.Run(view); icon.Visible = false; } catch (Exception ex) { ready.TrySetException(ex); } finally { lock (gate) window = null; exit(); } } public void Dispose() { lock (gate) { disposed = true; if (window is { IsHandleCreated: true } view) { try { view.BeginInvoke(() => { view.AllowClose = true; view.Close(); }); } catch (InvalidOperationException) { } } } thread?.Join(TimeSpan.FromSeconds(2)); } // Detach only this process; never hide or close the user's shared terminal window. [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FreeConsole(); }