Compare commits
72 Commits
Author | SHA1 | Date | |
---|---|---|---|
f6eb00759a | |||
468ec8be07 | |||
52b0a6fc85 | |||
5a43a1284e | |||
06207d9791 | |||
2f80af70ba | |||
aeb1b1d573 | |||
8451e684db | |||
9c8027428c | |||
8e04161a60 | |||
ccbb4525f3 | |||
529754934d | |||
0471ba88c1 | |||
3b55c79d30 | |||
b33ae3b9be | |||
67667c74da | |||
8033aebb23 | |||
0898d5cf11 | |||
cb741349fa | |||
cabfb165da | |||
5142477b5e | |||
a6b7ad9c1e | |||
b074a4e975 | |||
53d7668fd6 | |||
124b263499 | |||
3f74b1a4e7 | |||
1d3515a39d | |||
792437b839 | |||
5bfa7d3b5b | |||
c5e8534af7 | |||
5d6e3b6d1e | |||
ded5908ca2 | |||
519fe4968e | |||
f95b884d16 | |||
4136c13d5b | |||
0ab1afc2f8 | |||
3b59e51368 | |||
c7a93c2d82 | |||
43350aed36 | |||
6ad4d9c813 | |||
301309737b | |||
ab46442108 | |||
3c1aad7d27 | |||
d1b6d97fe8 | |||
15db118dd6 | |||
67315f90b0 | |||
6d75f88927 | |||
7fdf349c28 | |||
9a5f686dde | |||
b209cb2556 | |||
6e1f43bed0 | |||
c93a9a326e | |||
c055c59de7 | |||
4c71d6a6e0 | |||
3685c369b4 | |||
9bd088fac8 | |||
e0d640532c | |||
0be28ab205 | |||
62a3220f7f | |||
6e4ed82211 | |||
7b2a7bae21 | |||
72d751db71 | |||
e4b63073d8 | |||
d49f3ab030 | |||
62835065c0 | |||
196b93c7f3 | |||
f9a14ee3c0 | |||
7aafa935e1 | |||
2960afd9bd | |||
23098e0ebb | |||
32d3610b07 | |||
fbb65a039a |
18
App.xaml.cs
@ -1,6 +1,6 @@
|
|||||||
using System.Globalization;
|
using System.Windows;
|
||||||
using System.Windows;
|
|
||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using Hardcodet.Wpf.TaskbarNotification;
|
||||||
|
using unison.Handlers;
|
||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
@ -9,12 +9,16 @@ namespace unison
|
|||||||
private TaskbarIcon _systray;
|
private TaskbarIcon _systray;
|
||||||
private HotkeyHandler _hotkeys;
|
private HotkeyHandler _hotkeys;
|
||||||
private SnapcastHandler _snapcast;
|
private SnapcastHandler _snapcast;
|
||||||
|
private ShuffleHandler _shuffle;
|
||||||
private MPDHandler _mpd;
|
private MPDHandler _mpd;
|
||||||
|
private UpdateHandler _updater;
|
||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
//debug language
|
//debug language
|
||||||
//unison.Resources.Resources.Culture = CultureInfo.GetCultureInfo("fr-FR");
|
//unison.Resources.Resources.Culture = System.Globalization.CultureInfo.GetCultureInfo("fr-FR");
|
||||||
|
//unison.Resources.Resources.Culture = System.Globalization.CultureInfo.GetCultureInfo("es-ES");
|
||||||
|
|
||||||
|
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
@ -27,6 +31,12 @@ namespace unison
|
|||||||
_snapcast = new SnapcastHandler();
|
_snapcast = new SnapcastHandler();
|
||||||
Current.Properties["snapcast"] = _snapcast;
|
Current.Properties["snapcast"] = _snapcast;
|
||||||
|
|
||||||
|
_shuffle = new ShuffleHandler();
|
||||||
|
Current.Properties["shuffle"] = _shuffle;
|
||||||
|
|
||||||
|
_updater = new UpdateHandler();
|
||||||
|
Current.Properties["updater"] = _updater;
|
||||||
|
|
||||||
Current.MainWindow = new MainWindow();
|
Current.MainWindow = new MainWindow();
|
||||||
|
|
||||||
_systray = (TaskbarIcon)FindResource("SystrayTaskbar");
|
_systray = (TaskbarIcon)FindResource("SystrayTaskbar");
|
||||||
@ -37,7 +47,7 @@ namespace unison
|
|||||||
{
|
{
|
||||||
_systray.Dispose();
|
_systray.Dispose();
|
||||||
_snapcast.LaunchOrExit(true);
|
_snapcast.LaunchOrExit(true);
|
||||||
_hotkeys.RemoveHotKeys();
|
_hotkeys.RemoveHotkeys();
|
||||||
base.OnExit(e);
|
base.OnExit(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
51
CHANGELOG.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.3.1
|
||||||
|
|
||||||
|
*Released: 03/11/2022*
|
||||||
|
|
||||||
|
* Update .NET version from 5.0 to 6.0
|
||||||
|
* Fix: simple patch to avoid a crash concerning GetAlbumCover
|
||||||
|
* Fix: connection change now working
|
||||||
|
|
||||||
|
## v1.3
|
||||||
|
|
||||||
|
*Released: 18/04/2022*
|
||||||
|
|
||||||
|
* New feature: add support for readpicture, aka embedded cover art
|
||||||
|
* New feature: add support for MPD password
|
||||||
|
* Spanish translation
|
||||||
|
* Trim album release date
|
||||||
|
* Cover icon when a radio is playing
|
||||||
|
* Update MpcNET package from 1.3 to 1.4
|
||||||
|
* Fix: Snapcast not working with hostname
|
||||||
|
* Fix: some radios crash
|
||||||
|
* Fix: disable/enable radios and Snapcast with connection
|
||||||
|
|
||||||
|
## v1.2
|
||||||
|
|
||||||
|
*Released: 07/04/2022*
|
||||||
|
|
||||||
|
* New feature: support for custom shortcuts
|
||||||
|
* New feature: MPD stats
|
||||||
|
* Add GitHub repository link in settings
|
||||||
|
* MpcNET NuGet package integration
|
||||||
|
* Update Snapcast from v0.25 to v0.26
|
||||||
|
* Fix: hostname supported for connection
|
||||||
|
* Fix: crash when using invalid IP or Hostname
|
||||||
|
* Fix: crash when no internet connection when using RadioBrowser
|
||||||
|
|
||||||
|
## v1.1
|
||||||
|
|
||||||
|
*Released: 04/10/2021*
|
||||||
|
|
||||||
|
* Radio browser
|
||||||
|
* Mute shortcut
|
||||||
|
* Enter key works in settings textboxes
|
||||||
|
* Share current song by double-clicking
|
||||||
|
|
||||||
|
## v1.0
|
||||||
|
|
||||||
|
*Released: 03/09/2021*
|
||||||
|
|
||||||
|
* First release of unison
|
@ -15,20 +15,168 @@ 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 MainWindow _appWindow;
|
||||||
private readonly MPDHandler _mpd;
|
private readonly MPDHandler _mpd;
|
||||||
@ -36,9 +184,31 @@ namespace unison
|
|||||||
private IntPtr _windowHandle;
|
private IntPtr _windowHandle;
|
||||||
private HwndSource _source;
|
private HwndSource _source;
|
||||||
|
|
||||||
|
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,58 +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;
|
||||||
switch (vkey)
|
|
||||||
{
|
|
||||||
case VK_MEDIA_NEXT_TRACK:
|
|
||||||
_mpd.Next();
|
|
||||||
break;
|
|
||||||
case VK_MEDIA_PREV_TRACK:
|
|
||||||
_mpd.Prev();
|
|
||||||
break;
|
|
||||||
case VK_VOLUME_DOWN:
|
|
||||||
_mpd.VolumeDown();
|
|
||||||
break;
|
|
||||||
case VK_VOLUME_UP:
|
|
||||||
_mpd.VolumeUp();
|
|
||||||
break;
|
|
||||||
case VK_MEDIA_PLAY_PAUSE:
|
|
||||||
_mpd.PlayPause();
|
|
||||||
break;
|
|
||||||
case VK_ENTER:
|
|
||||||
if (_appWindow == null)
|
|
||||||
_appWindow = (MainWindow)Application.Current.MainWindow;
|
|
||||||
|
|
||||||
if (_appWindow.WindowState == WindowState.Minimized)
|
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())
|
||||||
|
{
|
||||||
|
if (_appWindow == null)
|
||||||
|
_appWindow = (MainWindow)Application.Current.MainWindow;
|
||||||
|
|
||||||
|
if (_appWindow.WindowState == WindowState.Minimized)
|
||||||
|
{
|
||||||
|
_appWindow.Show();
|
||||||
|
_appWindow.Activate();
|
||||||
|
_appWindow.WindowState = WindowState.Normal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_appWindow.IsActive)
|
||||||
|
{
|
||||||
|
_appWindow.Hide();
|
||||||
|
_appWindow.WindowState = WindowState.Minimized;
|
||||||
|
}
|
||||||
|
else // not minimized but not in front
|
||||||
{
|
{
|
||||||
_appWindow.Show();
|
_appWindow.Show();
|
||||||
_appWindow.Activate();
|
_appWindow.Activate();
|
||||||
_appWindow.WindowState = WindowState.Normal;
|
_appWindow.WindowState = WindowState.Normal;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
|
||||||
if (_appWindow.IsActive)
|
|
||||||
{
|
|
||||||
_appWindow.Hide();
|
|
||||||
_appWindow.WindowState = WindowState.Minimized;
|
|
||||||
}
|
|
||||||
else // not minimized but not in front
|
|
||||||
{
|
|
||||||
_appWindow.Show();
|
|
||||||
_appWindow.Activate();
|
|
||||||
_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -12,33 +13,51 @@ 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.Status;
|
using MpcNET.Commands.Status;
|
||||||
using MpcNET.Message;
|
using MpcNET.Message;
|
||||||
using MpcNET.Types;
|
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
|
||||||
{
|
{
|
||||||
private bool _connected;
|
private bool _connected;
|
||||||
public string _version;
|
private string _version;
|
||||||
private int _currentVolume;
|
private int _currentVolume;
|
||||||
|
private int _previousVolume;
|
||||||
private bool _currentRandom;
|
private bool _currentRandom;
|
||||||
private bool _currentRepeat;
|
private bool _currentRepeat;
|
||||||
private bool _currentSingle;
|
private bool _currentSingle;
|
||||||
private bool _currentConsume;
|
private bool _currentConsume;
|
||||||
private double _currentTime;
|
private double _currentTime;
|
||||||
private double _totalTime;
|
private double _totalTime;
|
||||||
|
private IEnumerable<IMpdFile> _Playlist;
|
||||||
|
|
||||||
private MpdStatus _currentStatus;
|
private MpdStatus _currentStatus;
|
||||||
private IMpdFile _currentSong;
|
private IMpdFile _currentSong;
|
||||||
private BitmapFrame _cover;
|
private BitmapImage _cover;
|
||||||
|
public Statistics _stats;
|
||||||
private readonly System.Timers.Timer _elapsedTimer;
|
private readonly System.Timers.Timer _elapsedTimer;
|
||||||
private DispatcherTimer _retryTimer;
|
private DispatcherTimer _retryTimer;
|
||||||
|
|
||||||
bool _isUpdatingStatus = false;
|
bool _isUpdatingStatus = false;
|
||||||
bool _isUpdatingSong = false;
|
bool _isUpdatingSong = false;
|
||||||
|
|
||||||
|
public IPAddress _ipAddress;
|
||||||
|
|
||||||
private event EventHandler ConnectionChanged;
|
private event EventHandler ConnectionChanged;
|
||||||
private event EventHandler StatusChanged;
|
private event EventHandler StatusChanged;
|
||||||
private event EventHandler SongChanged;
|
private event EventHandler SongChanged;
|
||||||
@ -47,17 +66,19 @@ namespace unison
|
|||||||
private MpcConnection _connection;
|
private MpcConnection _connection;
|
||||||
private MpcConnection _commandConnection;
|
private MpcConnection _commandConnection;
|
||||||
private IPEndPoint _mpdEndpoint;
|
private IPEndPoint _mpdEndpoint;
|
||||||
private CancellationTokenSource cancelToken;
|
|
||||||
|
public CancellationTokenSource _cancelCommand;
|
||||||
|
private CancellationTokenSource _cancelConnect;
|
||||||
|
|
||||||
public MPDHandler()
|
public MPDHandler()
|
||||||
{
|
{
|
||||||
cancelToken = new CancellationTokenSource();
|
Startup(null, null);
|
||||||
|
|
||||||
Initialize(null, null);
|
_stats = new Statistics();
|
||||||
|
|
||||||
_retryTimer = new DispatcherTimer();
|
_retryTimer = new DispatcherTimer();
|
||||||
_retryTimer.Interval = TimeSpan.FromSeconds(5);
|
_retryTimer.Interval = TimeSpan.FromSeconds(5);
|
||||||
_retryTimer.Tick += Initialize;
|
_retryTimer.Tick += Startup;
|
||||||
|
|
||||||
_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);
|
||||||
@ -130,7 +151,7 @@ namespace unison
|
|||||||
|
|
||||||
public async Task<T> SafelySendCommandAsync<T>(IMpcCommand<T> command)
|
public async Task<T> SafelySendCommandAsync<T>(IMpcCommand<T> command)
|
||||||
{
|
{
|
||||||
if (_commandConnection == null)
|
if (_commandConnection == null || !IsConnected())
|
||||||
{
|
{
|
||||||
Trace.WriteLine("[SafelySendCommandAsync] no command connection");
|
Trace.WriteLine("[SafelySendCommandAsync] no command connection");
|
||||||
return default(T);
|
return default(T);
|
||||||
@ -141,7 +162,7 @@ namespace unison
|
|||||||
IMpdMessage<T> response = await _commandConnection.SendAsync(command);
|
IMpdMessage<T> response = await _commandConnection.SendAsync(command);
|
||||||
if (!response.IsResponseValid)
|
if (!response.IsResponseValid)
|
||||||
{
|
{
|
||||||
var mpdError = response.Response?.Result?.MpdError;
|
string mpdError = response.Response?.Result?.MpdError;
|
||||||
if (mpdError != null && mpdError != "")
|
if (mpdError != null && mpdError != "")
|
||||||
throw new Exception(mpdError);
|
throw new Exception(mpdError);
|
||||||
else
|
else
|
||||||
@ -158,23 +179,59 @@ namespace unison
|
|||||||
return default(T);
|
return default(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Initialize(object sender, EventArgs e)
|
public async void Startup(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!_connected)
|
await Initialize();
|
||||||
Connect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Connect()
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
CancellationToken token = cancelToken.Token;
|
Trace.WriteLine("Initializing");
|
||||||
|
|
||||||
|
Disconnected();
|
||||||
|
|
||||||
|
if (!_connected)
|
||||||
|
await Connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnected()
|
||||||
|
{
|
||||||
|
_connected = false;
|
||||||
|
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
_commandConnection?.DisconnectAsync();
|
||||||
|
_connection?.DisconnectAsync();
|
||||||
|
|
||||||
|
_cancelConnect?.Cancel();
|
||||||
|
_cancelConnect = new CancellationTokenSource();
|
||||||
|
|
||||||
|
_cancelCommand?.Cancel();
|
||||||
|
_cancelCommand = new CancellationTokenSource();
|
||||||
|
|
||||||
|
_connection = null;
|
||||||
|
_commandConnection = null;
|
||||||
|
|
||||||
|
Trace.WriteLine("Disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Connect()
|
||||||
|
{
|
||||||
|
Trace.WriteLine("Connecting");
|
||||||
|
|
||||||
|
if (_cancelCommand.IsCancellationRequested || _cancelConnect.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_connection = await ConnectInternal(token);
|
_connection = await ConnectInternal(_cancelConnect.Token);
|
||||||
_commandConnection = await ConnectInternal(token);
|
_commandConnection = await ConnectInternal(_cancelCommand.Token);
|
||||||
}
|
}
|
||||||
catch(MpcNET.Exceptions.MpcConnectException exception)
|
catch (MpcNET.Exceptions.MpcConnectException e)
|
||||||
{
|
{
|
||||||
Trace.WriteLine("exception: " + exception);
|
_connected = false;
|
||||||
|
Trace.WriteLine($"Error in connect: {e.Message}");
|
||||||
|
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (_connection != null && _commandConnection != null)
|
if (_connection != null && _commandConnection != null)
|
||||||
{
|
{
|
||||||
@ -187,6 +244,7 @@ namespace unison
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_connected = false;
|
||||||
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -194,18 +252,41 @@ namespace unison
|
|||||||
await UpdateStatusAsync();
|
await UpdateStatusAsync();
|
||||||
await UpdateSongAsync();
|
await UpdateSongAsync();
|
||||||
|
|
||||||
Loop(token);
|
Loop(_cancelCommand.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MpcConnection> ConnectInternal(CancellationToken token)
|
private async Task<MpcConnection> ConnectInternal(CancellationToken token)
|
||||||
{
|
{
|
||||||
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out IPAddress ipAddress);
|
if (token.IsCancellationRequested)
|
||||||
|
return null;
|
||||||
|
|
||||||
_mpdEndpoint = new IPEndPoint(ipAddress, Properties.Settings.Default.mpd_port);
|
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out _ipAddress);
|
||||||
|
|
||||||
|
if (_ipAddress == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IPAddress[] addrList = Dns.GetHostAddresses(Properties.Settings.Default.mpd_host);
|
||||||
|
if (addrList.Length > 0)
|
||||||
|
{
|
||||||
|
foreach (IPAddress addr in addrList)
|
||||||
|
{
|
||||||
|
if (addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
||||||
|
_ipAddress = addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
MpcConnection connection = new MpcConnection(_mpdEndpoint);
|
||||||
await connection.ConnectAsync(token);
|
await connection.ConnectAsync(token);
|
||||||
|
|
||||||
/*if (!string.IsNullOrEmpty(Properties.Settings.Default.mpd_password))
|
if (!string.IsNullOrEmpty(Properties.Settings.Default.mpd_password))
|
||||||
{
|
{
|
||||||
IMpdMessage<string> result = await connection.SendAsync(new PasswordCommand(Properties.Settings.Default.mpd_password));
|
IMpdMessage<string> result = await connection.SendAsync(new PasswordCommand(Properties.Settings.Default.mpd_password));
|
||||||
if (!result.IsResponseValid)
|
if (!result.IsResponseValid)
|
||||||
@ -213,21 +294,11 @@ namespace unison
|
|||||||
string mpdError = result.Response?.Result?.MpdError;
|
string mpdError = result.Response?.Result?.MpdError;
|
||||||
Trace.WriteLine(mpdError);
|
Trace.WriteLine(mpdError);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Disconnected()
|
|
||||||
{
|
|
||||||
_connected = false;
|
|
||||||
|
|
||||||
_connection = null;
|
|
||||||
_commandConnection = null;
|
|
||||||
|
|
||||||
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Loop(CancellationToken token)
|
private void Loop(CancellationToken token)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@ -236,24 +307,31 @@ namespace unison
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested || _connection == null)
|
if (token.IsCancellationRequested || _connection == null || !IsConnected())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options"));
|
IMpdMessage<string> idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options update"));
|
||||||
|
|
||||||
if (idleChanges.IsResponseValid)
|
if (idleChanges.IsResponseValid)
|
||||||
await HandleIdleResponseAsync(idleChanges.Response.Content);
|
await HandleIdleResponseAsync(idleChanges.Response.Content);
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"Error in Idle connection thread (1): {idleChanges.Response?.Content}");
|
||||||
throw new Exception(idleChanges.Response?.Content);
|
throw new Exception(idleChanges.Response?.Content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
if (token.IsCancellationRequested)
|
||||||
Disconnected();
|
Trace.WriteLine($"Idle connection cancelled.");
|
||||||
|
else
|
||||||
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
await Initialize();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleIdleResponseAsync(string subsystems)
|
private async Task HandleIdleResponseAsync(string subsystems)
|
||||||
@ -271,6 +349,7 @@ namespace unison
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
await Initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,6 +374,7 @@ namespace unison
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
await Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
_isUpdatingStatus = false;
|
_isUpdatingStatus = false;
|
||||||
@ -302,6 +382,8 @@ namespace unison
|
|||||||
|
|
||||||
private async Task UpdateSongAsync()
|
private async Task UpdateSongAsync()
|
||||||
{
|
{
|
||||||
|
Trace.WriteLine("Updating song");
|
||||||
|
|
||||||
if (_connection == null || _isUpdatingSong)
|
if (_connection == null || _isUpdatingSong)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -321,39 +403,70 @@ namespace unison
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||||
|
await Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
_isUpdatingSong = false;
|
_isUpdatingSong = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void GetAlbumCover(string path, CancellationToken token = default)
|
private async void GetAlbumCover(string path, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
List<byte> data = new List<byte>();
|
List<byte> data = new List<byte>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_connection == null)
|
bool ReadPictureFailed = true;
|
||||||
return;
|
|
||||||
|
|
||||||
long totalBinarySize = 9999;
|
long totalBinarySize = 9999;
|
||||||
long currentSize = 0;
|
long currentSize = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
|
if (_connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
|
||||||
if (!albumReq.IsResponseValid)
|
if (!albumReq.IsResponseValid)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var response = albumReq.Response.Content;
|
MpdBinaryData response = albumReq.Response.Content;
|
||||||
if (response.Binary == 0)
|
if (response == null || response.Binary == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ReadPictureFailed = false;
|
||||||
|
totalBinarySize = response.Size;
|
||||||
|
currentSize += response.Binary;
|
||||||
|
data.AddRange(response.Data);
|
||||||
|
}
|
||||||
|
while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!ReadPictureFailed)
|
||||||
|
break;
|
||||||
|
if (_connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new ReadPictureCommand(path, currentSize));
|
||||||
|
if (!albumReq.IsResponseValid)
|
||||||
|
break;
|
||||||
|
|
||||||
|
MpdBinaryData response = albumReq.Response.Content;
|
||||||
|
if (response == null || response.Binary == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
totalBinarySize = response.Size;
|
totalBinarySize = response.Size;
|
||||||
currentSize += response.Binary;
|
currentSize += response.Binary;
|
||||||
data.AddRange(response.Data);
|
data.AddRange(response.Data);
|
||||||
} while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
}
|
||||||
|
while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
Trace.WriteLine("Exception caught while getting albumart: " + e);
|
Trace.WriteLine("Exception caught while getting albumart: " + e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -363,7 +476,12 @@ namespace unison
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
using MemoryStream stream = new MemoryStream(data.ToArray());
|
using MemoryStream stream = new MemoryStream(data.ToArray());
|
||||||
_cover = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
_cover = new BitmapImage();
|
||||||
|
_cover.BeginInit();
|
||||||
|
_cover.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
_cover.StreamSource = stream;
|
||||||
|
_cover.EndInit();
|
||||||
|
_cover.Freeze();
|
||||||
}
|
}
|
||||||
UpdateCover();
|
UpdateCover();
|
||||||
}
|
}
|
||||||
@ -395,7 +513,7 @@ namespace unison
|
|||||||
SongChanged?.Invoke(this, EventArgs.Empty);
|
SongChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
string uri = Regex.Escape(_currentSong.Path);
|
string uri = Regex.Escape(_currentSong.Path);
|
||||||
GetAlbumCover(uri);
|
GetAlbumCover(uri, _cancelCommand.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCover()
|
public void UpdateCover()
|
||||||
@ -405,16 +523,31 @@ namespace unison
|
|||||||
|
|
||||||
public IMpdFile GetCurrentSong() => _currentSong;
|
public IMpdFile GetCurrentSong() => _currentSong;
|
||||||
public MpdStatus GetStatus() => _currentStatus;
|
public MpdStatus GetStatus() => _currentStatus;
|
||||||
public BitmapFrame GetCover() => _cover;
|
public BitmapImage GetCover() => _cover;
|
||||||
public string GetVersion() => _version;
|
public string GetVersion() => _version;
|
||||||
|
public Statistics GetStats() => _stats;
|
||||||
public double GetCurrentTime() => _currentTime;
|
public double GetCurrentTime() => _currentTime;
|
||||||
|
public IEnumerable<IMpdFile> GetPlaylist() => _Playlist;
|
||||||
|
|
||||||
public bool IsConnected() => _connected;
|
public bool IsConnected() => _connected;
|
||||||
public bool IsPlaying() => _currentStatus?.State == MpdState.Play;
|
public bool IsPlaying() => _currentStatus?.State == MpdState.Play;
|
||||||
|
|
||||||
public void Prev() => SendCommand(new PreviousCommand());
|
public bool CanPrevNext = true;
|
||||||
public void Next() => SendCommand(new NextCommand());
|
|
||||||
|
public void Prev()
|
||||||
|
{
|
||||||
|
if (CanPrevNext)
|
||||||
|
SendCommand(new PreviousCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Next()
|
||||||
|
{
|
||||||
|
if (CanPrevNext)
|
||||||
|
SendCommand(new NextCommand());
|
||||||
|
}
|
||||||
|
|
||||||
public void PlayPause() => SendCommand(new PauseResumeCommand());
|
public void PlayPause() => SendCommand(new PauseResumeCommand());
|
||||||
|
public void Play(int pos) => SendCommand(new PlayCommand(pos));
|
||||||
|
|
||||||
public void Random() => SendCommand(new RandomCommand(!_currentRandom));
|
public void Random() => SendCommand(new RandomCommand(!_currentRandom));
|
||||||
public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
|
public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
|
||||||
@ -439,5 +572,65 @@ namespace unison
|
|||||||
_currentVolume = 0;
|
_currentVolume = 0;
|
||||||
SetVolume(_currentVolume);
|
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)
|
||||||
|
{
|
||||||
|
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 Task QueryPlaylist() => _Playlist = await SafelySendCommandAsync(new PlaylistCommand());
|
||||||
|
|
||||||
|
public int GetPlaylistCount()
|
||||||
|
{
|
||||||
|
if (_Playlist == null)
|
||||||
|
return 0;
|
||||||
|
return _Playlist.ToArray().Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void QueryStats()
|
||||||
|
{
|
||||||
|
Dictionary<string, string> Response = await SafelySendCommandAsync(new StatsCommand());
|
||||||
|
if (Response == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
81
Handlers/RadioHandler.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RadioBrowser;
|
||||||
|
using RadioBrowser.Models;
|
||||||
|
|
||||||
|
namespace unison.Handlers
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class RadioHandler
|
||||||
|
{
|
||||||
|
private readonly RadioBrowserClient _radioBrowser;
|
||||||
|
private readonly bool _connected = true;
|
||||||
|
|
||||||
|
public bool IsConnected() => _connected;
|
||||||
|
|
||||||
|
|
||||||
|
public RadioHandler()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_radioBrowser = new RadioBrowserClient();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.WriteLine("Exception while connecting to RadioBrowser: " + e.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<NameAndCount>> GetCountries()
|
||||||
|
{
|
||||||
|
return await _radioBrowser.Lists.GetCountriesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StationInfo>> AdvancedSearch(AdvancedSearchOptions options)
|
||||||
|
{
|
||||||
|
return await _radioBrowser.Search.AdvancedAsync(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
Handlers/ShuffleHandler.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using MpcNET;
|
||||||
|
using MpcNET.Commands.Database;
|
||||||
|
using MpcNET.Commands.Playback;
|
||||||
|
using MpcNET.Commands.Queue;
|
||||||
|
using MpcNET.Commands.Reflection;
|
||||||
|
using MpcNET.Tags;
|
||||||
|
using MpcNET.Types;
|
||||||
|
using MpcNET.Types.Filters;
|
||||||
|
|
||||||
|
namespace unison
|
||||||
|
{
|
||||||
|
class ShuffleHandler
|
||||||
|
{
|
||||||
|
private readonly MPDHandler _mpd;
|
||||||
|
public int AddedSongs = 0;
|
||||||
|
|
||||||
|
public List<string> SongList { get; }
|
||||||
|
|
||||||
|
public ShuffleHandler()
|
||||||
|
{
|
||||||
|
SongList = new();
|
||||||
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GetSongsFromFilter(List<IFilter> filter, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SongList.Clear();
|
||||||
|
|
||||||
|
int song = _mpd.GetStats().Songs;
|
||||||
|
IEnumerable<IMpdFile> response = await _mpd.SafelySendCommandAsync(new SearchCommand(filter, 0, song + 1));
|
||||||
|
foreach (IMpdFile file in response)
|
||||||
|
SongList.Add(file.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddToQueueRandom(int SongNumber, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int AddedSongs = 0;
|
||||||
|
|
||||||
|
var commandList = new CommandList();
|
||||||
|
int songTotal = _mpd.GetStats().Songs;
|
||||||
|
|
||||||
|
for (int i = 0; i < SongNumber; i++)
|
||||||
|
{
|
||||||
|
int song = new Random().Next(0, songTotal - 1);
|
||||||
|
commandList.Add(new SearchAddCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1));
|
||||||
|
AddedSongs++;
|
||||||
|
|
||||||
|
// play if stopped or unknown state (no queue managing at the moment, so mandatory)
|
||||||
|
if (i == 0 && (_mpd.GetStatus().State != MpdState.Play && _mpd.GetStatus().State != MpdState.Pause))
|
||||||
|
commandList.Add(new PlayCommand(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
await _mpd.SafelySendCommandAsync(commandList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddToQueueFilter(int SongNumber, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int AddedSongs = 0;
|
||||||
|
|
||||||
|
// more (or equal) requested songs than available => add everything
|
||||||
|
if (SongNumber >= SongList.Count)
|
||||||
|
{
|
||||||
|
var commandList = new CommandList();
|
||||||
|
foreach (string path in SongList)
|
||||||
|
{
|
||||||
|
commandList.Add(new AddCommand(path));
|
||||||
|
AddedSongs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _mpd.SafelySendCommandAsync(commandList);
|
||||||
|
}
|
||||||
|
// more available songs than requested =>
|
||||||
|
// we add unique indexes until we reach the requested amount
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HashSet<int> SongIndex = new();
|
||||||
|
while (SongIndex.Count < SongNumber)
|
||||||
|
{
|
||||||
|
int MaxIndex = new Random().Next(0, SongList.Count - 1);
|
||||||
|
SongIndex.Add(MaxIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandList = new CommandList();
|
||||||
|
foreach (int index in SongIndex)
|
||||||
|
{
|
||||||
|
commandList.Add(new AddCommand(SongList[index]));
|
||||||
|
AddedSongs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _mpd.SafelySendCommandAsync(commandList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,12 +14,22 @@ namespace unison
|
|||||||
{
|
{
|
||||||
if (Properties.Settings.Default.snapcast_startup)
|
if (Properties.Settings.Default.snapcast_startup)
|
||||||
{
|
{
|
||||||
var mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
if (mpd.IsConnected())
|
if (mpd.IsConnected())
|
||||||
LaunchOrExit();
|
LaunchOrExit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleExit(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_snapcast.Kill();
|
||||||
|
HasStarted = false;
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
UpdateInterface();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateInterface()
|
public void UpdateInterface()
|
||||||
{
|
{
|
||||||
TaskbarIcon Systray = (TaskbarIcon)Application.Current.Properties["systray"];
|
TaskbarIcon Systray = (TaskbarIcon)Application.Current.Properties["systray"];
|
||||||
@ -37,19 +47,24 @@ namespace unison
|
|||||||
{
|
{
|
||||||
if (!HasStarted && !ForceExit)
|
if (!HasStarted && !ForceExit)
|
||||||
{
|
{
|
||||||
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
|
||||||
_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 {mpd._ipAddress}";
|
||||||
_snapcast.StartInfo.CreateNoWindow = !Properties.Settings.Default.snapcast_window;
|
_snapcast.StartInfo.CreateNoWindow = !Properties.Settings.Default.snapcast_window;
|
||||||
|
_snapcast.EnableRaisingEvents = true;
|
||||||
|
_snapcast.Exited += new EventHandler(HandleExit);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_snapcast.Start();
|
_snapcast.Start();
|
||||||
}
|
}
|
||||||
catch (Exception err)
|
catch (Exception err)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"[{unison.Resources.Resources.Snapcast_Popup1}]\n" +
|
MessageBox.Show($"[{Resources.Resources.Snapcast_Popup1}]\n" +
|
||||||
$"{unison.Resources.Resources.Snapcast_Popup2} {err.Message}\n\n" +
|
$"{Resources.Resources.Snapcast_Popup2} {err.Message}\n\n" +
|
||||||
$"{unison.Resources.Resources.Snapcast_Popup3} {Properties.Settings.Default.snapcast_path}\n" +
|
$"{Resources.Resources.Snapcast_Popup3} {Properties.Settings.Default.snapcast_path}\n" +
|
||||||
$"{unison.Resources.Resources.Snapcast_Popup4}",
|
$"{Resources.Resources.Snapcast_Popup4}",
|
||||||
"unison", MessageBoxButton.OK, MessageBoxImage.Error);
|
"unison", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
58
Handlers/UpdateHandler.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Windows;
|
||||||
|
using AutoUpdaterDotNET;
|
||||||
|
|
||||||
|
namespace unison.Handlers
|
||||||
|
{
|
||||||
|
internal class UpdateHandler
|
||||||
|
{
|
||||||
|
readonly string xmlFile = "https://raw.githubusercontent.com/ZetaKebab/unison/main/Installer/unison.xml";
|
||||||
|
|
||||||
|
private bool _UpdateAvailable = false;
|
||||||
|
public bool UpdateAvailable() => _UpdateAvailable;
|
||||||
|
|
||||||
|
private bool _RequestedCheck = false;
|
||||||
|
|
||||||
|
public UpdateHandler()
|
||||||
|
{
|
||||||
|
AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(bool RequestCheck = false)
|
||||||
|
{
|
||||||
|
_RequestedCheck = RequestCheck;
|
||||||
|
AutoUpdater.Start(xmlFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CutVersionNumber(string number)
|
||||||
|
{
|
||||||
|
return number.Substring(0, number.LastIndexOf("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Error == null)
|
||||||
|
{
|
||||||
|
if (args.IsUpdateAvailable)
|
||||||
|
{
|
||||||
|
_UpdateAvailable = true;
|
||||||
|
string number = CutVersionNumber(args.CurrentVersion);
|
||||||
|
|
||||||
|
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||||
|
MainWin.UpdateUpdateStatus(number);
|
||||||
|
|
||||||
|
MessageBoxResult Result = MessageBox.Show($"{Resources.Resources.Update_Message1} {number}.\n{Resources.Resources.Update_Message2}",
|
||||||
|
"unison", MessageBoxButton.YesNo, MessageBoxImage.Information);
|
||||||
|
if (Result == MessageBoxResult.Yes)
|
||||||
|
AutoUpdater.DownloadUpdate(args);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_RequestedCheck)
|
||||||
|
MessageBox.Show($"{Resources.Resources.Update_NoUpdate}", "unison", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
Installer/unison.iss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#define Name "unison"
|
||||||
|
#define Version "1.3.1"
|
||||||
|
#define Snapcast "snapclient_0.26.0-1_win64"
|
||||||
|
#define Publisher "Th<54>o Marchal"
|
||||||
|
#define URL "https://github.com/ZetaKebab/unison"
|
||||||
|
#define ExeName "unison.exe"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppName={#Name}
|
||||||
|
AppVersion={#Version}
|
||||||
|
AppVerName={#Name} v{#Version}
|
||||||
|
AppPublisher={#Publisher}
|
||||||
|
AppPublisherURL={#URL}
|
||||||
|
AppSupportURL={#URL}
|
||||||
|
AppUpdatesURL={#URL}
|
||||||
|
DefaultDirName={autopf}\{#Name}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
ArchitecturesInstallIn64BitMode=x64
|
||||||
|
OutputBaseFilename="{#Name}-v{#Version}-setup"
|
||||||
|
OutputDir=..\publish\installer
|
||||||
|
SetupIconFile=..\Resources\icon-full.ico
|
||||||
|
UninstallDisplayIcon = "{app}\{#Name}.exe"
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||||
|
Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\publish\{#ExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\publish\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\publish\unison.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\publish\{#Snapcast}\*"; DestDir: "{app}\{#Snapcast}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{#Name}"; Filename: "{app}\{#ExeName}"
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#Name}.exe"; Parameters: "-frominstaller"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
7
Installer/unison.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<item>
|
||||||
|
<version>1.3.1.0</version>
|
||||||
|
<url>https://github.com/ZetaKebab/unison/releases/download/v1.3.1/unison-v1.3.1.zip</url>
|
||||||
|
<changelog>https://raw.githubusercontent.com/ZetaKebab/unison/main/CHANGELOG.md</changelog>
|
||||||
|
<mandatory>false</mandatory>
|
||||||
|
</item>
|
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:
|
||||||
|
@ -4,14 +4,14 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||||||
-->
|
-->
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration>Release</Configuration>
|
<Configuration>Release-Stable</Configuration>
|
||||||
<Platform>Any CPU</Platform>
|
<Platform>Any CPU</Platform>
|
||||||
<PublishDir>publish\</PublishDir>
|
<PublishDir>publish\</PublishDir>
|
||||||
<PublishProtocol>FileSystem</PublishProtocol>
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
<TargetFramework>net5.0-windows</TargetFramework>
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
<SelfContained>false</SelfContained>
|
<SelfContained>false</SelfContained>
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<PublishSingleFile>True</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<PublishReadyToRun>False</PublishReadyToRun>
|
<PublishReadyToRun>false</PublishReadyToRun>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
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>
|
44
README.md
@ -1,12 +1,13 @@
|
|||||||
# unison
|
# unison
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**unison** is a very simple [Music Player Daemon (MPD)](https://www.musicpd.org/) daemon client with the following goals:
|
**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
|
* lightweight window that can be toggled with shortcuts
|
||||||
* music control through shortcuts
|
* music control through rebindable shortcuts
|
||||||
* [Snapcast](https://mjaggard.github.io/snapcast/) integration
|
* [Snapcast](https://github.com/badaix/snapcast) integration
|
||||||
|
* Radio stations
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -14,31 +15,42 @@
|
|||||||
|
|
||||||
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.
|
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
|
### Shortcuts
|
||||||
|
|
||||||
You can control your music at anytime with the shortcuts. They can of course be used if the window is not visible.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Shuffle panel
|
||||||
|
|
||||||
|
One of unison's main feature is a complete shuffle system based on criterias, aka a smart playlist.
|
||||||
|
|
||||||
|
You have two options:
|
||||||
|
* **Add to queue** allows you to add a defined number of songs to the queue.
|
||||||
|
* **Continuous shuffle** allows you, as long as the program is running, to automatically add songs to the queue.
|
||||||
|
|
||||||
|
Each of these options work with filters, but if none are selected, it is based on the entire library.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Snapcast
|
### Snapcast
|
||||||
|
|
||||||
The main goal of embedding Snapcast is the ability to listen locally to music when I'm not using my main audio system. The computer running unison can then play music easily.
|
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.
|
||||||
|
|
||||||
## Caveats
|
### Radio stations
|
||||||
|
|
||||||
### Missing features
|
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.
|
||||||
|
|
||||||
* Custom shortcuts.
|

|
||||||
* MPD passwords: I don't really see the point, but if asked, I will integrate them.
|
|
||||||
* Any sort of playlist and queue management. I use other software to do it, but I might implement them at some point.
|
|
||||||
|
|
||||||
### Wanted features
|
## Planned features
|
||||||
|
|
||||||
* A complete shuffle system based on set criteria, aka a smart playlist.
|
* Playlist, queue and library management
|
||||||
* Radio integration.
|
* More options for the shuffle panel
|
||||||
|
* Dark mode
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
unison is translated in English and French. You can contribute if you want!
|
unison is translated in English, French and Spanish. You can contribute if you want!
|
287
Resources/Resources.Designer.cs
generated
@ -19,7 +19,7 @@ namespace unison.Resources {
|
|||||||
// class via a tool like ResGen or Visual Studio.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// with the /str option, or rebuild your VS project.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
public class Resources {
|
public class Resources {
|
||||||
@ -69,6 +69,87 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Settings.
|
/// Looks up a localized string similar to Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -115,7 +196,25 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Connected to MPD.
|
/// Looks up a localized string similar to Please note that since MPD passwords are not secure (they are sent in plain text to the server), they are saved as is in the setting file..
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ConnectionPasswordInfo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ConnectionPasswordInfo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Status:.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_ConnectionStatus {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_ConnectionStatus", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to connected to MPD.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Settings_ConnectionStatusConnected {
|
public static string Settings_ConnectionStatusConnected {
|
||||||
get {
|
get {
|
||||||
@ -124,7 +223,7 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Connecting....
|
/// Looks up a localized string similar to connecting....
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Settings_ConnectionStatusConnecting {
|
public static string Settings_ConnectionStatusConnecting {
|
||||||
get {
|
get {
|
||||||
@ -133,7 +232,7 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Not connected..
|
/// Looks up a localized string similar to not connected..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Settings_ConnectionStatusOffline {
|
public static string Settings_ConnectionStatusOffline {
|
||||||
get {
|
get {
|
||||||
@ -168,15 +267,6 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a localized string similar to 's updated MpcNET.
|
|
||||||
/// </summary>
|
|
||||||
public static string Settings_MpcNET {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("Settings_MpcNET", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Next track.
|
/// Looks up a localized string similar to Next track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -186,6 +276,15 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Password.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_Password {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_Password", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Play / pause.
|
/// Looks up a localized string similar to Play / pause.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -222,6 +321,24 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Show window.
|
/// Looks up a localized string similar to Show window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -339,6 +456,15 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Volume mute.
|
||||||
|
/// </summary>
|
||||||
|
public static string Settings_VolumeMute {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Settings_VolumeMute", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Volume offset.
|
/// Looks up a localized string similar to Volume offset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -411,6 +537,78 @@ namespace unison.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Stop Snapcast.
|
/// Looks up a localized string similar to Stop Snapcast.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -419,5 +617,68 @@ namespace unison.Resources {
|
|||||||
return ResourceManager.GetString("StopSnapcast", resourceCulture);
|
return ResourceManager.GetString("StopSnapcast", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Check for updates.
|
||||||
|
/// </summary>
|
||||||
|
public static string Update_ButtonCheck {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Update_ButtonCheck", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Start update.
|
||||||
|
/// </summary>
|
||||||
|
public static string Update_ButtonStart {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Update_ButtonStart", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Update available! New version is.
|
||||||
|
/// </summary>
|
||||||
|
public static string Update_Message1 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Update_Message1", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Install now?.
|
||||||
|
/// </summary>
|
||||||
|
public static string Update_Message2 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Update_Message2", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No update available..
|
||||||
|
/// </summary>
|
||||||
|
public static string Update_NoUpdate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Update_NoUpdate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to New version.
|
||||||
|
/// </summary>
|
||||||
|
public static string Update_String1 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Update_String1", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to available!.
|
||||||
|
/// </summary>
|
||||||
|
public static string Update_String2 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Update_String2", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
327
Resources/Resources.es-ES.resx
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
<?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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Exit" xml:space="preserve">
|
||||||
|
<value>Salir</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radios" xml:space="preserve">
|
||||||
|
<value>Radios</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Country" xml:space="preserve">
|
||||||
|
<value>Países</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Loading" xml:space="preserve">
|
||||||
|
<value>Cargando estaciones...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Name" xml:space="preserve">
|
||||||
|
<value>Nombre</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_NotFound" xml:space="preserve">
|
||||||
|
<value>¡No estaciones encontradas!</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Reset" xml:space="preserve">
|
||||||
|
<value>Reinicializar</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Search" xml:space="preserve">
|
||||||
|
<value>Búsqueda</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_SearchStation" xml:space="preserve">
|
||||||
|
<value>Buscar estación</value>
|
||||||
|
</data>
|
||||||
|
<data name="Radio_Tags" xml:space="preserve">
|
||||||
|
<value>Tags</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings" xml:space="preserve">
|
||||||
|
<value>Ajustes</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_About" xml:space="preserve">
|
||||||
|
<value>Acerca de</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_AboutInfo" xml:space="preserve">
|
||||||
|
<value>unison es un software libre. Es construido con las siguientes tecnologías:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectButton" xml:space="preserve">
|
||||||
|
<value>Conexión</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Connection" xml:space="preserve">
|
||||||
|
<value>Conexión</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionPasswordInfo" xml:space="preserve">
|
||||||
|
<value>Tenga en cuenta que, dado que las contraseñas de MPD no son seguras (se envían en texto sin formato al servidor), se guardan tal cual en el archivo de configuración.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatus" xml:space="preserve">
|
||||||
|
<value>Estado:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||||
|
<value>conectado a MPD</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||||
|
<value>conectando...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||||
|
<value>no conectado.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Host" xml:space="preserve">
|
||||||
|
<value>Host</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_License" xml:space="preserve">
|
||||||
|
<value>Licencia</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_MadeBy" xml:space="preserve">
|
||||||
|
<value>Creado por</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_NextTrack" xml:space="preserve">
|
||||||
|
<value>Pista siguiente</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Password" xml:space="preserve">
|
||||||
|
<value>Contraseña</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_PlayPause" xml:space="preserve">
|
||||||
|
<value>Jugar / pausa</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Port" xml:space="preserve">
|
||||||
|
<value>Puerto</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_PreviousTrack" xml:space="preserve">
|
||||||
|
<value>Pista precedente</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Shortcuts" xml:space="preserve">
|
||||||
|
<value>Atajos</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShortcutsInfo" xml:space="preserve">
|
||||||
|
<value>Tenga en cuenta que si no está reconocida la tecla, esto se debe a una limitación en el funcionamiento de las teclas virtuales.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShortcutsKey" xml:space="preserve">
|
||||||
|
<value>Entre una tecla...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||||
|
<value>Mostrar ventana</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo1" xml:space="preserve">
|
||||||
|
<value>Puede cambiar a su propia versión instalada localmente del cliente Snapcast con una ruta</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo2" xml:space="preserve">
|
||||||
|
<value> absoluta</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastInfo3" xml:space="preserve">
|
||||||
|
<value>.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastLauch" xml:space="preserve">
|
||||||
|
<value>Lanzar al inicio</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastPath" xml:space="preserve">
|
||||||
|
<value>Ruta del ejecutable</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastPort" xml:space="preserve">
|
||||||
|
<value>Puerto</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastResetButton" xml:space="preserve">
|
||||||
|
<value>Reinicializar</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SnapcastWindow" xml:space="preserve">
|
||||||
|
<value>Mostrar ventana de Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SourceCode1" xml:space="preserve">
|
||||||
|
<value>Código fuente disponible gratuitamente</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_SourceCode2" xml:space="preserve">
|
||||||
|
<value>aquí</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Version" xml:space="preserve">
|
||||||
|
<value>Versión:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeDown" xml:space="preserve">
|
||||||
|
<value>Bajar volumen</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeMute" xml:space="preserve">
|
||||||
|
<value>Volumen mudo</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeOffset" xml:space="preserve">
|
||||||
|
<value>Intervalo de volumen</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_VolumeUp" xml:space="preserve">
|
||||||
|
<value>Subir volumen</value>
|
||||||
|
</data>
|
||||||
|
<data name="ShowWindow" xml:space="preserve">
|
||||||
|
<value>Mostrar ventana</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup1" xml:space="preserve">
|
||||||
|
<value>Error Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup2" xml:space="preserve">
|
||||||
|
<value>Ruta no válida:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup3" xml:space="preserve">
|
||||||
|
<value>Ruta actual:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Snapcast_Popup4" xml:space="preserve">
|
||||||
|
<value>Puede restablecerlo en la configuración si es necesario.</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartSnapcast" xml:space="preserve">
|
||||||
|
<value>Iniciar Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats" xml:space="preserve">
|
||||||
|
<value>Estadísticas</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Albums" xml:space="preserve">
|
||||||
|
<value>Álbumes:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Artists" xml:space="preserve">
|
||||||
|
<value>Artistas:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_LastDatabaseUpdate" xml:space="preserve">
|
||||||
|
<value>Última actualización de la base de datos:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Songs" xml:space="preserve">
|
||||||
|
<value>Canciones:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_TotalPlaytime" xml:space="preserve">
|
||||||
|
<value>Tiempo de juego total:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_TotalTimePlayed" xml:space="preserve">
|
||||||
|
<value>Tiempo total jugado:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Stats_Uptime" xml:space="preserve">
|
||||||
|
<value>Tiempo de actividad de MPD:</value>
|
||||||
|
</data>
|
||||||
|
<data name="StopSnapcast" xml:space="preserve">
|
||||||
|
<value>Parar Snapcast</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_ButtonCheck" xml:space="preserve">
|
||||||
|
<value>Buscar actualización</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_ButtonStart" xml:space="preserve">
|
||||||
|
<value>Actualizar</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_Message1" xml:space="preserve">
|
||||||
|
<value>¡Actualización disponible! La nueva versión es la</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_Message2" xml:space="preserve">
|
||||||
|
<value>¿Instalar ahora?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_NoUpdate" xml:space="preserve">
|
||||||
|
<value>No actualización disponible.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_String1" xml:space="preserve">
|
||||||
|
<value>¡Nueva versión</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_String2" xml:space="preserve">
|
||||||
|
<value>disponible!</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
@ -112,14 +112,41 @@
|
|||||||
<value>2.0</value>
|
<value>2.0</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<resheader name="reader">
|
<resheader name="reader">
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Exit" xml:space="preserve">
|
<data name="Exit" xml:space="preserve">
|
||||||
<value>Quitter</value>
|
<value>Quitter</value>
|
||||||
</data>
|
</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>Aucune 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">
|
<data name="Settings" xml:space="preserve">
|
||||||
<value>Configuration</value>
|
<value>Configuration</value>
|
||||||
</data>
|
</data>
|
||||||
@ -135,14 +162,20 @@
|
|||||||
<data name="Settings_Connection" xml:space="preserve">
|
<data name="Settings_Connection" xml:space="preserve">
|
||||||
<value>Connexion</value>
|
<value>Connexion</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_ConnectionPasswordInfo" xml:space="preserve">
|
||||||
|
<value>Veuillez noter que comme les mots de passe de MPD ne sont pas sécurisés (ils sont envoyés en clair au serveur), ils sont sauvegardés tels quels.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatus" xml:space="preserve">
|
||||||
|
<value>Statut :</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||||
<value>Connecté à MPD</value>
|
<value>connecté à MPD</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||||
<value>Connexion en cours...</value>
|
<value>connexion en cours...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||||
<value>Non connecté.</value>
|
<value>non connecté.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_Host" xml:space="preserve">
|
<data name="Settings_Host" xml:space="preserve">
|
||||||
<value>Hôte</value>
|
<value>Hôte</value>
|
||||||
@ -153,12 +186,12 @@
|
|||||||
<data name="Settings_MadeBy" xml:space="preserve">
|
<data name="Settings_MadeBy" xml:space="preserve">
|
||||||
<value>Créé par</value>
|
<value>Créé par</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_MpcNET" xml:space="preserve">
|
|
||||||
<value> et sa version de MpcNET</value>
|
|
||||||
</data>
|
|
||||||
<data name="Settings_NextTrack" xml:space="preserve">
|
<data name="Settings_NextTrack" xml:space="preserve">
|
||||||
<value>Piste suivante</value>
|
<value>Piste suivante</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_Password" xml:space="preserve">
|
||||||
|
<value>Mot de passe</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_PlayPause" xml:space="preserve">
|
<data name="Settings_PlayPause" xml:space="preserve">
|
||||||
<value>Jouer / pause</value>
|
<value>Jouer / pause</value>
|
||||||
</data>
|
</data>
|
||||||
@ -171,6 +204,12 @@
|
|||||||
<data name="Settings_Shortcuts" xml:space="preserve">
|
<data name="Settings_Shortcuts" xml:space="preserve">
|
||||||
<value>Raccourcis</value>
|
<value>Raccourcis</value>
|
||||||
</data>
|
</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">
|
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||||
<value>Afficher la fenêtre</value>
|
<value>Afficher la fenêtre</value>
|
||||||
</data>
|
</data>
|
||||||
@ -210,6 +249,9 @@
|
|||||||
<data name="Settings_VolumeDown" xml:space="preserve">
|
<data name="Settings_VolumeDown" xml:space="preserve">
|
||||||
<value>Baisse de volume</value>
|
<value>Baisse de volume</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_VolumeMute" xml:space="preserve">
|
||||||
|
<value>Volume en sourdine</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_VolumeOffset" xml:space="preserve">
|
<data name="Settings_VolumeOffset" xml:space="preserve">
|
||||||
<value>Écart de volume</value>
|
<value>Écart de volume</value>
|
||||||
</data>
|
</data>
|
||||||
@ -234,7 +276,52 @@
|
|||||||
<data name="StartSnapcast" xml:space="preserve">
|
<data name="StartSnapcast" xml:space="preserve">
|
||||||
<value>Démarrer Snapcast</value>
|
<value>Démarrer Snapcast</value>
|
||||||
</data>
|
</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">
|
<data name="StopSnapcast" xml:space="preserve">
|
||||||
<value>Stopper Snapcast</value>
|
<value>Stopper Snapcast</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Update_ButtonCheck" xml:space="preserve">
|
||||||
|
<value>Vérifier les mises à jour</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_ButtonStart" xml:space="preserve">
|
||||||
|
<value>Mettre à jour</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_Message1" xml:space="preserve">
|
||||||
|
<value>Mise à jour disponible ! La nouvelle version est la</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_Message2" xml:space="preserve">
|
||||||
|
<value>Installer maintenant ?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_NoUpdate" xml:space="preserve">
|
||||||
|
<value>Pas de mise à jour disponible.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_String1" xml:space="preserve">
|
||||||
|
<value>Nouvelle version</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_String2" xml:space="preserve">
|
||||||
|
<value>disponible !</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -112,14 +112,41 @@
|
|||||||
<value>2.0</value>
|
<value>2.0</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<resheader name="reader">
|
<resheader name="reader">
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Exit" xml:space="preserve">
|
<data name="Exit" xml:space="preserve">
|
||||||
<value>Exit</value>
|
<value>Exit</value>
|
||||||
</data>
|
</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">
|
<data name="Settings" xml:space="preserve">
|
||||||
<value>Settings</value>
|
<value>Settings</value>
|
||||||
</data>
|
</data>
|
||||||
@ -135,14 +162,20 @@
|
|||||||
<data name="Settings_Connection" xml:space="preserve">
|
<data name="Settings_Connection" xml:space="preserve">
|
||||||
<value>Connection</value>
|
<value>Connection</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_ConnectionPasswordInfo" xml:space="preserve">
|
||||||
|
<value>Please note that since MPD passwords are not secure (they are sent in plain text to the server), they are saved as is in the setting file.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_ConnectionStatus" xml:space="preserve">
|
||||||
|
<value>Status:</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||||
<value>Connected to MPD</value>
|
<value>connected to MPD</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||||
<value>Connecting...</value>
|
<value>connecting...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||||
<value>Not connected.</value>
|
<value>not connected.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_Host" xml:space="preserve">
|
<data name="Settings_Host" xml:space="preserve">
|
||||||
<value>Host</value>
|
<value>Host</value>
|
||||||
@ -153,12 +186,12 @@
|
|||||||
<data name="Settings_MadeBy" xml:space="preserve">
|
<data name="Settings_MadeBy" xml:space="preserve">
|
||||||
<value>Made by</value>
|
<value>Made by</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_MpcNET" xml:space="preserve">
|
|
||||||
<value>'s updated MpcNET</value>
|
|
||||||
</data>
|
|
||||||
<data name="Settings_NextTrack" xml:space="preserve">
|
<data name="Settings_NextTrack" xml:space="preserve">
|
||||||
<value>Next track</value>
|
<value>Next track</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_Password" xml:space="preserve">
|
||||||
|
<value>Password</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_PlayPause" xml:space="preserve">
|
<data name="Settings_PlayPause" xml:space="preserve">
|
||||||
<value>Play / pause</value>
|
<value>Play / pause</value>
|
||||||
</data>
|
</data>
|
||||||
@ -171,6 +204,12 @@
|
|||||||
<data name="Settings_Shortcuts" xml:space="preserve">
|
<data name="Settings_Shortcuts" xml:space="preserve">
|
||||||
<value>Shortcuts</value>
|
<value>Shortcuts</value>
|
||||||
</data>
|
</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">
|
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||||
<value>Show window</value>
|
<value>Show window</value>
|
||||||
</data>
|
</data>
|
||||||
@ -210,6 +249,9 @@
|
|||||||
<data name="Settings_VolumeDown" xml:space="preserve">
|
<data name="Settings_VolumeDown" xml:space="preserve">
|
||||||
<value>Volume down</value>
|
<value>Volume down</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Settings_VolumeMute" xml:space="preserve">
|
||||||
|
<value>Volume mute</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_VolumeOffset" xml:space="preserve">
|
<data name="Settings_VolumeOffset" xml:space="preserve">
|
||||||
<value>Volume offset</value>
|
<value>Volume offset</value>
|
||||||
</data>
|
</data>
|
||||||
@ -234,7 +276,52 @@
|
|||||||
<data name="StartSnapcast" xml:space="preserve">
|
<data name="StartSnapcast" xml:space="preserve">
|
||||||
<value>Start Snapcast</value>
|
<value>Start Snapcast</value>
|
||||||
</data>
|
</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">
|
<data name="StopSnapcast" xml:space="preserve">
|
||||||
<value>Stop Snapcast</value>
|
<value>Stop Snapcast</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Update_ButtonCheck" xml:space="preserve">
|
||||||
|
<value>Check for updates</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_ButtonStart" xml:space="preserve">
|
||||||
|
<value>Start update</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_Message1" xml:space="preserve">
|
||||||
|
<value>Update available! New version is</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_Message2" xml:space="preserve">
|
||||||
|
<value>Install now?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_NoUpdate" xml:space="preserve">
|
||||||
|
<value>No update available.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_String1" xml:space="preserve">
|
||||||
|
<value>New version</value>
|
||||||
|
</data>
|
||||||
|
<data name="Update_String2" xml:space="preserve">
|
||||||
|
<value>available!</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -1,15 +1,28 @@
|
|||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:sys="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
<system:String x:Key="snapcastPath">snapclient_0.25.0-1_win64</system:String>
|
<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="snapcastPort">1704</system:String>
|
||||||
|
|
||||||
<system:String x:Key="connectionOk1"></system:String>
|
|
||||||
<system:String x:Key="connectionOk2"></system:String>
|
|
||||||
<system:String x:Key="connectionFail"></system:String>
|
|
||||||
|
|
||||||
<system:String x:Key="playButton"></system:String>
|
<system:String x:Key="playButton"></system:String>
|
||||||
<system:String x:Key="pauseButton"></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>
|
</ResourceDictionary>
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
BIN
Resources/nothing.png
Normal file
After Width: | Height: | Size: 369 B |
BIN
Resources/radio.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Screenshots/screen-mainwindow.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
Screenshots/screen-radio.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
Screenshots/screen-shortcuts.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
Screenshots/screen-shuffle.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
Screenshots/screen-systray.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 14 KiB |
@ -28,7 +28,7 @@
|
|||||||
</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"/>
|
||||||
@ -104,18 +104,28 @@
|
|||||||
<VisualBrush Visual="{Binding ElementName=mask}"/>
|
<VisualBrush Visual="{Binding ElementName=mask}"/>
|
||||||
</StackPanel.OpacityMask>
|
</StackPanel.OpacityMask>
|
||||||
<Image x:Name="Cover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/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="/Resources/nocover.png" />
|
<Image x:Name="NoCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" Visibility="Collapsed" />
|
||||||
|
<Image x:Name="RadioCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/radio.png" Visibility="Collapsed" />
|
||||||
</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">
|
||||||
<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 HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,0">
|
||||||
<StackPanel Orientation="Horizontal">
|
<Button x:Name="Shuffle" Padding="5, 2" Click="Shuffle_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" Margin="0,0,10,0">
|
||||||
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
|
<emoji:TextBlock Text="🔁" Padding="0,0,0,2"/>
|
||||||
</StackPanel>
|
<TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/>
|
||||||
</Button>
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button x:Name="Radio" Padding="5, 2" HorizontalAlignment="Left" Click="Radios_Clicked" 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">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
<Grid x:Name="ConnectionOkIcon" 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" />
|
||||||
@ -127,12 +137,13 @@
|
|||||||
<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" />
|
<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>
|
||||||
<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="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="0,0,10,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 Text="Shuffle" Margin="5, 0, 0, 0"/>
|
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>-->
|
</Button>
|
||||||
|
|
||||||
<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"/>
|
||||||
|
@ -6,12 +6,15 @@ using System.Windows.Threading;
|
|||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Controls.Primitives;
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly Settings _settingsWin;
|
private readonly Settings _settingsWin;
|
||||||
|
private readonly Radios _radiosWin;
|
||||||
|
private readonly Shuffle _shuffleWin;
|
||||||
private readonly DispatcherTimer _timer;
|
private readonly DispatcherTimer _timer;
|
||||||
private readonly MPDHandler _mpd;
|
private readonly MPDHandler _mpd;
|
||||||
|
|
||||||
@ -23,6 +26,8 @@ namespace unison
|
|||||||
WindowState = WindowState.Minimized;
|
WindowState = WindowState.Minimized;
|
||||||
|
|
||||||
_settingsWin = new Settings();
|
_settingsWin = new Settings();
|
||||||
|
_radiosWin = new Radios();
|
||||||
|
_shuffleWin = new Shuffle();
|
||||||
_timer = new DispatcherTimer();
|
_timer = new DispatcherTimer();
|
||||||
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
|
||||||
@ -44,9 +49,17 @@ namespace unison
|
|||||||
{
|
{
|
||||||
if (_mpd.IsConnected())
|
if (_mpd.IsConnected())
|
||||||
{
|
{
|
||||||
Snapcast.IsEnabled = true;
|
_mpd.QueryStats();
|
||||||
|
_settingsWin.UpdateStats();
|
||||||
|
|
||||||
ConnectionOkIcon.Visibility = Visibility.Visible;
|
ConnectionOkIcon.Visibility = Visibility.Visible;
|
||||||
ConnectionFailIcon.Visibility = Visibility.Collapsed;
|
ConnectionFailIcon.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
Snapcast.IsEnabled = true;
|
||||||
|
if (_radiosWin.IsConnected())
|
||||||
|
Radio.IsEnabled = true;
|
||||||
|
|
||||||
|
_shuffleWin.Initialize();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -54,12 +67,15 @@ namespace unison
|
|||||||
DefaultState(true);
|
DefaultState(true);
|
||||||
ConnectionOkIcon.Visibility = Visibility.Collapsed;
|
ConnectionOkIcon.Visibility = Visibility.Collapsed;
|
||||||
ConnectionFailIcon.Visibility = Visibility.Visible;
|
ConnectionFailIcon.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
Snapcast.IsEnabled = false;
|
||||||
|
Radio.IsEnabled = false;
|
||||||
}
|
}
|
||||||
_settingsWin.UpdateConnectionStatus();
|
_settingsWin.UpdateConnectionStatus();
|
||||||
Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
|
Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnSongChanged(object sender, EventArgs e)
|
public async void OnSongChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (_mpd.GetCurrentSong() == null)
|
if (_mpd.GetCurrentSong() == null)
|
||||||
return;
|
return;
|
||||||
@ -68,11 +84,12 @@ namespace unison
|
|||||||
SongTitle.Text = _mpd.GetCurrentSong().Title;
|
SongTitle.Text = _mpd.GetCurrentSong().Title;
|
||||||
else if (_mpd.GetCurrentSong().HasName && _mpd.GetCurrentSong().Name.Length > 0)
|
else if (_mpd.GetCurrentSong().HasName && _mpd.GetCurrentSong().Name.Length > 0)
|
||||||
SongTitle.Text = _mpd.GetCurrentSong().Name;
|
SongTitle.Text = _mpd.GetCurrentSong().Name;
|
||||||
else
|
else if (_mpd.GetCurrentSong().Path != null)
|
||||||
{
|
{
|
||||||
int start = _mpd.GetCurrentSong().Path.LastIndexOf("/") + 1;
|
int start = _mpd.GetCurrentSong().Path.LastIndexOf("/") + 1;
|
||||||
int end = _mpd.GetCurrentSong().Path.LastIndexOf(".");
|
int end = _mpd.GetCurrentSong().Path.LastIndexOf(".");
|
||||||
SongTitle.Text = _mpd.GetCurrentSong().Path.Substring(start, end - start);
|
if (start > 0 && end > 0 && end > start)
|
||||||
|
SongTitle.Text = _mpd.GetCurrentSong().Path.Substring(start, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
SongTitle.ToolTip = _mpd.GetCurrentSong().Path;
|
SongTitle.ToolTip = _mpd.GetCurrentSong().Path;
|
||||||
@ -80,7 +97,7 @@ namespace unison
|
|||||||
SongAlbum.Text = _mpd.GetCurrentSong().Album;
|
SongAlbum.Text = _mpd.GetCurrentSong().Album;
|
||||||
|
|
||||||
if (_mpd.GetCurrentSong().Date != null)
|
if (_mpd.GetCurrentSong().Date != null)
|
||||||
SongAlbum.Text += $" ({ _mpd.GetCurrentSong().Date})";
|
SongAlbum.Text += $" ({_mpd.GetCurrentSong().Date.Split("-")[0]})";
|
||||||
|
|
||||||
SongGenre.Text = _mpd.GetCurrentSong().Genre;
|
SongGenre.Text = _mpd.GetCurrentSong().Genre;
|
||||||
SongFormat.Text = _mpd.GetCurrentSong().Path.Substring(_mpd.GetCurrentSong().Path.LastIndexOf(".") + 1);
|
SongFormat.Text = _mpd.GetCurrentSong().Path.Substring(_mpd.GetCurrentSong().Path.LastIndexOf(".") + 1);
|
||||||
@ -104,6 +121,15 @@ namespace unison
|
|||||||
_timer.Start();
|
_timer.Start();
|
||||||
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
|
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Trace.WriteLine("Song changed called!");
|
||||||
|
|
||||||
|
if (_shuffleWin.GetContinuous())
|
||||||
|
{
|
||||||
|
_mpd.CanPrevNext = false;
|
||||||
|
await _shuffleWin.HandleContinuous();
|
||||||
|
_mpd.CanPrevNext = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStatusChanged(object sender, EventArgs e)
|
public void OnStatusChanged(object sender, EventArgs e)
|
||||||
@ -147,8 +173,9 @@ namespace unison
|
|||||||
PlayPause.Text = (string)Application.Current.FindResource("pauseButton");
|
PlayPause.Text = (string)Application.Current.FindResource("pauseButton");
|
||||||
TimeSlider.Value = 50;
|
TimeSlider.Value = 50;
|
||||||
TimeSlider.IsEnabled = false;
|
TimeSlider.IsEnabled = false;
|
||||||
NoCover.Visibility = Visibility.Visible;
|
NoCover.Visibility = Visibility.Collapsed;
|
||||||
Cover.Visibility = Visibility.Collapsed;
|
Cover.Visibility = Visibility.Collapsed;
|
||||||
|
RadioCover.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
if (LostConnection)
|
if (LostConnection)
|
||||||
{
|
{
|
||||||
@ -160,16 +187,18 @@ namespace unison
|
|||||||
|
|
||||||
public void OnCoverChanged(object sender, EventArgs e)
|
public void OnCoverChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (_mpd.GetCover() == null)
|
NoCover.Visibility = Visibility.Collapsed;
|
||||||
{
|
Cover.Visibility = Visibility.Collapsed;
|
||||||
|
RadioCover.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
if (_mpd.GetCurrentSong().Time == -1)
|
||||||
|
RadioCover.Visibility = Visibility.Visible;
|
||||||
|
else if (_mpd.GetCover() == null)
|
||||||
NoCover.Visibility = Visibility.Visible;
|
NoCover.Visibility = Visibility.Visible;
|
||||||
Cover.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
else if (Cover.Source != _mpd.GetCover())
|
else if (Cover.Source != _mpd.GetCover())
|
||||||
{
|
{
|
||||||
Cover.Source = _mpd.GetCover();
|
Cover.Source = _mpd.GetCover();
|
||||||
Cover.Visibility = Visibility.Visible;
|
Cover.Visibility = Visibility.Visible;
|
||||||
NoCover.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +230,6 @@ namespace unison
|
|||||||
public void Pause_Clicked(object sender, RoutedEventArgs e) => _mpd.PlayPause();
|
public void Pause_Clicked(object sender, RoutedEventArgs e) => _mpd.PlayPause();
|
||||||
public void Previous_Clicked(object sender, RoutedEventArgs e) => _mpd.Prev();
|
public void Previous_Clicked(object sender, RoutedEventArgs e) => _mpd.Prev();
|
||||||
public void Next_Clicked(object sender, RoutedEventArgs e) => _mpd.Next();
|
public void Next_Clicked(object sender, RoutedEventArgs e) => _mpd.Next();
|
||||||
|
|
||||||
public void Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
|
public void Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
|
||||||
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
|
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
|
||||||
public void Single_Clicked(object sender, RoutedEventArgs e) => _mpd.Single();
|
public void Single_Clicked(object sender, RoutedEventArgs e) => _mpd.Single();
|
||||||
@ -214,6 +242,24 @@ namespace unison
|
|||||||
snapcast.LaunchOrExit();
|
snapcast.LaunchOrExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Radios_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_radiosWin.Show();
|
||||||
|
_radiosWin.Activate();
|
||||||
|
|
||||||
|
if (_radiosWin.WindowState == WindowState.Minimized)
|
||||||
|
_radiosWin.WindowState = WindowState.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shuffle_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_shuffleWin.Show();
|
||||||
|
_shuffleWin.Activate();
|
||||||
|
|
||||||
|
if (_shuffleWin.WindowState == WindowState.Minimized)
|
||||||
|
_shuffleWin.WindowState = WindowState.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
public void Settings_Clicked(object sender, RoutedEventArgs e)
|
public void Settings_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_settingsWin.Show();
|
_settingsWin.Show();
|
||||||
@ -247,6 +293,11 @@ namespace unison
|
|||||||
slider.ToolTip = (int)slider.Value;
|
slider.ToolTip = (int)slider.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateUpdateStatus(string version)
|
||||||
|
{
|
||||||
|
_settingsWin.UpdateUpdateStatus(version);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnSourceInitialized(EventArgs e)
|
protected override void OnSourceInitialized(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnSourceInitialized(e);
|
base.OnSourceInitialized(e);
|
||||||
@ -254,6 +305,17 @@ 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);
|
||||||
|
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 x:Name="SearchButton" 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>
|
173
Views/Radios.xaml.cs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using unison.Handlers;
|
||||||
|
using RadioBrowser.Models;
|
||||||
|
|
||||||
|
namespace unison
|
||||||
|
{
|
||||||
|
public partial class Radios : Window
|
||||||
|
{
|
||||||
|
private MPDHandler _mpd;
|
||||||
|
RadioHandler _radio;
|
||||||
|
|
||||||
|
public bool IsConnected() => _radio.IsConnected();
|
||||||
|
|
||||||
|
public Radios()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Initialize()
|
||||||
|
{
|
||||||
|
_radio = new RadioHandler();
|
||||||
|
|
||||||
|
if (!_radio.IsConnected())
|
||||||
|
SearchButton.IsEnabled = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CountryList.Items.Add(new CountryListItem { Name = "", Count = 0 });
|
||||||
|
|
||||||
|
List<NameAndCount> Countries = await _radio.GetCountries();
|
||||||
|
foreach (NameAndCount Country in Countries)
|
||||||
|
{
|
||||||
|
CountryList.Items.Add(new CountryListItem
|
||||||
|
{
|
||||||
|
Name = Country.Name,
|
||||||
|
Count = Country.Stationcount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.WriteLine("Exception while getting countries in RadioBrowser: " + e.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CleanString(string str)
|
||||||
|
{
|
||||||
|
return str.Replace("\r\n", "").Replace("\n", "").Replace("\r", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SearchHandler(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Return)
|
||||||
|
Search_Clicked(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Trace.WriteLine("Error on RadioBrowser search: " + except.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SearchAdvanced(string name, string country, string tags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SearchStatus.Text = unison.Resources.Resources.Radio_Loading;
|
||||||
|
|
||||||
|
List<StationInfo> advancedSearch = await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
return await _radio.AdvancedSearch(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)
|
||||||
|
{
|
||||||
|
Trace.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)
|
||||||
|
{
|
||||||
|
Trace.WriteLine("Error: Invalid index.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (station.Url == null)
|
||||||
|
{
|
||||||
|
Trace.WriteLine("Error: Invalid station.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
_mpd.ClearAddAndPlay(station.Url.AbsoluteUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reset_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
NameSearch.Text = "";
|
||||||
|
TagSearch.Text = "";
|
||||||
|
CountryList.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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:properties="clr-namespace:unison.Resources"
|
xmlns:properties="clr-namespace:unison.Resources" xmlns:sys="clr-namespace:System;assembly=System.Runtime"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Closing="Window_Closing" Title="{x:Static properties:Resources.Settings}" ResizeMode="CanMinimize" Icon="/Resources/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,31 +24,34 @@
|
|||||||
<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>
|
||||||
<TextBlock>
|
<emoji:EmojiInline Text="📶"/>
|
||||||
<emoji:EmojiInline Text="📶"/>
|
<Run Text="{x:Static properties:Resources.Settings_Connection}"/>
|
||||||
<Run Text="{x:Static properties:Resources.Settings_Connection}"/>
|
</TextBlock>
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<Grid VerticalAlignment="Top">
|
<Grid VerticalAlignment="Top">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_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" TextChanged="MpdConnectTextBox" 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="{x:Static properties:Resources.Settings_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" PreviewTextInput="NumberValidationTextBox" MaxLength="5" 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="{x:Static properties:Resources.Settings_Password}" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||||
<TextBox x:Name="MpdPassword" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
<PasswordBox x:Name="MpdPassword" KeyDown="ConnectHandler" Width="250" Margin="10,2,0,0"/>
|
||||||
</StackPanel>-->
|
<TextBlock Text="{x:Static properties:Resources.Settings_ConnectionPasswordInfo}" TextWrapping="Wrap" Margin="10,5,0,0" MaxWidth="250" HorizontalAlignment="Left"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock TextWrapping="Wrap" Margin="5,10,0,0">
|
||||||
|
<Run Text="{x:Static properties:Resources.Settings_ConnectionStatus}" FontWeight="Bold" />
|
||||||
|
<Run x:Name="ConnectionStatus" Text="{x:Static properties:Resources.Settings_ConnectionStatusOffline}"/>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
<TextBlock x:Name="ConnectionStatus" Text="{x:Static properties:Resources.Settings_ConnectionStatusOffline}" TextWrapping="Wrap" Margin="5,10,0,0"/>
|
|
||||||
<Button x:Name="ConnectButton" Content="{x:Static properties:Resources.Settings_ConnectButton}" 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>
|
||||||
@ -46,13 +59,118 @@
|
|||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="{x:Static properties:Resources.Settings_Shortcuts}">
|
||||||
|
<DockPanel Margin="8">
|
||||||
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock>
|
||||||
|
<emoji:EmojiInline Text="⌨️ "/>
|
||||||
|
<Run Text="{x:Static properties:Resources.Settings_Shortcuts}"></Run>
|
||||||
|
</TextBlock>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid>
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
|
||||||
|
<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"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid Margin="0,5,0,0" x:Name="RebindKeyWrapper">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<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="{x:Static properties:Resources.Settings_PreviousTrack}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="2" Margin="1,1,1,1"/>
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Settings_PlayPause}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="3" Margin="1,1,1,1"/>
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeUp}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="4" Margin="1,1,1,1"/>
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeDown}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="5" Margin="1,1,1,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"/>
|
||||||
|
|
||||||
|
<StackPanel x:Name="Shortcut_NextTrack" Orientation="Horizontal" Grid.Column="1" Grid.Row="0" Margin="10,0,0,2" Grid.RowSpan="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_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>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="{x:Static properties:Resources.Settings_ShortcutsInfo}" TextWrapping="Wrap" MaxWidth="440" TextAlignment="Justify" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="ShortcutsReset_Clicked"/>
|
||||||
|
</StackPanel>
|
||||||
|
</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>
|
||||||
@ -78,51 +196,53 @@
|
|||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem Header="{x:Static properties:Resources.Settings_Shortcuts}">
|
<TabItem Header="Shuffle">
|
||||||
<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>
|
||||||
<TextBlock>
|
<emoji:EmojiInline Text="🔁 "/>
|
||||||
<emoji:EmojiInline Text="⌨️ "/>
|
<Run Text="Shuffle"></Run>
|
||||||
<Run Text="{x:Static properties:Resources.Settings_Shortcuts}"></Run>
|
</TextBlock>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid MaxWidth="500">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run>The shuffle window allows to add random songs to your queue. Both options take into account the filter.</Run>
|
||||||
|
<Run>If the filter is empty, the entire music library is taken into account.</Run><LineBreak/><LineBreak/>
|
||||||
|
|
||||||
|
<Run FontWeight="Bold">Add to queue</Run><LineBreak/>
|
||||||
|
<Run>Add a fixed number of songs to the queue. It can take a long time to add more than 100 songs, so the option is limited to 1000 songs.</Run>
|
||||||
|
<LineBreak/><LineBreak/>
|
||||||
|
|
||||||
|
<Run FontWeight="Bold">Continuous shuffle</Run><LineBreak/>
|
||||||
|
<Run>By enabling this option, unison will automatically add songs to the queue so you never run out of songs to listen to.</Run>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox.Header>
|
</Grid>
|
||||||
<Grid>
|
</GroupBox>
|
||||||
<StackPanel>
|
</DockPanel>
|
||||||
<StackPanel Orientation="Horizontal">
|
</TabItem>
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeOffset}" TextWrapping="Wrap"/>
|
|
||||||
<TextBox x:Name="VolumeOffset" TextWrapping="Wrap" Width="25" PreviewTextInput="NumberValidationTextBox" Margin="8,2,0,0"/>
|
|
||||||
</StackPanel>
|
|
||||||
<Grid MinWidth="300" Margin="0,5,0,0">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition/>
|
|
||||||
<ColumnDefinition/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
<RowDefinition/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_NextTrack}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="0" Margin="1"/>
|
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_PreviousTrack}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="1" Margin="1"/>
|
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_PlayPause}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="2" Margin="1"/>
|
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeUp}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="3" Margin="1"/>
|
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_VolumeDown}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="4" Margin="1"/>
|
|
||||||
<TextBlock Text="{x:Static properties:Resources.Settings_ShowWindow}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="5" Margin="1"/>
|
|
||||||
|
|
||||||
<TextBlock Text="ctrl + media_next" TextWrapping="Wrap" Grid.Column="1" Grid.Row="0" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<TabItem Header="{x:Static properties:Resources.Stats}">
|
||||||
<TextBlock Text="ctrl + media_prev" TextWrapping="Wrap" Grid.Column="1" Grid.Row="1" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<DockPanel Margin="8">
|
||||||
<TextBlock Text="ctrl + media_play" TextWrapping="Wrap" Grid.Column="1" Grid.Row="2" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
<TextBlock Text="ctrl + volume_up" TextWrapping="Wrap" Grid.Column="1" Grid.Row="3" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<GroupBox.Header>
|
||||||
<TextBlock Text="ctrl + volume_down" TextWrapping="Wrap" Grid.Column="1" Grid.Row="4" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<TextBlock>
|
||||||
<TextBlock Text="ctrl + alt + enter" TextWrapping="Wrap" Grid.Column="1" Grid.Row="5" Margin="1" HorizontalAlignment="Right" FontWeight="Bold"/>
|
<emoji:EmojiInline Text="📊"/>
|
||||||
</Grid>
|
<Run Text="{x:Static properties:Resources.Stats}"/>
|
||||||
</StackPanel>
|
</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>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
@ -136,19 +256,28 @@
|
|||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<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" VerticalAlignment="Top">
|
||||||
<Run Text="{x:Static properties:Resources.Settings_Version}"/>
|
<Run Text="{x:Static properties:Resources.Settings_Version}"/>
|
||||||
<Run Text="{Binding GetVersion, Mode = OneWay}"/>
|
<Run Text="{Binding GetVersion, Mode = OneWay}"/>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
<TextBlock x:Name="UpdateText" TextWrapping="Wrap" VerticalAlignment="Top">
|
||||||
|
<Run x:Name="UpdateText2" Text="New version X.X.X available!" FontWeight="Bold"/>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button x:Name="UpdateButton" Content="{x:Static properties:Resources.Update_ButtonCheck}" Margin="0,3,0,10" Width="150" Click="CheckUpdates" HorizontalAlignment="Left"/>
|
||||||
|
|
||||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||||
<Run Text="{x:Static properties:Resources.Settings_AboutInfo}" /><LineBreak/>
|
<Run Text="{x:Static properties:Resources.Settings_AboutInfo}" /><LineBreak/>
|
||||||
※ <Hyperlink NavigateUri="https://github.com/Difegue/Stylophone" RequestNavigate="Hyperlink_RequestNavigate">Stylophone</Hyperlink><Run Text="{x:Static properties:Resources.Settings_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><LineBreak/>
|
||||||
|
※ <Hyperlink NavigateUri="https://github.com/ravibpatel/AutoUpdater.NET" RequestNavigate="Hyperlink_RequestNavigate">AutoUpdater.NET</Hyperlink><LineBreak/>
|
||||||
|
※ <Hyperlink NavigateUri="https://github.com/badaix/snapcast" RequestNavigate="Hyperlink_RequestNavigate">Snapcast</Hyperlink>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Margin="0,10,0,0">
|
<TextBlock Margin="0,10,0,0">
|
||||||
<Run Text="{x:Static properties:Resources.Settings_SourceCode1}" />
|
<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">
|
||||||
<Run Text="{x:Static properties:Resources.Settings_SourceCode2}" />
|
<Run Text="{x:Static properties:Resources.Settings_SourceCode2}" />
|
||||||
</Hyperlink>.
|
</Hyperlink>.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@ -161,7 +290,11 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="{x:Static properties:Resources.Settings_License}" Height="20" VerticalAlignment="Bottom">
|
||||||
|
<DockPanel Margin="8">
|
||||||
<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>
|
||||||
<TextBlock>
|
<TextBlock>
|
||||||
|
@ -2,12 +2,16 @@
|
|||||||
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.Threading.Tasks;
|
||||||
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;
|
||||||
|
using unison.Handlers;
|
||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
@ -33,6 +37,9 @@ namespace unison
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HotkeyHandler _hotkeys = (HotkeyHandler)Application.Current.Properties["hotkeys"];
|
||||||
|
|
||||||
|
|
||||||
public Settings()
|
public Settings()
|
||||||
{
|
{
|
||||||
InitHwnd();
|
InitHwnd();
|
||||||
@ -41,14 +48,38 @@ 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.Password = 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();
|
||||||
|
|
||||||
|
UpdateText.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
InitializeShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateConnectionStatus()
|
||||||
|
{
|
||||||
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
if (mpd.IsConnected())
|
||||||
|
{
|
||||||
|
ConnectionStatus.Text = $"{unison.Resources.Resources.Settings_ConnectionStatusConnected} {mpd.GetVersion()}.";
|
||||||
|
ConnectButton.IsEnabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusOffline;
|
||||||
|
ConnectButton.IsEnabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
|
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
|
||||||
@ -57,6 +88,18 @@ namespace unison
|
|||||||
e.Handled = regex.IsMatch(e.Text);
|
e.Handled = regex.IsMatch(e.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MpdConnectTextBox(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TextBox textBox = (TextBox)sender;
|
||||||
|
|
||||||
|
if (textBox.Text == Properties.Settings.Default.mpd_host)
|
||||||
|
ConnectButton.IsEnabled = false;
|
||||||
|
else
|
||||||
|
ConnectButton.IsEnabled = true;
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||||
{
|
{
|
||||||
ProcessStartInfo psi = new(e.Uri.AbsoluteUri);
|
ProcessStartInfo psi = new(e.Uri.AbsoluteUri);
|
||||||
@ -65,21 +108,18 @@ namespace unison
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateConnectionStatus()
|
|
||||||
{
|
|
||||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
|
||||||
if (mpd.IsConnected())
|
|
||||||
ConnectionStatus.Text = $"{unison.Resources.Resources.Settings_ConnectionStatusConnected} {mpd.GetVersion()}.";
|
|
||||||
else
|
|
||||||
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusOffline;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MPDConnect_Clicked(object sender, RoutedEventArgs e)
|
private void MPDConnect_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (!ConnectButton.IsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
SaveSettings();
|
SaveSettings();
|
||||||
|
|
||||||
|
ConnectButton.IsEnabled = false;
|
||||||
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting;
|
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting;
|
||||||
|
|
||||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
mpd.Connect();
|
Task.Run(async () => { await mpd.Initialize(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)
|
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)
|
||||||
@ -88,17 +128,35 @@ namespace unison
|
|||||||
SnapcastPort.Text = (string)Application.Current.FindResource("snapcastPort");
|
SnapcastPort.Text = (string)Application.Current.FindResource("snapcastPort");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void UpdateStats()
|
||||||
{
|
{
|
||||||
Properties.Settings.Default.mpd_host = MpdHost.Text;
|
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
Properties.Settings.Default.mpd_port = int.Parse(MpdPort.Text, CultureInfo.InvariantCulture);
|
StatSong.Text = mpd.GetStats().Songs.ToString();
|
||||||
//Properties.Settings.Default.mpd_password = MpdPassword.Text;
|
StatAlbum.Text = mpd.GetStats().Albums.ToString();
|
||||||
Properties.Settings.Default.snapcast_startup = (bool)SnapcastStartup.IsChecked;
|
StatArtist.Text = mpd.GetStats().Artists.ToString();
|
||||||
Properties.Settings.Default.snapcast_window = (bool)SnapcastWindow.IsChecked;
|
StatTotalPlaytime.Text = mpd.GetStats().TotalPlaytime.ToString();
|
||||||
Properties.Settings.Default.snapcast_path = SnapcastPath.Text;
|
StatUptime.Text = mpd.GetStats().Uptime.ToString();
|
||||||
Properties.Settings.Default.snapcast_port = int.Parse(SnapcastPort.Text, CultureInfo.InvariantCulture);
|
StatTotalTimePlayed.Text = mpd.GetStats().TotalTimePlayed.ToString();
|
||||||
Properties.Settings.Default.volume_offset = int.Parse(VolumeOffset.Text, CultureInfo.InvariantCulture);
|
StatDatabaseUpdate.Text = mpd.GetStats().DatabaseUpdate.ToString();
|
||||||
Properties.Settings.Default.Save();
|
}
|
||||||
|
|
||||||
|
private void ConnectHandler(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Return)
|
||||||
|
MPDConnect_Clicked(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckUpdates(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateHandler updater = (UpdateHandler)Application.Current.Properties["updater"];
|
||||||
|
updater.Start(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateUpdateStatus(string version)
|
||||||
|
{
|
||||||
|
UpdateText.Visibility = Visibility.Visible;
|
||||||
|
UpdateText2.Text = unison.Resources.Resources.Update_String1 + " " + version + " " + unison.Resources.Resources.Update_String2;
|
||||||
|
UpdateButton.Content = unison.Resources.Resources.Update_ButtonStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Closing(object sender, CancelEventArgs e)
|
private void Window_Closing(object sender, CancelEventArgs e)
|
||||||
@ -114,5 +172,234 @@ namespace unison
|
|||||||
WindowInteropHelper helper = new(this);
|
WindowInteropHelper helper = new(this);
|
||||||
helper.EnsureHandle();
|
helper.EnsureHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SaveSettings()
|
||||||
|
{
|
||||||
|
Properties.Settings.Default.mpd_host = MpdHost.Text;
|
||||||
|
Properties.Settings.Default.mpd_port = int.Parse(MpdPort.Text, CultureInfo.InvariantCulture);
|
||||||
|
Properties.Settings.Default.mpd_password = MpdPassword.Password;
|
||||||
|
Properties.Settings.Default.snapcast_startup = (bool)SnapcastStartup.IsChecked;
|
||||||
|
Properties.Settings.Default.snapcast_window = (bool)SnapcastWindow.IsChecked;
|
||||||
|
Properties.Settings.Default.snapcast_path = SnapcastPath.Text;
|
||||||
|
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.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Hotkeys
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint GetMod(StackPanel stackPanel)
|
||||||
|
{
|
||||||
|
return (uint)(GetMOD(stackPanel.Children.OfType<ComboBox>().First().SelectedItem.ToString()) | GetMOD(stackPanel.Children.OfType<ComboBox>().Last().SelectedItem.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
117
Views/Shuffle.xaml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<Window x:Class="unison.Shuffle"
|
||||||
|
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:local="clr-namespace:unison" xmlns:sys="clr-namespace:System;assembly=System.Runtime"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Shuffle" Closing="Window_Closing" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<x:Array x:Key="FilterType" Type="sys:String">
|
||||||
|
<sys:String>Song</sys:String>
|
||||||
|
<sys:String>Artist</sys:String>
|
||||||
|
<sys:String>Album</sys:String>
|
||||||
|
<sys:String>Year</sys:String>
|
||||||
|
<sys:String>Genre</sys:String>
|
||||||
|
<sys:String>Directory</sys:String>
|
||||||
|
</x:Array>
|
||||||
|
<x:Array x:Key="OperatorTypeA" Type="sys:String">
|
||||||
|
<sys:String>contains</sys:String>
|
||||||
|
<sys:String>is</sys:String>
|
||||||
|
<sys:String>is not</sys:String>
|
||||||
|
</x:Array>
|
||||||
|
<x:Array x:Key="OperatorTypeB" Type="sys:String">
|
||||||
|
<sys:String>is</sys:String>
|
||||||
|
<sys:String>is not</sys:String>
|
||||||
|
</x:Array>
|
||||||
|
<x:Array x:Key="OperatorTypeC" Type="sys:String">
|
||||||
|
<sys:String>is</sys:String>
|
||||||
|
</x:Array>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="FilterPanel">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
|
||||||
|
<ComboBox x:Name="FilterType" SelectionChanged="FilterType_SelectionChanged" ItemsSource="{StaticResource FilterType}" SelectedIndex="0" Width="100" ScrollViewer.CanContentScroll="False" FocusVisualStyle="{x:Null}"/>
|
||||||
|
<ComboBox x:Name="FilterOperator" SelectionChanged="OperatorType_SelectionChanged" ItemsSource="{StaticResource OperatorTypeA}" SelectedIndex="0" Width="80" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
|
||||||
|
<ComboBox x:Name="FilterList" SelectedIndex="0" Width="240" Visibility="Collapsed" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
|
||||||
|
<TextBox x:Name="FilterValue" KeyUp="QueryFilterHandler" Width="240" Margin="5,0,0,0"/>
|
||||||
|
<Button Content="-" Padding="5, 2" Click="RemoveFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
|
||||||
|
<Button Content="+" Padding="5, 2" Click="AddFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="5,0,5,5">
|
||||||
|
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock>
|
||||||
|
<emoji:EmojiInline Text="🔡"/>
|
||||||
|
<Run Text="Filter"/>
|
||||||
|
</TextBlock>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<StackPanel Orientation="Vertical" Margin="5,0,5,0">
|
||||||
|
|
||||||
|
<StackPanel x:Name="FilterPanel">
|
||||||
|
<ContentPresenter ContentTemplate="{StaticResource FilterPanel}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="SongFilterPanel" Margin="0,10,0,0">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="Number of songs in filter: "/><Run x:Name="SongFilterNumber" FontWeight="Bold"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Margin="0,5,0,0">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
|
||||||
|
<Button Content="Query filter" Click="UpdateFilter_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="0,0,10,0"/>
|
||||||
|
<Button Content="Reset" Click="Reset_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
|
||||||
|
<TextBlock x:Name="QueryFilterText" Text="Querying filter..." Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
|
||||||
|
<GroupBox DockPanel.Dock="Right" Padding="0,4,0,0" Width="248">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock>
|
||||||
|
<emoji:EmojiInline Text="➕"/>
|
||||||
|
<Run Text="Add to queue"/>
|
||||||
|
</TextBlock>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<StackPanel Orientation="Vertical" Margin="5,5,5,0">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||||
|
<TextBlock Text="Songs to add" Margin="0,0,5,5"/>
|
||||||
|
<TextBox x:Name="SongNumber" KeyUp="AddToQueueHandler" PreviewTextInput="QueueValidationTextBox" MaxLength="4" Text="15" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
|
||||||
|
<Button Content="Add to queue" Click="AddToQueue_Clicked" Padding="5, 2" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
|
||||||
|
<TextBlock x:Name="SearchStatus" Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed">
|
||||||
|
<Run Text="Adding "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs..."/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox DockPanel.Dock="Left" Padding="0,4,0,0" Width="248" Margin="0,0,5,0">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock>
|
||||||
|
<emoji:EmojiInline Text="♾️"/>
|
||||||
|
<Run Text="Continuous shuffle"/>
|
||||||
|
</TextBlock>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="5,7,5,0">
|
||||||
|
<CheckBox x:Name="ContinuousShuffle" Checked="ContinuousShuffle_Checked" Unchecked="ContinuousShuffle_Checked" FocusVisualStyle="{x:Null}" VerticalAlignment="Top">
|
||||||
|
<TextBlock Text="Enable continuous shuffle" TextWrapping="Wrap"/>
|
||||||
|
</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
419
Views/Shuffle.xaml.cs
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MpcNET.Commands.Database;
|
||||||
|
using MpcNET.Tags;
|
||||||
|
using MpcNET.Types;
|
||||||
|
using MpcNET.Types.Filters;
|
||||||
|
|
||||||
|
namespace unison
|
||||||
|
{
|
||||||
|
public partial class Shuffle : Window
|
||||||
|
{
|
||||||
|
private readonly MPDHandler _mpd;
|
||||||
|
private readonly ShuffleHandler _shuffle;
|
||||||
|
|
||||||
|
List<string> GenreList { get; }
|
||||||
|
List<string> FolderList { get; }
|
||||||
|
List<IFilter> Filters { get; }
|
||||||
|
|
||||||
|
bool _continuous = false;
|
||||||
|
|
||||||
|
public Shuffle()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
GenreList = new();
|
||||||
|
FolderList = new();
|
||||||
|
Filters = new();
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
|
||||||
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||||
|
_shuffle = (ShuffleHandler)Application.Current.Properties["shuffle"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
ListGenre(_mpd._cancelCommand.Token);
|
||||||
|
ListFolder(_mpd._cancelCommand.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void ListGenre(CancellationToken token)
|
||||||
|
{
|
||||||
|
if (GenreList.Count != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null));
|
||||||
|
|
||||||
|
if (Response == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (string genre in Response)
|
||||||
|
GenreList.Add(genre);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void ListFolder(CancellationToken token)
|
||||||
|
{
|
||||||
|
if (FolderList.Count != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand(""));
|
||||||
|
|
||||||
|
if (Response == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (IMpdFilePath folder in Response)
|
||||||
|
FolderList.Add(folder.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsFilterEmpty()
|
||||||
|
{
|
||||||
|
if (Filters.Count() == 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T FindParent<T>(DependencyObject child) where T : DependencyObject
|
||||||
|
{
|
||||||
|
var parent = VisualTreeHelper.GetParent(child);
|
||||||
|
|
||||||
|
if (parent == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (parent is T)
|
||||||
|
return parent as T;
|
||||||
|
else
|
||||||
|
return FindParent<T>(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFilter_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveFilter_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (FilterPanel.Children.Count > 1)
|
||||||
|
FilterPanel.Children.Remove(FindParent<ContentPresenter>(sender as Button));
|
||||||
|
else
|
||||||
|
Reset_Clicked(null, null);
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reset_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
|
||||||
|
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
_shuffle.SongList.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITag FilterEquivalence_Type(string value)
|
||||||
|
{
|
||||||
|
if (value == "Song")
|
||||||
|
return MpdTags.Title;
|
||||||
|
else if (value == "Artist")
|
||||||
|
return MpdTags.Artist;
|
||||||
|
else if (value == "Album")
|
||||||
|
return MpdTags.Album;
|
||||||
|
else if (value == "Year")
|
||||||
|
return MpdTags.Date;
|
||||||
|
else if (value == "Genre")
|
||||||
|
return MpdTags.Genre;
|
||||||
|
return MpdTags.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FilterOperator FilterEquivalence_Operator(string value)
|
||||||
|
{
|
||||||
|
if (value == "contains")
|
||||||
|
return FilterOperator.Contains;
|
||||||
|
else if (value == "is")
|
||||||
|
return FilterOperator.Equal;
|
||||||
|
else if (value == "is not")
|
||||||
|
return FilterOperator.Different;
|
||||||
|
return FilterOperator.Equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FilterType_Change(object sender, string Operator, List<string> Listing)
|
||||||
|
{
|
||||||
|
ComboBox comboBox = sender as ComboBox;
|
||||||
|
StackPanel stackPanel = comboBox.Parent as StackPanel;
|
||||||
|
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
|
||||||
|
{
|
||||||
|
if (child.Name == "FilterOperator")
|
||||||
|
{
|
||||||
|
child.ItemsSource = (Array)FindResource(Operator);
|
||||||
|
child.SelectedItem = child.Items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.Name == "FilterList")
|
||||||
|
{
|
||||||
|
child.Visibility = Visibility.Visible;
|
||||||
|
child.ItemsSource = Listing;
|
||||||
|
child.SelectedItem = child.Items[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
|
||||||
|
{
|
||||||
|
if (child.Name == "FilterValue")
|
||||||
|
child.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FilterType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
string item = e.AddedItems[0].ToString();
|
||||||
|
if (item == "Genre")
|
||||||
|
FilterType_Change(sender, "OperatorTypeB", GenreList);
|
||||||
|
else if (item == "Directory")
|
||||||
|
FilterType_Change(sender, "OperatorTypeC", FolderList);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ComboBox combobox = sender as ComboBox;
|
||||||
|
StackPanel stackpanel = combobox.Parent as StackPanel;
|
||||||
|
foreach (ComboBox child in stackpanel.Children.OfType<ComboBox>())
|
||||||
|
{
|
||||||
|
if (child.Name == "FilterOperator")
|
||||||
|
{
|
||||||
|
child.ItemsSource = (Array)FindResource("OperatorTypeA");
|
||||||
|
child.SelectedItem = child.Items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.Name == "FilterList")
|
||||||
|
child.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
foreach (TextBox child in stackpanel.Children.OfType<TextBox>())
|
||||||
|
{
|
||||||
|
if (child.Name == "FilterValue")
|
||||||
|
child.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OperatorType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
QueryFilterText.Visibility = Visibility.Visible;
|
||||||
|
await UpdateFilter();
|
||||||
|
TimedText(QueryFilterText, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimedText(TextBlock textBlock, int time)
|
||||||
|
{
|
||||||
|
DispatcherTimer Timer = new DispatcherTimer();
|
||||||
|
Timer.Interval = TimeSpan.FromSeconds(time);
|
||||||
|
Timer.Tick += (sender, args) =>
|
||||||
|
{
|
||||||
|
Timer.Stop();
|
||||||
|
textBlock.Visibility = Visibility.Collapsed;
|
||||||
|
};
|
||||||
|
Timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateFilter()
|
||||||
|
{
|
||||||
|
Filters.Clear();
|
||||||
|
|
||||||
|
foreach (ContentPresenter superChild in FilterPanel.Children)
|
||||||
|
{
|
||||||
|
ITag tag = MpdTags.Title;
|
||||||
|
FilterOperator op = FilterOperator.None;
|
||||||
|
string value = "";
|
||||||
|
bool isDir = false;
|
||||||
|
|
||||||
|
StackPanel stackPanel = VisualTreeHelper.GetChild(superChild, 0) as StackPanel;
|
||||||
|
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
|
||||||
|
{
|
||||||
|
if (child.Name == "FilterValue")
|
||||||
|
value = child.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
|
||||||
|
{
|
||||||
|
if (child.Name == "FilterType")
|
||||||
|
{
|
||||||
|
if (child.SelectedItem.ToString() == "Directory")
|
||||||
|
isDir = true;
|
||||||
|
else
|
||||||
|
tag = FilterEquivalence_Type(child.SelectedItem.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.Name == "FilterOperator")
|
||||||
|
op = FilterEquivalence_Operator(child.SelectedItem.ToString());
|
||||||
|
|
||||||
|
if (child.Name == "FilterList" && child.Visibility == Visibility.Visible)
|
||||||
|
value = child.SelectedItem.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != "")
|
||||||
|
{
|
||||||
|
if (!isDir)
|
||||||
|
Filters.Add(new FilterTag(tag, value, op));
|
||||||
|
else
|
||||||
|
Filters.Add(new FilterBase(value, FilterOperator.None));
|
||||||
|
|
||||||
|
await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await _shuffle.GetSongsFromFilter(Filters, _mpd._cancelCommand.Token);
|
||||||
|
});
|
||||||
|
SongFilterPanel.Visibility = Visibility.Visible;
|
||||||
|
SongFilterNumber.Text = _shuffle.SongList.Count.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueryFilterHandler(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Return)
|
||||||
|
UpdateFilter_Clicked(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueueValidationTextBox(object sender, TextCompositionEventArgs e)
|
||||||
|
{
|
||||||
|
Regex regex = new Regex("[^0-9]+");
|
||||||
|
e.Handled = regex.IsMatch(e.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueueValidationNumber()
|
||||||
|
{
|
||||||
|
int Number;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Number = int.Parse(SongNumber.Text);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number < 1)
|
||||||
|
SongNumber.Text = "1";
|
||||||
|
if (IsFilterEmpty())
|
||||||
|
{
|
||||||
|
if (Number > 100)
|
||||||
|
SongNumber.Text = "100";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Number > 1000)
|
||||||
|
SongNumber.Text = "1000";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void AddToQueue()
|
||||||
|
{
|
||||||
|
if (_mpd.GetStats() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await UpdateFilter();
|
||||||
|
QueueValidationNumber();
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Added => Adding songs...
|
||||||
|
// to
|
||||||
|
// Added X songs! (display for 5 seconds)
|
||||||
|
|
||||||
|
NumberAddedSongs.Text = SongNumber.Text;
|
||||||
|
SearchStatus.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
int Num = int.Parse(SongNumber.Text);
|
||||||
|
await AddToQueue_Internal(Num);
|
||||||
|
|
||||||
|
TimedText(SearchStatus, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddToQueue_Internal(int Num)
|
||||||
|
{
|
||||||
|
if (IsFilterEmpty())
|
||||||
|
{
|
||||||
|
await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await _shuffle.AddToQueueRandom(Num, _mpd._cancelCommand.Token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await _shuffle.AddToQueueFilter(Num, _mpd._cancelCommand.Token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToQueueHandler(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Return)
|
||||||
|
AddToQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToQueue_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
AddToQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetContinuous()
|
||||||
|
{
|
||||||
|
return _continuous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleContinuous()
|
||||||
|
{
|
||||||
|
if (!_continuous)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int PlaylistLength = _mpd.GetStatus().PlaylistLength;
|
||||||
|
int Num = 10 - PlaylistLength;
|
||||||
|
if (Num < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await UpdateFilter();
|
||||||
|
await AddToQueue_Internal(Num);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ContinuousShuffle.IsChecked == true)
|
||||||
|
_continuous = true;
|
||||||
|
else
|
||||||
|
_continuous = false;
|
||||||
|
|
||||||
|
if (_mpd.GetStatus().PlaylistLength < 10)
|
||||||
|
await HandleContinuous();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Closing(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitHwnd()
|
||||||
|
{
|
||||||
|
WindowInteropHelper helper = new(this);
|
||||||
|
helper.EnsureHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,11 +25,16 @@
|
|||||||
<Image Width="16" Height="16" emoji:Image.Source="🔊" />
|
<Image Width="16" Height="16" emoji:Image.Source="🔊" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<!--<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
|
<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.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.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="🛠️" />
|
||||||
|
@ -59,6 +59,30 @@ namespace unison
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ICommand Radios
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new DelegateCommand
|
||||||
|
{
|
||||||
|
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Radios_Clicked(null, null),
|
||||||
|
CanExecuteFunc = () => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand Shuffle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new DelegateCommand
|
||||||
|
{
|
||||||
|
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Shuffle_Clicked(null, null),
|
||||||
|
CanExecuteFunc = () => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ICommand Settings
|
public ICommand Settings
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
BIN
snapclient_0.26.0-1_win64/snapclient.exe
Normal file
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net5.0-windows</TargetFramework>
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<ApplicationIcon>Resources\icon-full.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\icon-full.ico</ApplicationIcon>
|
||||||
<Win32Resource></Win32Resource>
|
<Win32Resource></Win32Resource>
|
||||||
<StartupObject>unison.App</StartupObject>
|
<StartupObject>unison.App</StartupObject>
|
||||||
<Version>1.0</Version>
|
<Version>1.4</Version>
|
||||||
<Company />
|
<Company />
|
||||||
<Authors>Théo Marchal</Authors>
|
<Authors>Théo Marchal</Authors>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageProjectUrl>https://git.n700.ovh/keb/unison</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/ZetaKebab/unison</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://git.n700.ovh/keb/unison</RepositoryUrl>
|
<RepositoryUrl>https://github.com/ZetaKebab/unison</RepositoryUrl>
|
||||||
<Copyright>Théo Marchal</Copyright>
|
<Copyright>Théo Marchal</Copyright>
|
||||||
<PackageIconUrl />
|
<PackageIconUrl />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -22,13 +22,8 @@
|
|||||||
<None Remove="Resources\icon-mini.ico" />
|
<None Remove="Resources\icon-mini.ico" />
|
||||||
<None Remove="Resources\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="Resources\nothing.png" />
|
||||||
<None Remove="snapclient_0.25.0-1_win64\ogg.dll" />
|
<None Remove="Resources\radio.png" />
|
||||||
<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>
|
||||||
@ -45,39 +40,23 @@
|
|||||||
<Resource Include="Resources\nocover.png">
|
<Resource Include="Resources\nocover.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Resource>
|
</Resource>
|
||||||
|
<Resource Include="Resources\nothing.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
|
<Resource Include="Resources\radio.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</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="Autoupdater.NET.Official" Version="1.7.6" />
|
||||||
<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.4.0" />
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MpcNET\MpcNET.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -94,6 +73,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Resources\Resources.es-ES.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Update="Resources\Resources.fr-FR.resx">
|
<EmbeddedResource Update="Resources\Resources.fr-FR.resx">
|
||||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@ -108,6 +90,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
|
||||||
|