20 Commits

Author SHA1 Message Date
f6eb00759a Cleanup 2022-11-13 15:52:27 +01:00
468ec8be07 Cleaning 2022-11-13 15:28:10 +01:00
52b0a6fc85 Bugfix 2022-11-13 14:01:07 +01:00
5a43a1284e Looks like most of shuffle issues are resolved 2022-11-11 17:57:50 +01:00
06207d9791 Merge branch 'main' into shuffle 2022-11-11 01:31:39 +01:00
2f80af70ba Fix radio architecture 2022-11-11 01:24:13 +01:00
aeb1b1d573 New licence tab 2022-11-08 01:12:33 +01:00
8451e684db Message when checking for updates and there are none 2022-11-08 00:55:10 +01:00
9c8027428c Better handling of a closed instance of Snapcast 2022-11-07 18:53:37 +01:00
8e04161a60 Fix Snapcast link 2022-11-07 13:04:27 +01:00
ccbb4525f3 Update system implemented 2022-11-06 15:44:53 +01:00
529754934d Installer file 2022-11-06 00:41:13 +01:00
0471ba88c1 Cover images working in all cases 2022-11-05 01:05:46 +01:00
3b55c79d30 Merge branch 'main' into shuffle 2022-11-03 22:20:37 +01:00
b33ae3b9be Update publish profile 2022-11-03 22:20:21 +01:00
67667c74da Update CHANGELOG to 1.3.1 2022-11-03 02:03:37 +01:00
8033aebb23 Restore auto-reconnect 2022-11-03 00:39:03 +01:00
0898d5cf11 Fix connection change not working 2022-11-03 00:19:35 +01:00
cb741349fa Ugly patch to avoid exception in GetAlbumCover 2022-10-29 23:46:43 +02:00
cabfb165da Update .NET version to 6.0 2022-10-29 22:44:41 +02:00
23 changed files with 806 additions and 425 deletions

View File

@ -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
{ {
@ -11,12 +11,13 @@ namespace unison
private SnapcastHandler _snapcast; private SnapcastHandler _snapcast;
private ShuffleHandler _shuffle; 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 = CultureInfo.GetCultureInfo("es-ES"); //unison.Resources.Resources.Culture = System.Globalization.CultureInfo.GetCultureInfo("es-ES");
base.OnStartup(e); base.OnStartup(e);
@ -33,6 +34,9 @@ namespace unison
_shuffle = new ShuffleHandler(); _shuffle = new ShuffleHandler();
Current.Properties["shuffle"] = _shuffle; 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");

View File

@ -1,5 +1,13 @@
# Changelog # 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 ## v1.3
*Released: 18/04/2022* *Released: 18/04/2022*

View File

@ -48,7 +48,7 @@ namespace unison
private MpdStatus _currentStatus; private MpdStatus _currentStatus;
private IMpdFile _currentSong; private IMpdFile _currentSong;
private BitmapFrame _cover; private BitmapImage _cover;
public Statistics _stats; public Statistics _stats;
private readonly System.Timers.Timer _elapsedTimer; private readonly System.Timers.Timer _elapsedTimer;
private DispatcherTimer _retryTimer; private DispatcherTimer _retryTimer;
@ -66,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()
{ {
Initialize(null, null); Startup(null, null);
_stats = new Statistics(); _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);
@ -149,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);
@ -177,21 +179,52 @@ 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()
{ {
_cancelToken = new CancellationTokenSource(); Trace.WriteLine("Initializing");
CancellationToken token = _cancelToken.Token;
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 e) catch (MpcNET.Exceptions.MpcConnectException e)
{ {
@ -219,11 +252,14 @@ 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)
{ {
if (token.IsCancellationRequested)
return null;
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out _ipAddress); IPAddress.TryParse(Properties.Settings.Default.mpd_host, out _ipAddress);
if (_ipAddress == null) if (_ipAddress == null)
@ -263,18 +299,6 @@ namespace unison
return connection; return connection;
} }
public void Disconnected()
{
_connected = false;
ConnectionChanged?.Invoke(this, EventArgs.Empty);
if (_connection != null)
_connection = null;
if (_commandConnection != null)
_commandConnection = null;
}
private void Loop(CancellationToken token) private void Loop(CancellationToken token)
{ {
Task.Run(async () => Task.Run(async () =>
@ -283,8 +307,7 @@ namespace unison
{ {
try try
{ {
token.ThrowIfCancellationRequested(); if (token.IsCancellationRequested || _connection == null || !IsConnected())
if (token.IsCancellationRequested || _connection == null)
break; break;
IMpdMessage<string> idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options update")); IMpdMessage<string> idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options update"));
@ -293,14 +316,17 @@ namespace unison
await HandleIdleResponseAsync(idleChanges.Response.Content); await HandleIdleResponseAsync(idleChanges.Response.Content);
else else
{ {
Trace.WriteLine($"Error in Idle connection thread: {idleChanges.Response?.Content}"); 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; break;
} }
} }
@ -323,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();
} }
} }
@ -347,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;
@ -354,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;
@ -373,13 +403,17 @@ 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
{ {
@ -392,7 +426,7 @@ namespace unison
if (_connection == null) if (_connection == null)
return; return;
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new ReadPictureCommand(path, currentSize)); IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
if (!albumReq.IsResponseValid) if (!albumReq.IsResponseValid)
break; break;
@ -414,7 +448,7 @@ namespace unison
if (_connection == null) if (_connection == null)
return; return;
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize)); IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new ReadPictureCommand(path, currentSize));
if (!albumReq.IsResponseValid) if (!albumReq.IsResponseValid)
break; break;
@ -430,6 +464,9 @@ namespace unison
} }
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;
} }
@ -439,14 +476,12 @@ namespace unison
else else
{ {
using MemoryStream stream = new MemoryStream(data.ToArray()); using MemoryStream stream = new MemoryStream(data.ToArray());
try _cover = new BitmapImage();
{ _cover.BeginInit();
_cover = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); _cover.CacheOption = BitmapCacheOption.OnLoad;
} _cover.StreamSource = stream;
catch (System.NotSupportedException) _cover.EndInit();
{ _cover.Freeze();
_cover = null;
}
} }
UpdateCover(); UpdateCover();
} }
@ -478,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()
@ -488,7 +523,7 @@ 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 Statistics GetStats() => _stats;
public double GetCurrentTime() => _currentTime; public double GetCurrentTime() => _currentTime;

81
Handlers/RadioHandler.cs Normal file
View 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);
}
}
}

View File

@ -1,134 +1,108 @@
using MpcNET.Commands.Database; 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.Queue;
using MpcNET.Commands.Reflection;
using MpcNET.Tags; using MpcNET.Tags;
using MpcNET.Types; using MpcNET.Types;
using MpcNET.Types.Filters; using MpcNET.Types.Filters;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace unison namespace unison
{ {
class ShuffleHandler class ShuffleHandler
{ {
private MPDHandler _mpd; private readonly MPDHandler _mpd;
public List<string> _songList { get; }
public int AddedSongs = 0; public int AddedSongs = 0;
public List<string> SongList { get; }
public ShuffleHandler() public ShuffleHandler()
{ {
_songList = new(); SongList = new();
_mpd = (MPDHandler)Application.Current.Properties["mpd"]; _mpd = (MPDHandler)Application.Current.Properties["mpd"];
} }
private bool IsOnMainThread() public async Task GetSongsFromFilter(List<IFilter> filter, CancellationToken token)
{ {
return Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread; 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 GetSongsFromFilter(List<IFilter> filter) public async Task AddToQueueRandom(int SongNumber, CancellationToken token)
{ {
Debug.WriteLine("[GetSongsFromFilterBefore] is on main thread => " + IsOnMainThread()); if (token.IsCancellationRequested)
await Task.Run(async() => return;
int AddedSongs = 0;
var commandList = new CommandList();
int songTotal = _mpd.GetStats().Songs;
for (int i = 0; i < SongNumber; i++)
{ {
Debug.WriteLine("[GetSongsFromFilterAfter] is on main thread => " + IsOnMainThread()); int song = new Random().Next(0, songTotal - 1);
commandList.Add(new SearchAddCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1));
AddedSongs++;
_songList.Clear(); // play if stopped or unknown state (no queue managing at the moment, so mandatory)
int song = _mpd.GetStats().Songs; if (i == 0 && (_mpd.GetStatus().State != MpdState.Play && _mpd.GetStatus().State != MpdState.Pause))
commandList.Add(new PlayCommand(0));
}
IEnumerable<IMpdFile> response = await _mpd.SafelySendCommandAsync(new SearchCommand(filter, 0, song + 1)); await _mpd.SafelySendCommandAsync(commandList);
Debug.WriteLine("got response => " + response.Count());
foreach (IMpdFile file in response)
{
_songList.Add(file.Path);
Debug.WriteLine(file.Path);
}
});
} }
public async Task AddToQueueRandom(int SongNumber) public async Task AddToQueueFilter(int SongNumber, CancellationToken token)
{ {
Debug.WriteLine("Add To Queue Random"); if (token.IsCancellationRequested)
return;
await Task.Run(async () => int AddedSongs = 0;
// more (or equal) requested songs than available => add everything
if (SongNumber >= SongList.Count)
{ {
int AddedSongs = 0; var commandList = new CommandList();
foreach (string path in SongList)
Debug.WriteLine("song to add => " + SongNumber);
for (int i = 0; i < SongNumber; i++)
{ {
// generate random number commandList.Add(new AddCommand(path));
int song = new Random().Next(0, _mpd.GetStats().Songs - 1); AddedSongs++;
Debug.WriteLine("song " + song + " - song total " + _mpd.GetStats().Songs);
IEnumerable<IMpdFile> Response = await _mpd.SafelySendCommandAsync(new SearchCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1));
Debug.WriteLine("got response");
await Task.Delay(1);
if (Response.Count() > 0)
{
string filePath = Response.First().Path;
_mpd.AddSong(filePath);
Debug.WriteLine("song path => " + filePath);
if (i == 0)
{
if (!_mpd.IsPlaying())
_mpd.Play(0);
}
AddedSongs++;
}
} }
});
Debug.WriteLine("Add To Queue Random - finished"); await _mpd.SafelySendCommandAsync(commandList);
} }
// more available songs than requested =>
public async Task AddToQueueFilter(int SongNumber) // we add unique indexes until we reach the requested amount
{ else
Debug.WriteLine("Add To Queue Filter");
await Task.Run(async () =>
{ {
int AddedSongs = 0; HashSet<int> SongIndex = new();
while (SongIndex.Count < SongNumber)
Debug.WriteLine("song to add => " + SongNumber);
// more requested songs than available => add everything
if (SongNumber > _songList.Count)
{ {
foreach (string path in _songList) int MaxIndex = new Random().Next(0, SongList.Count - 1);
{ SongIndex.Add(MaxIndex);
await Task.Delay(1);
_mpd.AddSong(path);
Debug.WriteLine("song path => " + path);
AddedSongs++;
}
} }
// more available songs than requested =>
// we add unique indexes until we reach the requested amount var commandList = new CommandList();
else foreach (int index in SongIndex)
{ {
HashSet<int> SongIndex = new(); commandList.Add(new AddCommand(SongList[index]));
Debug.WriteLine("while - before"); AddedSongs++;
while (SongIndex.Count < SongNumber)
{
int MaxIndex = new Random().Next(0, _songList.Count - 1);
SongIndex.Add(MaxIndex);
}
foreach (int index in SongIndex)
_mpd.AddSong(_songList[index]);
Debug.WriteLine("while - after");
} }
});
Debug.WriteLine("Add To Queue Filter - finished"); await _mpd.SafelySendCommandAsync(commandList);
}
} }
} }
} }

View File

@ -20,6 +20,16 @@ namespace unison
} }
} }
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"];
@ -42,16 +52,19 @@ namespace unison
_snapcast.StartInfo.FileName = Properties.Settings.Default.snapcast_path + @"\snapclient.exe"; _snapcast.StartInfo.FileName = Properties.Settings.Default.snapcast_path + @"\snapclient.exe";
_snapcast.StartInfo.Arguments = $"--host {mpd._ipAddress}"; _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
View 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
View 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
View 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>

View File

@ -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>

View File

@ -6,9 +6,8 @@
* lightweight window that can be toggled with shortcuts * lightweight window that can be toggled with shortcuts
* music control through rebindable shortcuts * music control through rebindable shortcuts
* shuffle panel * [Snapcast](https://github.com/badaix/snapcast) integration
* [Snapcast](https://mjaggard.github.io/snapcast/) integration * Radio stations
* radio stations
## Features ## Features

View File

@ -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 {
@ -196,7 +196,7 @@ namespace unison.Resources {
} }
/// <summary> /// <summary>
/// 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), there are saved as is in the setting file.. /// 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> /// </summary>
public static string Settings_ConnectionPasswordInfo { public static string Settings_ConnectionPasswordInfo {
get { get {
@ -617,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);
}
}
} }
} }

View File

@ -112,10 +112,10 @@
<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>Salir</value> <value>Salir</value>
@ -303,4 +303,25 @@
<data name="StopSnapcast" xml:space="preserve"> <data name="StopSnapcast" xml:space="preserve">
<value>Parar Snapcast</value> <value>Parar Snapcast</value>
</data> </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> </root>

View File

@ -112,10 +112,10 @@
<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>
@ -298,9 +298,30 @@
<value>Temps d'écoute écoulé :</value> <value>Temps d'écoute écoulé :</value>
</data> </data>
<data name="Stats_Uptime" xml:space="preserve"> <data name="Stats_Uptime" xml:space="preserve">
<value>MPD lancé depuis : </value> <value>MPD lancé depuis :</value>
</data> </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>

View File

@ -112,10 +112,10 @@
<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>
@ -163,7 +163,7 @@
<value>Connection</value> <value>Connection</value>
</data> </data>
<data name="Settings_ConnectionPasswordInfo" xml:space="preserve"> <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), there are saved as is in the setting file.</value> <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>
<data name="Settings_ConnectionStatus" xml:space="preserve"> <data name="Settings_ConnectionStatus" xml:space="preserve">
<value>Status:</value> <value>Status:</value>
@ -303,4 +303,25 @@
<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>

View File

@ -7,8 +7,6 @@ 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; using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace unison namespace unison
{ {
@ -124,21 +122,14 @@ namespace unison
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time); EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
} }
Debug.WriteLine("Song changed called!"); Trace.WriteLine("Song changed called!");
if (!_shuffleWin.GetContinuous()) if (_shuffleWin.GetContinuous())
return; {
_mpd.CanPrevNext = false;
Debug.WriteLine("playlist length => " + _mpd.GetStatus().PlaylistLength); await _shuffleWin.HandleContinuous();
_mpd.CanPrevNext = true;
if (_mpd.GetStatus().PlaylistLength > 4) }
return;
Debug.WriteLine("start continuous handling");
_mpd.CanPrevNext = false;
await _shuffleWin.HandleContinuous();
_mpd.CanPrevNext = true;
Debug.WriteLine("finished continuous");
} }
public void OnStatusChanged(object sender, EventArgs e) public void OnStatusChanged(object sender, EventArgs e)
@ -302,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);

View File

@ -37,7 +37,7 @@
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0"> <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="{x:Static properties:Resources.Radio_Search}" Click="Search_Clicked" Padding="5, 2"/> <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"/> <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" /> <TextBlock x:Name="SearchStatus" Margin="15,1,0,0" FontStyle="Italic" />
</StackPanel> </StackPanel>

View File

@ -1,88 +1,42 @@
using RadioBrowser; using System;
using RadioBrowser.Models;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Collections.Generic; using System.Collections.Generic;
using unison.Handlers;
using RadioBrowser.Models;
namespace unison namespace unison
{ {
public class CountryListItem
{
public uint Count { get; set; }
public string Name { get; set; }
public override string ToString()
{
if (Name == "")
return "None";
return $"{Name} ({Count})";
}
}
public class StationListItem
{
public string Name { get; set; }
public string Codec { get; set; }
public string Tags { get; set; }
public int Bitrate { get; set; }
public Uri Url { get; set; }
private string _country;
public string Country
{
get
{
if (_country.Length == 0)
return "🏴‍☠️";
return string.Concat(_country.ToUpper().Select(x => char.ConvertFromUtf32(x + 0x1F1A5))); // return emoji
}
set
{
_country = value;
}
}
}
public partial class Radios : Window public partial class Radios : Window
{ {
private RadioBrowserClient _radioBrowser;
private MPDHandler _mpd; private MPDHandler _mpd;
private bool _connected = true; RadioHandler _radio;
public bool IsConnected() => _connected; public bool IsConnected() => _radio.IsConnected();
public Radios() public Radios()
{ {
InitializeComponent(); InitializeComponent();
Initialize();
try
{
_radioBrowser = new RadioBrowserClient();
Initialize();
}
catch (Exception e)
{
Debug.WriteLine("Exception while connecting to RadioBrowser: " + e.Message);
return;
}
_connected = true;
} }
public async void Initialize() public async void Initialize()
{ {
_radio = new RadioHandler();
if (!_radio.IsConnected())
SearchButton.IsEnabled = false;
try try
{ {
List<NameAndCount> Countries = await _radioBrowser.Lists.GetCountriesAsync();
CountryList.Items.Add(new CountryListItem { Name = "", Count = 0 }); CountryList.Items.Add(new CountryListItem { Name = "", Count = 0 });
List<NameAndCount> Countries = await _radio.GetCountries();
foreach (NameAndCount Country in Countries) foreach (NameAndCount Country in Countries)
{ {
CountryList.Items.Add(new CountryListItem CountryList.Items.Add(new CountryListItem
@ -94,27 +48,49 @@ namespace unison
} }
catch (Exception e) catch (Exception e)
{ {
Debug.WriteLine("Exception while getting countries in RadioBrowser: " + e.Message); Trace.WriteLine("Exception while getting countries in RadioBrowser: " + e.Message);
return; return;
} }
} }
private string CleanString(string str) private static string CleanString(string str)
{ {
return str.Replace("\r\n", "").Replace("\n", "").Replace("\r", ""); 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) public async Task SearchAdvanced(string name, string country, string tags)
{ {
try try
{ {
SearchStatus.Text = unison.Resources.Resources.Radio_Loading; SearchStatus.Text = unison.Resources.Resources.Radio_Loading;
List<StationInfo> advancedSearch = await _radioBrowser.Search.AdvancedAsync(new AdvancedSearchOptions List<StationInfo> advancedSearch = await Task.Run(async () =>
{ {
Name = name, return await _radio.AdvancedSearch(new AdvancedSearchOptions
Country = country, {
TagList = tags Name = name,
Country = country,
TagList = tags
});
}); });
RadioListGrid.Items.Clear(); RadioListGrid.Items.Clear();
@ -140,7 +116,7 @@ namespace unison
} }
catch (Exception except) catch (Exception except)
{ {
Debug.WriteLine("Error on RadioBrowser search advanced: " + except.Message); Trace.WriteLine("Error on RadioBrowser search advanced: " + except.Message);
} }
} }
@ -160,13 +136,13 @@ namespace unison
} }
catch (ArgumentOutOfRangeException) catch (ArgumentOutOfRangeException)
{ {
Debug.WriteLine("Error: Invalid index."); Trace.WriteLine("Error: Invalid index.");
return; return;
} }
if (station.Url == null) if (station.Url == null)
{ {
Debug.WriteLine("Error: Invalid station."); Trace.WriteLine("Error: Invalid station.");
return; return;
} }
@ -174,19 +150,6 @@ namespace unison
_mpd.ClearAddAndPlay(station.Url.AbsoluteUri); _mpd.ClearAddAndPlay(station.Url.AbsoluteUri);
} }
private async void Search_Clicked(object sender, RoutedEventArgs e)
{
try
{
CountryListItem a = (CountryListItem)CountryList.SelectedItem;
await SearchAdvanced(NameSearch.Text, a?.Name, TagSearch.Text);
}
catch (Exception except)
{
Debug.WriteLine("Error on RadioBrowser search: " + except.Message);
}
}
private void Reset_Clicked(object sender, RoutedEventArgs e) private void Reset_Clicked(object sender, RoutedEventArgs e)
{ {
NameSearch.Text = ""; NameSearch.Text = "";
@ -194,12 +157,6 @@ namespace unison
CountryList.SelectedIndex = 0; CountryList.SelectedIndex = 0;
} }
private void SearchHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
Search_Clicked(null, null);
}
private void Window_Closing(object sender, CancelEventArgs e) private void Window_Closing(object sender, CancelEventArgs e)
{ {
e.Cancel = true; e.Cancel = true;

View File

@ -102,7 +102,7 @@
<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="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> <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}"> <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"/> <TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button> </Button>
</StackPanel> </StackPanel>
@ -110,7 +110,7 @@
<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="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> <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}"> <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"/> <TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button> </Button>
</StackPanel> </StackPanel>
@ -118,7 +118,7 @@
<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="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> <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}"> <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"/> <TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button> </Button>
</StackPanel> </StackPanel>
@ -126,7 +126,7 @@
<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="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> <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}"> <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"/> <TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button> </Button>
</StackPanel> </StackPanel>
@ -134,7 +134,7 @@
<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="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> <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}"> <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"/> <TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button> </Button>
</StackPanel> </StackPanel>
@ -142,7 +142,7 @@
<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="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> <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}"> <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"/> <TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button> </Button>
</StackPanel> </StackPanel>
@ -150,7 +150,7 @@
<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="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> <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}"> <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"/> <TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -256,16 +256,24 @@
</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/MpcNET" RequestNavigate="Hyperlink_RequestNavigate">MpcNET</Hyperlink><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><LineBreak/> ※ <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> ※ <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}" />
@ -282,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>

View File

@ -5,11 +5,13 @@ using System.IO;
using System.Linq; 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.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
{ {
@ -60,6 +62,8 @@ namespace unison
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(); InitializeShortcuts();
} }
@ -111,14 +115,11 @@ namespace unison
SaveSettings(); SaveSettings();
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
if (mpd.IsConnected())
mpd = new MPDHandler();
ConnectButton.IsEnabled = false; ConnectButton.IsEnabled = false;
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting; ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting;
System.Threading.Tasks.Task.Run(() => { mpd.Connect(); }); MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
Task.Run(async () => { await mpd.Initialize(); });
} }
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e) private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)
@ -145,6 +146,19 @@ namespace unison
MPDConnect_Clicked(null, null); 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)
{ {
e.Cancel = true; e.Cancel = true;
@ -376,12 +390,12 @@ namespace unison
InitializeShortcuts(); InitializeShortcuts();
} }
public uint GetMod(StackPanel stackPanel) private uint GetMod(StackPanel stackPanel)
{ {
return (uint)(GetMOD(stackPanel.Children.OfType<ComboBox>().First().SelectedItem.ToString()) | GetMOD(stackPanel.Children.OfType<ComboBox>().Last().SelectedItem.ToString())); return (uint)(GetMOD(stackPanel.Children.OfType<ComboBox>().First().SelectedItem.ToString()) | GetMOD(stackPanel.Children.OfType<ComboBox>().Last().SelectedItem.ToString()));
} }
public uint GetVk(StackPanel stackPanel) private uint GetVk(StackPanel stackPanel)
{ {
Button button = stackPanel.Children.OfType<Button>().First(); Button button = stackPanel.Children.OfType<Button>().First();
TextBlock textBlock = (TextBlock)button.Content; TextBlock textBlock = (TextBlock)button.Content;

View File

@ -35,7 +35,7 @@
<ComboBox x:Name="FilterType" SelectionChanged="FilterType_SelectionChanged" ItemsSource="{StaticResource FilterType}" SelectedIndex="0" Width="100" ScrollViewer.CanContentScroll="False" FocusVisualStyle="{x:Null}"/> <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="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}"/> <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" Width="240" Margin="5,0,0,0"/> <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="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"/> <Button Content="+" Padding="5, 2" Click="AddFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
</StackPanel> </StackPanel>
@ -68,7 +68,7 @@
<StackPanel Orientation="Horizontal" Margin="0,5,0,5"> <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="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}"/> <Button Content="Reset" Click="Reset_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
<TextBlock Text="Querying filter..." Margin="15,3,0,0" FontStyle="Italic" Visibility="Visible" /> <TextBlock x:Name="QueryFilterText" Text="Querying filter..." Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
@ -86,12 +86,12 @@
<StackPanel Orientation="Vertical" Margin="5,5,5,0"> <StackPanel Orientation="Vertical" Margin="5,5,5,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="Songs to add" Margin="0,0,5,5"/> <TextBlock Text="Songs to add" Margin="0,0,5,5"/>
<TextBox x:Name="SongNumber" PreviewTextInput="QueueValidationTextBox" MaxLength="4" Text="15" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/> <TextBox x:Name="SongNumber" KeyUp="AddToQueueHandler" PreviewTextInput="QueueValidationTextBox" MaxLength="4" Text="15" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0"> <StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Button Content="Add to queue" Click="AddToQueue_Clicked" Padding="5, 2" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/> <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"> <TextBlock x:Name="SearchStatus" Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed">
<Run Text="Added "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs"/> <Run Text="Adding "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs..."/>
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

View File

@ -1,37 +1,40 @@
using MpcNET.Commands.Database; using System;
using MpcNET.Tags;
using MpcNET.Types;
using MpcNET.Types.Filters;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading;
using System.Linq;
using System.Text.RegularExpressions; 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 namespace unison
{ {
public partial class Shuffle : Window public partial class Shuffle : Window
{ {
private MPDHandler _mpd; private readonly MPDHandler _mpd;
private ShuffleHandler _shuffle; private readonly ShuffleHandler _shuffle;
List<string> GenreList { get; }
List<string> FolderList { get; }
List<IFilter> Filters { get; }
bool _continuous = false; bool _continuous = false;
List<string> _genreList { get; }
List<string> _folderList { get; }
List<IFilter> _filters { get; }
public Shuffle() public Shuffle()
{ {
InitializeComponent(); InitializeComponent();
_genreList = new(); GenreList = new();
_folderList = new(); FolderList = new();
_filters = new(); Filters = new();
SongFilterNumber.Text = "0"; SongFilterNumber.Text = "0";
_mpd = (MPDHandler)Application.Current.Properties["mpd"]; _mpd = (MPDHandler)Application.Current.Properties["mpd"];
@ -40,13 +43,16 @@ namespace unison
public void Initialize() public void Initialize()
{ {
ListGenre(); ListGenre(_mpd._cancelCommand.Token);
ListFolder(); ListFolder(_mpd._cancelCommand.Token);
} }
public async void ListGenre() public async void ListGenre(CancellationToken token)
{ {
if (_genreList.Count != 0) if (GenreList.Count != 0)
return;
if (token.IsCancellationRequested)
return; return;
List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null)); List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null));
@ -55,12 +61,15 @@ namespace unison
return; return;
foreach (string genre in Response) foreach (string genre in Response)
_genreList.Add(genre); GenreList.Add(genre);
} }
public async void ListFolder() public async void ListFolder(CancellationToken token)
{ {
if (_folderList.Count != 0) if (FolderList.Count != 0)
return;
if (token.IsCancellationRequested)
return; return;
IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand("")); IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand(""));
@ -69,34 +78,16 @@ namespace unison
return; return;
foreach (IMpdFilePath folder in Response) foreach (IMpdFilePath folder in Response)
_folderList.Add(folder.Name); FolderList.Add(folder.Name);
} }
private bool IsFilterEmpty() private bool IsFilterEmpty()
{ {
if (_filters.Count() == 0) if (Filters.Count() == 0)
return true; return true;
return false; return false;
} }
private void Window_Closing(object sender, CancelEventArgs e)
{
e.Cancel = true;
WindowState = WindowState.Minimized;
Hide();
}
public void InitHwnd()
{
WindowInteropHelper helper = new(this);
helper.EnsureHandle();
}
private bool IsOnMainThread()
{
return App.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread;
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject private T FindParent<T>(DependencyObject child) where T : DependencyObject
{ {
var parent = VisualTreeHelper.GetParent(child); var parent = VisualTreeHelper.GetParent(child);
@ -130,7 +121,7 @@ namespace unison
FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count); FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") }); FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
SongFilterNumber.Text = "0"; SongFilterNumber.Text = "0";
_shuffle._songList.Clear(); _shuffle.SongList.Clear();
} }
private ITag FilterEquivalence_Type(string value) private ITag FilterEquivalence_Type(string value)
@ -159,65 +150,6 @@ namespace unison
return FilterOperator.Equal; return FilterOperator.Equal;
} }
private async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
{
await UpdateFilter();
}
private async Task UpdateFilter()
{
Debug.WriteLine("update filter => start");
_filters.Clear();
Debug.WriteLine("is on main thread => " + IsOnMainThread());
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 _shuffle.GetSongsFromFilter(_filters);
SongFilterPanel.Visibility = Visibility.Visible;
SongFilterNumber.Text = _shuffle._songList.Count.ToString();
}
}
Debug.WriteLine("update filter => stop");
}
private void FilterType_Change(object sender, string Operator, List<string> Listing) private void FilterType_Change(object sender, string Operator, List<string> Listing)
{ {
ComboBox comboBox = sender as ComboBox; ComboBox comboBox = sender as ComboBox;
@ -249,9 +181,9 @@ namespace unison
{ {
string item = e.AddedItems[0].ToString(); string item = e.AddedItems[0].ToString();
if (item == "Genre") if (item == "Genre")
FilterType_Change(sender, "OperatorTypeB", _genreList); FilterType_Change(sender, "OperatorTypeB", GenreList);
else if (item == "Directory") else if (item == "Directory")
FilterType_Change(sender, "OperatorTypeC", _folderList); FilterType_Change(sender, "OperatorTypeC", FolderList);
else else
{ {
ComboBox combobox = sender as ComboBox; ComboBox combobox = sender as ComboBox;
@ -278,10 +210,86 @@ namespace unison
private void OperatorType_SelectionChanged(object sender, SelectionChangedEventArgs e) private void OperatorType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
Debug.WriteLine("selection changed => operator type");
SongFilterNumber.Text = "0"; 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) private void QueueValidationTextBox(object sender, TextCompositionEventArgs e)
{ {
Regex regex = new Regex("[^0-9]+"); Regex regex = new Regex("[^0-9]+");
@ -290,48 +298,79 @@ namespace unison
private void QueueValidationNumber() private void QueueValidationNumber()
{ {
if (int.Parse(SongNumber.Text) < 1) int Number;
try
{
Number = int.Parse(SongNumber.Text);
}
catch (Exception)
{
return;
}
if (Number < 1)
SongNumber.Text = "1"; SongNumber.Text = "1";
if (int.Parse(SongNumber.Text) > 1000) if (IsFilterEmpty())
SongNumber.Text = "1000"; {
if (Number > 100)
SongNumber.Text = "100";
}
else
{
if (Number > 1000)
SongNumber.Text = "1000";
}
} }
private async void AddToQueue_Clicked(object sender, RoutedEventArgs e) private async void AddToQueue()
{ {
QueueValidationNumber();
if (_mpd.GetStats() == null) if (_mpd.GetStats() == null)
return; return;
NumberAddedSongs.Text = "0"; await UpdateFilter();
QueueValidationNumber();
// TODO
// Added => Adding songs...
// to
// Added X songs! (display for 5 seconds)
NumberAddedSongs.Text = SongNumber.Text;
SearchStatus.Visibility = Visibility.Visible; SearchStatus.Visibility = Visibility.Visible;
// start dispatcher int Num = int.Parse(SongNumber.Text);
// write _shuffle.AddedSongs in dispatcher await AddToQueue_Internal(Num);
await AddToQueue(int.Parse(SongNumber.Text)); TimedText(SearchStatus, 2);
Debug.WriteLine("add to queue finished");
SearchStatus.Visibility = Visibility.Collapsed;
} }
private async Task AddToQueue(int NumberToAdd) private async Task AddToQueue_Internal(int Num)
{ {
await UpdateFilter();
Debug.WriteLine("check filters");
if (IsFilterEmpty()) if (IsFilterEmpty())
await _shuffle.AddToQueueRandom(NumberToAdd); {
await Task.Run(async () =>
{
await _shuffle.AddToQueueRandom(Num, _mpd._cancelCommand.Token);
});
}
else else
{ {
Debug.WriteLine("add to queue filter - before"); await Task.Run(async () =>
await _shuffle.AddToQueueFilter(NumberToAdd); {
Debug.WriteLine("add to queue filter - after"); await _shuffle.AddToQueueFilter(Num, _mpd._cancelCommand.Token);
});
} }
}
Debug.WriteLine("add to queue finished"); 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() public bool GetContinuous()
@ -342,14 +381,15 @@ namespace unison
public async Task HandleContinuous() public async Task HandleContinuous()
{ {
if (!_continuous) if (!_continuous)
{
Debug.WriteLine("continuous return nothing!");
return; return;
}
Debug.WriteLine("continuous __before__ add to queue"); int PlaylistLength = _mpd.GetStatus().PlaylistLength;
await AddToQueue(5); int Num = 10 - PlaylistLength;
Debug.WriteLine("continuous __after__ add to queue"); if (Num < 1)
return;
await UpdateFilter();
await AddToQueue_Internal(Num);
} }
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e) private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
@ -359,8 +399,21 @@ namespace unison
else else
_continuous = false; _continuous = false;
if (_mpd.GetStatus().PlaylistLength < 5) if (_mpd.GetStatus().PlaylistLength < 10)
await HandleContinuous(); 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();
}
} }
} }

View File

@ -2,12 +2,12 @@
<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.3</Version> <Version>1.4</Version>
<Company /> <Company />
<Authors>Théo Marchal</Authors> <Authors>Théo Marchal</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
@ -52,6 +52,7 @@
</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" />
<PackageReference Include="RadioBrowser" Version="0.6.1" /> <PackageReference Include="RadioBrowser" Version="0.6.1" />