UI update, almost complete support of Taskbar, classes separation and organization

This commit is contained in:
Théo Marchal 2021-08-14 01:20:38 +02:00
parent 6b192fc978
commit 0ba7d20ad2
18 changed files with 790 additions and 221 deletions

View File

@ -2,8 +2,15 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:unison"
StartupUri="MainWindow.xaml">
ShutdownMode="OnExplicitShutdown">
<!--StartupUri="MainWindow.xaml"-->
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Views/Systray.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -1,17 +1,36 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows;
using Hardcodet.Wpf.TaskbarNotification;
namespace unison
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private TaskbarIcon Systray;
private HotkeyHandler Hotkeys;
private SnapcastHandler Snapcast;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Hotkeys = new HotkeyHandler();
Current.Properties["hotkeys"] = Hotkeys;
Snapcast = new SnapcastHandler();
Current.Properties["snapcast"] = Snapcast;
Current.MainWindow = new MainWindow();
Systray = (TaskbarIcon)FindResource("SystrayTaskbar");
Current.Properties["notify"] = Systray;
}
protected override void OnExit(ExitEventArgs e)
{
Systray.Dispose();
Snapcast.Stop();
Hotkeys.RemoveHotKeys();
base.OnExit(e);
}
}
}

111
Handlers/HotkeyHandler.cs Normal file
View File

@ -0,0 +1,111 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace unison
{
public class HotkeyHandler
{
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private const int HOTKEY_ID = 9000;
// modifiers
private const uint MOD_NONE = 0x0000;
private const uint MOD_ALT = 0x0001;
private const uint MOD_CONTROL = 0x0002;
private const uint MOD_SHIFT = 0x0004;
private const uint MOD_WIN = 0x0008;
// reference => https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
private const uint VK_MEDIA_PREV_TRACK = 0xB1;
private const uint VK_MEDIA_NEXT_TRACK = 0xB0;
private const uint VK_MEDIA_PLAY_PAUSE = 0xB3;
private const uint VK_VOLUME_UP = 0xAF;
private const uint VK_VOLUME_DOWN = 0xAE;
private const uint VK_ENTER = 0x0D;
private IntPtr _windowHandle;
private HwndSource _source;
public HotkeyHandler()
{
}
public void Activate(Window win)
{
if (_source == null)
{
_windowHandle = new WindowInteropHelper(win).Handle;
_source = HwndSource.FromHwnd(_windowHandle);
_source.AddHook(HwndHook);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_PREV_TRACK);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_NEXT_TRACK);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, 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);
}
}
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_HOTKEY = 0x0312;
if (msg == WM_HOTKEY && wParam.ToInt32() == HOTKEY_ID)
{
uint vkey = (((uint)lParam >> 16) & 0xFFFF);
MainWindow AppWindow = (MainWindow)Application.Current.MainWindow;
switch (vkey)
{
case VK_MEDIA_NEXT_TRACK:
Trace.WriteLine("TEST super important");
AppWindow.Next_Clicked(null, null);
break;
case VK_MEDIA_PREV_TRACK:
AppWindow.Previous_Clicked(null, null);
break;
case VK_VOLUME_DOWN:
AppWindow._currentVolume -= 5;
AppWindow.ChangeVolume(AppWindow._currentVolume);
break;
case VK_VOLUME_UP:
AppWindow._currentVolume += 5;
AppWindow.ChangeVolume(AppWindow._currentVolume);
break;
case VK_MEDIA_PLAY_PAUSE:
AppWindow.Pause_Clicked(null, null);
break;
case VK_ENTER:
if (AppWindow.WindowState == WindowState.Minimized)
{
AppWindow.Show();
AppWindow.Activate();
AppWindow.WindowState = WindowState.Normal;
}
else
{
AppWindow.Hide();
AppWindow.WindowState = WindowState.Minimized;
}
break;
}
handled = true;
}
return IntPtr.Zero;
}
public void RemoveHotKeys()
{
_source.RemoveHook(HwndHook);
UnregisterHotKey(_windowHandle, HOTKEY_ID);
}
}
}

9
Handlers/MPDHandler.cs Normal file
View File

@ -0,0 +1,9 @@
namespace unison
{
public class MPDHandler
{
public MPDHandler()
{
}
}
}

View File

@ -0,0 +1,41 @@
using System.Diagnostics;
namespace unison
{
public class SnapcastHandler
{
private readonly Process _snapcast = new();
public bool Started { get; private set; }
private string _snapcastVersion = "snapclient_0.25.0-1_win64";
public SnapcastHandler()
{
}
public void Start(string host)
{
if (!Started)
{
_snapcast.StartInfo.FileName = _snapcastVersion + @"\snapclient.exe";
_snapcast.StartInfo.Arguments = "--host " + host;
_snapcast.StartInfo.CreateNoWindow = true;
_snapcast.Start();
Started = true;
}
else
{
_snapcast.Kill();
Started = false;
}
}
public void Stop()
{
if (Started)
{
_snapcast.Kill();
Started = false;
}
}
}
}

22
LICENSE
View File

@ -1,19 +1,9 @@
MIT License Copyright (c) <year> <copyright holders>
MIT License Copyright (c) 2021 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
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:
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:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,69 +0,0 @@
<Window x:Class="unison.MainWindow"
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:local="clr-namespace:unison"
mc:Ignorable="d"
Title="unison"
Closing="Window_Closing" Icon="/images/nocover.png" ResizeMode="CanMinimize" WindowStyle="SingleBorderWindow" SizeToContent="WidthAndHeight">
<Grid Background="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" MinHeight="280">
<Grid x:Name="TopLayout" Margin="10,10,10,0" VerticalAlignment="Top" Width="Auto" MinHeight="220">
<Grid x:Name="Display" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="220,0,0,0" MinHeight="220" MinWidth="400">
<Grid x:Name="CurrentSong" Margin="10,0,10,0" VerticalAlignment="Top" MinHeight="140">
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock x:Name="SongTitle" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="20"/>
<TextBlock x:Name="SongArtist" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Bold" FontSize="18"/>
<TextBlock x:Name="SongAlbum" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="16"/>
<TextBlock x:Name="Bitrate" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal"/>
</StackPanel>
</Grid>
<Grid x:Name="Controls" VerticalAlignment="Bottom">
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" VerticalAlignment="Center">
<Grid HorizontalAlignment="Center" Margin="0,0,0,5">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="CurrentTime" Text="0:00" TextWrapping="Wrap" HorizontalAlignment="Left"/>
<Slider x:Name="TimeSlider" Height="18" MinWidth="320" Margin="5,0,5,0" HorizontalAlignment="Center" Maximum="100"/>
<TextBlock x:Name="EndTime" Text="0:00" TextWrapping="Wrap" Height="18" HorizontalAlignment="Right"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top">
<Button x:Name="PreviousTrack" Content="⏪︎" Click="Previous_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Left"/>
<Button x:Name="PauseButton" Content="⏯️" Click="Pause_Clicked" FontSize="18" FontWeight="Bold" Background="{x:Null}" BorderBrush="{x:Null}" Margin="10,0,10,0"/>
<Button x:Name="NextTrack" Content="⏩︎" Click="Next_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Right"/>
</StackPanel>
<Grid VerticalAlignment="Stretch">
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" HorizontalAlignment="Center" Margin="0,6,0,0">
<TextBlock Text="🔈" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Slider x:Name="VolumeSlider" Maximum="100" Value="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="180" FlowDirection="LeftToRight" Margin="5,0,5,0"/>
<TextBlock Text="🔊" TextWrapping="Wrap" HorizontalAlignment="Right" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Bottom">
<Button x:Name="Random" Content="🔀" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="{x:Null}" BorderBrush="{x:Null}" FontSize="15" Click="Random_Clicked" Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}"/>
<Button x:Name="Repeat" Content="🔁" Background="{x:Null}" FontSize="15" BorderBrush="{x:Null}" Click="Repeat_Clicked"/>
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Bottom">
<Button x:Name="Single" Content="🔂" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="15" Background="{x:Null}" BorderBrush="{x:Null}" Click="Single_Clicked"/>
<Button x:Name="Consume" Content="❎" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="15" BorderBrush="{x:Null}" Background="{x:Null}" Click="Consume_Clicked"/>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</Grid>
<Border x:Name="Cover_Border" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Left" VerticalAlignment="Center" MaxWidth="215" MaxHeight="215" >
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="images/nocover.png" MinWidth="215" MinHeight="215" MaxWidth="215" MaxHeight="215"/>
</Border>
</Grid>
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.AppWorkspaceBrushKey}}" Width="Auto" Margin="0,-2,0,0" MinHeight="40">
<Button x:Name="Snapcast" Content="Start Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="10,0,0,0" Padding="5, 2"/>
<TextBlock x:Name="DebugText" HorizontalAlignment="Center" Text="debug" TextWrapping="Wrap" VerticalAlignment="Center" TextAlignment="Center" MinWidth="350"/>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
<!--<Button x:Name="Settings" Content="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0"/>
<Button x:Name="Shuffle" Content="Settings" Padding="5, 2"/>-->
</StackPanel>
</Grid>
</Grid>
</Window>

126
Views/MainWindow.xaml Normal file
View File

@ -0,0 +1,126 @@
<Window x:Class="unison.MainWindow"
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:clr="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:unison"
mc:Ignorable="d"
Title="unison"
Closing="Window_Closing" Icon="/unison.ico" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">
<Grid Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}" MinHeight="270">
<Grid x:Name="TopLayout" Margin="10,0,10,0" VerticalAlignment="Stretch" Width="Auto" Height="Auto">
<Grid x:Name="Display" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="225,0,0,0" Height="Auto" Width="Auto">
<GroupBox Height="220" VerticalAlignment="Center">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="🎶"/>
</StackPanel>
</GroupBox.Header>
<Grid>
<Grid x:Name="CurrentSong" Margin="10,0,10,0" VerticalAlignment="Top" MinHeight="80">
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<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="SongAlbum" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="16" Text="Album"/>
<TextBlock x:Name="Bitrate" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" Text="Bitrate"/>
</StackPanel>
</Grid>
<Grid x:Name="Controls" VerticalAlignment="Top" Margin="10,106,10,0">
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" VerticalAlignment="Top">
<Grid HorizontalAlignment="Center" VerticalAlignment="Top">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="CurrentTime" Text="0:00" TextWrapping="Wrap" HorizontalAlignment="Left"/>
<Slider x:Name="TimeSlider" MinWidth="320" Margin="5,0,5,0" HorizontalAlignment="Center" Maximum="100"/>
<TextBlock x:Name="EndTime" Text="0:00" TextWrapping="Wrap" Height="18" HorizontalAlignment="Right"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,5,0,0">
<Button x:Name="PreviousTrack" Click="Previous_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Left">
<emoji:TextBlock ColorBlend="True" Text="⏪" FontSize="20"/>
</Button>
<Button x:Name="PauseButton" Click="Pause_Clicked" FontSize="18" FontWeight="Bold" Background="{x:Null}" BorderBrush="{x:Null}" Margin="10,0,10,0">
<emoji:TextBlock x:Name="PauseButtonEmoji" ColorBlend="True" Text="⏯️" FontSize="20"/>
</Button>
<Button x:Name="NextTrack" Click="Next_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Right">
<emoji:TextBlock ColorBlend="True" Text="⏩" FontSize="20"/>
</Button>
</StackPanel>
<Grid VerticalAlignment="Stretch" Margin="0,2.5,0,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0,6,0,0">
<TextBlock Text="🔈" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Slider x:Name="VolumeSlider" Maximum="100" Value="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="180" FlowDirection="LeftToRight" Margin="5,0,5,0" Foreground="{x:Null}" Background="{x:Null}"/>
<TextBlock Text="🔊" TextWrapping="Wrap" HorizontalAlignment="Right" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Bottom" MinHeight="35">
<Border BorderThickness="0,0,0,0" BorderBrush="Black" HorizontalAlignment="Stretch" VerticalAlignment="Top" SnapsToDevicePixels="True">
<Button x:Name="Random" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="{x:Null}" BorderBrush="{x:Null}" FontSize="18" Click="Random_Clicked">
<emoji:TextBlock ColorBlend="True" Text="🔀" FontSize="18" Margin="0" Padding="0, 0, 0, 2" />
</Button>
</Border>
<Border BorderThickness="0, 0, 0, 2" BorderBrush="Black" VerticalAlignment="Top" Margin="5,0,0,0">
<Button x:Name="Repeat" Background="{x:Null}" FontSize="18" BorderBrush="{x:Null}" Click="Repeat_Clicked">
<emoji:TextBlock ColorBlend="True" Text="🔁" FontSize="18" Margin="0" Padding="0, 0, 0, 2" />
</Button>
</Border>
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Bottom" MinHeight="35">
<Border BorderThickness="0,0,0,0" BorderBrush="Black" HorizontalAlignment="Stretch" VerticalAlignment="Top" SnapsToDevicePixels="True" Margin="0,0,5,0">
<Button x:Name="Single" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" BorderBrush="{x:Null}" Background="{x:Null}" Click="Single_Clicked">
<emoji:TextBlock ColorBlend="True" Text="🔂" FontSize="18" Margin="0" Padding="0, 0, 0, 2" TextDecorations="{x:Null}"/>
</Button>
</Border>
<Border BorderThickness="0,0,0,0" BorderBrush="Black" HorizontalAlignment="Stretch" VerticalAlignment="Top" SnapsToDevicePixels="True">
<Button x:Name="Consume" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" BorderBrush="{x:Null}" Background="{x:Null}" Click="Consume_Clicked">
<emoji:TextBlock ColorBlend="True" Text="🆓" FontSize="18" Margin="0" Padding="0, 0, 0, 2"/>
</Button>
</Border>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</Grid>
</GroupBox>
</Grid>
<Border x:Name="Cover_Border" Padding="2" HorizontalAlignment="Left" VerticalAlignment="Top" MinWidth="219" MinHeight="219" MaxWidth="219" MaxHeight="219" BorderThickness="1" BorderBrush="#FFD5DFE5" CornerRadius="4">
<Grid>
<Border Name="mask" Background="White" CornerRadius="3"/>
<StackPanel>
<StackPanel.OpacityMask>
<VisualBrush Visual="{Binding ElementName=mask}"/>
</StackPanel.OpacityMask>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="/images/nocover.png" />
</StackPanel>
</Grid>
</Border>
</Grid>
<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}}">
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🔊"/>
<TextBlock x:Name="SnapcastText" Text="Start Snapcast" Margin="5, 0, 0, 0"/>
</StackPanel>
</Button>
<TextBlock x:Name="Connection" HorizontalAlignment="Center" Text="Not connected" TextWrapping="Wrap" VerticalAlignment="Center" TextAlignment="Center" MinWidth="350" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}"/>
<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}}">
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🔁"/>
<TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/>
</StackPanel>
</Button>
<Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🛠️"/>
<TextBlock Text="Settings" Margin="5, 0, 0, 0"/>
</StackPanel>
</Button>
</StackPanel>
</Grid>
<Image Margin="0,0,503,127" Stretch="Fill" Source="pack://siteoforigin:,,,/Image1.png"/>
</Grid>
</Window>

View File

@ -3,47 +3,43 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using MPDCtrl.Models;
using System.Windows.Interop;
using unison.Views;
namespace unison
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly MPC _mpd = new();
private bool _connected = false;
private int _currentVolume;
private bool _connected;
public int _currentVolume;
private bool _currentRandom;
private bool _currentRepeat;
private bool _currentSingle;
private bool _currentConsume;
private double _currentElapsed;
private readonly Process _snapcast = new Process();
private bool _snapcastStarted = false;
private string _snapcastVersion = "snapclient_0.25.0-1_win64";
private string _mpdHost = "192.168.1.13";
private int _mpdPort = 6600;
private string _mpdPassword = null;
Settings SettingsWindow = new Settings();
public MainWindow()
{
InitHwnd();
InitializeComponent();
WindowState = WindowState.Minimized;
ConnectToMPD();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(0.2);
timer.Tick += timer_Tick;
timer.Tick += Timer_Tick;
timer.Start();
}
@ -64,7 +60,7 @@ namespace unison
}
}
void timer_Tick(object sender, EventArgs e)
private void Timer_Tick(object sender, EventArgs e)
{
LoopMPD();
UpdateInterface();
@ -102,14 +98,14 @@ namespace unison
public void UpdateButton(ref Button button, bool b)
{
if (b)
button.Foreground = System.Windows.SystemColors.GradientActiveCaptionBrush;
button.Foreground = SystemColors.GradientActiveCaptionBrush;
else
button.Foreground = System.Windows.SystemColors.DesktopBrush;
button.Foreground = SystemColors.DesktopBrush;
}
public string FormatSeconds(double time)
{
var timespan = TimeSpan.FromSeconds(time);
TimeSpan timespan = TimeSpan.FromSeconds(time);
return timespan.ToString(@"mm\:ss");
}
@ -138,16 +134,17 @@ namespace unison
}
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
PauseButton.Content = "⏸";
PauseButtonEmoji.Text = "⏸️";
else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
PauseButton.Content = "▶️";
PauseButtonEmoji.Text = "▶️";
if (_snapcastStarted)
Snapcast.Content = "Stop Snapcast";
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
if (snapcast.Started)
SnapcastText.Text = "Stop Snapcast";
else
Snapcast.Content = "Start Snapcast";
SnapcastText.Text = "Start Snapcast";
DebugText.Text = _mpd.MpdHost + ":" + _mpd.MpdPort;
Connection.Text = (_connected ? "✔️" : "❌") + _mpd.MpdHost + ":" + _mpd.MpdPort;
UpdateButton(ref Random, _currentRandom);
UpdateButton(ref Repeat, _currentRepeat);
@ -155,7 +152,7 @@ namespace unison
UpdateButton(ref Consume, _currentConsume);
}
private async void Pause_Clicked(object sender, RoutedEventArgs e)
public async void Pause_Clicked(object sender, RoutedEventArgs e)
{
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
await _mpd.MpdPlaybackPause();
@ -163,17 +160,17 @@ namespace unison
await _mpd.MpdPlaybackPlay(_currentVolume);
}
private async void Previous_Clicked(object sender, RoutedEventArgs e)
public async void Previous_Clicked(object sender, RoutedEventArgs e)
{
await _mpd.MpdPlaybackPrev(_currentVolume);
}
private async void Next_Clicked(object sender, RoutedEventArgs e)
public async void Next_Clicked(object sender, RoutedEventArgs e)
{
await _mpd.MpdPlaybackNext(_currentVolume);
}
private async void Random_Clicked(object sender, RoutedEventArgs e)
public async void Random_Clicked(object sender, RoutedEventArgs e)
{
await _mpd.MpdSetRandom(!_currentRandom);
}
@ -193,121 +190,52 @@ namespace unison
await _mpd.MpdSetConsume(!_currentConsume);
}
private async void ChangeVolume(int value)
public async void ChangeVolume(int value)
{
await _mpd.MpdSetVolume(value);
}
private void Snapcast_Clicked(object sender, RoutedEventArgs e)
public void Snapcast_Clicked(object sender, RoutedEventArgs e)
{
if (!_snapcastStarted)
{
_snapcast.StartInfo.FileName = _snapcastVersion + @"\snapclient.exe";
_snapcast.StartInfo.Arguments = "--host " + _mpd.MpdHost;
_snapcast.StartInfo.CreateNoWindow = true;
_snapcast.Start();
_snapcastStarted = true;
}
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
if (!snapcast.Started)
snapcast.Start(_mpd.MpdHost);
else
snapcast.Stop();
}
public void Settings_Clicked(object sender, RoutedEventArgs e)
{
if (SettingsWindow.WindowState == WindowState.Normal)
{
_snapcast.Kill();
_snapcastStarted = false;
SettingsWindow.Hide();
SettingsWindow.WindowState = WindowState.Minimized;
}
else if (SettingsWindow.WindowState == WindowState.Minimized)
{
SettingsWindow.WindowState = WindowState.Normal;
SettingsWindow.Show();
}
}
private void Window_Closing(object sender, CancelEventArgs e)
{
if (_snapcastStarted)
_snapcast.Kill();
e.Cancel = true;
WindowState = WindowState.Minimized;
Hide();
}
// hotkeys handling
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private const int HOTKEY_ID = 9000;
// modifiers
private const uint MOD_NONE = 0x0000;
private const uint MOD_ALT = 0x0001;
private const uint MOD_CONTROL = 0x0002;
private const uint MOD_SHIFT = 0x0004;
private const uint MOD_WIN = 0x0008;
// reference => https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
private const uint VK_MEDIA_PREV_TRACK = 0xB1;
private const uint VK_MEDIA_NEXT_TRACK = 0xB0;
private const uint VK_MEDIA_PLAY_PAUSE = 0xB3;
private const uint VK_VOLUME_UP = 0xAF;
private const uint VK_VOLUME_DOWN = 0xAE;
private IntPtr _windowHandle;
private HwndSource _source;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
_windowHandle = new WindowInteropHelper(this).Handle;
_source = HwndSource.FromHwnd(_windowHandle);
_source.AddHook(HwndHook);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_PREV_TRACK);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_NEXT_TRACK);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL /*| MOD_ALT*/, VK_MEDIA_PLAY_PAUSE);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_UP);
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_DOWN);
HotkeyHandler hk = (HotkeyHandler)Application.Current.Properties["hotkeys"];
hk.Activate(this);
}
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
public void InitHwnd()
{
const int WM_HOTKEY = 0x0312;
switch (msg)
{
case WM_HOTKEY:
switch (wParam.ToInt32())
{
case HOTKEY_ID:
int vkey = (((int)lParam >> 16) & 0xFFFF);
if (vkey == VK_MEDIA_NEXT_TRACK)
{
Next_Clicked(null, null);
}
else if (vkey == VK_MEDIA_PREV_TRACK)
{
Previous_Clicked(null, null);
}
else if (vkey == VK_VOLUME_DOWN)
{
_currentVolume--;
ChangeVolume(_currentVolume);
}
else if (vkey == VK_VOLUME_UP)
{
_currentVolume++;
ChangeVolume(_currentVolume);
}
else if (vkey == VK_MEDIA_PLAY_PAUSE)
{
Pause_Clicked(null, null);
}
handled = true;
break;
}
break;
}
return IntPtr.Zero;
}
protected override void OnClosed(EventArgs e)
{
_source.RemoveHook(HwndHook);
UnregisterHotKey(_windowHandle, HOTKEY_ID);
base.OnClosed(e);
WindowInteropHelper helper = new(this);
helper.EnsureHandle();
}
}
}

118
Views/Settings.xaml Normal file
View File

@ -0,0 +1,118 @@
<Window x:Class="unison.Views.Settings"
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.Views"
mc:Ignorable="d"
Closing="Window_Closing" Title="Settings" ResizeMode="CanMinimize" Icon="/unison.ico" WindowStyle="ToolWindow" SizeToContent="WidthAndHeight">
<Grid>
<StackPanel Orientation="Vertical">
<TabControl Margin="10">
<TabItem Header="MPD">
<DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="📶 Connection"/>
</StackPanel>
</GroupBox.Header>
<Grid VerticalAlignment="Top">
<StackPanel>
<StackPanel>
<TextBlock Text="Host" TextWrapping="Wrap" Margin="5,0,0,0"/>
<TextBox Text="192.168.0.1" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
</StackPanel>
<StackPanel Margin="0,5,0,0">
<TextBlock Text="Port" TextWrapping="Wrap" Margin="5,0,0,0"/>
<TextBox Text="6600" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
</StackPanel>
<StackPanel Margin="0,5,0,0">
<TextBlock Text="Password" TextWrapping="Wrap" Margin="5,0,0,0"/>
<TextBox Text="" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
</StackPanel>
<Button Content="Connect" Margin="0,10,0,0" Width="120"/>
</StackPanel>
</Grid>
</GroupBox>
</DockPanel>
</TabItem>
<TabItem Header="Snapcast">
<DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🔊 Snapcast"/>
</StackPanel>
</GroupBox.Header>
<Grid VerticalAlignment="Top">
<StackPanel>
<StackPanel>
<CheckBox Margin="5, 5, 0, 0">
<TextBlock Text="Launch at startup" TextWrapping="Wrap"/>
</CheckBox>
<TextBlock Text="Executable path" TextWrapping="Wrap" Margin="5,5,0,0"/>
<TextBox Text="snapclient_0.25.0-1_win64" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
<TextBlock TextWrapping="Wrap" Margin="5,5,0,0" TextAlignment="Left" Width="250">
You can change to your own locally installed version of the Snapcast client with an <Run FontStyle="Italic" FontWeight="DemiBold">absolute</Run> path.
</TextBlock>
<Button Content="Reset" Margin="0,10,0,0" Width="120"/>
</StackPanel>
</StackPanel>
</Grid>
</GroupBox>
</DockPanel>
</TabItem>
<TabItem Header="About" Height="20" VerticalAlignment="Bottom">
<DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<emoji:TextBlock Text="🎶 unison"/>
</GroupBox.Header>
<Grid VerticalAlignment="Top">
<StackPanel Orientation="Vertical">
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" VerticalAlignment="Top">
<Run Text="Version:"/>
<Run Text="{Binding GetVersion, Mode = OneWay}"/>
</TextBlock>
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
unison is free software. It is built with the following technologies:<LineBreak/>
※ <Hyperlink NavigateUri="https://torum.github.io/MPDCtrl/" RequestNavigate="Hyperlink_RequestNavigate">MPDCtrl</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>
</TextBlock>
<TextBlock Margin="0,10,0,0">
Source code freely available
<Hyperlink NavigateUri="https://git.n700.ovh/keb/unison" RequestNavigate="Hyperlink_RequestNavigate">
here
</Hyperlink>.
</TextBlock>
<TextBlock Margin="0,10,0,0">
Made by
<Hyperlink NavigateUri="https://marchal.dev" RequestNavigate="Hyperlink_RequestNavigate">
Théo Marchal
</Hyperlink>.
</TextBlock>
</StackPanel>
</Grid>
</GroupBox>
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0" Margin="0,10,0,0">
<GroupBox.Header>
<emoji:TextBlock Text="📝 License"/>
</GroupBox.Header>
<Grid VerticalAlignment="Top">
<TextBlock Text="{Binding GetLicense, Mode = OneWay}" TextWrapping="Wrap" Width="400" TextAlignment="Justify" />
</Grid>
</GroupBox>
</DockPanel>
</TabItem>
</TabControl>
</StackPanel>
</Grid>
</Window>

55
Views/Settings.xaml.cs Normal file
View File

@ -0,0 +1,55 @@
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Navigation;
namespace unison.Views
{
public partial class Settings : Window
{
public string GetVersion => Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
public Settings()
{
InitializeComponent();
DataContext = this;
WindowState = WindowState.Minimized;
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
ProcessStartInfo psi = new(e.Uri.AbsoluteUri);
psi.UseShellExecute = true;
Process.Start(psi);
e.Handled = true;
}
private void Window_Closing(object sender, CancelEventArgs e)
{
e.Cancel = true;
WindowState = WindowState.Minimized;
Hide();
}
public string GetLicense
{
get
{
try
{
var Reader = new StreamReader("LICENSE");
string file = "";
file = file + Reader.ReadToEnd();
return file;
}
catch (IOException e)
{
return e.Message;
}
}
}
}
}

53
Views/Systray.xaml Normal file
View File

@ -0,0 +1,53 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:local="clr-namespace:unison">
<tb:TaskbarIcon x:Name="SystrayTaskbar" xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
IconSource="/unison.ico" ToolTipText="{Binding GetAppText}" DoubleClickCommand="{Binding ShowWindowCommand}" x:Key="SystrayTaskbar">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem IsEnabled="False">
<MenuItem.Icon>
<Image Source="/unison.ico" Width="16" Height="16"/>
</MenuItem.Icon>
<MenuItem.Header>
<TextBlock Text="{Binding GetAppText}" />
</MenuItem.Header>
</MenuItem>
<Separator />
<MenuItem Header="Show window" Command="{Binding ShowWindowCommand}">
<MenuItem.Icon>
<Image Width="16" Height="16" emoji:Image.Source="▶️" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Name="test_snapcast_item" Header="{Binding SnapcastText}" Command="{Binding Snapcast}">
<MenuItem.Icon>
<Image Width="16" Height="16" emoji:Image.Source="🔊" />
</MenuItem.Icon>
</MenuItem>
<!--<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
<MenuItem.Icon>
<Image Width="16" Height="16" emoji:Image.Source="🔀" />
</MenuItem.Icon>
</MenuItem>-->
<MenuItem Header="Settings" Command="{Binding Settings}">
<MenuItem.Icon>
<Image Width="16" Height="16" emoji:Image.Source="🛠️" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}">
<MenuItem.Icon>
<Image Width="16" Height="16" emoji:Image.Source="❌" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
<tb:TaskbarIcon.DataContext>
<local:NotifyIconViewModel />
</tb:TaskbarIcon.DataContext>
</tb:TaskbarIcon>
</ResourceDictionary>

23
Views/Systray.xaml.cs Normal file
View File

@ -0,0 +1,23 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
/*namespace unison
{
public partial class Systray : ResourceDictionary, INotifyPropertyChanged
{
public Systray()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}*/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/unison.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -11,19 +11,31 @@
</PropertyGroup>
<ItemGroup>
<None Remove="images\nocover-test.png" />
<None Remove="images\nocover.png" />
<None Remove="LICENSE" />
<None Remove="snapclient_0.25.0-1_win64\FLAC.dll" />
<None Remove="snapclient_0.25.0-1_win64\ogg.dll" />
<None Remove="snapclient_0.25.0-1_win64\opus.dll" />
<None Remove="snapclient_0.25.0-1_win64\snapclient.exe" />
<None Remove="snapclient_0.25.0-1_win64\soxr.dll" />
<None Remove="snapclient_0.25.0-1_win64\vorbis.dll" />
<None Remove="unison.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="images\nocover-test.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
<Resource Include="images\nocover.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
<Content Include="LICENSE">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Resource Include="unison.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
<Content Include="snapclient_0.25.0-1_win64\FLAC.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -44,4 +56,9 @@
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Emoji.Wpf" Version="0.3.3" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,27 @@
using System;
using System.Windows.Input;
namespace unison
{
public class DelegateCommand : ICommand
{
public Action CommandAction { get; set; }
public Func<bool> CanExecuteFunc { get; set; }
public void Execute(object parameter)
{
CommandAction();
}
public bool CanExecute(object parameter)
{
return CanExecuteFunc == null || CanExecuteFunc();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}

View File

@ -0,0 +1,114 @@
using System.Windows;
using System.Windows.Input;
using System.Reflection;
using System.ComponentModel;
using System;
using System.Windows.Threading;
namespace unison
{
public class NotifyIconViewModel : INotifyPropertyChanged
{
private DispatcherTimer timer;
public NotifyIconViewModel()
{
//timer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, OnTimerTick, Application.Current.Dispatcher);
}
private void OnTimerTick(object sender, EventArgs e)
{
//fire a property change event for the timestamp
//Application.Current.Dispatcher.BeginInvoke(new Action(() => OnPropertyChanged("SnapcastText")));
}
public string GetAppText => "unison v" + Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
public ICommand ShowWindowCommand => new DelegateCommand
{
CommandAction = () =>
{
Window AppWindow = Application.Current.MainWindow;
AppWindow.Show();
AppWindow.Activate();
if (AppWindow.WindowState == WindowState.Minimized)
AppWindow.WindowState = WindowState.Normal;
},
CanExecuteFunc = () => true
};
public ICommand ExitApplicationCommand => new DelegateCommand
{
CommandAction = () =>
{
Application.Current.Shutdown();
},
CanExecuteFunc = () => true
};
public string SnapcastText
{
get
{
//Application.Current.Dispatcher.BeginInvoke(new Action(() => OnPropertyChanged("SnapcastText")));
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
return snapcast.Started ? "Stop Snapcast" : "Start Snapcast";
}
}
public ICommand Snapcast
{
get
{
Application.Current.Dispatcher.BeginInvoke(new Action(() => OnPropertyChanged("SnapcastText")));
NotifyPropertyChanged("SnapcastText");
return new DelegateCommand
{
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Snapcast_Clicked(null, null),
CanExecuteFunc = () => true
};
}
}
public ICommand Settings
{
get
{
return new DelegateCommand
{
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Settings_Clicked(null, null),
CanExecuteFunc = () => true
};
}
}
public string GetSnapcastText
{
get
{
/*if (Application.Current.MainWindow != null)
{
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
return snapcast.Started ? "Stop Snapcast" : "Start Snapcast";
}
return "not initialized";*/
return SnapcastText;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}