UI update, almost complete support of Taskbar, classes separation and organization
This commit is contained in:
parent
6b192fc978
commit
0ba7d20ad2
11
App.xaml
11
App.xaml
@ -2,8 +2,15 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:unison"
|
xmlns:local="clr-namespace:unison"
|
||||||
StartupUri="MainWindow.xaml">
|
ShutdownMode="OnExplicitShutdown">
|
||||||
|
<!--StartupUri="MainWindow.xaml"-->
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="Views/Systray.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
39
App.xaml.cs
39
App.xaml.cs
@ -1,17 +1,36 @@
|
|||||||
using System;
|
using System.Windows;
|
||||||
using System.Collections.Generic;
|
using Hardcodet.Wpf.TaskbarNotification;
|
||||||
using System.Configuration;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for App.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class App : Application
|
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
111
Handlers/HotkeyHandler.cs
Normal 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
9
Handlers/MPDHandler.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace unison
|
||||||
|
{
|
||||||
|
public class MPDHandler
|
||||||
|
{
|
||||||
|
public MPDHandler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
Handlers/SnapcastHandler.cs
Normal file
41
Handlers/SnapcastHandler.cs
Normal 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
22
LICENSE
@ -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
|
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
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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:
|
||||||
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
|
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||||
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
|
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
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
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.
|
||||||
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.
|
|
@ -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
126
Views/MainWindow.xaml
Normal 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>
|
@ -3,47 +3,43 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Windows.Interop;
|
|
||||||
|
|
||||||
using MPDCtrl.Models;
|
using MPDCtrl.Models;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using unison.Views;
|
||||||
|
|
||||||
namespace unison
|
namespace unison
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for MainWindow.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly MPC _mpd = new();
|
private readonly MPC _mpd = new();
|
||||||
private bool _connected = false;
|
private bool _connected;
|
||||||
private int _currentVolume;
|
public int _currentVolume;
|
||||||
private bool _currentRandom;
|
private bool _currentRandom;
|
||||||
private bool _currentRepeat;
|
private bool _currentRepeat;
|
||||||
private bool _currentSingle;
|
private bool _currentSingle;
|
||||||
private bool _currentConsume;
|
private bool _currentConsume;
|
||||||
private double _currentElapsed;
|
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 string _mpdHost = "192.168.1.13";
|
||||||
private int _mpdPort = 6600;
|
private int _mpdPort = 6600;
|
||||||
private string _mpdPassword = null;
|
private string _mpdPassword = null;
|
||||||
|
|
||||||
|
Settings SettingsWindow = new Settings();
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
|
InitHwnd();
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
|
||||||
ConnectToMPD();
|
ConnectToMPD();
|
||||||
DispatcherTimer timer = new DispatcherTimer();
|
DispatcherTimer timer = new DispatcherTimer();
|
||||||
timer.Interval = TimeSpan.FromSeconds(0.2);
|
timer.Interval = TimeSpan.FromSeconds(0.2);
|
||||||
timer.Tick += timer_Tick;
|
timer.Tick += Timer_Tick;
|
||||||
timer.Start();
|
timer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +60,7 @@ namespace unison
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void timer_Tick(object sender, EventArgs e)
|
private void Timer_Tick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
LoopMPD();
|
LoopMPD();
|
||||||
UpdateInterface();
|
UpdateInterface();
|
||||||
@ -102,14 +98,14 @@ namespace unison
|
|||||||
public void UpdateButton(ref Button button, bool b)
|
public void UpdateButton(ref Button button, bool b)
|
||||||
{
|
{
|
||||||
if (b)
|
if (b)
|
||||||
button.Foreground = System.Windows.SystemColors.GradientActiveCaptionBrush;
|
button.Foreground = SystemColors.GradientActiveCaptionBrush;
|
||||||
else
|
else
|
||||||
button.Foreground = System.Windows.SystemColors.DesktopBrush;
|
button.Foreground = SystemColors.DesktopBrush;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FormatSeconds(double time)
|
public string FormatSeconds(double time)
|
||||||
{
|
{
|
||||||
var timespan = TimeSpan.FromSeconds(time);
|
TimeSpan timespan = TimeSpan.FromSeconds(time);
|
||||||
return timespan.ToString(@"mm\:ss");
|
return timespan.ToString(@"mm\:ss");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,16 +134,17 @@ namespace unison
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
|
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
|
||||||
PauseButton.Content = "⏸";
|
PauseButtonEmoji.Text = "⏸️";
|
||||||
else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
|
else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
|
||||||
PauseButton.Content = "▶️";
|
PauseButtonEmoji.Text = "▶️";
|
||||||
|
|
||||||
if (_snapcastStarted)
|
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||||
Snapcast.Content = "Stop Snapcast";
|
if (snapcast.Started)
|
||||||
|
SnapcastText.Text = "Stop Snapcast";
|
||||||
else
|
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 Random, _currentRandom);
|
||||||
UpdateButton(ref Repeat, _currentRepeat);
|
UpdateButton(ref Repeat, _currentRepeat);
|
||||||
@ -155,7 +152,7 @@ namespace unison
|
|||||||
UpdateButton(ref Consume, _currentConsume);
|
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)
|
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
|
||||||
await _mpd.MpdPlaybackPause();
|
await _mpd.MpdPlaybackPause();
|
||||||
@ -163,17 +160,17 @@ namespace unison
|
|||||||
await _mpd.MpdPlaybackPlay(_currentVolume);
|
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);
|
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);
|
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);
|
await _mpd.MpdSetRandom(!_currentRandom);
|
||||||
}
|
}
|
||||||
@ -193,121 +190,52 @@ namespace unison
|
|||||||
await _mpd.MpdSetConsume(!_currentConsume);
|
await _mpd.MpdSetConsume(!_currentConsume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ChangeVolume(int value)
|
public async void ChangeVolume(int value)
|
||||||
{
|
{
|
||||||
await _mpd.MpdSetVolume(value);
|
await _mpd.MpdSetVolume(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Snapcast_Clicked(object sender, RoutedEventArgs e)
|
public void Snapcast_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!_snapcastStarted)
|
SnapcastHandler snapcast = (SnapcastHandler)Application.Current.Properties["snapcast"];
|
||||||
{
|
if (!snapcast.Started)
|
||||||
_snapcast.StartInfo.FileName = _snapcastVersion + @"\snapclient.exe";
|
snapcast.Start(_mpd.MpdHost);
|
||||||
_snapcast.StartInfo.Arguments = "--host " + _mpd.MpdHost;
|
|
||||||
_snapcast.StartInfo.CreateNoWindow = true;
|
|
||||||
_snapcast.Start();
|
|
||||||
_snapcastStarted = true;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
|
snapcast.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Settings_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (SettingsWindow.WindowState == WindowState.Normal)
|
||||||
{
|
{
|
||||||
_snapcast.Kill();
|
SettingsWindow.Hide();
|
||||||
_snapcastStarted = false;
|
SettingsWindow.WindowState = WindowState.Minimized;
|
||||||
|
}
|
||||||
|
else if (SettingsWindow.WindowState == WindowState.Minimized)
|
||||||
|
{
|
||||||
|
SettingsWindow.WindowState = WindowState.Normal;
|
||||||
|
SettingsWindow.Show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Closing(object sender, CancelEventArgs e)
|
private void Window_Closing(object sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
if (_snapcastStarted)
|
e.Cancel = true;
|
||||||
_snapcast.Kill();
|
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)
|
protected override void OnSourceInitialized(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnSourceInitialized(e);
|
base.OnSourceInitialized(e);
|
||||||
|
HotkeyHandler hk = (HotkeyHandler)Application.Current.Properties["hotkeys"];
|
||||||
_windowHandle = new WindowInteropHelper(this).Handle;
|
hk.Activate(this);
|
||||||
_source = HwndSource.FromHwnd(_windowHandle);
|
|
||||||
_source.AddHook(HwndHook);
|
|
||||||
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_PREV_TRACK);
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_MEDIA_NEXT_TRACK);
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL /*| MOD_ALT*/, VK_MEDIA_PLAY_PAUSE);
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_UP);
|
|
||||||
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_VOLUME_DOWN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
public void InitHwnd()
|
||||||
{
|
{
|
||||||
const int WM_HOTKEY = 0x0312;
|
WindowInteropHelper helper = new(this);
|
||||||
switch (msg)
|
helper.EnsureHandle();
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
118
Views/Settings.xaml
Normal file
118
Views/Settings.xaml
Normal 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
55
Views/Settings.xaml.cs
Normal 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
53
Views/Systray.xaml
Normal 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
23
Views/Systray.xaml.cs
Normal 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
BIN
images/unison.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -11,19 +11,31 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="images\nocover-test.png" />
|
||||||
<None Remove="images\nocover.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\FLAC.dll" />
|
||||||
<None Remove="snapclient_0.25.0-1_win64\ogg.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\opus.dll" />
|
||||||
<None Remove="snapclient_0.25.0-1_win64\snapclient.exe" />
|
<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\soxr.dll" />
|
||||||
<None Remove="snapclient_0.25.0-1_win64\vorbis.dll" />
|
<None Remove="snapclient_0.25.0-1_win64\vorbis.dll" />
|
||||||
|
<None Remove="unison.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Resource Include="images\nocover-test.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
<Resource Include="images\nocover.png">
|
<Resource Include="images\nocover.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Resource>
|
</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">
|
<Content Include="snapclient_0.25.0-1_win64\FLAC.dll">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@ -44,4 +56,9 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Emoji.Wpf" Version="0.3.3" />
|
||||||
|
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
27
windowless/DelegateCommand.cs
Normal file
27
windowless/DelegateCommand.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
windowless/NotifyIconViewModel.cs
Normal file
114
windowless/NotifyIconViewModel.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user