Compare commits
33 Commits
984d2056de
...
v1.2
Author | SHA1 | Date | |
---|---|---|---|
301309737b | |||
ab46442108 | |||
3c1aad7d27 | |||
d1b6d97fe8 | |||
15db118dd6 | |||
67315f90b0 | |||
6d75f88927 | |||
7fdf349c28 | |||
9a5f686dde | |||
b209cb2556 | |||
6e1f43bed0 | |||
9bd088fac8 | |||
0be28ab205 | |||
62a3220f7f | |||
6e4ed82211 | |||
7b2a7bae21 | |||
72d751db71 | |||
e4b63073d8 | |||
d49f3ab030 | |||
62835065c0 | |||
196b93c7f3 | |||
f9a14ee3c0 | |||
7aafa935e1 | |||
2960afd9bd | |||
23098e0ebb | |||
32d3610b07 | |||
fbb65a039a | |||
4d180871ca | |||
b87bff54c4 | |||
be8cef35d3 | |||
c568a957f7 | |||
2c8696155a | |||
48b5bc5d28 |
1
App.xaml
@ -8,6 +8,7 @@
|
|||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceDictionary Source="Views/Systray.xaml" />
|
<ResourceDictionary Source="Views/Systray.xaml" />
|
||||||
|
<ResourceDictionary Source="Resources/Resources.xaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
<Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
|
<Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
|
||||||
|
36
App.xaml.cs
@ -1,39 +1,43 @@
|
|||||||
using System.Windows;
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using Hardcodet.Wpf.TaskbarNotification;
|
||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
private TaskbarIcon Systray;
|
private TaskbarIcon _systray;
|
||||||
private HotkeyHandler Hotkeys;
|
private HotkeyHandler _hotkeys;
|
||||||
private SnapcastHandler Snapcast;
|
private SnapcastHandler _snapcast;
|
||||||
private MPDHandler MPD;
|
private MPDHandler _mpd;
|
||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
|
//debug language
|
||||||
|
//unison.Resources.Resources.Culture = CultureInfo.GetCultureInfo("fr-FR");
|
||||||
|
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
MPD = new MPDHandler();
|
_mpd = new MPDHandler();
|
||||||
Current.Properties["mpd"] = MPD;
|
Current.Properties["mpd"] = _mpd;
|
||||||
|
|
||||||
Hotkeys = new HotkeyHandler();
|
_hotkeys = new HotkeyHandler();
|
||||||
Current.Properties["hotkeys"] = Hotkeys;
|
Current.Properties["hotkeys"] = _hotkeys;
|
||||||
|
|
||||||
Snapcast = new SnapcastHandler();
|
_snapcast = new SnapcastHandler();
|
||||||
Current.Properties["snapcast"] = Snapcast;
|
Current.Properties["snapcast"] = _snapcast;
|
||||||
|
|
||||||
Current.MainWindow = new MainWindow();
|
Current.MainWindow = new MainWindow();
|
||||||
|
|
||||||
Systray = (TaskbarIcon)FindResource("SystrayTaskbar");
|
_systray = (TaskbarIcon)FindResource("SystrayTaskbar");
|
||||||
Current.Properties["systray"] = Systray;
|
Current.Properties["systray"] = _systray;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnExit(ExitEventArgs e)
|
protected override void OnExit(ExitEventArgs e)
|
||||||
{
|
{
|
||||||
Systray.Dispose();
|
_systray.Dispose();
|
||||||
Snapcast.Stop();
|
_snapcast.LaunchOrExit(true);
|
||||||
Hotkeys.RemoveHotKeys();
|
_hotkeys.RemoveHotkeys();
|
||||||
base.OnExit(e);
|
base.OnExit(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
@ -16,29 +15,200 @@ namespace unison
|
|||||||
|
|
||||||
private const int HOTKEY_ID = 9000;
|
private const int HOTKEY_ID = 9000;
|
||||||
|
|
||||||
// modifiers
|
public enum MOD : int
|
||||||
private const uint MOD_NONE = 0x0000;
|
{
|
||||||
private const uint MOD_ALT = 0x0001;
|
None = 0x0000,
|
||||||
private const uint MOD_CONTROL = 0x0002;
|
Alt = 0x0001,
|
||||||
private const uint MOD_SHIFT = 0x0004;
|
Control = 0x0002,
|
||||||
private const uint MOD_WIN = 0x0008;
|
Shift = 0x0004,
|
||||||
|
//Win = 0x0008
|
||||||
|
};
|
||||||
|
|
||||||
// reference => https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
// reference => https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||||
private const uint VK_MEDIA_PREV_TRACK = 0xB1;
|
public enum VK : int
|
||||||
private const uint VK_MEDIA_NEXT_TRACK = 0xB0;
|
{
|
||||||
private const uint VK_MEDIA_PLAY_PAUSE = 0xB3;
|
None = 0x00,
|
||||||
private const uint VK_VOLUME_UP = 0xAF;
|
Back = 0x08,
|
||||||
private const uint VK_VOLUME_DOWN = 0xAE;
|
Tab = 0x09,
|
||||||
private const uint VK_ENTER = 0x0D;
|
Clear = 0x0c,
|
||||||
|
Return = 0x0d,
|
||||||
|
Menu = 0x12,
|
||||||
|
Pause = 0x13,
|
||||||
|
Capital = 0x14,
|
||||||
|
Kana = 0x15,
|
||||||
|
Hangul = 0x15,
|
||||||
|
Junja = 0x17,
|
||||||
|
Final = 0x18,
|
||||||
|
Hanja = 0x19,
|
||||||
|
Kanji = 0x19,
|
||||||
|
Escape = 0x1b,
|
||||||
|
Convert = 0x1c,
|
||||||
|
NonConvert = 0x1d,
|
||||||
|
Accept = 0x1e,
|
||||||
|
ModeChange = 0x1f,
|
||||||
|
Space = 0x20,
|
||||||
|
Prior = 0x21,
|
||||||
|
Next = 0x22,
|
||||||
|
End = 0x23,
|
||||||
|
Home = 0x24,
|
||||||
|
Left = 0x25,
|
||||||
|
Up = 0x26,
|
||||||
|
Right = 0x27,
|
||||||
|
Down = 0x28,
|
||||||
|
Select = 0x29,
|
||||||
|
Print = 0x2a,
|
||||||
|
Execute = 0x2b,
|
||||||
|
Snapshot = 0x2c,
|
||||||
|
Insert = 0x2d,
|
||||||
|
Delete = 0x2e,
|
||||||
|
Help = 0x2f,
|
||||||
|
NumRow0 = 0x30,
|
||||||
|
NumRow1 = 0x31,
|
||||||
|
NumRow2 = 0x32,
|
||||||
|
NumRow3 = 0x33,
|
||||||
|
NumRow4 = 0x34,
|
||||||
|
NumRow5 = 0x35,
|
||||||
|
NumRow6 = 0x36,
|
||||||
|
NumRow7 = 0x37,
|
||||||
|
NumRow8 = 0x38,
|
||||||
|
NumRow9 = 0x39,
|
||||||
|
A = 0x41,
|
||||||
|
B = 0x42,
|
||||||
|
C = 0x43,
|
||||||
|
D = 0x44,
|
||||||
|
E = 0x45,
|
||||||
|
F = 0x46,
|
||||||
|
G = 0x47,
|
||||||
|
H = 0x48,
|
||||||
|
I = 0x49,
|
||||||
|
J = 0x4a,
|
||||||
|
K = 0x4b,
|
||||||
|
L = 0x4c,
|
||||||
|
M = 0x4d,
|
||||||
|
N = 0x4e,
|
||||||
|
O = 0x4f,
|
||||||
|
P = 0x50,
|
||||||
|
Q = 0x51,
|
||||||
|
R = 0x52,
|
||||||
|
S = 0x53,
|
||||||
|
T = 0x54,
|
||||||
|
U = 0x55,
|
||||||
|
V = 0x56,
|
||||||
|
W = 0x57,
|
||||||
|
X = 0x58,
|
||||||
|
Y = 0x59,
|
||||||
|
Z = 0x5a,
|
||||||
|
Apps = 0x5d,
|
||||||
|
Sleep = 0x5f,
|
||||||
|
NumPad0 = 0x60,
|
||||||
|
NumPad1 = 0x61,
|
||||||
|
NumPad2 = 0x62,
|
||||||
|
NumPad3 = 0x63,
|
||||||
|
NumPad4 = 0x64,
|
||||||
|
NumPad5 = 0x65,
|
||||||
|
NumPad6 = 0x66,
|
||||||
|
NumPad7 = 0x67,
|
||||||
|
NumPad8 = 0x68,
|
||||||
|
NumPad9 = 0x69,
|
||||||
|
Multiply = 0x6a,
|
||||||
|
Add = 0x6b,
|
||||||
|
Separator = 0x6c,
|
||||||
|
Subtract = 0x6d,
|
||||||
|
Decimal = 0x6e,
|
||||||
|
Divide = 0x6f,
|
||||||
|
F1 = 0x70,
|
||||||
|
F2 = 0x71,
|
||||||
|
F3 = 0x72,
|
||||||
|
F4 = 0x73,
|
||||||
|
F5 = 0x74,
|
||||||
|
F6 = 0x75,
|
||||||
|
F7 = 0x76,
|
||||||
|
F8 = 0x77,
|
||||||
|
F9 = 0x78,
|
||||||
|
F10 = 0x79,
|
||||||
|
F11 = 0x7a,
|
||||||
|
F12 = 0x7b,
|
||||||
|
F13 = 0x7c,
|
||||||
|
F14 = 0x7d,
|
||||||
|
F15 = 0x7e,
|
||||||
|
F16 = 0x7f,
|
||||||
|
F17 = 0x80,
|
||||||
|
F18 = 0x81,
|
||||||
|
F19 = 0x82,
|
||||||
|
F20 = 0x83,
|
||||||
|
F21 = 0x84,
|
||||||
|
F22 = 0x85,
|
||||||
|
F23 = 0x86,
|
||||||
|
F24 = 0x87,
|
||||||
|
NumLock = 0x90,
|
||||||
|
Scroll = 0x91,
|
||||||
|
LMenu = 0xa4,
|
||||||
|
RMenu = 0xa5,
|
||||||
|
BrowserBack = 0xa6,
|
||||||
|
BrowserForward = 0xa7,
|
||||||
|
BrowserRefresh = 0xa8,
|
||||||
|
BrowserStop = 0xa9,
|
||||||
|
BrowserSearch = 0xaa,
|
||||||
|
BrowserFavorites = 0xab,
|
||||||
|
BrowserHome = 0xac,
|
||||||
|
VolumeMute = 0xad,
|
||||||
|
VolumeDown = 0xae,
|
||||||
|
VolumeUp = 0xaf,
|
||||||
|
MediaNextTrack = 0xb0,
|
||||||
|
MediaPreviousTrack = 0xb1,
|
||||||
|
MediaStop = 0xb2,
|
||||||
|
MediaPlayPause = 0xb3,
|
||||||
|
LaunchMail = 0xb4,
|
||||||
|
LaunchMediaSelect = 0xb5,
|
||||||
|
LaunchApp1 = 0xb6,
|
||||||
|
LaunchApp2 = 0xb7,
|
||||||
|
};
|
||||||
|
|
||||||
|
public struct HotkeyPair
|
||||||
|
{
|
||||||
|
public MOD mod;
|
||||||
|
public VK vk;
|
||||||
|
|
||||||
|
public HotkeyPair(MOD _mod, VK _vk) { mod = _mod; vk = _vk; }
|
||||||
|
|
||||||
|
public uint GetMOD() { return (uint)mod; }
|
||||||
|
public uint GetVK() { return (uint)vk; }
|
||||||
|
|
||||||
|
public void SetMOD(MOD modmod) { mod = modmod; }
|
||||||
|
public void SetVK(VK vkvk) { vk = vkvk; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private MainWindow _appWindow;
|
||||||
|
private readonly MPDHandler _mpd;
|
||||||
|
|
||||||
private IntPtr _windowHandle;
|
private IntPtr _windowHandle;
|
||||||
private HwndSource _source;
|
private HwndSource _source;
|
||||||
|
|
||||||
private readonly MPDHandler mpd;
|
public HotkeyPair _NextTrack;
|
||||||
|
public HotkeyPair _PreviousTrack;
|
||||||
|
public HotkeyPair _PlayPause;
|
||||||
|
public HotkeyPair _VolumeUp;
|
||||||
|
public HotkeyPair _VolumeDown;
|
||||||
|
public HotkeyPair _VolumeMute;
|
||||||
|
public HotkeyPair _ShowWindow;
|
||||||
|
public HotkeyPair[] _Shortcuts;
|
||||||
|
|
||||||
public HotkeyHandler()
|
public HotkeyHandler()
|
||||||
{
|
{
|
||||||
mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_NextTrack = new HotkeyPair((MOD)Properties.Settings.Default.nextTrack_mod, (VK)Properties.Settings.Default.nextTrack_vk);
|
||||||
|
_PreviousTrack = new HotkeyPair((MOD)Properties.Settings.Default.previousTrack_mod, (VK)Properties.Settings.Default.previousTrack_vk);
|
||||||
|
_PlayPause = new HotkeyPair((MOD)Properties.Settings.Default.playPause_mod, (VK)Properties.Settings.Default.playPause_vk);
|
||||||
|
_VolumeUp = new HotkeyPair((MOD)Properties.Settings.Default.volumeUp_mod, (VK)Properties.Settings.Default.volumeUp_vk);
|
||||||
|
_VolumeDown = new HotkeyPair((MOD)Properties.Settings.Default.volumeDown_mod, (VK)Properties.Settings.Default.volumeDown_vk);
|
||||||
|
_VolumeMute = new HotkeyPair((MOD)Properties.Settings.Default.volumeMute_mod, (VK)Properties.Settings.Default.volumeMute_vk);
|
||||||
|
_ShowWindow = new HotkeyPair((MOD)Properties.Settings.Default.showWindow_mod, (VK)Properties.Settings.Default.showWindow_vk);
|
||||||
|
_Shortcuts = new HotkeyPair[] { _NextTrack, _PreviousTrack, _PlayPause, _VolumeUp, _VolumeDown, _VolumeMute, _ShowWindow };
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Activate(Window win)
|
public void Activate(Window win)
|
||||||
@ -47,17 +217,28 @@ namespace unison
|
|||||||
{
|
{
|
||||||
_windowHandle = new WindowInteropHelper(win).Handle;
|
_windowHandle = new WindowInteropHelper(win).Handle;
|
||||||
_source = HwndSource.FromHwnd(_windowHandle);
|
_source = HwndSource.FromHwnd(_windowHandle);
|
||||||
_source.AddHook(HwndHook);
|
AddHotkeys();
|
||||||
|
|
||||||
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, VK_MEDIA_PLAY_PAUSE);
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_UP);
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_DOWN);
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL | MOD_ALT, VK_ENTER);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddHotkeys()
|
||||||
|
{
|
||||||
|
_source.AddHook(HwndHook);
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, _NextTrack.GetMOD(), _NextTrack.GetVK());
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, _PreviousTrack.GetMOD(), _PreviousTrack.GetVK());
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, _PlayPause.GetMOD(), _PlayPause.GetVK());
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, _VolumeUp.GetMOD(), _VolumeUp.GetVK());
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, _VolumeDown.GetMOD(), _VolumeDown.GetVK());
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, _VolumeMute.GetMOD(), _VolumeMute.GetVK());
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, _ShowWindow.GetMOD(), _ShowWindow.GetVK());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveHotkeys()
|
||||||
|
{
|
||||||
|
_source.RemoveHook(HwndHook);
|
||||||
|
UnregisterHotKey(_windowHandle, HOTKEY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||||
{
|
{
|
||||||
const int WM_HOTKEY = 0x0312;
|
const int WM_HOTKEY = 0x0312;
|
||||||
@ -65,62 +246,48 @@ namespace unison
|
|||||||
if (msg == WM_HOTKEY && wParam.ToInt32() == HOTKEY_ID)
|
if (msg == WM_HOTKEY && wParam.ToInt32() == HOTKEY_ID)
|
||||||
{
|
{
|
||||||
uint vkey = ((uint)lParam >> 16) & 0xFFFF;
|
uint vkey = ((uint)lParam >> 16) & 0xFFFF;
|
||||||
MainWindow AppWindow = (MainWindow)Application.Current.MainWindow;
|
|
||||||
switch (vkey)
|
if (vkey == _NextTrack.GetVK())
|
||||||
|
_mpd.Next();
|
||||||
|
else if (vkey == _PreviousTrack.GetVK())
|
||||||
|
_mpd.Prev();
|
||||||
|
else if (vkey == _PlayPause.GetVK())
|
||||||
|
_mpd.PlayPause();
|
||||||
|
else if (vkey == _VolumeUp.GetVK())
|
||||||
|
_mpd.VolumeUp();
|
||||||
|
else if (vkey == _VolumeDown.GetVK())
|
||||||
|
_mpd.VolumeDown();
|
||||||
|
else if (vkey == _VolumeMute.GetVK())
|
||||||
|
_mpd.VolumeMute();
|
||||||
|
else if (vkey == _ShowWindow.GetVK())
|
||||||
{
|
{
|
||||||
case VK_MEDIA_NEXT_TRACK:
|
if (_appWindow == null)
|
||||||
mpd.Next();
|
_appWindow = (MainWindow)Application.Current.MainWindow;
|
||||||
break;
|
|
||||||
case VK_MEDIA_PREV_TRACK:
|
if (_appWindow.WindowState == WindowState.Minimized)
|
||||||
mpd.Prev();
|
|
||||||
break;
|
|
||||||
case VK_VOLUME_DOWN:
|
|
||||||
mpd._currentVolume -= Properties.Settings.Default.volume_offset;
|
|
||||||
if (mpd._currentVolume < 0)
|
|
||||||
mpd._currentVolume = 0;
|
|
||||||
mpd.SetVolume(mpd._currentVolume);
|
|
||||||
break;
|
|
||||||
case VK_VOLUME_UP:
|
|
||||||
mpd._currentVolume += Properties.Settings.Default.volume_offset;
|
|
||||||
if (mpd._currentVolume > 100)
|
|
||||||
mpd._currentVolume = 100;
|
|
||||||
mpd.SetVolume(mpd._currentVolume);
|
|
||||||
break;
|
|
||||||
case VK_MEDIA_PLAY_PAUSE:
|
|
||||||
mpd.PlayPause();
|
|
||||||
break;
|
|
||||||
case VK_ENTER:
|
|
||||||
if (AppWindow.WindowState == WindowState.Minimized)
|
|
||||||
{
|
{
|
||||||
AppWindow.Show();
|
_appWindow.Show();
|
||||||
AppWindow.Activate();
|
_appWindow.Activate();
|
||||||
AppWindow.WindowState = WindowState.Normal;
|
_appWindow.WindowState = WindowState.Normal;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (AppWindow.IsActive)
|
if (_appWindow.IsActive)
|
||||||
{
|
{
|
||||||
AppWindow.Hide();
|
_appWindow.Hide();
|
||||||
AppWindow.WindowState = WindowState.Minimized;
|
_appWindow.WindowState = WindowState.Minimized;
|
||||||
}
|
}
|
||||||
else // not minimized but not in front
|
else // not minimized but not in front
|
||||||
{
|
{
|
||||||
AppWindow.Show();
|
_appWindow.Show();
|
||||||
AppWindow.Activate();
|
_appWindow.Activate();
|
||||||
AppWindow.WindowState = WindowState.Normal;
|
_appWindow.WindowState = WindowState.Normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
return IntPtr.Zero;
|
return IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveHotKeys()
|
|
||||||
{
|
|
||||||
_source.RemoveHook(HwndHook);
|
|
||||||
UnregisterHotKey(_windowHandle, HOTKEY_ID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ using System.Windows.Threading;
|
|||||||
using MpcNET;
|
using MpcNET;
|
||||||
using MpcNET.Commands.Database;
|
using MpcNET.Commands.Database;
|
||||||
using MpcNET.Commands.Playback;
|
using MpcNET.Commands.Playback;
|
||||||
|
using MpcNET.Commands.Queue;
|
||||||
using MpcNET.Commands.Reflection;
|
using MpcNET.Commands.Reflection;
|
||||||
using MpcNET.Commands.Status;
|
using MpcNET.Commands.Status;
|
||||||
using MpcNET.Message;
|
using MpcNET.Message;
|
||||||
@ -19,55 +20,63 @@ using MpcNET.Types;
|
|||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
|
public class Statistics
|
||||||
|
{
|
||||||
|
public int Songs { get; set; }
|
||||||
|
public int Albums { get; set; }
|
||||||
|
public int Artists { get; set; }
|
||||||
|
public string TotalPlaytime { get; set; }
|
||||||
|
public string Uptime { get; set; }
|
||||||
|
public string TotalTimePlayed { get; set; }
|
||||||
|
public string DatabaseUpdate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class MPDHandler
|
public class MPDHandler
|
||||||
{
|
{
|
||||||
public bool _connected;
|
private bool _connected;
|
||||||
public string _version;
|
public string _version;
|
||||||
public int _currentVolume;
|
private int _currentVolume;
|
||||||
public bool _currentRandom;
|
private int _previousVolume;
|
||||||
public bool _currentRepeat;
|
private bool _currentRandom;
|
||||||
public bool _currentSingle;
|
private bool _currentRepeat;
|
||||||
public bool _currentConsume;
|
private bool _currentSingle;
|
||||||
public double _currentTime;
|
private bool _currentConsume;
|
||||||
public double _totalTime;
|
private double _currentTime;
|
||||||
BitmapFrame _cover;
|
private double _totalTime;
|
||||||
|
|
||||||
public event EventHandler ConnectionChanged;
|
|
||||||
public event EventHandler StatusChanged;
|
|
||||||
public event EventHandler SongChanged;
|
|
||||||
public event EventHandler CoverChanged;
|
|
||||||
|
|
||||||
public static MpdStatus BOGUS_STATUS = new MpdStatus(0, false, false, false, false, -1, -1, -1, MpdState.Unknown, -1, -1, -1, -1, TimeSpan.Zero, TimeSpan.Zero, -1, -1, -1, -1, -1, "", "");
|
|
||||||
public MpdStatus CurrentStatus { get; private set; } = BOGUS_STATUS;
|
|
||||||
|
|
||||||
IMpdFile CurrentSong { get; set; }
|
|
||||||
|
|
||||||
|
private MpdStatus _currentStatus;
|
||||||
|
private IMpdFile _currentSong;
|
||||||
|
private BitmapFrame _cover;
|
||||||
|
public Statistics _stats;
|
||||||
private readonly System.Timers.Timer _elapsedTimer;
|
private readonly System.Timers.Timer _elapsedTimer;
|
||||||
private async void ElapsedTimer(object sender, System.Timers.ElapsedEventArgs e)
|
private DispatcherTimer _retryTimer;
|
||||||
{
|
|
||||||
if ((_currentTime < _totalTime) && (CurrentStatus.State == MpdState.Play))
|
bool _isUpdatingStatus = false;
|
||||||
{
|
bool _isUpdatingSong = false;
|
||||||
_currentTime += 0.5;
|
|
||||||
await Task.Delay(5);
|
bool _invalidIp = false;
|
||||||
}
|
|
||||||
else
|
private event EventHandler ConnectionChanged;
|
||||||
{
|
private event EventHandler StatusChanged;
|
||||||
_elapsedTimer.Stop();
|
private event EventHandler SongChanged;
|
||||||
}
|
private event EventHandler CoverChanged;
|
||||||
}
|
|
||||||
|
|
||||||
private MpcConnection _connection;
|
private MpcConnection _connection;
|
||||||
private MpcConnection _commandConnection;
|
private MpcConnection _commandConnection;
|
||||||
|
|
||||||
private IPEndPoint _mpdEndpoint;
|
private IPEndPoint _mpdEndpoint;
|
||||||
|
|
||||||
private CancellationTokenSource cancelToken;
|
private CancellationTokenSource cancelToken;
|
||||||
|
|
||||||
public MPDHandler()
|
public MPDHandler()
|
||||||
{
|
{
|
||||||
cancelToken = new CancellationTokenSource();
|
cancelToken = new CancellationTokenSource();
|
||||||
|
|
||||||
Initialize();
|
Initialize(null, null);
|
||||||
|
|
||||||
|
_stats = new Statistics();
|
||||||
|
|
||||||
|
_retryTimer = new DispatcherTimer();
|
||||||
|
_retryTimer.Interval = TimeSpan.FromSeconds(5);
|
||||||
|
_retryTimer.Tick += Initialize;
|
||||||
|
|
||||||
_elapsedTimer = new System.Timers.Timer(500);
|
_elapsedTimer = new System.Timers.Timer(500);
|
||||||
_elapsedTimer.Elapsed += new System.Timers.ElapsedEventHandler(ElapsedTimer);
|
_elapsedTimer.Elapsed += new System.Timers.ElapsedEventHandler(ElapsedTimer);
|
||||||
@ -78,8 +87,21 @@ namespace unison
|
|||||||
CoverChanged += OnCoverChanged;
|
CoverChanged += OnCoverChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnConnectionChanged(object sender, EventArgs e)
|
private void ElapsedTimer(object sender, System.Timers.ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if ((_currentTime < _totalTime || _totalTime == -1) && (_currentStatus.State == MpdState.Play))
|
||||||
|
_currentTime += 0.5;
|
||||||
|
else
|
||||||
|
_elapsedTimer.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnConnectionChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!_connected && !_invalidIp)
|
||||||
|
_retryTimer.Start();
|
||||||
|
else
|
||||||
|
_retryTimer.Stop();
|
||||||
|
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||||
@ -87,8 +109,7 @@ namespace unison
|
|||||||
|
|
||||||
SnapcastHandler Snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
SnapcastHandler Snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||||
Snapcast.OnConnectionChanged(sender, e);
|
Snapcast.OnConnectionChanged(sender, e);
|
||||||
|
});
|
||||||
}, DispatcherPriority.ContextIdle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnStatusChanged(object sender, EventArgs e)
|
static void OnStatusChanged(object sender, EventArgs e)
|
||||||
@ -97,7 +118,7 @@ namespace unison
|
|||||||
{
|
{
|
||||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||||
MainWin.OnStatusChanged(sender, e);
|
MainWin.OnStatusChanged(sender, e);
|
||||||
}, DispatcherPriority.ContextIdle);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnSongChanged(object sender, EventArgs e)
|
static void OnSongChanged(object sender, EventArgs e)
|
||||||
@ -106,7 +127,7 @@ namespace unison
|
|||||||
{
|
{
|
||||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||||
MainWin.OnSongChanged(sender, e);
|
MainWin.OnSongChanged(sender, e);
|
||||||
}, DispatcherPriority.ContextIdle);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnCoverChanged(object sender, EventArgs e)
|
static void OnCoverChanged(object sender, EventArgs e)
|
||||||
@ -115,194 +136,30 @@ namespace unison
|
|||||||
{
|
{
|
||||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||||
MainWin.OnCoverChanged(sender, e);
|
MainWin.OnCoverChanged(sender, e);
|
||||||
}, DispatcherPriority.ContextIdle);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Initialize()
|
public void SendCommand<T>(IMpcCommand<T> command)
|
||||||
{
|
|
||||||
Connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Connect()
|
|
||||||
{
|
|
||||||
var token = cancelToken.Token;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_connection = await ConnectInternal(token);
|
|
||||||
_commandConnection = await ConnectInternal(token);
|
|
||||||
}
|
|
||||||
catch(MpcNET.Exceptions.MpcConnectException exception)
|
|
||||||
{
|
|
||||||
Trace.WriteLine("exception: " + exception);
|
|
||||||
}
|
|
||||||
if (_connection.IsConnected)
|
|
||||||
{
|
|
||||||
_connected = true;
|
|
||||||
_version = _connection.Version;
|
|
||||||
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
await UpdateStatusAsync();
|
|
||||||
await UpdateSongAsync();
|
|
||||||
|
|
||||||
Loop(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<MpcConnection> ConnectInternal(CancellationToken token)
|
|
||||||
{
|
|
||||||
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out IPAddress ipAddress);
|
|
||||||
|
|
||||||
_mpdEndpoint = new IPEndPoint(ipAddress, Properties.Settings.Default.mpd_port);
|
|
||||||
MpcConnection connection = new MpcConnection(_mpdEndpoint);
|
|
||||||
await connection.ConnectAsync(token);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Properties.Settings.Default.mpd_password))
|
|
||||||
{
|
|
||||||
IMpdMessage<string> result = await connection.SendAsync(new PasswordCommand(Properties.Settings.Default.mpd_password));
|
|
||||||
if (!result.IsResponseValid)
|
|
||||||
{
|
|
||||||
string mpdError = result.Response?.Result?.MpdError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Loop(CancellationToken token)
|
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
while (true)
|
await SafelySendCommandAsync(command);
|
||||||
{
|
});
|
||||||
try
|
|
||||||
{
|
|
||||||
if (token.IsCancellationRequested || _connection == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
Trace.WriteLine("loop");
|
|
||||||
var idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options"));
|
|
||||||
|
|
||||||
if (idleChanges.IsResponseValid)
|
|
||||||
await HandleIdleResponseAsync(idleChanges.Response.Content);
|
|
||||||
else
|
|
||||||
throw new Exception(idleChanges.Response?.Content);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"Error in Idle connection thread: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleIdleResponseAsync(string subsystems)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (subsystems.Contains("player") || subsystems.Contains("mixer") || subsystems.Contains("output") || subsystems.Contains("options"))
|
|
||||||
{
|
|
||||||
await UpdateStatusAsync();
|
|
||||||
|
|
||||||
if (subsystems.Contains("player"))
|
|
||||||
{
|
|
||||||
await UpdateSongAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateStatusCommand()
|
|
||||||
{
|
|
||||||
if (_commandConnection == null) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MpdStatus response = await SafelySendCommandAsync(new StatusCommand());
|
|
||||||
|
|
||||||
if (response != null)
|
|
||||||
{
|
|
||||||
CurrentStatus = response;
|
|
||||||
UpdateStatus();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
|
||||||
Connect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isUpdatingStatus = false;
|
|
||||||
private async Task UpdateStatusAsync()
|
|
||||||
{
|
|
||||||
if (_connection == null) return;
|
|
||||||
|
|
||||||
if (_isUpdatingStatus) return;
|
|
||||||
_isUpdatingStatus = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IMpdMessage<MpdStatus> response = await _connection.SendAsync(new StatusCommand());
|
|
||||||
if (response != null && response.IsResponseValid)
|
|
||||||
{
|
|
||||||
CurrentStatus = response.Response.Content;
|
|
||||||
UpdateStatus();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
|
||||||
Connect();
|
|
||||||
}
|
|
||||||
_isUpdatingStatus = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isUpdatingSong = false;
|
|
||||||
private async Task UpdateSongAsync()
|
|
||||||
{
|
|
||||||
if (_connection == null) return;
|
|
||||||
|
|
||||||
if (_isUpdatingSong) return;
|
|
||||||
_isUpdatingSong = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IMpdMessage<IMpdFile> response = await _connection.SendAsync(new CurrentSongCommand());
|
|
||||||
if (response != null && response.IsResponseValid)
|
|
||||||
{
|
|
||||||
CurrentSong = response.Response.Content;
|
|
||||||
UpdateSong();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
|
||||||
Connect();
|
|
||||||
}
|
|
||||||
_isUpdatingSong = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> SafelySendCommandAsync<T>(IMpcCommand<T> command)
|
public async Task<T> SafelySendCommandAsync<T>(IMpcCommand<T> command)
|
||||||
{
|
{
|
||||||
|
if (_commandConnection == null)
|
||||||
|
{
|
||||||
|
Trace.WriteLine("[SafelySendCommandAsync] no command connection");
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _commandConnection.SendAsync(command);
|
IMpdMessage<T> response = await _commandConnection.SendAsync(command);
|
||||||
if (!response.IsResponseValid)
|
if (!response.IsResponseValid)
|
||||||
{
|
{
|
||||||
// If we have an MpdError string, only show that as the error to avoid extra noise
|
|
||||||
var mpdError = response.Response?.Result?.MpdError;
|
var mpdError = response.Response?.Result?.MpdError;
|
||||||
if (mpdError != null && mpdError != "")
|
if (mpdError != null && mpdError != "")
|
||||||
throw new Exception(mpdError);
|
throw new Exception(mpdError);
|
||||||
@ -320,66 +177,267 @@ namespace unison
|
|||||||
return default(T);
|
return default(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void GetAlbumBitmap(string path, CancellationToken token = default)
|
private void Initialize(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!_connected)
|
||||||
|
Connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Connect()
|
||||||
|
{
|
||||||
|
CancellationToken token = cancelToken.Token;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_connection = await ConnectInternal(token);
|
||||||
|
_commandConnection = await ConnectInternal(token);
|
||||||
|
}
|
||||||
|
catch(MpcNET.Exceptions.MpcConnectException)
|
||||||
|
{
|
||||||
|
_invalidIp = true;
|
||||||
|
}
|
||||||
|
if (_connection != null && _commandConnection != null)
|
||||||
|
{
|
||||||
|
_invalidIp = false;
|
||||||
|
if (_connection.IsConnected && _commandConnection.IsConnected)
|
||||||
|
{
|
||||||
|
_connected = true;
|
||||||
|
_version = _connection.Version;
|
||||||
|
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateStatusAsync();
|
||||||
|
await UpdateSongAsync();
|
||||||
|
|
||||||
|
Loop(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MpcConnection> ConnectInternal(CancellationToken token)
|
||||||
|
{
|
||||||
|
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out IPAddress ipAddress);
|
||||||
|
|
||||||
|
if (ipAddress == null)
|
||||||
|
{
|
||||||
|
IPAddress[] addrList;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
addrList = Dns.GetHostAddresses(Properties.Settings.Default.mpd_host);
|
||||||
|
if (addrList.Length > 0)
|
||||||
|
ipAddress = addrList[0];
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw new MpcNET.Exceptions.MpcConnectException("No correct IP provided by user.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mpdEndpoint = new IPEndPoint(ipAddress, Properties.Settings.Default.mpd_port);
|
||||||
|
MpcConnection connection = new MpcConnection(_mpdEndpoint);
|
||||||
|
await connection.ConnectAsync(token);
|
||||||
|
|
||||||
|
/*if (!string.IsNullOrEmpty(Properties.Settings.Default.mpd_password))
|
||||||
|
{
|
||||||
|
IMpdMessage<string> result = await connection.SendAsync(new PasswordCommand(Properties.Settings.Default.mpd_password));
|
||||||
|
if (!result.IsResponseValid)
|
||||||
|
{
|
||||||
|
string mpdError = result.Response?.Result?.MpdError;
|
||||||
|
Trace.WriteLine(mpdError);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Disconnected()
|
||||||
|
{
|
||||||
|
_connected = false;
|
||||||
|
|
||||||
|
_connection = null;
|
||||||
|
_commandConnection = null;
|
||||||
|
|
||||||
|
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Loop(CancellationToken token)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested || _connection == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options"));
|
||||||
|
|
||||||
|
if (idleChanges.IsResponseValid)
|
||||||
|
await HandleIdleResponseAsync(idleChanges.Response.Content);
|
||||||
|
else
|
||||||
|
throw new Exception(idleChanges.Response?.Content);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
Disconnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleIdleResponseAsync(string subsystems)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (subsystems.Contains("player") || subsystems.Contains("mixer") || subsystems.Contains("output") || subsystems.Contains("options"))
|
||||||
|
{
|
||||||
|
await UpdateStatusAsync();
|
||||||
|
|
||||||
|
if (subsystems.Contains("player"))
|
||||||
|
await UpdateSongAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateStatusAsync()
|
||||||
|
{
|
||||||
|
if (_connection == null || _isUpdatingStatus)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isUpdatingStatus = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IMpdMessage<MpdStatus> response = await _connection.SendAsync(new StatusCommand());
|
||||||
|
if (response != null && response.IsResponseValid)
|
||||||
|
{
|
||||||
|
_currentStatus = response.Response.Content;
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_isUpdatingStatus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateSongAsync()
|
||||||
|
{
|
||||||
|
if (_connection == null || _isUpdatingSong)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isUpdatingSong = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IMpdMessage<IMpdFile> response = await _connection.SendAsync(new CurrentSongCommand());
|
||||||
|
if (response != null && response.IsResponseValid)
|
||||||
|
{
|
||||||
|
_currentSong = response.Response.Content;
|
||||||
|
UpdateSong();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_isUpdatingSong = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void GetAlbumCover(string path, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
List<byte> data = new List<byte>();
|
List<byte> data = new List<byte>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_connection == null) // We got cancelled
|
|
||||||
return;
|
|
||||||
|
|
||||||
long totalBinarySize = 9999;
|
long totalBinarySize = 9999;
|
||||||
long currentSize = 0;
|
long currentSize = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
if (_connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
|
var albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
|
||||||
if (!albumReq.IsResponseValid) break;
|
if (!albumReq.IsResponseValid)
|
||||||
|
break;
|
||||||
|
|
||||||
var response = albumReq.Response.Content;
|
var response = albumReq.Response.Content;
|
||||||
if (response.Binary == 0) break; // MPD isn't giving us any more data, let's roll with what we have.
|
if (response.Binary == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
totalBinarySize = response.Size;
|
totalBinarySize = response.Size;
|
||||||
currentSize += response.Binary;
|
currentSize += response.Binary;
|
||||||
data.AddRange(response.Data);
|
data.AddRange(response.Data);
|
||||||
//Debug.WriteLine($"Downloading albumart: {currentSize}/{totalBinarySize}");
|
|
||||||
} while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
} while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.WriteLine("Exception caught while getting albumart: " + e);
|
Trace.WriteLine("Exception caught while getting albumart: " + e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Count == 0)
|
if (data.Count == 0)
|
||||||
{
|
|
||||||
Trace.WriteLine("empty cover");
|
|
||||||
_cover = null;
|
_cover = null;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using MemoryStream stream = new MemoryStream(data.ToArray());
|
using MemoryStream stream = new MemoryStream(data.ToArray());
|
||||||
|
try
|
||||||
|
{
|
||||||
_cover = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
_cover = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||||
}
|
}
|
||||||
|
catch (System.NotSupportedException)
|
||||||
|
{
|
||||||
|
_cover = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
UpdateCover();
|
UpdateCover();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSong()
|
public void UpdateStatus()
|
||||||
{
|
{
|
||||||
if (!_connected)
|
if (!_connected || _currentStatus == null)
|
||||||
return;
|
|
||||||
if (CurrentSong == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_currentTime = CurrentStatus.Elapsed.TotalSeconds;
|
_currentRandom = _currentStatus.Random;
|
||||||
_totalTime = CurrentSong.Time;
|
_currentRepeat = _currentStatus.Repeat;
|
||||||
|
_currentConsume = _currentStatus.Consume;
|
||||||
|
_currentSingle = _currentStatus.Single;
|
||||||
|
_currentVolume = _currentStatus.Volume;
|
||||||
|
|
||||||
|
StatusChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSong()
|
||||||
|
{
|
||||||
|
if (!_connected || _currentSong == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_currentTime = _currentStatus.Elapsed.TotalSeconds;
|
||||||
|
_totalTime = _currentSong.Time;
|
||||||
if (!_elapsedTimer.Enabled)
|
if (!_elapsedTimer.Enabled)
|
||||||
_elapsedTimer.Start();
|
_elapsedTimer.Start();
|
||||||
|
|
||||||
SongChanged?.Invoke(this, EventArgs.Empty);
|
SongChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
string uri = Regex.Escape(CurrentSong.Path);
|
string uri = Regex.Escape(_currentSong.Path);
|
||||||
GetAlbumBitmap(uri);
|
GetAlbumCover(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCover()
|
public void UpdateCover()
|
||||||
@ -387,40 +445,92 @@ namespace unison
|
|||||||
CoverChanged?.Invoke(this, EventArgs.Empty);
|
CoverChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatus()
|
public IMpdFile GetCurrentSong() => _currentSong;
|
||||||
{
|
public MpdStatus GetStatus() => _currentStatus;
|
||||||
if (!_connected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (CurrentStatus == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_currentRandom = CurrentStatus.Random;
|
|
||||||
_currentRepeat = CurrentStatus.Repeat;
|
|
||||||
_currentConsume = CurrentStatus.Consume;
|
|
||||||
_currentSingle = CurrentStatus.Single;
|
|
||||||
_currentVolume = CurrentStatus.Volume;
|
|
||||||
|
|
||||||
StatusChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IMpdFile GetCurrentSong() => CurrentSong;
|
|
||||||
public MpdStatus GetStatus() => CurrentStatus;
|
|
||||||
public BitmapFrame GetCover() => _cover;
|
public BitmapFrame GetCover() => _cover;
|
||||||
public string GetVersion() => _version;
|
public string GetVersion() => _version;
|
||||||
|
public Statistics GetStats() => _stats;
|
||||||
|
public double GetCurrentTime() => _currentTime;
|
||||||
|
|
||||||
public async void Prev() => await SafelySendCommandAsync(new PreviousCommand());
|
public bool IsConnected() => _connected;
|
||||||
public async void Next() => await SafelySendCommandAsync(new NextCommand());
|
public bool IsPlaying() => _currentStatus?.State == MpdState.Play;
|
||||||
public async void PlayPause() =>await SafelySendCommandAsync(new PauseResumeCommand());
|
|
||||||
|
|
||||||
public async void Random() => await SafelySendCommandAsync(new RandomCommand(!_currentRandom));
|
public void Prev() => SendCommand(new PreviousCommand());
|
||||||
public async void Repeat() => await SafelySendCommandAsync(new RepeatCommand(!_currentRepeat));
|
public void Next() => SendCommand(new NextCommand());
|
||||||
public async void Single() => await SafelySendCommandAsync(new SingleCommand(!_currentSingle));
|
public void PlayPause() => SendCommand(new PauseResumeCommand());
|
||||||
public async void Consume() => await SafelySendCommandAsync(new ConsumeCommand(!_currentConsume));
|
|
||||||
|
|
||||||
public async void SetVolume(int value) => await SafelySendCommandAsync(new SetVolumeCommand((byte)value));
|
public void Random() => SendCommand(new RandomCommand(!_currentRandom));
|
||||||
public async void SetTime(double value) => await SafelySendCommandAsync(new SeekCurCommand(value));
|
public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
|
||||||
|
public void Single() => SendCommand(new SingleCommand(!_currentSingle));
|
||||||
|
public void Consume() => SendCommand(new ConsumeCommand(!_currentConsume));
|
||||||
|
|
||||||
public bool IsPlaying() => CurrentStatus?.State == MpdState.Play;
|
public void SetTime(double value) => SendCommand(new SeekCurCommand(value));
|
||||||
|
public void SetVolume(int value) => SendCommand(new SetVolumeCommand((byte)value));
|
||||||
|
|
||||||
|
public void VolumeUp()
|
||||||
|
{
|
||||||
|
_currentVolume += Properties.Settings.Default.volume_offset;
|
||||||
|
if (_currentVolume > 100)
|
||||||
|
_currentVolume = 100;
|
||||||
|
SetVolume(_currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void VolumeDown()
|
||||||
|
{
|
||||||
|
_currentVolume -= Properties.Settings.Default.volume_offset;
|
||||||
|
if (_currentVolume < 0)
|
||||||
|
_currentVolume = 0;
|
||||||
|
SetVolume(_currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void VolumeMute()
|
||||||
|
{
|
||||||
|
if (_currentVolume == 0)
|
||||||
|
{
|
||||||
|
_currentVolume = _previousVolume;
|
||||||
|
_previousVolume = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_previousVolume = _currentVolume;
|
||||||
|
_currentVolume = 0;
|
||||||
|
}
|
||||||
|
SetVolume(_currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearQueue() => SendCommand(new ClearCommand());
|
||||||
|
public void PlayCommand() => SendCommand(new PlayCommand(0));
|
||||||
|
|
||||||
|
public void AddSong(string Uri)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("AddCommand path: " + Uri);
|
||||||
|
SendCommand(new AddCommand(Uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearAddAndPlay(string Uri)
|
||||||
|
{
|
||||||
|
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new ClearCommand(), new AddCommand(Uri), new PlayCommand(0) });
|
||||||
|
SendCommand(commandList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void QueryStats()
|
||||||
|
{
|
||||||
|
Dictionary<string, string> response = await SafelySendCommandAsync(new StatsCommand());
|
||||||
|
|
||||||
|
_stats.Songs = int.Parse(response["songs"]);
|
||||||
|
_stats.Albums = int.Parse(response["albums"]);
|
||||||
|
_stats.Artists = int.Parse(response["artists"]);
|
||||||
|
|
||||||
|
TimeSpan time;
|
||||||
|
time = TimeSpan.FromSeconds(int.Parse(response["uptime"]));
|
||||||
|
_stats.Uptime = time.ToString(@"dd\:hh\:mm\:ss");
|
||||||
|
time = TimeSpan.FromSeconds(int.Parse(response["db_playtime"]));
|
||||||
|
_stats.TotalPlaytime = time.ToString(@"dd\:hh\:mm\:ss");
|
||||||
|
time = TimeSpan.FromSeconds(int.Parse(response["playtime"]));
|
||||||
|
_stats.TotalTimePlayed = time.ToString(@"dd\:hh\:mm\:ss");
|
||||||
|
|
||||||
|
DateTime date = new DateTime(1970, 1, 1).AddSeconds(int.Parse(response["db_update"])).ToLocalTime();
|
||||||
|
_stats.DatabaseUpdate = date.ToString("dd/MM/yyyy @ HH:mm");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
|
||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using Hardcodet.Wpf.TaskbarNotification;
|
||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
@ -9,19 +8,15 @@ namespace unison
|
|||||||
public class SnapcastHandler
|
public class SnapcastHandler
|
||||||
{
|
{
|
||||||
private readonly Process _snapcast = new();
|
private readonly Process _snapcast = new();
|
||||||
public bool Started { get; private set; }
|
public bool HasStarted { get; private set; }
|
||||||
|
|
||||||
public SnapcastHandler()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnConnectionChanged(object sender, EventArgs e)
|
public void OnConnectionChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (Properties.Settings.Default.snapcast_startup)
|
if (Properties.Settings.Default.snapcast_startup)
|
||||||
{
|
{
|
||||||
var mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
var mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
if (mpd._connected)
|
if (mpd.IsConnected())
|
||||||
Start();
|
LaunchOrExit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,12 +30,12 @@ namespace unison
|
|||||||
{
|
{
|
||||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||||
MainWin.OnSnapcastChanged();
|
MainWin.OnSnapcastChanged();
|
||||||
}, DispatcherPriority.ContextIdle);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void LaunchOrExit(bool ForceExit = false)
|
||||||
{
|
{
|
||||||
if (!Started)
|
if (!HasStarted && !ForceExit)
|
||||||
{
|
{
|
||||||
_snapcast.StartInfo.FileName = Properties.Settings.Default.snapcast_path + @"\snapclient.exe";
|
_snapcast.StartInfo.FileName = Properties.Settings.Default.snapcast_path + @"\snapclient.exe";
|
||||||
_snapcast.StartInfo.Arguments = $"--host {Properties.Settings.Default.mpd_host}";
|
_snapcast.StartInfo.Arguments = $"--host {Properties.Settings.Default.mpd_host}";
|
||||||
@ -51,30 +46,23 @@ namespace unison
|
|||||||
}
|
}
|
||||||
catch (Exception err)
|
catch (Exception err)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"[Snapcast error]\nInvalid path: {err.Message}\n\nCurrent path: {Properties.Settings.Default.snapcast_path}\nYou can reset it in the settings if needed.",
|
MessageBox.Show($"[{unison.Resources.Resources.Snapcast_Popup1}]\n" +
|
||||||
|
$"{unison.Resources.Resources.Snapcast_Popup2} {err.Message}\n\n" +
|
||||||
|
$"{unison.Resources.Resources.Snapcast_Popup3} {Properties.Settings.Default.snapcast_path}\n" +
|
||||||
|
$"{unison.Resources.Resources.Snapcast_Popup4}",
|
||||||
"unison", MessageBoxButton.OK, MessageBoxImage.Error);
|
"unison", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
Trace.WriteLine(err.Message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Started = true;
|
HasStarted = true;
|
||||||
UpdateInterface();
|
|
||||||
}
|
}
|
||||||
else
|
else if (HasStarted)
|
||||||
{
|
{
|
||||||
_snapcast.Kill();
|
_snapcast.Kill();
|
||||||
Started = false;
|
HasStarted = false;
|
||||||
UpdateInterface();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
if (!ForceExit)
|
||||||
{
|
|
||||||
if (Started)
|
|
||||||
{
|
|
||||||
_snapcast.Kill();
|
|
||||||
Started = false;
|
|
||||||
UpdateInterface();
|
UpdateInterface();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
MIT License Copyright (c) 2021 Théo Marchal
|
MIT License Copyright (c) 2022 Théo Marchal
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
178
Properties/Settings.Designer.cs
generated
@ -73,7 +73,19 @@ namespace unison.Properties {
|
|||||||
|
|
||||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Configuration.DefaultSettingValueAttribute("snapclient_0.25.0-1_win64")]
|
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||||
|
public bool snapcast_window {
|
||||||
|
get {
|
||||||
|
return ((bool)(this["snapcast_window"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["snapcast_window"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("snapclient_0.26.0-1_win64")]
|
||||||
public string snapcast_path {
|
public string snapcast_path {
|
||||||
get {
|
get {
|
||||||
return ((string)(this["snapcast_path"]));
|
return ((string)(this["snapcast_path"]));
|
||||||
@ -109,13 +121,169 @@ namespace unison.Properties {
|
|||||||
|
|
||||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
[global::System.Configuration.DefaultSettingValueAttribute("2")]
|
||||||
public bool snapcast_window {
|
public uint nextTrack_mod {
|
||||||
get {
|
get {
|
||||||
return ((bool)(this["snapcast_window"]));
|
return ((uint)(this["nextTrack_mod"]));
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
this["snapcast_window"] = value;
|
this["nextTrack_mod"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("176")]
|
||||||
|
public uint nextTrack_vk {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["nextTrack_vk"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["nextTrack_vk"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("2")]
|
||||||
|
public uint previousTrack_mod {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["previousTrack_mod"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["previousTrack_mod"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("177")]
|
||||||
|
public uint previousTrack_vk {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["previousTrack_vk"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["previousTrack_vk"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("2")]
|
||||||
|
public uint playPause_mod {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["playPause_mod"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["playPause_mod"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("179")]
|
||||||
|
public uint playPause_vk {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["playPause_vk"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["playPause_vk"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("2")]
|
||||||
|
public uint volumeUp_mod {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["volumeUp_mod"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["volumeUp_mod"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("175")]
|
||||||
|
public uint volumeUp_vk {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["volumeUp_vk"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["volumeUp_vk"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("2")]
|
||||||
|
public uint volumeDown_mod {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["volumeDown_mod"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["volumeDown_mod"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("174")]
|
||||||
|
public uint volumeDown_vk {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["volumeDown_vk"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["volumeDown_vk"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("2")]
|
||||||
|
public uint volumeMute_mod {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["volumeMute_mod"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["volumeMute_mod"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("173")]
|
||||||
|
public uint volumeMute_vk {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["volumeMute_vk"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["volumeMute_vk"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("3")]
|
||||||
|
public uint showWindow_mod {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["showWindow_mod"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["showWindow_mod"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("13")]
|
||||||
|
public uint showWindow_vk {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["showWindow_vk"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["showWindow_vk"] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<Value Profile="(Default)">False</Value>
|
<Value Profile="(Default)">False</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
<Setting Name="snapcast_path" Type="System.String" Scope="User">
|
<Setting Name="snapcast_path" Type="System.String" Scope="User">
|
||||||
<Value Profile="(Default)">snapclient_0.25.0-1_win64</Value>
|
<Value Profile="(Default)">snapclient_0.26.0-1_win64</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
<Setting Name="snapcast_port" Type="System.Int32" Scope="User">
|
<Setting Name="snapcast_port" Type="System.Int32" Scope="User">
|
||||||
<Value Profile="(Default)">1704</Value>
|
<Value Profile="(Default)">1704</Value>
|
||||||
@ -26,5 +26,47 @@
|
|||||||
<Setting Name="volume_offset" Type="System.Int32" Scope="User">
|
<Setting Name="volume_offset" Type="System.Int32" Scope="User">
|
||||||
<Value Profile="(Default)">5</Value>
|
<Value Profile="(Default)">5</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
|
<Setting Name="nextTrack_mod" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">2</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="nextTrack_vk" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">176</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="previousTrack_mod" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">2</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="previousTrack_vk" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">177</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="playPause_mod" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">2</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="playPause_vk" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">179</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="volumeUp_mod" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">2</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="volumeUp_vk" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">175</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="volumeDown_mod" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">2</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="volumeDown_vk" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">174</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="volumeMute_mod" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">2</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="volumeMute_vk" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">173</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="showWindow_mod" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">3</Value>
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="showWindow_vk" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">13</Value>
|
||||||
|
</Setting>
|
||||||
</Settings>
|
</Settings>
|
||||||
</SettingsFile>
|
</SettingsFile>
|
48
README.md
@ -1,3 +1,49 @@
|
|||||||
# unison
|
# unison
|
||||||
|
|
||||||
mpd client
|

|
||||||
|
|
||||||
|
**unison** is a very simple [Music Player Daemon (MPD)](https://www.musicpd.org/) daemon client with the following goals:
|
||||||
|
|
||||||
|
* lightweight window that can be toggled with shortcuts
|
||||||
|
* music control through rebindable shortcuts
|
||||||
|
* [Snapcast](https://mjaggard.github.io/snapcast/) integration
|
||||||
|
* Radio stations
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Window
|
||||||
|
|
||||||
|
By default, unison works as a daemon in the taskbar system tray. You can display the main window when needed at any time with a shortcut.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Shortcuts
|
||||||
|
|
||||||
|
You can control your music at anytime with the shortcuts. They are usable system-wide, even if the window is not visible. They are of course fully rebindable.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Snapcast
|
||||||
|
|
||||||
|
Embedding a Snapcast client allows to listen to music on multiple devices. For example, if you music is on a distant server connected to speakers in your living room, you can still listen to it on your computer running unison with this integrated client.
|
||||||
|
|
||||||
|
### Radio stations
|
||||||
|
|
||||||
|
Through [Radio-Browser](https://www.radio-browser.info), a community database, you can play radio streams directly from unison. There are more than 28,000 stations recorded on this service, so it is a nice way to discover new music and cultures.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
### Missing features
|
||||||
|
|
||||||
|
* MPD passwords: I don't really see the point, but if asked, I will integrate them.
|
||||||
|
|
||||||
|
### Planned features
|
||||||
|
|
||||||
|
* A complete shuffle system based on set criteria, aka a smart playlist.
|
||||||
|
* Playlist, queue and library management. I use other software to do it, but I will implement them at some point.
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
unison is translated in English and French. You can contribute if you want!
|
594
Resources/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace unison.Resources {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
public class Resources {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("unison.Resources.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Exit.
|
||||||
|
/// </summary>
|
||||||
|
public static string Exit {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Exit", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Country.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_Country {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_Country", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Loading stations....
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_Loading {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_Loading", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Name.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_Name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_Name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No stations found!.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_NotFound {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_NotFound", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Reset.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_Reset {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_Reset", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Search.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_Search {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_Search", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Search station.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_SearchStation {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_SearchStation", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Tags.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radio_Tags {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radio_Tags", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Radios.
|
||||||
|
/// </summary>
|
||||||
|
public static string Radios {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Radios", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Settings.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to About.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_About {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_About", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to unison is a free software. It is built with the following technologies:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_AboutInfo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_AboutInfo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Connect.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ConnectButton {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ConnectButton", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Connection.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_Connection {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_Connection", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Connected to MPD.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ConnectionStatusConnected {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ConnectionStatusConnected", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Connecting....
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ConnectionStatusConnecting {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ConnectionStatusConnecting", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Not connected..
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ConnectionStatusOffline {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ConnectionStatusOffline", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Host.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_Host {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_Host", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to License.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_License {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_License", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Made by.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_MadeBy {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_MadeBy", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Next track.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_NextTrack {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_NextTrack", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Play / pause.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_PlayPause {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_PlayPause", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Port.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_Port {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_Port", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Previous track.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_PreviousTrack {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_PreviousTrack", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Shortcuts.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_Shortcuts {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_Shortcuts", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Please note that if the input key is not recognized, this is due to a limitation on how virtual keys work..
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ShortcutsInfo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ShortcutsInfo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Enter a key....
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ShortcutsKey {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ShortcutsKey", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Show window.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ShowWindow {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ShowWindow", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You can change to your own locally installed version of the Snapcast client with an.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastInfo1 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastInfo1", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to absolute .
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastInfo2 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastInfo2", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to path..
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastInfo3 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastInfo3", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch at startup.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastLauch {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastLauch", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Executable path.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastPath {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastPath", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Port.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastPort {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastPort", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Reset.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastResetButton {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastResetButton", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Show Snapcast window.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SnapcastWindow {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SnapcastWindow", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Source code freely available.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SourceCode1 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SourceCode1", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to here.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_SourceCode2 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_SourceCode2", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Version:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_Version {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_Version", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Volume down.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_VolumeDown {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_VolumeDown", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Volume mute.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_VolumeMute {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_VolumeMute", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Volume offset.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_VolumeOffset {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_VolumeOffset", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Volume up.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_VolumeUp {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_VolumeUp", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Show window.
|
||||||
|
/// </summary>
|
||||||
|
public static string ShowWindow {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ShowWindow", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Snapcast error.
|
||||||
|
/// </summary>
|
||||||
|
public static string Snapcast_Popup1 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Snapcast_Popup1", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Invalid path:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Snapcast_Popup2 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Snapcast_Popup2", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Current path:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Snapcast_Popup3 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Snapcast_Popup3", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You can reset it in the settings if needed..
|
||||||
|
/// </summary>
|
||||||
|
public static string Snapcast_Popup4 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Snapcast_Popup4", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Start Snapcast.
|
||||||
|
/// </summary>
|
||||||
|
public static string StartSnapcast {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("StartSnapcast", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Stats.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Albums:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats_Albums {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats_Albums", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Artists:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats_Artists {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats_Artists", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Last database update:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats_LastDatabaseUpdate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats_LastDatabaseUpdate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Songs:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats_Songs {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats_Songs", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Total playtime:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats_TotalPlaytime {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats_TotalPlaytime", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Total time played:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats_TotalTimePlayed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats_TotalTimePlayed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to MPD uptime:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Stats_Uptime {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Stats_Uptime", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Stop Snapcast.
|
||||||
|
/// </summary>
|
||||||
|
public static string StopSnapcast {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("StopSnapcast", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
297
Resources/Resources.fr-FR.resx
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Exit" xml:space="preserve">
|
||||||
|
<value>Quitter</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radios" xml:space="preserve">
|
||||||
|
<value>Radios</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Country" xml:space="preserve">
|
||||||
|
<value>Pays</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Loading" xml:space="preserve">
|
||||||
|
<value>Recherche de stations...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Name" xml:space="preserve">
|
||||||
|
<value>Nom</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_NotFound" xml:space="preserve">
|
||||||
|
<value>Aucun station trouvée !</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Reset" xml:space="preserve">
|
||||||
|
<value>Réinitialiser</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Search" xml:space="preserve">
|
||||||
|
<value>Chercher</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_SearchStation" xml:space="preserve">
|
||||||
|
<value>Recherche de station</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Tags" xml:space="preserve">
|
||||||
|
<value>Tags</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings" xml:space="preserve">
|
||||||
|
<value>Configuration</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_About" xml:space="preserve">
|
||||||
|
<value>À propos</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_AboutInfo" xml:space="preserve">
|
||||||
|
<value>unison est un logiciel libre. Il est développé avec les technologies suivantes :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectButton" xml:space="preserve">
|
||||||
|
<value>Connexion</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Connection" xml:space="preserve">
|
||||||
|
<value>Connexion</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||||
|
<value>Connecté à MPD</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||||
|
<value>Connexion en cours...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||||
|
<value>Non connecté.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Host" xml:space="preserve">
|
||||||
|
<value>Hôte</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_License" xml:space="preserve">
|
||||||
|
<value>Licence</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_MadeBy" xml:space="preserve">
|
||||||
|
<value>Créé par</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_NextTrack" xml:space="preserve">
|
||||||
|
<value>Piste suivante</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_PlayPause" xml:space="preserve">
|
||||||
|
<value>Jouer / pause</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Port" xml:space="preserve">
|
||||||
|
<value>Port</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_PreviousTrack" xml:space="preserve">
|
||||||
|
<value>Piste précédente</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Shortcuts" xml:space="preserve">
|
||||||
|
<value>Raccourcis</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShortcutsInfo" xml:space="preserve">
|
||||||
|
<value>Veuillez noter que si votre touche n'est pas reconnue, cela est du à la manière dont les touches virtuelles sont gérées par Windows.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShortcutsKey" xml:space="preserve">
|
||||||
|
<value>Appuyez sur une touche...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||||
|
<value>Afficher la fenêtre</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo1" xml:space="preserve">
|
||||||
|
<value>Il est possible de mettre votre version localement installé de Snapcast avec un </value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo2" xml:space="preserve">
|
||||||
|
<value>chemin absolu</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo3" xml:space="preserve">
|
||||||
|
<value>.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastLauch" xml:space="preserve">
|
||||||
|
<value>Lancer au démarrage</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastPath" xml:space="preserve">
|
||||||
|
<value>Chemin de l'exécutable</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastPort" xml:space="preserve">
|
||||||
|
<value>Port</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastResetButton" xml:space="preserve">
|
||||||
|
<value>Réinitialiser</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastWindow" xml:space="preserve">
|
||||||
|
<value>Afficher la fenêtre de Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SourceCode1" xml:space="preserve">
|
||||||
|
<value>Code source librement disponible</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SourceCode2" xml:space="preserve">
|
||||||
|
<value>ici</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Version" xml:space="preserve">
|
||||||
|
<value>Version :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeDown" xml:space="preserve">
|
||||||
|
<value>Baisse de volume</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeMute" xml:space="preserve">
|
||||||
|
<value>Volume en sourdine</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeOffset" xml:space="preserve">
|
||||||
|
<value>Écart de volume</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeUp" xml:space="preserve">
|
||||||
|
<value>Augmentation de volume</value>
|
||||||
|
</data>
|
||||||
|
<data name="ShowWindow" xml:space="preserve">
|
||||||
|
<value>Montrer la fenêtre</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup1" xml:space="preserve">
|
||||||
|
<value>Erreur Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup2" xml:space="preserve">
|
||||||
|
<value>Chemin invalide :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup3" xml:space="preserve">
|
||||||
|
<value>Chemin actuel :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup4" xml:space="preserve">
|
||||||
|
<value>Vous pouvez le réinitialiser dans la configuration.</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartSnapcast" xml:space="preserve">
|
||||||
|
<value>Démarrer Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats" xml:space="preserve">
|
||||||
|
<value>Stats</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Albums" xml:space="preserve">
|
||||||
|
<value>Albums :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Artists" xml:space="preserve">
|
||||||
|
<value>Artistes :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_LastDatabaseUpdate" xml:space="preserve">
|
||||||
|
<value>Mise à jour de la base de données :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Songs" xml:space="preserve">
|
||||||
|
<value>Morceaux :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_TotalPlaytime" xml:space="preserve">
|
||||||
|
<value>Temps total :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_TotalTimePlayed" xml:space="preserve">
|
||||||
|
<value>Temps d'écoute écoulé :</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Uptime" xml:space="preserve">
|
||||||
|
<value>MPD lancé depuis : </value>
|
||||||
|
</data>
|
||||||
|
<data name="StopSnapcast" xml:space="preserve">
|
||||||
|
<value>Stopper Snapcast</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
297
Resources/Resources.resx
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Exit" xml:space="preserve">
|
||||||
|
<value>Exit</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radios" xml:space="preserve">
|
||||||
|
<value>Radios</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Country" xml:space="preserve">
|
||||||
|
<value>Country</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Loading" xml:space="preserve">
|
||||||
|
<value>Loading stations...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Name" xml:space="preserve">
|
||||||
|
<value>Name</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_NotFound" xml:space="preserve">
|
||||||
|
<value>No stations found!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Reset" xml:space="preserve">
|
||||||
|
<value>Reset</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Search" xml:space="preserve">
|
||||||
|
<value>Search</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_SearchStation" xml:space="preserve">
|
||||||
|
<value>Search station</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Tags" xml:space="preserve">
|
||||||
|
<value>Tags</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings" xml:space="preserve">
|
||||||
|
<value>Settings</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_About" xml:space="preserve">
|
||||||
|
<value>About</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_AboutInfo" xml:space="preserve">
|
||||||
|
<value>unison is a free software. It is built with the following technologies:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectButton" xml:space="preserve">
|
||||||
|
<value>Connect</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Connection" xml:space="preserve">
|
||||||
|
<value>Connection</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||||
|
<value>Connected to MPD</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||||
|
<value>Connecting...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||||
|
<value>Not connected.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Host" xml:space="preserve">
|
||||||
|
<value>Host</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_License" xml:space="preserve">
|
||||||
|
<value>License</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_MadeBy" xml:space="preserve">
|
||||||
|
<value>Made by</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_NextTrack" xml:space="preserve">
|
||||||
|
<value>Next track</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_PlayPause" xml:space="preserve">
|
||||||
|
<value>Play / pause</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Port" xml:space="preserve">
|
||||||
|
<value>Port</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_PreviousTrack" xml:space="preserve">
|
||||||
|
<value>Previous track</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Shortcuts" xml:space="preserve">
|
||||||
|
<value>Shortcuts</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShortcutsInfo" xml:space="preserve">
|
||||||
|
<value>Please note that if the input key is not recognized, this is due to a limitation on how virtual keys work.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShortcutsKey" xml:space="preserve">
|
||||||
|
<value>Enter a key...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||||
|
<value>Show window</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo1" xml:space="preserve">
|
||||||
|
<value>You can change to your own locally installed version of the Snapcast client with an</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo2" xml:space="preserve">
|
||||||
|
<value> absolute </value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo3" xml:space="preserve">
|
||||||
|
<value>path.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastLauch" xml:space="preserve">
|
||||||
|
<value>Launch at startup</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastPath" xml:space="preserve">
|
||||||
|
<value>Executable path</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastPort" xml:space="preserve">
|
||||||
|
<value>Port</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastResetButton" xml:space="preserve">
|
||||||
|
<value>Reset</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastWindow" xml:space="preserve">
|
||||||
|
<value>Show Snapcast window</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SourceCode1" xml:space="preserve">
|
||||||
|
<value>Source code freely available</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SourceCode2" xml:space="preserve">
|
||||||
|
<value>here</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Version" xml:space="preserve">
|
||||||
|
<value>Version:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeDown" xml:space="preserve">
|
||||||
|
<value>Volume down</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeMute" xml:space="preserve">
|
||||||
|
<value>Volume mute</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeOffset" xml:space="preserve">
|
||||||
|
<value>Volume offset</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeUp" xml:space="preserve">
|
||||||
|
<value>Volume up</value>
|
||||||
|
</data>
|
||||||
|
<data name="ShowWindow" xml:space="preserve">
|
||||||
|
<value>Show window</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup1" xml:space="preserve">
|
||||||
|
<value>Snapcast error</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup2" xml:space="preserve">
|
||||||
|
<value>Invalid path:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup3" xml:space="preserve">
|
||||||
|
<value>Current path:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup4" xml:space="preserve">
|
||||||
|
<value>You can reset it in the settings if needed.</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartSnapcast" xml:space="preserve">
|
||||||
|
<value>Start Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats" xml:space="preserve">
|
||||||
|
<value>Stats</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Albums" xml:space="preserve">
|
||||||
|
<value>Albums:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Artists" xml:space="preserve">
|
||||||
|
<value>Artists:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_LastDatabaseUpdate" xml:space="preserve">
|
||||||
|
<value>Last database update:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Songs" xml:space="preserve">
|
||||||
|
<value>Songs:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_TotalPlaytime" xml:space="preserve">
|
||||||
|
<value>Total playtime:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_TotalTimePlayed" xml:space="preserve">
|
||||||
|
<value>Total time played:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Uptime" xml:space="preserve">
|
||||||
|
<value>MPD uptime:</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopSnapcast" xml:space="preserve">
|
||||||
|
<value>Stop Snapcast</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
28
Resources/Resources.xaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:sys="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
|
<system:String x:Key="snapcastPath">snapclient_0.26.0-1_win64</system:String>
|
||||||
|
<system:String x:Key="snapcastPort">1704</system:String>
|
||||||
|
|
||||||
|
<system:String x:Key="playButton"></system:String>
|
||||||
|
<system:String x:Key="pauseButton"></system:String>
|
||||||
|
|
||||||
|
<system:String x:Key="volume_offset">5</system:String>
|
||||||
|
|
||||||
|
<system:UInt32 x:Key="nextTrack_mod">2</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="nextTrack_vk">176</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="previousTrack_mod">2</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="previousTrack_vk">177</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="playPause_mod">2</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="playPause_vk">179</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="volumeUp_mod">2</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="volumeUp_vk">175</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="volumeDown_mod">2</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="volumeDown_vk">174</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="volumeMute_mod">2</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="volumeMute_vk">173</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="showWindow_mod">3</system:UInt32>
|
||||||
|
<system:UInt32 x:Key="showWindow_vk">13</system:UInt32>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 294 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
Screenshots/screen1.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
Screenshots/screen2.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
Screenshots/screen3.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Screenshots/screen4.png
Normal file
After Width: | Height: | Size: 21 KiB |
@ -4,11 +4,9 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||||
xmlns:clr="clr-namespace:System;assembly=mscorlib"
|
xmlns:properties="clr-namespace:unison.Resources"
|
||||||
xmlns:local="clr-namespace:unison"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="unison"
|
Title="unison" Closing="Window_Closing" Icon="/Resources/icon-full.ico" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">
|
||||||
Closing="Window_Closing" Icon="/images/icon-full.ico" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">
|
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<Style TargetType="Border" x:Key="UnselectedButton">
|
<Style TargetType="Border" x:Key="UnselectedButton">
|
||||||
@ -19,7 +17,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}" MinHeight="270">
|
<Grid Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}" MinHeight="270" MinWidth="650">
|
||||||
<Grid x:Name="TopLayout" Margin="10,0,10,0" VerticalAlignment="Stretch" Width="Auto" Height="Auto">
|
<Grid x:Name="TopLayout" Margin="10,0,10,0" VerticalAlignment="Stretch" Width="Auto" Height="Auto">
|
||||||
<Grid x:Name="Display" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="225,0,0,0" Height="Auto" Width="Auto">
|
<Grid x:Name="Display" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="225,0,0,0" Height="Auto" Width="Auto">
|
||||||
<GroupBox Height="220" VerticalAlignment="Center">
|
<GroupBox Height="220" VerticalAlignment="Center">
|
||||||
@ -30,14 +28,14 @@
|
|||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid x:Name="CurrentSong" Margin="10,0,10,0" VerticalAlignment="Top" MinHeight="80">
|
<Grid x:Name="CurrentSong" Margin="10,0,10,0" VerticalAlignment="Top" MinHeight="80">
|
||||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center" MouseDown="MouseDownClipboard">
|
||||||
<TextBlock x:Name="SongTitle" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="20" Text="Title"/>
|
<TextBlock x:Name="SongTitle" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="20" Text="Title"/>
|
||||||
<TextBlock x:Name="SongArtist" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Bold" FontSize="18" Text="Artist"/>
|
<TextBlock x:Name="SongArtist" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Bold" FontSize="18" Text="Artist"/>
|
||||||
<TextBlock x:Name="SongAlbum" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="16" Text="Album"/>
|
<TextBlock x:Name="SongAlbum" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="16" Text="Album"/>
|
||||||
<TextBlock TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" Margin="0,2,0,0">
|
<TextBlock x:Name="SongInfo" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" Margin="0,2,0,0">
|
||||||
<Run x:Name="SongGenre"/>
|
<Run x:Name="SongGenre"/>
|
||||||
<Run> – </Run>
|
<Run x:Name="SongInfoDash"> – </Run>
|
||||||
<Run x:Name="Format"/>
|
<Run x:Name="SongFormat"/>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -63,7 +61,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Grid VerticalAlignment="Stretch" Margin="0,18,0,0">
|
<Grid VerticalAlignment="Stretch" Margin="0,18,0,0">
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0,2,0,0">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="10,2,10,0">
|
||||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="16"/>
|
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="16"/>
|
||||||
<Slider x:Name="VolumeSlider" Maximum="100" Value="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="180" FlowDirection="LeftToRight" PreviewMouseUp="VolumeSlider_DragCompleted" FocusVisualStyle="{x:Null}"/>
|
<Slider x:Name="VolumeSlider" Maximum="100" Value="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="180" FlowDirection="LeftToRight" PreviewMouseUp="VolumeSlider_DragCompleted" FocusVisualStyle="{x:Null}"/>
|
||||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="7.5,0,0,0" FontSize="16"/>
|
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="7.5,0,0,0" FontSize="16"/>
|
||||||
@ -105,20 +103,37 @@
|
|||||||
<StackPanel.OpacityMask>
|
<StackPanel.OpacityMask>
|
||||||
<VisualBrush Visual="{Binding ElementName=mask}"/>
|
<VisualBrush Visual="{Binding ElementName=mask}"/>
|
||||||
</StackPanel.OpacityMask>
|
</StackPanel.OpacityMask>
|
||||||
<Image x:Name="Cover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/images/nocover.png" Visibility="Collapsed" />
|
<Image x:Name="Cover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" Visibility="Collapsed" />
|
||||||
<Image x:Name="NoCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/images/nocover.png" />
|
<Image x:Name="NoCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" Width="Auto" MinHeight="40">
|
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" Width="Auto" MinHeight="40">
|
||||||
|
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
||||||
<Button x:Name="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="10,0,0,0" Padding="5, 2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
|
<Button x:Name="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="10,0,0,0" Padding="5, 2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
|
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
|
||||||
<TextBlock x:Name="SnapcastText" Text="Start Snapcast" Margin="5, 0, 0, 0"/>
|
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<TextBlock x:Name="Connection" HorizontalAlignment="Center" Text="Not connected" TextWrapping="Wrap" VerticalAlignment="Center" TextAlignment="Center" MinWidth="350" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}"/>
|
<Button x:Name="Radio" Padding="5, 2" HorizontalAlignment="Left" Click="Radios_Clicked" Margin="5,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<emoji:TextBlock Text="📻" Padding="0,0,0,2"/>
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Radios}" Margin="5, 0, 0, 0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Grid x:Name="ConnectionOkIcon" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||||
|
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||||
|
</Grid>
|
||||||
|
<Grid x:Name="ConnectionFailIcon" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||||
|
</Grid>
|
||||||
|
<TextBlock x:Name="Connection" HorizontalAlignment="Center" Text="Not connected" TextWrapping="Wrap" VerticalAlignment="Top" TextAlignment="Center" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" Margin="5,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
||||||
<!--<Button x:Name="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
<!--<Button x:Name="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
@ -129,7 +144,7 @@
|
|||||||
<Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}">
|
<Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<emoji:TextBlock Text="🛠️" Padding="0,0,0,2"/>
|
<emoji:TextBlock Text="🛠️" Padding="0,0,0,2"/>
|
||||||
<TextBlock Text="Settings" Margin="5, 0, 0, 0"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings}" Margin="5, 0, 0, 0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
@ -9,35 +9,188 @@ using System.Windows.Controls.Primitives;
|
|||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
public partial class MainWindow : Window, INotifyPropertyChanged
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
public readonly Settings SettingsWindow = new Settings();
|
private readonly Settings _settingsWin;
|
||||||
|
private readonly Radios _radiosWin;
|
||||||
private readonly MPDHandler mpd;
|
private readonly DispatcherTimer _timer;
|
||||||
|
private readonly MPDHandler _mpd;
|
||||||
DispatcherTimer timer = new DispatcherTimer();
|
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitHwnd();
|
InitHwnd();
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
DefaultState(true);
|
||||||
WindowState = WindowState.Minimized;
|
WindowState = WindowState.Minimized;
|
||||||
|
|
||||||
mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
_settingsWin = new Settings();
|
||||||
|
_radiosWin = new Radios();
|
||||||
|
_timer = new DispatcherTimer();
|
||||||
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
|
||||||
timer.Interval = TimeSpan.FromSeconds(0.5);
|
_timer.Interval = TimeSpan.FromSeconds(0.5);
|
||||||
timer.Tick += Timer_Tick;
|
_timer.Tick += Timer_Tick;
|
||||||
timer.Start();
|
_timer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Timer_Tick(object sender, EventArgs e)
|
private void Timer_Tick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (mpd.GetCurrentSong() == null)
|
if (_mpd.GetCurrentSong() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CurrentTime.Text = FormatSeconds(mpd._currentTime);
|
CurrentTime.Text = FormatSeconds(_mpd.GetCurrentTime());
|
||||||
TimeSlider.Value = mpd._currentTime / mpd.GetCurrentSong().Time * 100;
|
TimeSlider.Value = _mpd.GetCurrentTime() / _mpd.GetCurrentSong().Time * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConnectionChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_mpd.IsConnected())
|
||||||
|
{
|
||||||
|
Snapcast.IsEnabled = true;
|
||||||
|
ConnectionOkIcon.Visibility = Visibility.Visible;
|
||||||
|
ConnectionFailIcon.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
DefaultState(true);
|
||||||
|
ConnectionOkIcon.Visibility = Visibility.Collapsed;
|
||||||
|
ConnectionFailIcon.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
_settingsWin.UpdateConnectionStatus();
|
||||||
|
Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSongChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_mpd.GetCurrentSong() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_mpd.GetCurrentSong().HasTitle && _mpd.GetCurrentSong().Title.Length > 0)
|
||||||
|
SongTitle.Text = _mpd.GetCurrentSong().Title;
|
||||||
|
else if (_mpd.GetCurrentSong().HasName && _mpd.GetCurrentSong().Name.Length > 0)
|
||||||
|
SongTitle.Text = _mpd.GetCurrentSong().Name;
|
||||||
|
else if (_mpd.GetCurrentSong().Path != null)
|
||||||
|
{
|
||||||
|
int start = _mpd.GetCurrentSong().Path.LastIndexOf("/") + 1;
|
||||||
|
int end = _mpd.GetCurrentSong().Path.LastIndexOf(".");
|
||||||
|
if (start > 0 && end > 0 && end > start)
|
||||||
|
SongTitle.Text = _mpd.GetCurrentSong().Path.Substring(start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
SongTitle.ToolTip = _mpd.GetCurrentSong().Path;
|
||||||
|
SongArtist.Text = _mpd.GetCurrentSong().Artist;
|
||||||
|
SongAlbum.Text = _mpd.GetCurrentSong().Album;
|
||||||
|
|
||||||
|
if (_mpd.GetCurrentSong().Date != null)
|
||||||
|
SongAlbum.Text += $" ({ _mpd.GetCurrentSong().Date})";
|
||||||
|
|
||||||
|
SongGenre.Text = _mpd.GetCurrentSong().Genre;
|
||||||
|
SongFormat.Text = _mpd.GetCurrentSong().Path.Substring(_mpd.GetCurrentSong().Path.LastIndexOf(".") + 1);
|
||||||
|
if (SongGenre.Text.Length == 0 || SongFormat.Text.Length == 0)
|
||||||
|
SongInfoDash.Text = "";
|
||||||
|
else
|
||||||
|
SongInfoDash.Text = " – ";
|
||||||
|
|
||||||
|
TimeSlider.IsEnabled = true;
|
||||||
|
if (_mpd.GetCurrentSong().Time == -1)
|
||||||
|
{
|
||||||
|
CurrentTime.Text = "";
|
||||||
|
EndTime.Text = "";
|
||||||
|
_timer.Stop();
|
||||||
|
TimeSlider.Value = 50;
|
||||||
|
TimeSlider.IsEnabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_timer.IsEnabled)
|
||||||
|
_timer.Start();
|
||||||
|
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStatusChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_mpd.GetStatus() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (VolumeSlider.Value != _mpd.GetStatus().Volume)
|
||||||
|
{
|
||||||
|
VolumeSlider.Value = _mpd.GetStatus().Volume;
|
||||||
|
VolumeSlider.ToolTip = _mpd.GetStatus().Volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateButton(ref BorderRandom, _mpd.GetStatus().Random);
|
||||||
|
UpdateButton(ref BorderRepeat, _mpd.GetStatus().Repeat);
|
||||||
|
UpdateButton(ref BorderSingle, _mpd.GetStatus().Single);
|
||||||
|
UpdateButton(ref BorderConsume, _mpd.GetStatus().Consume);
|
||||||
|
|
||||||
|
if (_mpd.IsPlaying())
|
||||||
|
PlayPause.Text = (string)Application.Current.FindResource("playButton");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlayPause.Text = (string)Application.Current.FindResource("pauseButton");
|
||||||
|
if (_mpd.GetStatus().State == MpcNET.MpdState.Stop)
|
||||||
|
{
|
||||||
|
DefaultState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mpd.QueryStats();
|
||||||
|
_settingsWin.UpdateStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DefaultState(bool LostConnection = false)
|
||||||
|
{
|
||||||
|
SongTitle.Text = "";
|
||||||
|
SongArtist.Text = "";
|
||||||
|
SongAlbum.Text = "";
|
||||||
|
SongGenre.Text = "";
|
||||||
|
SongInfoDash.Text = "";
|
||||||
|
SongFormat.Text = "";
|
||||||
|
CurrentTime.Text = "";
|
||||||
|
EndTime.Text = "";
|
||||||
|
PlayPause.Text = (string)Application.Current.FindResource("pauseButton");
|
||||||
|
TimeSlider.Value = 50;
|
||||||
|
TimeSlider.IsEnabled = false;
|
||||||
|
NoCover.Visibility = Visibility.Visible;
|
||||||
|
Cover.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
if (LostConnection)
|
||||||
|
{
|
||||||
|
ConnectionOkIcon.Visibility = Visibility.Collapsed;
|
||||||
|
ConnectionFailIcon.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCoverChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_mpd.GetCover() == null)
|
||||||
|
{
|
||||||
|
NoCover.Visibility = Visibility.Visible;
|
||||||
|
Cover.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
else if (Cover.Source != _mpd.GetCover())
|
||||||
|
{
|
||||||
|
Cover.Source = _mpd.GetCover();
|
||||||
|
Cover.Visibility = Visibility.Visible;
|
||||||
|
NoCover.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSnapcastChanged()
|
||||||
|
{
|
||||||
|
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||||
|
if (snapcast.HasStarted)
|
||||||
|
SnapcastText.Text = unison.Resources.Resources.StopSnapcast;
|
||||||
|
else
|
||||||
|
SnapcastText.Text = unison.Resources.Resources.StartSnapcast;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRadioBrowserConnected()
|
||||||
|
{
|
||||||
|
Radio.IsEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateButton(ref Border border, bool b)
|
public void UpdateButton(ref Border border, bool b)
|
||||||
@ -56,119 +209,43 @@ namespace unison
|
|||||||
return timespan.ToString(@"mm\:ss");
|
return timespan.ToString(@"mm\:ss");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnConnectionChanged(object sender, EventArgs e)
|
public void Pause_Clicked(object sender, RoutedEventArgs e) => _mpd.PlayPause();
|
||||||
{
|
public void Previous_Clicked(object sender, RoutedEventArgs e) => _mpd.Prev();
|
||||||
Connection.Text = (mpd._connected ? "✔️" : "❌") + $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
|
public void Next_Clicked(object sender, RoutedEventArgs e) => _mpd.Next();
|
||||||
SettingsWindow.UpdateConnectionStatus();
|
|
||||||
if (mpd._connected)
|
|
||||||
Snapcast.IsEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnSongChanged(object sender, EventArgs e)
|
public void Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
|
||||||
{
|
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
|
||||||
if (mpd.GetCurrentSong() == null)
|
public void Single_Clicked(object sender, RoutedEventArgs e) => _mpd.Single();
|
||||||
return;
|
public void Consume_Clicked(object sender, RoutedEventArgs e) => _mpd.Consume();
|
||||||
|
public void ChangeVolume(int value) => _mpd.SetVolume(value);
|
||||||
if (!mpd.GetCurrentSong().HasName)
|
|
||||||
SongTitle.Text = mpd.GetCurrentSong().Title;
|
|
||||||
else
|
|
||||||
SongTitle.Text = mpd.GetCurrentSong().Title;
|
|
||||||
SongTitle.ToolTip = mpd.GetCurrentSong().Path;
|
|
||||||
SongArtist.Text = mpd.GetCurrentSong().Artist;
|
|
||||||
SongAlbum.Text = mpd.GetCurrentSong().Album;
|
|
||||||
SongGenre.Text = mpd.GetCurrentSong().Genre;
|
|
||||||
|
|
||||||
if (mpd.GetCurrentSong().Date != null)
|
|
||||||
SongAlbum.Text += $" ({ mpd.GetCurrentSong().Date})";
|
|
||||||
Format.Text = mpd.GetCurrentSong().Path.Substring(mpd.GetCurrentSong().Path.LastIndexOf(".") + 1);
|
|
||||||
|
|
||||||
EndTime.Text = FormatSeconds(mpd.GetCurrentSong().Time);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnStatusChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (mpd.GetStatus() == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (VolumeSlider.Value != mpd._currentVolume)
|
|
||||||
{
|
|
||||||
VolumeSlider.Value = mpd._currentVolume;
|
|
||||||
VolumeSlider.ToolTip = mpd._currentVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mpd.IsPlaying())
|
|
||||||
PlayPause.Text = "\xedb4";
|
|
||||||
else
|
|
||||||
PlayPause.Text = "\xedb5";
|
|
||||||
|
|
||||||
UpdateButton(ref BorderRandom, mpd._currentRandom);
|
|
||||||
UpdateButton(ref BorderRepeat, mpd._currentRepeat);
|
|
||||||
UpdateButton(ref BorderSingle, mpd._currentSingle);
|
|
||||||
UpdateButton(ref BorderConsume, mpd._currentConsume);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnCoverChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (mpd.GetCover() == null)
|
|
||||||
{
|
|
||||||
NoCover.Visibility = Visibility.Visible;
|
|
||||||
Cover.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
else if (Cover.Source != mpd.GetCover())
|
|
||||||
{
|
|
||||||
Cover.Source = mpd.GetCover();
|
|
||||||
Cover.Visibility = Visibility.Visible;
|
|
||||||
NoCover.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnSnapcastChanged()
|
|
||||||
{
|
|
||||||
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
|
||||||
if (snapcast.Started)
|
|
||||||
SnapcastText.Text = "Stop Snapcast";
|
|
||||||
else
|
|
||||||
SnapcastText.Text = "Start Snapcast";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Pause_Clicked(object sender, RoutedEventArgs e) => mpd.PlayPause();
|
|
||||||
public void Previous_Clicked(object sender, RoutedEventArgs e) => mpd.Prev();
|
|
||||||
public void Next_Clicked(object sender, RoutedEventArgs e) => mpd.Next();
|
|
||||||
|
|
||||||
public void Random_Clicked(object sender, RoutedEventArgs e) => mpd.Random();
|
|
||||||
public void Repeat_Clicked(object sender, RoutedEventArgs e) => mpd.Repeat();
|
|
||||||
public void Single_Clicked(object sender, RoutedEventArgs e) => mpd.Single();
|
|
||||||
public void Consume_Clicked(object sender, RoutedEventArgs e) => mpd.Consume();
|
|
||||||
public void ChangeVolume(int value) => mpd.SetVolume(value);
|
|
||||||
|
|
||||||
public void Snapcast_Clicked(object sender, RoutedEventArgs e)
|
public void Snapcast_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||||
if (!snapcast.Started)
|
snapcast.LaunchOrExit();
|
||||||
snapcast.Start();
|
}
|
||||||
else
|
|
||||||
snapcast.Stop();
|
public void Radios_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_radiosWin.Show();
|
||||||
|
_radiosWin.Activate();
|
||||||
|
|
||||||
|
if (_radiosWin.WindowState == WindowState.Minimized)
|
||||||
|
_radiosWin.WindowState = WindowState.Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Settings_Clicked(object sender, RoutedEventArgs e)
|
public void Settings_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
SettingsWindow.Show();
|
_settingsWin.Show();
|
||||||
SettingsWindow.Activate();
|
_settingsWin.Activate();
|
||||||
|
|
||||||
if (SettingsWindow.WindowState == WindowState.Minimized)
|
if (_settingsWin.WindowState == WindowState.Minimized)
|
||||||
SettingsWindow.WindowState = WindowState.Normal;
|
_settingsWin.WindowState = WindowState.Normal;
|
||||||
}
|
|
||||||
|
|
||||||
private void Window_Closing(object sender, CancelEventArgs e)
|
|
||||||
{
|
|
||||||
e.Cancel = true;
|
|
||||||
WindowState = WindowState.Minimized;
|
|
||||||
Hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimeSlider_DragStarted(object sender, DragStartedEventArgs e)
|
private void TimeSlider_DragStarted(object sender, DragStartedEventArgs e)
|
||||||
{
|
{
|
||||||
timer.Stop();
|
_timer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimeSlider_DragCompleted(object sender, MouseButtonEventArgs e)
|
private void TimeSlider_DragCompleted(object sender, MouseButtonEventArgs e)
|
||||||
@ -176,18 +253,18 @@ namespace unison
|
|||||||
Slider slider = (Slider)sender;
|
Slider slider = (Slider)sender;
|
||||||
|
|
||||||
double SongPercentage = slider.Value;
|
double SongPercentage = slider.Value;
|
||||||
double SongTime = mpd._totalTime;
|
double SongTime = _mpd.GetCurrentSong().Time;
|
||||||
double SeekTime = SongPercentage / 100 * SongTime;
|
double SeekTime = SongPercentage / 100 * SongTime;
|
||||||
|
|
||||||
mpd.SetTime(SeekTime);
|
_mpd.SetTime(SeekTime);
|
||||||
timer.Start();
|
_timer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VolumeSlider_DragCompleted(object sender, MouseButtonEventArgs e)
|
private void VolumeSlider_DragCompleted(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
Slider slider = (Slider)sender;
|
Slider slider = (Slider)sender;
|
||||||
mpd.SetVolume((int)slider.Value);
|
_mpd.SetVolume((int)slider.Value);
|
||||||
slider.ToolTip = mpd._currentVolume;
|
slider.ToolTip = (int)slider.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnSourceInitialized(EventArgs e)
|
protected override void OnSourceInitialized(EventArgs e)
|
||||||
@ -197,17 +274,28 @@ namespace unison
|
|||||||
hk.Activate(this);
|
hk.Activate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MouseDownClipboard(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ClickCount == 2)
|
||||||
|
{
|
||||||
|
string CopyText = SongTitle.Text + " - " + SongArtist.Text + "\n";
|
||||||
|
CopyText += SongAlbum.Text + "\n";
|
||||||
|
CopyText += SongTitle.ToolTip;
|
||||||
|
Clipboard.SetText(CopyText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void InitHwnd()
|
public void InitHwnd()
|
||||||
{
|
{
|
||||||
WindowInteropHelper helper = new(this);
|
WindowInteropHelper helper = new(this);
|
||||||
helper.EnsureHandle();
|
helper.EnsureHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
private void Window_Closing(object sender, CancelEventArgs e)
|
||||||
|
|
||||||
protected virtual void OnPropertyChanged(string propertyName)
|
|
||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
e.Cancel = true;
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
74
Views/Radios.xaml
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<Window x:Class="unison.Radios"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||||
|
xmlns:properties="clr-namespace:unison.Resources"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Radios" Closing="Window_Closing" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="5,0,5,0">
|
||||||
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock>
|
||||||
|
<emoji:EmojiInline Text="📻"/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Radio_SearchStation}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<StackPanel Orientation="Vertical" Margin="5,0,5,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Radio_Name}" Margin="0,0,0,5"/>
|
||||||
|
<TextBox x:Name="NameSearch" KeyDown="SearchHandler" Width="200" Margin="0,4,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" Margin="20,0,0,0">
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Radio_Tags}" Margin="0,0,0,5"/>
|
||||||
|
<TextBox x:Name="TagSearch" KeyDown="SearchHandler" Width="300" Margin="0,4,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" Margin="20,0,0,0">
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Radio_Country}" Margin="0,0,0,5"/>
|
||||||
|
<ComboBox x:Name="CountryList" SelectedIndex="0" KeyDown="SearchHandler" Width="240" ScrollViewer.CanContentScroll="False"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||||
|
<Button Content="{x:Static properties:Resources.Radio_Search}" Click="Search_Clicked" Padding="5, 2"/>
|
||||||
|
<Button Content="{x:Static properties:Resources.Radio_Reset}" Click="Reset_Clicked" Margin="10,0,0,0" Padding="5, 2"/>
|
||||||
|
<TextBlock x:Name="SearchStatus" Margin="15,1,0,0" FontStyle="Italic" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid Margin="5,10,5,5" MaxHeight="600" MinWidth="800" MaxWidth="800">
|
||||||
|
<Grid.Resources>
|
||||||
|
<DataTemplate x:Key="CountryTemplate">
|
||||||
|
<emoji:TextBlock TextAlignment="Center" Text="{Binding Country}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</Grid.Resources>
|
||||||
|
<DataGrid Name="RadioListGrid" MouseDoubleClick="Row_DoubleClick" CanUserAddRows="False" CanUserDeleteRows="False"
|
||||||
|
CanUserReorderColumns="False" CanUserResizeRows="False" IsReadOnly="True" SelectionMode="Single"
|
||||||
|
HeadersVisibility="Column" GridLinesVisibility="None" VirtualizingPanel.ScrollUnit="Pixel">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Header="🏳️" CellTemplate="{StaticResource CountryTemplate}" MinWidth="25" />
|
||||||
|
<DataGridTextColumn Header="{x:Static properties:Resources.Radio_Name}" Binding="{Binding Name}" MinWidth="50"/>
|
||||||
|
<DataGridTextColumn Header="Codec" Binding="{Binding Codec}" MinWidth="47"/>
|
||||||
|
<DataGridTextColumn Header="Bitrate" Binding="{Binding Bitrate}" MinWidth="47"/>
|
||||||
|
<DataGridTextColumn Header="Tags" Binding="{Binding Tags}" MinWidth="50"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
<DataGrid.CellStyle>
|
||||||
|
<Style TargetType="DataGridCell">
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.CellStyle>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
208
Views/Radios.xaml.cs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
using RadioBrowser;
|
||||||
|
using RadioBrowser.Models;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace unison
|
||||||
|
{
|
||||||
|
public class CountryListItem
|
||||||
|
{
|
||||||
|
public uint Count { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (Name == "")
|
||||||
|
return "None";
|
||||||
|
return $"{Name} ({Count})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StationListItem
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Codec { get; set; }
|
||||||
|
public string Tags { get; set; }
|
||||||
|
public int Bitrate { get; set; }
|
||||||
|
public Uri Url { get; set; }
|
||||||
|
|
||||||
|
private string _country;
|
||||||
|
public string Country
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_country.Length == 0)
|
||||||
|
return "🏴☠️";
|
||||||
|
return string.Concat(_country.ToUpper().Select(x => char.ConvertFromUtf32(x + 0x1F1A5))); // return emoji
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_country = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Radios : Window
|
||||||
|
{
|
||||||
|
private RadioBrowserClient _radioBrowser;
|
||||||
|
private MPDHandler _mpd;
|
||||||
|
|
||||||
|
public Radios()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_radioBrowser = new RadioBrowserClient();
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Exception while connecting to RadioBrowser: " + e.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||||
|
MainWin.OnRadioBrowserConnected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Initialize()
|
||||||
|
{
|
||||||
|
List<NameAndCount> Countries = await _radioBrowser.Lists.GetCountriesAsync();
|
||||||
|
CountryList.Items.Add(new CountryListItem { Name = "", Count = 0 });
|
||||||
|
|
||||||
|
foreach (NameAndCount Country in Countries)
|
||||||
|
{
|
||||||
|
CountryList.Items.Add(new CountryListItem
|
||||||
|
{
|
||||||
|
Name = Country.Name,
|
||||||
|
Count = Country.Stationcount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CleanString(string str)
|
||||||
|
{
|
||||||
|
return str.Replace("\r\n", "").Replace("\n", "").Replace("\r", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SearchAdvanced(string name, string country, string tags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SearchStatus.Text = unison.Resources.Resources.Radio_Loading;
|
||||||
|
|
||||||
|
List<StationInfo> advancedSearch = await _radioBrowser.Search.AdvancedAsync(new AdvancedSearchOptions
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Country = country,
|
||||||
|
TagList = tags
|
||||||
|
});
|
||||||
|
|
||||||
|
RadioListGrid.Items.Clear();
|
||||||
|
if (advancedSearch.Count > 0)
|
||||||
|
{
|
||||||
|
SearchStatus.Text = "";
|
||||||
|
foreach (StationInfo station in advancedSearch)
|
||||||
|
{
|
||||||
|
RadioListGrid.Items.Add(new StationListItem
|
||||||
|
{
|
||||||
|
Name = CleanString(station.Name),
|
||||||
|
Country = station.CountryCode,
|
||||||
|
Codec = station.Codec,
|
||||||
|
Bitrate = station.Bitrate,
|
||||||
|
Url = station.Url,
|
||||||
|
Tags = string.Join(", ", station.Tags)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FitToContent();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SearchStatus.Text = unison.Resources.Resources.Radio_NotFound;
|
||||||
|
}
|
||||||
|
catch (Exception except)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Error on RadioBrowser search advanced: " + except.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FitToContent()
|
||||||
|
{
|
||||||
|
foreach (DataGridColumn column in RadioListGrid.Columns)
|
||||||
|
column.Width = new DataGridLength(1.0, DataGridLengthUnitType.SizeToCells);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Row_DoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
DataGrid grid = sender as DataGrid;
|
||||||
|
StationListItem station;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
station = grid.Items[grid.SelectedIndex] as StationListItem;
|
||||||
|
}
|
||||||
|
catch (ArgumentOutOfRangeException)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Error: Invalid index.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (station.Url == null)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Error: Invalid station.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
_mpd.ClearAddAndPlay(station.Url.AbsoluteUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Search_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CountryListItem a = (CountryListItem)CountryList.SelectedItem;
|
||||||
|
await SearchAdvanced(NameSearch.Text, a?.Name, TagSearch.Text);
|
||||||
|
}
|
||||||
|
catch (Exception except)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Error on RadioBrowser search: " + except.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reset_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
NameSearch.Text = "";
|
||||||
|
TagSearch.Text = "";
|
||||||
|
CountryList.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SearchHandler(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Return)
|
||||||
|
Search_Clicked(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Closing(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitHwnd()
|
||||||
|
{
|
||||||
|
WindowInteropHelper helper = new(this);
|
||||||
|
helper.EnsureHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,19 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||||
xmlns:local="clr-namespace:unison"
|
xmlns:properties="clr-namespace:unison.Resources" xmlns:sys="clr-namespace:System;assembly=System.Runtime"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Closing="Window_Closing" Title="Settings" ResizeMode="CanMinimize" Icon="/images/icon-full.ico" WindowStyle="ToolWindow" SizeToContent="WidthAndHeight">
|
Closing="Window_Closing" Title="{x:Static properties:Resources.Settings}" ResizeMode="CanMinimize" Icon="/Resources/icon-full.ico" WindowStyle="ToolWindow" SizeToContent="WidthAndHeight">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<x:Array x:Key="ShortcutItems" Type="sys:String">
|
||||||
|
<sys:String>None</sys:String>
|
||||||
|
<sys:String>Alt</sys:String>
|
||||||
|
<sys:String>Control</sys:String>
|
||||||
|
<sys:String>Shift</sys:String>
|
||||||
|
</x:Array>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<TabControl Margin="10">
|
<TabControl Margin="10">
|
||||||
@ -14,60 +24,83 @@
|
|||||||
<DockPanel Margin="8">
|
<DockPanel Margin="8">
|
||||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
<GroupBox.Header>
|
<GroupBox.Header>
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock>
|
||||||
<emoji:TextBlock Text="📶 Connection"/>
|
<emoji:EmojiInline Text="📶"/>
|
||||||
</StackPanel>
|
<Run Text="{x:Static properties:Resources.Settings_Connection}"/>
|
||||||
|
</TextBlock>
|
||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<Grid VerticalAlignment="Top">
|
<Grid VerticalAlignment="Top">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="Host" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_Host}" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||||
<TextBox x:Name="MpdHost" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
<TextBox x:Name="MpdHost" KeyDown="ConnectHandler" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Margin="0,5,0,0">
|
<StackPanel Margin="0,5,0,0">
|
||||||
<TextBlock Text="Port" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_Port}" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||||
<TextBox x:Name="MpdPort" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
<TextBox x:Name="MpdPort" KeyDown="ConnectHandler" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Margin="0,5,0,0">
|
<!--<StackPanel Margin="0,5,0,0">
|
||||||
<TextBlock Text="Password" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
<TextBlock Text="Password" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||||
<TextBox x:Name="MpdPassword" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
<TextBox x:Name="MpdPassword" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>-->
|
||||||
|
|
||||||
<TextBlock x:Name="ConnectionStatus" Text="Not connected." TextWrapping="Wrap" Margin="5,10,0,0"/>
|
<TextBlock x:Name="ConnectionStatus" Text="{x:Static properties:Resources.Settings_ConnectionStatusOffline}" TextWrapping="Wrap" Margin="5,10,0,0"/>
|
||||||
<Button x:Name="ConnectButton" Content="Connect" Margin="0,10,0,0" Width="120" Click="MPDConnect_Clicked"/>
|
<Button x:Name="ConnectButton" Content="{x:Static properties:Resources.Settings_ConnectButton}" Margin="0,10,0,0" Width="120" Click="MPDConnect_Clicked"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="{x:Static properties:Resources.Stats}">
|
||||||
|
<DockPanel Margin="8">
|
||||||
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock>
|
||||||
|
<emoji:EmojiInline Text="📊"/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid VerticalAlignment="Top">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats_Songs}"/><Run Text=" "/><Run x:Name="StatSong"/><LineBreak/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats_Albums}"/><Run Text=" "/><Run x:Name="StatAlbum"/><LineBreak/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats_Artists}"/><Run Text=" "/><Run x:Name="StatArtist"/><LineBreak/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats_TotalPlaytime}"/><Run Text=" "/><Run x:Name="StatTotalPlaytime"/><LineBreak/><LineBreak/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats_Uptime}"/><Run Text=" "/><Run x:Name="StatUptime"/><LineBreak/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats_TotalTimePlayed}"/><Run Text=" "/><Run x:Name="StatTotalTimePlayed"/><LineBreak/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Stats_LastDatabaseUpdate}"/><Run Text=" "/><Run x:Name="StatDatabaseUpdate"/>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
<TabItem Header="Snapcast">
|
<TabItem Header="Snapcast">
|
||||||
<DockPanel Margin="8">
|
<DockPanel Margin="8">
|
||||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
<GroupBox.Header>
|
<GroupBox.Header>
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<emoji:TextBlock Text="🔊 Snapcast"/>
|
<emoji:TextBlock Text="🔊 Snapcast"/>
|
||||||
</StackPanel>
|
|
||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<Grid VerticalAlignment="Top">
|
<Grid VerticalAlignment="Top">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<CheckBox x:Name="SnapcastStartup" Margin="5, 5, 0, 0">
|
<CheckBox x:Name="SnapcastStartup" Margin="5, 5, 0, 0">
|
||||||
<TextBlock Text="Launch at startup" TextWrapping="Wrap"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastLauch}" TextWrapping="Wrap"/>
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox x:Name="SnapcastWindow" Margin="5,2.5,0,0">
|
<CheckBox x:Name="SnapcastWindow" Margin="5,2.5,0,0">
|
||||||
<TextBlock Text="Show Snapcast window" TextWrapping="Wrap"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastWindow}" TextWrapping="Wrap"/>
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<TextBlock Text="Port" TextWrapping="Wrap" Margin="5,5,0,0"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPort}" TextWrapping="Wrap" Margin="5,5,0,0"/>
|
||||||
<TextBox x:Name="SnapcastPort" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
|
<TextBox x:Name="SnapcastPort" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
|
||||||
<TextBlock Text="Executable path" TextWrapping="Wrap" Margin="5,5,0,0"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPath}" TextWrapping="Wrap" Margin="5,5,0,0"/>
|
||||||
<TextBox x:Name="SnapcastPath" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
|
<TextBox x:Name="SnapcastPath" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
|
||||||
<TextBlock TextWrapping="Wrap" Margin="5,5,0,0" TextAlignment="Left" Width="250">
|
<TextBlock TextWrapping="Wrap" Margin="5,5,0,0" TextAlignment="Left" Width="250">
|
||||||
You can change to your own locally installed version of the Snapcast client with an <Run FontStyle="Italic" FontWeight="DemiBold">absolute</Run> path.
|
<Run Text="{x:Static properties:Resources.Settings_SnapcastInfo1}" /><Run Text="{x:Static properties:Resources.Settings_SnapcastInfo2}" FontStyle="Italic" FontWeight="DemiBold" /><Run Text="{x:Static properties:Resources.Settings_SnapcastInfo3}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<Button Content="Reset" Margin="0,10,0,0" Width="120" Click="SnapcastReset_Clicked"/>
|
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="SnapcastReset_Clicked"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -75,21 +108,23 @@
|
|||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem Header="Shortcuts">
|
<TabItem Header="{x:Static properties:Resources.Settings_Shortcuts}">
|
||||||
<DockPanel Margin="8">
|
<DockPanel Margin="8">
|
||||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
<GroupBox.Header>
|
<GroupBox.Header>
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock>
|
||||||
<emoji:TextBlock Text="⌨️ Shortcuts"/>
|
<emoji:EmojiInline Text="⌨️ "/>
|
||||||
</StackPanel>
|
<Run Text="{x:Static properties:Resources.Settings_Shortcuts}"></Run>
|
||||||
|
</TextBlock>
|
||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<Grid>
|
<Grid>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
|
||||||
<TextBlock Text="Volume offset" TextWrapping="Wrap"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeOffset}" TextWrapping="Wrap" Margin="0,2,0,0"/>
|
||||||
<TextBox x:Name="VolumeOffset" TextWrapping="Wrap" Width="25" PreviewTextInput="NumberValidationTextBox" Margin="8,2,0,0"/>
|
<TextBox x:Name="VolumeOffset" TextWrapping="Wrap" Width="25" PreviewTextInput="NumberValidationTextBox" Margin="8,2,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Grid MinWidth="220" Margin="0,5,0,0">
|
|
||||||
|
<Grid Margin="0,5,0,0" x:Name="RebindKeyWrapper">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
@ -101,28 +136,87 @@
|
|||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock Text="Next track" TextWrapping="Wrap" Grid.Column="0" Grid.Row="0" Margin="1"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_NextTrack}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="0" Margin="1,1,1,1" Grid.RowSpan="2"/>
|
||||||
<TextBlock Text="Previous track" TextWrapping="Wrap" Grid.Column="0" Grid.Row="1" Margin="1"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_PreviousTrack}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="2" Margin="1,1,1,1"/>
|
||||||
<TextBlock Text="Play / Pause" TextWrapping="Wrap" Grid.Column="0" Grid.Row="2" Margin="1"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_PlayPause}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="3" Margin="1,1,1,1"/>
|
||||||
<TextBlock Text="Volume up" TextWrapping="Wrap" Grid.Column="0" Grid.Row="3" Margin="1"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeUp}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="4" Margin="1,1,1,1"/>
|
||||||
<TextBlock Text="Volume down" TextWrapping="Wrap" Grid.Column="0" Grid.Row="4" Margin="1"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeDown}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="5" Margin="1,1,1,1"/>
|
||||||
<TextBlock Text="Show window" TextWrapping="Wrap" Grid.Column="0" Grid.Row="5" Margin="1"/>
|
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeMute}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="6" Margin="1,1,1,1"/>
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Settings_ShowWindow}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="7" Margin="1,1,1,1"/>
|
||||||
|
|
||||||
<TextBlock Text="ctrl + media_next" TextWrapping="Wrap" Grid.Column="1" Grid.Row="0" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<StackPanel x:Name="Shortcut_NextTrack" Orientation="Horizontal" Grid.Column="1" Grid.Row="0" Margin="10,0,0,2" Grid.RowSpan="2">
|
||||||
<TextBlock Text="ctrl + media_prev" TextWrapping="Wrap" Grid.Column="1" Grid.Row="1" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
<TextBlock Text="ctrl + media_play" TextWrapping="Wrap" Grid.Column="1" Grid.Row="2" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
<TextBlock Text="ctrl + volume_up" TextWrapping="Wrap" Grid.Column="1" Grid.Row="3" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||||
<TextBlock Text="ctrl + volume_down" TextWrapping="Wrap" Grid.Column="1" Grid.Row="4" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||||
<TextBlock Text="ctrl + alt + enter" TextWrapping="Wrap" Grid.Column="1" Grid.Row="5" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Shortcut_PreviousTrack" Orientation="Horizontal" Grid.Column="1" Grid.Row="2" Margin="10,0,0,2">
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||||
|
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Shortcut_PlayPause" Orientation="Horizontal" Grid.Column="1" Grid.Row="3" Margin="10,0,0,2">
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||||
|
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Shortcut_VolumeUp" Orientation="Horizontal" Grid.Column="1" Grid.Row="4" Margin="10,0,0,2">
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||||
|
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Shortcut_VolumeDown" Orientation="Horizontal" Grid.Column="1" Grid.Row="5" Margin="10,0,0,2">
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||||
|
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Shortcut_VolumeMute" Orientation="Horizontal" Grid.Column="1" Grid.Row="6" Margin="10,0,0,2">
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||||
|
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Shortcut_ShowWindow" Orientation="Horizontal" Grid.Column="1" Grid.Row="7" Margin="10,0,0,0">
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||||
|
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||||
|
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Settings_ShortcutsInfo}" TextWrapping="Wrap" Margin="0,2,0,0" MaxWidth="420" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="ShortcutsReset_Clicked"/>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem Header="About" Height="20" VerticalAlignment="Bottom">
|
<TabItem Header="{x:Static properties:Resources.Settings_About}" Height="20" VerticalAlignment="Bottom">
|
||||||
<DockPanel Margin="8">
|
<DockPanel Margin="8">
|
||||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
<GroupBox.Header>
|
<GroupBox.Header>
|
||||||
@ -131,23 +225,24 @@
|
|||||||
<Grid VerticalAlignment="Top">
|
<Grid VerticalAlignment="Top">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" VerticalAlignment="Top">
|
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" VerticalAlignment="Top">
|
||||||
<Run Text="Version:"/>
|
<Run Text="{x:Static properties:Resources.Settings_Version}"/>
|
||||||
<Run Text="{Binding GetVersion, Mode = OneWay}"/>
|
<Run Text="{Binding GetVersion, Mode = OneWay}"/>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||||
unison is free software. It is built with the following technologies:<LineBreak/>
|
<Run Text="{x:Static properties:Resources.Settings_AboutInfo}" /><LineBreak/>
|
||||||
※ <Hyperlink NavigateUri="https://github.com/Difegue/Stylophone" RequestNavigate="Hyperlink_RequestNavigate">Stylophone</Hyperlink>'s updated MpcNET<LineBreak/>
|
※ <Hyperlink NavigateUri="https://github.com/Difegue/MpcNET" RequestNavigate="Hyperlink_RequestNavigate">MpcNET</Hyperlink><LineBreak/>
|
||||||
※ <Hyperlink NavigateUri="https://github.com/hardcodet/wpf-notifyicon" RequestNavigate="Hyperlink_RequestNavigate">wpf-notifyicon</Hyperlink><LineBreak/>
|
※ <Hyperlink NavigateUri="https://github.com/hardcodet/wpf-notifyicon" RequestNavigate="Hyperlink_RequestNavigate">wpf-notifyicon</Hyperlink><LineBreak/>
|
||||||
※ <Hyperlink NavigateUri="https://github.com/samhocevar/emoji.wpf" RequestNavigate="Hyperlink_RequestNavigate">Emoji.WPF</Hyperlink>
|
※ <Hyperlink NavigateUri="https://github.com/samhocevar/emoji.wpf" RequestNavigate="Hyperlink_RequestNavigate">Emoji.WPF</Hyperlink><LineBreak/>
|
||||||
|
※ <Hyperlink NavigateUri="https://github.com/tof4/RadioBrowser" RequestNavigate="Hyperlink_RequestNavigate">RadioBrowser</Hyperlink>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Margin="0,10,0,0">
|
<TextBlock Margin="0,10,0,0">
|
||||||
Source code freely available
|
<Run Text="{x:Static properties:Resources.Settings_SourceCode1}" />
|
||||||
<Hyperlink NavigateUri="https://git.n700.ovh/keb/unison" RequestNavigate="Hyperlink_RequestNavigate">
|
<Hyperlink NavigateUri="https://github.com/ZetaKebab/unison" RequestNavigate="Hyperlink_RequestNavigate">
|
||||||
here
|
<Run Text="{x:Static properties:Resources.Settings_SourceCode2}" />
|
||||||
</Hyperlink>.
|
</Hyperlink>.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Margin="0,10,0,0">
|
<TextBlock Margin="0,10,0,0">
|
||||||
Made by
|
<Run Text="{x:Static properties:Resources.Settings_MadeBy}" />
|
||||||
<Hyperlink NavigateUri="https://marchal.dev" RequestNavigate="Hyperlink_RequestNavigate">
|
<Hyperlink NavigateUri="https://marchal.dev" RequestNavigate="Hyperlink_RequestNavigate">
|
||||||
Théo Marchal
|
Théo Marchal
|
||||||
</Hyperlink>.
|
</Hyperlink>.
|
||||||
@ -158,7 +253,10 @@
|
|||||||
|
|
||||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0" Margin="0,10,0,0">
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0" Margin="0,10,0,0">
|
||||||
<GroupBox.Header>
|
<GroupBox.Header>
|
||||||
<emoji:TextBlock Text="📝 License"/>
|
<TextBlock>
|
||||||
|
<emoji:EmojiInline Text="📝 "/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Settings_License}" />
|
||||||
|
</TextBlock>
|
||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<Grid VerticalAlignment="Top">
|
<Grid VerticalAlignment="Top">
|
||||||
<TextBlock Text="{Binding GetLicense, Mode = OneWay}" TextWrapping="Wrap" Width="500" TextAlignment="Justify" />
|
<TextBlock Text="{Binding GetLicense, Mode = OneWay}" TextWrapping="Wrap" Width="500" TextAlignment="Justify" />
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
@ -13,9 +15,6 @@ namespace unison
|
|||||||
{
|
{
|
||||||
public partial class Settings : Window
|
public partial class Settings : Window
|
||||||
{
|
{
|
||||||
private string defaultSnapcastPath = "snapclient_0.25.0-1_win64";
|
|
||||||
private string defaultSnapcastPort = "1704";
|
|
||||||
|
|
||||||
public static string GetVersion => Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
public static string GetVersion => Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||||
|
|
||||||
public static string GetLicense
|
public static string GetLicense
|
||||||
@ -36,6 +35,8 @@ namespace unison
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HotkeyHandler _hotkeys = (HotkeyHandler)Application.Current.Properties["hotkeys"];
|
||||||
|
|
||||||
public Settings()
|
public Settings()
|
||||||
{
|
{
|
||||||
InitHwnd();
|
InitHwnd();
|
||||||
@ -44,14 +45,67 @@ namespace unison
|
|||||||
|
|
||||||
WindowState = WindowState.Minimized;
|
WindowState = WindowState.Minimized;
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize()
|
||||||
|
{
|
||||||
MpdHost.Text = Properties.Settings.Default.mpd_host;
|
MpdHost.Text = Properties.Settings.Default.mpd_host;
|
||||||
MpdPort.Text = Properties.Settings.Default.mpd_port.ToString();
|
MpdPort.Text = Properties.Settings.Default.mpd_port.ToString();
|
||||||
MpdPassword.Text = Properties.Settings.Default.mpd_password;
|
//MpdPassword.Text = Properties.Settings.Default.mpd_password;
|
||||||
SnapcastStartup.IsChecked = Properties.Settings.Default.snapcast_startup;
|
SnapcastStartup.IsChecked = Properties.Settings.Default.snapcast_startup;
|
||||||
SnapcastWindow.IsChecked = Properties.Settings.Default.snapcast_window;
|
SnapcastWindow.IsChecked = Properties.Settings.Default.snapcast_window;
|
||||||
SnapcastPath.Text = Properties.Settings.Default.snapcast_path;
|
SnapcastPath.Text = Properties.Settings.Default.snapcast_path;
|
||||||
SnapcastPort.Text = Properties.Settings.Default.snapcast_port.ToString();
|
SnapcastPort.Text = Properties.Settings.Default.snapcast_port.ToString();
|
||||||
VolumeOffset.Text = Properties.Settings.Default.volume_offset.ToString();
|
VolumeOffset.Text = Properties.Settings.Default.volume_offset.ToString();
|
||||||
|
|
||||||
|
InitializeShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeShortcuts()
|
||||||
|
{
|
||||||
|
System.Collections.Generic.IEnumerable<StackPanel> stackPanelCollection = RebindKeyWrapper.Children.OfType<StackPanel>();
|
||||||
|
StackPanel[] stackPanelList = stackPanelCollection.ToArray();
|
||||||
|
|
||||||
|
// Default state
|
||||||
|
for (int i = 0; i < stackPanelList.Length; i++)
|
||||||
|
{
|
||||||
|
ComboBox[] comboBoxList = stackPanelList[i].Children.OfType<ComboBox>().ToArray();
|
||||||
|
foreach (ComboBox comboBox in comboBoxList) // default status (for reset)
|
||||||
|
{
|
||||||
|
comboBox.FontWeight = FontWeights.Light;
|
||||||
|
comboBox.SelectedItem = "None";
|
||||||
|
}
|
||||||
|
TextBlock textBlock = (TextBlock)stackPanelList[i].Children.OfType<Button>().FirstOrDefault().Content;
|
||||||
|
textBlock.Text = "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate values
|
||||||
|
for (int i = 0; i < stackPanelList.Length; i++)
|
||||||
|
{
|
||||||
|
// setup MOD
|
||||||
|
HotkeyHandler.MOD mod = _hotkeys._Shortcuts[i].mod;
|
||||||
|
|
||||||
|
HotkeyHandler.MOD[] MODList = System.Enum.GetValues(typeof(HotkeyHandler.MOD))
|
||||||
|
.OfType<HotkeyHandler.MOD>()
|
||||||
|
.Select(x => x & _hotkeys._Shortcuts[i].mod)
|
||||||
|
.Where(x => x != HotkeyHandler.MOD.None)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
ComboBox[] comboBox = stackPanelList[i].Children.OfType<ComboBox>().ToArray();
|
||||||
|
for (int j = 0; j < MODList.Length; j++)
|
||||||
|
{
|
||||||
|
comboBox[j].SelectedItem = MODList[j].ToString();
|
||||||
|
if (comboBox[j].SelectedItem.ToString() != "None")
|
||||||
|
comboBox[j].FontWeight = FontWeights.Bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup VK
|
||||||
|
TextBlock textBlock = (TextBlock)stackPanelList[i].Children.OfType<Button>().FirstOrDefault().Content;
|
||||||
|
textBlock.Text = _hotkeys._Shortcuts[i].vk.ToString();
|
||||||
|
if (textBlock.Text != "None")
|
||||||
|
textBlock.FontWeight = FontWeights.Bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
|
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
|
||||||
@ -71,39 +125,224 @@ namespace unison
|
|||||||
public void UpdateConnectionStatus()
|
public void UpdateConnectionStatus()
|
||||||
{
|
{
|
||||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
if (mpd._connected)
|
if (mpd.IsConnected())
|
||||||
ConnectionStatus.Text = "Connected to MPD " + mpd.GetVersion() + ".";
|
ConnectionStatus.Text = $"{unison.Resources.Resources.Settings_ConnectionStatusConnected} {mpd.GetVersion()}.";
|
||||||
else
|
else
|
||||||
ConnectionStatus.Text = "Not connected.";
|
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusOffline;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MPDConnect_Clicked(object sender, RoutedEventArgs e)
|
private void MPDConnect_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
SaveSettings();
|
SaveSettings();
|
||||||
ConnectionStatus.Text = "Connecting...";
|
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting;
|
||||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
mpd.Connect();
|
mpd.Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)
|
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
SnapcastPath.Text = defaultSnapcastPath;
|
SnapcastPath.Text = (string)Application.Current.FindResource("snapcastPath");
|
||||||
SnapcastPort.Text = defaultSnapcastPort;
|
SnapcastPort.Text = (string)Application.Current.FindResource("snapcastPort");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateStats()
|
||||||
|
{
|
||||||
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
StatSong.Text = mpd.GetStats().Songs.ToString();
|
||||||
|
StatAlbum.Text = mpd.GetStats().Albums.ToString();
|
||||||
|
StatArtist.Text = mpd.GetStats().Artists.ToString();
|
||||||
|
StatTotalPlaytime.Text = mpd.GetStats().TotalPlaytime.ToString();
|
||||||
|
StatUptime.Text = mpd.GetStats().Uptime.ToString();
|
||||||
|
StatTotalTimePlayed.Text = mpd.GetStats().TotalTimePlayed.ToString();
|
||||||
|
StatDatabaseUpdate.Text = mpd.GetStats().DatabaseUpdate.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HotkeyChanged()
|
||||||
|
{
|
||||||
|
_hotkeys.RemoveHotkeys();
|
||||||
|
_hotkeys.AddHotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ref HotkeyHandler.HotkeyPair GetHotkeyVariable(string Name)
|
||||||
|
{
|
||||||
|
if (Name == "Shortcut_NextTrack")
|
||||||
|
return ref _hotkeys._NextTrack;
|
||||||
|
if (Name == "Shortcut_PreviousTrack")
|
||||||
|
return ref _hotkeys._PreviousTrack;
|
||||||
|
if (Name == "Shortcut_PlayPause")
|
||||||
|
return ref _hotkeys._PlayPause;
|
||||||
|
if (Name == "Shortcut_VolumeUp")
|
||||||
|
return ref _hotkeys._VolumeUp;
|
||||||
|
if (Name == "Shortcut_VolumeDown")
|
||||||
|
return ref _hotkeys._VolumeDown;
|
||||||
|
if (Name == "Shortcut_VolumeMute")
|
||||||
|
return ref _hotkeys._VolumeMute;
|
||||||
|
if (Name == "Shortcut_ShowWindow")
|
||||||
|
return ref _hotkeys._ShowWindow;
|
||||||
|
return ref _hotkeys._NextTrack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHotkey_MOD(string Name, HotkeyHandler.MOD mod) => GetHotkeyVariable(Name).SetMOD(mod);
|
||||||
|
private void UpdateHotkey_VK(string Name, HotkeyHandler.VK vk) => GetHotkeyVariable(Name).SetVK(vk);
|
||||||
|
|
||||||
|
private void MOD_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ComboBox comboBox = (ComboBox)sender;
|
||||||
|
StackPanel stackPanel = (StackPanel)comboBox.Parent;
|
||||||
|
System.Collections.Generic.IEnumerable<ComboBox> stackPanelCollection = stackPanel.Children.OfType<ComboBox>();
|
||||||
|
|
||||||
|
HotkeyHandler.MOD MOD1, MOD2;
|
||||||
|
|
||||||
|
// we need to do this because the element is modified -after- this function
|
||||||
|
if (comboBox.Tag.ToString() == "MOD1")
|
||||||
|
{
|
||||||
|
MOD1 = GetMOD(e.AddedItems[0].ToString());
|
||||||
|
MOD2 = GetMOD(stackPanelCollection.Last().Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MOD1 = GetMOD(stackPanelCollection.First().Text);
|
||||||
|
MOD2 = GetMOD(e.AddedItems[0].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.AddedItems[0].ToString() == "None")
|
||||||
|
comboBox.FontWeight = FontWeights.Light;
|
||||||
|
else
|
||||||
|
comboBox.FontWeight = FontWeights.Bold;
|
||||||
|
|
||||||
|
HotkeyHandler.MOD ModKey = MOD1 | MOD2;
|
||||||
|
|
||||||
|
UpdateHotkey_MOD(stackPanel.Name, ModKey);
|
||||||
|
HotkeyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemapKey_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Button button = (Button)sender;
|
||||||
|
TextBlock textBlock = (TextBlock)button.Content;
|
||||||
|
textBlock.Text = unison.Resources.Resources.Settings_ShortcutsKey;
|
||||||
|
textBlock.FontWeight = FontWeights.Bold;
|
||||||
|
button.PreviewKeyDown += DetectPressedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetectPressedKey(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
|
||||||
|
Key pressedKey = e.Key;
|
||||||
|
HotkeyHandler.VK VirtualKey = GetVirtualKey(pressedKey);
|
||||||
|
|
||||||
|
Button button = (Button)sender;
|
||||||
|
TextBlock textBlock = (TextBlock)button.Content;
|
||||||
|
StackPanel stackPanel = (StackPanel)button.Parent;
|
||||||
|
|
||||||
|
if (VirtualKey == HotkeyHandler.VK.None)
|
||||||
|
{
|
||||||
|
pressedKey = Key.None;
|
||||||
|
textBlock.FontWeight = FontWeights.Light;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
textBlock.FontWeight = FontWeights.Bold;
|
||||||
|
|
||||||
|
textBlock.Text = pressedKey.ToString();
|
||||||
|
button.PreviewKeyDown -= DetectPressedKey;
|
||||||
|
|
||||||
|
UpdateHotkey_VK(stackPanel.Name, VirtualKey);
|
||||||
|
HotkeyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HotkeyHandler.VK GetVirtualKey(Key key)
|
||||||
|
{
|
||||||
|
foreach (object value in System.Enum.GetValues(typeof(HotkeyHandler.VK)))
|
||||||
|
{
|
||||||
|
if (key.ToString().ToLower() == value.ToString().ToLower())
|
||||||
|
return (HotkeyHandler.VK)value;
|
||||||
|
}
|
||||||
|
return HotkeyHandler.VK.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HotkeyHandler.MOD GetMOD(string str)
|
||||||
|
{
|
||||||
|
foreach (object value in System.Enum.GetValues(typeof(HotkeyHandler.MOD)))
|
||||||
|
{
|
||||||
|
if (str.ToLower() == value.ToString().ToLower())
|
||||||
|
return (HotkeyHandler.MOD)value;
|
||||||
|
}
|
||||||
|
return HotkeyHandler.MOD.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShortcutsReset_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Properties.Settings.Default.nextTrack_mod = (uint)Application.Current.FindResource("nextTrack_mod");
|
||||||
|
Properties.Settings.Default.nextTrack_vk = (uint)Application.Current.FindResource("nextTrack_vk");
|
||||||
|
Properties.Settings.Default.previousTrack_mod = (uint)Application.Current.FindResource("previousTrack_mod");
|
||||||
|
Properties.Settings.Default.previousTrack_vk = (uint)Application.Current.FindResource("previousTrack_vk");
|
||||||
|
Properties.Settings.Default.playPause_mod = (uint)Application.Current.FindResource("playPause_mod");
|
||||||
|
Properties.Settings.Default.playPause_vk = (uint)Application.Current.FindResource("playPause_vk");
|
||||||
|
Properties.Settings.Default.volumeUp_mod = (uint)Application.Current.FindResource("volumeUp_mod");
|
||||||
|
Properties.Settings.Default.volumeUp_vk = (uint)Application.Current.FindResource("volumeUp_vk");
|
||||||
|
Properties.Settings.Default.volumeDown_mod = (uint)Application.Current.FindResource("volumeDown_mod");
|
||||||
|
Properties.Settings.Default.volumeDown_vk = (uint)Application.Current.FindResource("volumeDown_vk");
|
||||||
|
Properties.Settings.Default.volumeMute_mod = (uint)Application.Current.FindResource("volumeMute_mod");
|
||||||
|
Properties.Settings.Default.volumeMute_vk = (uint)Application.Current.FindResource("volumeMute_vk");
|
||||||
|
Properties.Settings.Default.showWindow_mod = (uint)Application.Current.FindResource("showWindow_mod");
|
||||||
|
Properties.Settings.Default.showWindow_vk = (uint)Application.Current.FindResource("showWindow_vk");
|
||||||
|
|
||||||
|
_hotkeys.Initialize();
|
||||||
|
HotkeyChanged();
|
||||||
|
InitializeShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetMod(StackPanel stackPanel)
|
||||||
|
{
|
||||||
|
return (uint)(GetMOD(stackPanel.Children.OfType<ComboBox>().First().SelectedItem.ToString()) | GetMOD(stackPanel.Children.OfType<ComboBox>().Last().SelectedItem.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetVk(StackPanel stackPanel)
|
||||||
|
{
|
||||||
|
Button button = stackPanel.Children.OfType<Button>().First();
|
||||||
|
TextBlock textBlock = (TextBlock)button.Content;
|
||||||
|
return (uint)(HotkeyHandler.VK)System.Enum.Parse(typeof(HotkeyHandler.VK), textBlock.Text, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings()
|
||||||
{
|
{
|
||||||
Properties.Settings.Default.mpd_host = MpdHost.Text;
|
Properties.Settings.Default.mpd_host = MpdHost.Text;
|
||||||
Properties.Settings.Default.mpd_port = int.Parse(MpdPort.Text, CultureInfo.InvariantCulture);
|
Properties.Settings.Default.mpd_port = int.Parse(MpdPort.Text, CultureInfo.InvariantCulture);
|
||||||
Properties.Settings.Default.mpd_password = MpdPassword.Text;
|
//Properties.Settings.Default.mpd_password = MpdPassword.Text;
|
||||||
Properties.Settings.Default.snapcast_startup = (bool)SnapcastStartup.IsChecked;
|
Properties.Settings.Default.snapcast_startup = (bool)SnapcastStartup.IsChecked;
|
||||||
Properties.Settings.Default.snapcast_window = (bool)SnapcastWindow.IsChecked;
|
Properties.Settings.Default.snapcast_window = (bool)SnapcastWindow.IsChecked;
|
||||||
Properties.Settings.Default.snapcast_path = SnapcastPath.Text;
|
Properties.Settings.Default.snapcast_path = SnapcastPath.Text;
|
||||||
Properties.Settings.Default.snapcast_port = int.Parse(SnapcastPort.Text, CultureInfo.InvariantCulture);
|
Properties.Settings.Default.snapcast_port = int.Parse(SnapcastPort.Text, CultureInfo.InvariantCulture);
|
||||||
Properties.Settings.Default.volume_offset = int.Parse(VolumeOffset.Text, CultureInfo.InvariantCulture);
|
Properties.Settings.Default.volume_offset = int.Parse(VolumeOffset.Text, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
Properties.Settings.Default.nextTrack_mod = GetMod(Shortcut_NextTrack);
|
||||||
|
Properties.Settings.Default.nextTrack_vk = GetVk(Shortcut_NextTrack);
|
||||||
|
Properties.Settings.Default.previousTrack_mod = GetMod(Shortcut_PreviousTrack);
|
||||||
|
Properties.Settings.Default.previousTrack_vk = GetVk(Shortcut_PreviousTrack);
|
||||||
|
Properties.Settings.Default.playPause_mod = GetMod(Shortcut_PlayPause);
|
||||||
|
Properties.Settings.Default.playPause_vk = GetVk(Shortcut_PlayPause);
|
||||||
|
Properties.Settings.Default.volumeUp_mod = GetMod(Shortcut_VolumeUp);
|
||||||
|
Properties.Settings.Default.volumeUp_vk = GetVk(Shortcut_VolumeUp);
|
||||||
|
Properties.Settings.Default.volumeDown_mod = GetMod(Shortcut_VolumeDown);
|
||||||
|
Properties.Settings.Default.volumeDown_vk = GetVk(Shortcut_VolumeDown);
|
||||||
|
Properties.Settings.Default.volumeMute_mod = GetMod(Shortcut_VolumeMute);
|
||||||
|
Properties.Settings.Default.volumeMute_vk = GetVk(Shortcut_VolumeMute);
|
||||||
|
Properties.Settings.Default.showWindow_mod = GetMod(Shortcut_ShowWindow);
|
||||||
|
Properties.Settings.Default.showWindow_vk = GetVk(Shortcut_ShowWindow);
|
||||||
|
|
||||||
Properties.Settings.Default.Save();
|
Properties.Settings.Default.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ConnectHandler(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Return)
|
||||||
|
MPDConnect_Clicked(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
private void Window_Closing(object sender, CancelEventArgs e)
|
private void Window_Closing(object sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
|
@ -2,47 +2,53 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
xmlns:tb="http://www.hardcodet.net/taskbar"
|
||||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||||
|
xmlns:properties="clr-namespace:unison.Resources"
|
||||||
xmlns:local="clr-namespace:unison">
|
xmlns:local="clr-namespace:unison">
|
||||||
|
|
||||||
<ContextMenu x:Shared="false" x:Key="SystrayMenu">
|
<ContextMenu x:Shared="false" x:Key="SystrayMenu">
|
||||||
<MenuItem IsEnabled="False">
|
<MenuItem IsEnabled="False">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Image Source="/images/icon-full.ico" Width="16" Height="16"/>
|
<Image Source="/Resources/icon-full.ico" Width="16" Height="16"/>
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
<MenuItem.Header>
|
<MenuItem.Header>
|
||||||
<TextBlock Text="{Binding GetAppText}" />
|
<TextBlock Text="{Binding GetAppText}" />
|
||||||
</MenuItem.Header>
|
</MenuItem.Header>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Show window" Command="{Binding ShowWindowCommand}">
|
<MenuItem Header="{x:Static properties:Resources.ShowWindow}" Command="{Binding ShowWindowCommand}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Image Width="16" Height="16" emoji:Image.Source="▶️" />
|
<Image Width="16" Height="16" emoji:Image.Source="▶️" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Name="test_snapcast_item" Header="{Binding SnapcastText}" Command="{Binding Snapcast}">
|
<MenuItem Header="{Binding SnapcastText}" Command="{Binding Snapcast}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Image Width="16" Height="16" emoji:Image.Source="🔊" />
|
<Image Width="16" Height="16" emoji:Image.Source="🔊" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem Header="{x:Static properties:Resources.Radios}" Command="{Binding Radios}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Image Width="16" Height="16" emoji:Image.Source="📻" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
<!--<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
|
<!--<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Image Width="16" Height="16" emoji:Image.Source="🔀" />
|
<Image Width="16" Height="16" emoji:Image.Source="🔀" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>-->
|
</MenuItem>-->
|
||||||
<MenuItem Header="Settings" Command="{Binding Settings}">
|
<MenuItem Header="{x:Static properties:Resources.Settings}" Command="{Binding Settings}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Image Width="16" Height="16" emoji:Image.Source="🛠️" />
|
<Image Width="16" Height="16" emoji:Image.Source="🛠️" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}">
|
<MenuItem Header="{x:Static properties:Resources.Exit}" Command="{Binding ExitApplicationCommand}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Image Width="16" Height="16" emoji:Image.Source="❌" />
|
<Image Width="16" Height="16" emoji:Image.Source="❌" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
|
|
||||||
<tb:TaskbarIcon x:Key="SystrayTaskbar" IconSource="/images/icon-mini.ico" ToolTipText="{Binding GetAppText}" DoubleClickCommand="{Binding ShowWindowCommand}" ContextMenu="{StaticResource SystrayMenu}">
|
<tb:TaskbarIcon x:Key="SystrayTaskbar" IconSource="/Resources/icon-mini.ico" ToolTipText="{Binding GetAppText}" DoubleClickCommand="{Binding ShowWindowCommand}" ContextMenu="{StaticResource SystrayMenu}">
|
||||||
<tb:TaskbarIcon.DataContext>
|
<tb:TaskbarIcon.DataContext>
|
||||||
<local:SystrayViewModel />
|
<local:SystrayViewModel />
|
||||||
</tb:TaskbarIcon.DataContext>
|
</tb:TaskbarIcon.DataContext>
|
||||||
|
@ -8,6 +8,8 @@ namespace unison
|
|||||||
{
|
{
|
||||||
public class SystrayViewModel : INotifyPropertyChanged
|
public class SystrayViewModel : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
public static string GetAppText => "unison v" + Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
public static string GetAppText => "unison v" + Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||||
|
|
||||||
public static ICommand ShowWindowCommand => new DelegateCommand
|
public static ICommand ShowWindowCommand => new DelegateCommand
|
||||||
@ -26,10 +28,7 @@ namespace unison
|
|||||||
|
|
||||||
public static ICommand ExitApplicationCommand => new DelegateCommand
|
public static ICommand ExitApplicationCommand => new DelegateCommand
|
||||||
{
|
{
|
||||||
CommandAction = () =>
|
CommandAction = () => Application.Current.Shutdown(),
|
||||||
{
|
|
||||||
Application.Current.Shutdown();
|
|
||||||
},
|
|
||||||
CanExecuteFunc = () => true
|
CanExecuteFunc = () => true
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,7 +37,7 @@ namespace unison
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||||
return snapcast.Started ? "Stop Snapcast" : "Start Snapcast";
|
return snapcast.HasStarted ? unison.Resources.Resources.StopSnapcast : unison.Resources.Resources.StartSnapcast;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,30 +50,39 @@ namespace unison
|
|||||||
CommandAction = () =>
|
CommandAction = () =>
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.BeginInvoke(new Action(() => OnPropertyChanged("SnapcastText")));
|
Application.Current.Dispatcher.BeginInvoke(new Action(() => OnPropertyChanged("SnapcastText")));
|
||||||
((MainWindow)Application.Current.MainWindow).Snapcast_Clicked(null, null);
|
|
||||||
|
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||||
|
snapcast.LaunchOrExit();
|
||||||
},
|
},
|
||||||
CanExecuteFunc = () => true
|
CanExecuteFunc = () => true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ICommand Radios
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new DelegateCommand
|
||||||
|
{
|
||||||
|
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Radios_Clicked(null, null),
|
||||||
|
CanExecuteFunc = () => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ICommand Settings
|
public ICommand Settings
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new DelegateCommand
|
return new DelegateCommand
|
||||||
{
|
{
|
||||||
CommandAction = () =>
|
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Settings_Clicked(null, null),
|
||||||
{
|
|
||||||
((MainWindow)Application.Current.MainWindow).Settings_Clicked(null, null);
|
|
||||||
},
|
|
||||||
CanExecuteFunc = () => true
|
CanExecuteFunc = () => true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
public virtual void OnPropertyChanged(string propertyName)
|
public virtual void OnPropertyChanged(string propertyName)
|
||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
BIN
snapclient_0.26.0-1_win64/snapclient.exe
Normal file
@ -4,10 +4,10 @@
|
|||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net5.0-windows</TargetFramework>
|
<TargetFramework>net5.0-windows</TargetFramework>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<ApplicationIcon>images\icon-full.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\icon-full.ico</ApplicationIcon>
|
||||||
<Win32Resource></Win32Resource>
|
<Win32Resource></Win32Resource>
|
||||||
<StartupObject>unison.App</StartupObject>
|
<StartupObject>unison.App</StartupObject>
|
||||||
<Version>0.0.1</Version>
|
<Version>1.2</Version>
|
||||||
<Company />
|
<Company />
|
||||||
<Authors>Théo Marchal</Authors>
|
<Authors>Théo Marchal</Authors>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
@ -18,17 +18,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="images\icon-full.ico" />
|
<None Remove="Resources\icon-full.ico" />
|
||||||
<None Remove="images\icon-mini.ico" />
|
<None Remove="Resources\icon-mini.ico" />
|
||||||
<None Remove="images\nocover.png" />
|
<None Remove="Resources\nocover.png" />
|
||||||
<None Remove="LICENSE" />
|
<None Remove="LICENSE" />
|
||||||
<None Remove="snapclient_0.25.0-1_win64\FLAC.dll" />
|
|
||||||
<None Remove="snapclient_0.25.0-1_win64\ogg.dll" />
|
|
||||||
<None Remove="snapclient_0.25.0-1_win64\opus.dll" />
|
|
||||||
<None Remove="snapclient_0.25.0-1_win64\README.txt" />
|
|
||||||
<None Remove="snapclient_0.25.0-1_win64\snapclient.exe" />
|
|
||||||
<None Remove="snapclient_0.25.0-1_win64\soxr.dll" />
|
|
||||||
<None Remove="snapclient_0.25.0-1_win64\vorbis.dll" />
|
|
||||||
<None Include="LICENSE">
|
<None Include="LICENSE">
|
||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
<PackagePath></PackagePath>
|
<PackagePath></PackagePath>
|
||||||
@ -36,48 +29,25 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="images\icon-full.ico">
|
<Resource Include="Resources\icon-full.ico">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Resource>
|
</Resource>
|
||||||
<Resource Include="images\icon-mini.ico">
|
<Resource Include="Resources\icon-mini.ico">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Resource>
|
</Resource>
|
||||||
<Resource Include="images\nocover.png">
|
<Resource Include="Resources\nocover.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Resource>
|
</Resource>
|
||||||
<Content Include="LICENSE">
|
<Content Include="LICENSE">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="snapclient_0.25.0-1_win64\README.txt">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="snapclient_0.25.0-1_win64\FLAC.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="snapclient_0.25.0-1_win64\ogg.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="snapclient_0.25.0-1_win64\opus.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="snapclient_0.25.0-1_win64\snapclient.exe">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="snapclient_0.25.0-1_win64\soxr.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="snapclient_0.25.0-1_win64\vorbis.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Emoji.Wpf" Version="0.3.3" />
|
<PackageReference Include="Emoji.Wpf" Version="0.3.3" />
|
||||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
|
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
|
||||||
</ItemGroup>
|
<PackageReference Include="RadioBrowser" Version="0.6.1" />
|
||||||
|
<PackageReference Include="MpcNET" Version="1.3.0" />
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MpcNET\MpcNET.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -86,6 +56,21 @@
|
|||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
<DependentUpon>Settings.settings</DependentUpon>
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="Resources\Resources.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Resources\Resources.fr-FR.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Update="Resources\Resources.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -93,6 +78,27 @@
|
|||||||
<Generator>SettingsSingleFileGenerator</Generator>
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="snapclient_0.26.0-1_win64\FLAC.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="snapclient_0.26.0-1_win64\ogg.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="snapclient_0.26.0-1_win64\opus.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="snapclient_0.26.0-1_win64\README.txt">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="snapclient_0.26.0-1_win64\snapclient.exe">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="snapclient_0.26.0-1_win64\soxr.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="snapclient_0.26.0-1_win64\vorbis.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
11
unison.sln
@ -4,11 +4,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
VisualStudioVersion = 16.0.31515.178
|
VisualStudioVersion = 16.0.31515.178
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "unison", "unison.csproj", "{489048C4-3FCA-4573-B34C-943D03F94D04}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "unison", "unison.csproj", "{489048C4-3FCA-4573-B34C-943D03F94D04}"
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
|
||||||
{230556C6-5AC3-4FD8-8947-C9ABF1416D19} = {230556C6-5AC3-4FD8-8947-C9ABF1416D19}
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MpcNET", "..\MpcNET\MpcNET.csproj", "{230556C6-5AC3-4FD8-8947-C9ABF1416D19}"
|
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@ -23,12 +18,6 @@ Global
|
|||||||
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release|Any CPU.Build.0 = Release|Any CPU
|
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU
|
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release-Stable|Any CPU.Build.0 = Release|Any CPU
|
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release-Stable|Any CPU.Build.0 = Release|Any CPU
|
||||||
{230556C6-5AC3-4FD8-8947-C9ABF1416D19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{230556C6-5AC3-4FD8-8947-C9ABF1416D19}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{230556C6-5AC3-4FD8-8947-C9ABF1416D19}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{230556C6-5AC3-4FD8-8947-C9ABF1416D19}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{230556C6-5AC3-4FD8-8947-C9ABF1416D19}.Release-Stable|Any CPU.ActiveCfg = Release-Stable|Any CPU
|
|
||||||
{230556C6-5AC3-4FD8-8947-C9ABF1416D19}.Release-Stable|Any CPU.Build.0 = Release-Stable|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|