using System; using System.Collections.Generic; using System.Threading.Tasks; using System.ComponentModel; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; using System.Runtime.InteropServices; using System.Windows.Interop; using MPDCtrl.Models; namespace unison { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { private readonly MPC _mpd = new(); private bool _connected = false; private int _currentVolume; private bool _currentRandom; private bool _currentRepeat; private bool _currentSingle; private bool _currentConsume; private double _currentElapsed; private readonly Process _snapcast = new Process(); private bool _snapcastStarted = false; private string _snapcastVersion = "snapclient_0.25.0-1_win64"; private string _mpdHost = "192.168.1.13"; private int _mpdPort = 6600; private string _mpdPassword = null; public MainWindow() { InitializeComponent(); ConnectToMPD(); DispatcherTimer timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromSeconds(0.2); timer.Tick += timer_Tick; timer.Start(); } public async void ConnectToMPD() { Trace.WriteLine("Trying to connect..."); _connected = await _mpd.MpdCommandConnectionStart(_mpdHost, _mpdPort, _mpdPassword); if (_connected) { await _mpd.MpdQueryStatus(); await Task.Delay(5); _currentVolume = _mpd.MpdStatus.MpdVolume; _currentRandom = _mpd.MpdStatus.MpdRandom; _currentRepeat = _mpd.MpdStatus.MpdRepeat; _currentSingle = _mpd.MpdStatus.MpdSingle; _currentConsume = _mpd.MpdStatus.MpdConsume; _currentElapsed = _mpd.MpdStatus.MpdSongElapsed; } } void timer_Tick(object sender, EventArgs e) { LoopMPD(); UpdateInterface(); } public void CheckStatus(ref T a, T b) { if (Comparer.Default.Compare(a, b) != 0) a = b; } public async void LoopMPD() { if (!_connected) return; var status = await _mpd.MpdQueryStatus(); //Trace.WriteLine(status.ResultText); await Task.Delay(5); if (status != null) { CheckStatus(ref _currentVolume, _mpd.MpdStatus.MpdVolume); CheckStatus(ref _currentRandom, _mpd.MpdStatus.MpdRandom); CheckStatus(ref _currentRepeat, _mpd.MpdStatus.MpdRepeat); CheckStatus(ref _currentSingle, _mpd.MpdStatus.MpdSingle); CheckStatus(ref _currentConsume, _mpd.MpdStatus.MpdConsume); CheckStatus(ref _currentElapsed, _mpd.MpdStatus.MpdSongElapsed); } await _mpd.MpdQueryCurrentSong(); await Task.Delay(5); } public void UpdateButton(ref Button button, bool b) { if (b) button.Foreground = System.Windows.SystemColors.GradientActiveCaptionBrush; else button.Foreground = System.Windows.SystemColors.DesktopBrush; } public string FormatSeconds(double time) { var timespan = TimeSpan.FromSeconds(time); return timespan.ToString(@"mm\:ss"); } public void UpdateInterface() { if (_mpd.MpdCurrentSong != null) { SongTitle.Text = _mpd.MpdCurrentSong.Title; SongTitle.ToolTip = _mpd.MpdCurrentSong.File; SongArtist.Text = _mpd.MpdCurrentSong.Artist; SongAlbum.Text = _mpd.MpdCurrentSong.Album + " (" + _mpd.MpdCurrentSong.Date + ")"; Bitrate.Text = _mpd.MpdCurrentSong.File.Substring(_mpd.MpdCurrentSong.File.LastIndexOf(".") + 1); Bitrate.Text += " – "; Bitrate.Text += _mpd.MpdStatus.MpdBitrate + "kbps"; CurrentTime.Text = FormatSeconds(_currentElapsed); EndTime.Text = FormatSeconds(_mpd.MpdStatus.MpdSongTime); TimeSlider.Value = _currentElapsed / _mpd.MpdCurrentSong.TimeSort * 100; } if (VolumeSlider.Value != _currentVolume) { VolumeSlider.Value = _currentVolume; VolumeSlider.ToolTip = _currentVolume; } if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play) PauseButton.Content = "⏸"; else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause) PauseButton.Content = "▶️"; if (_snapcastStarted) Snapcast.Content = "Stop Snapcast"; else Snapcast.Content = "Start Snapcast"; DebugText.Text = _mpd.MpdHost + ":" + _mpd.MpdPort; UpdateButton(ref Random, _currentRandom); UpdateButton(ref Repeat, _currentRepeat); UpdateButton(ref Single, _currentSingle); UpdateButton(ref Consume, _currentConsume); } private async void Pause_Clicked(object sender, RoutedEventArgs e) { if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play) await _mpd.MpdPlaybackPause(); else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause) await _mpd.MpdPlaybackPlay(_currentVolume); } private async void Previous_Clicked(object sender, RoutedEventArgs e) { await _mpd.MpdPlaybackPrev(_currentVolume); } private async void Next_Clicked(object sender, RoutedEventArgs e) { await _mpd.MpdPlaybackNext(_currentVolume); } private async void Random_Clicked(object sender, RoutedEventArgs e) { await _mpd.MpdSetRandom(!_currentRandom); } private async void Repeat_Clicked(object sender, RoutedEventArgs e) { await _mpd.MpdSetRepeat(!_currentRepeat); } private async void Single_Clicked(object sender, RoutedEventArgs e) { await _mpd.MpdSetSingle(!_currentSingle); } private async void Consume_Clicked(object sender, RoutedEventArgs e) { await _mpd.MpdSetConsume(!_currentConsume); } private async void ChangeVolume(int value) { await _mpd.MpdSetVolume(value); } private void Snapcast_Clicked(object sender, RoutedEventArgs e) { if (!_snapcastStarted) { _snapcast.StartInfo.FileName = _snapcastVersion + @"\snapclient.exe"; _snapcast.StartInfo.Arguments = "--host " + _mpd.MpdHost; _snapcast.StartInfo.CreateNoWindow = true; _snapcast.Start(); _snapcastStarted = true; } else { _snapcast.Kill(); _snapcastStarted = false; } } private void Window_Closing(object sender, CancelEventArgs e) { if (_snapcastStarted) _snapcast.Kill(); } // hotkeys handling [DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); [DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id); private const int HOTKEY_ID = 9000; // modifiers private const uint MOD_NONE = 0x0000; private const uint MOD_ALT = 0x0001; private const uint MOD_CONTROL = 0x0002; private const uint MOD_SHIFT = 0x0004; private const uint MOD_WIN = 0x0008; // reference => https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes private const uint VK_MEDIA_PREV_TRACK = 0xB1; private const uint VK_MEDIA_NEXT_TRACK = 0xB0; private const uint VK_MEDIA_PLAY_PAUSE = 0xB3; private const uint VK_VOLUME_UP = 0xAF; private const uint VK_VOLUME_DOWN = 0xAE; private IntPtr _windowHandle; private HwndSource _source; protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); _windowHandle = new WindowInteropHelper(this).Handle; _source = HwndSource.FromHwnd(_windowHandle); _source.AddHook(HwndHook); RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_PREV_TRACK); RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_NEXT_TRACK); RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL /*| MOD_ALT*/, VK_MEDIA_PLAY_PAUSE); RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_UP); RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_DOWN); } private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { const int WM_HOTKEY = 0x0312; switch (msg) { case WM_HOTKEY: switch (wParam.ToInt32()) { case HOTKEY_ID: int vkey = (((int)lParam >> 16) & 0xFFFF); if (vkey == VK_MEDIA_NEXT_TRACK) { Next_Clicked(null, null); } else if (vkey == VK_MEDIA_PREV_TRACK) { Previous_Clicked(null, null); } else if (vkey == VK_VOLUME_DOWN) { _currentVolume--; ChangeVolume(_currentVolume); } else if (vkey == VK_VOLUME_UP) { _currentVolume++; ChangeVolume(_currentVolume); } else if (vkey == VK_MEDIA_PLAY_PAUSE) { Pause_Clicked(null, null); } handled = true; break; } break; } return IntPtr.Zero; } protected override void OnClosed(EventArgs e) { _source.RemoveHook(HwndHook); UnregisterHotKey(_windowHandle, HOTKEY_ID); base.OnClosed(e); } } }