diff --git a/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 b/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2
index bdb375a..4e4a2cb 100644
Binary files a/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 and b/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 differ
diff --git a/.vs/hass-workstation-service/v16/.suo b/.vs/hass-workstation-service/v16/.suo
index 9a5ddda..fd6daf5 100644
Binary files a/.vs/hass-workstation-service/v16/.suo and b/.vs/hass-workstation-service/v16/.suo differ
diff --git a/UserInterface/.gitignore b/UserInterface/.gitignore
new file mode 100644
index 0000000..2bbc24e
--- /dev/null
+++ b/UserInterface/.gitignore
@@ -0,0 +1,25 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/vscode,dotnetcore
+# Edit at https://www.toptal.com/developers/gitignore?templates=vscode,dotnetcore
+
+### DotnetCore ###
+# .NET Core build folders
+bin/
+obj/
+
+# Common node modules locations
+/node_modules
+/wwwroot/node_modules
+
+### vscode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# End of https://www.toptal.com/developers/gitignore/api/vscode,dotnetcore
+
+# ignore logs
+logs/
\ No newline at end of file
diff --git a/UserInterface/App.axaml b/UserInterface/App.axaml
new file mode 100644
index 0000000..172252f
--- /dev/null
+++ b/UserInterface/App.axaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/UserInterface/App.axaml.cs b/UserInterface/App.axaml.cs
new file mode 100644
index 0000000..eaa87a9
--- /dev/null
+++ b/UserInterface/App.axaml.cs
@@ -0,0 +1,29 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using UserInterface.ViewModels;
+using UserInterface.Views;
+
+namespace UserInterface
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow
+ {
+ DataContext = new MainWindowViewModel(),
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+ }
+}
diff --git a/UserInterface/Assets/hass-workstation-logo.ico b/UserInterface/Assets/hass-workstation-logo.ico
new file mode 100644
index 0000000..323eb64
Binary files /dev/null and b/UserInterface/Assets/hass-workstation-logo.ico differ
diff --git a/UserInterface/ITrayIcon.cs b/UserInterface/ITrayIcon.cs
new file mode 100644
index 0000000..ff33265
--- /dev/null
+++ b/UserInterface/ITrayIcon.cs
@@ -0,0 +1,13 @@
+namespace MangaReader.Avalonia.Platform
+{
+ public interface ITrayIcon : System.IDisposable
+ {
+ System.Windows.Input.ICommand DoubleClickCommand { get; set; }
+
+ System.Windows.Input.ICommand BalloonClickedCommand { get; set; }
+
+ void SetIcon();
+
+ void ShowBalloon(string text, object state);
+ }
+}
diff --git a/UserInterface/Program.cs b/UserInterface/Program.cs
new file mode 100644
index 0000000..bef6f96
--- /dev/null
+++ b/UserInterface/Program.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Logging.Serilog;
+using Avalonia.ReactiveUI;
+using System;
+
+namespace UserInterface
+{
+ class Program
+ {
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToDebug()
+ .UseReactiveUI();
+ }
+}
diff --git a/UserInterface/Properties/PublishProfiles/FolderProfile.pubxml b/UserInterface/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 0000000..bac3ebc
--- /dev/null
+++ b/UserInterface/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,17 @@
+
+
+
+
+ Release
+ Any CPU
+ bin\Release\netcoreapp3.1\publish\
+ FileSystem
+ netcoreapp3.1
+ win-x64
+ false
+ True
+ False
+
+
\ No newline at end of file
diff --git a/UserInterface/Properties/PublishProfiles/FolderProfile.pubxml.user b/UserInterface/Properties/PublishProfiles/FolderProfile.pubxml.user
new file mode 100644
index 0000000..312c6e3
--- /dev/null
+++ b/UserInterface/Properties/PublishProfiles/FolderProfile.pubxml.user
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/UserInterface/UserInterface.csproj b/UserInterface/UserInterface.csproj
new file mode 100644
index 0000000..94e5121
--- /dev/null
+++ b/UserInterface/UserInterface.csproj
@@ -0,0 +1,28 @@
+
+
+ WinExe
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(Filename)
+
+
+
diff --git a/UserInterface/UserInterface.csproj.user b/UserInterface/UserInterface.csproj.user
new file mode 100644
index 0000000..3d9bdf3
--- /dev/null
+++ b/UserInterface/UserInterface.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ <_LastSelectedProfileId>C:\Users\Maurits\Documents\Repo\hass-desktop-service\UserInterface\Properties\PublishProfiles\FolderProfile.pubxml
+
+
\ No newline at end of file
diff --git a/UserInterface/ViewLocator.cs b/UserInterface/ViewLocator.cs
new file mode 100644
index 0000000..4a3da86
--- /dev/null
+++ b/UserInterface/ViewLocator.cs
@@ -0,0 +1,32 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using System;
+using UserInterface.ViewModels;
+
+namespace UserInterface
+{
+ public class ViewLocator : IDataTemplate
+ {
+ public bool SupportsRecycling => false;
+
+ public IControl Build(object data)
+ {
+ var name = data.GetType().FullName.Replace("ViewModel", "View");
+ var type = Type.GetType(name);
+
+ if (type != null)
+ {
+ return (Control)Activator.CreateInstance(type);
+ }
+ else
+ {
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+ }
+
+ public bool Match(object data)
+ {
+ return data is ViewModelBase;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UserInterface/ViewModels/BrokerSettingsViewModel.cs b/UserInterface/ViewModels/BrokerSettingsViewModel.cs
new file mode 100644
index 0000000..f57b81a
--- /dev/null
+++ b/UserInterface/ViewModels/BrokerSettingsViewModel.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace UserInterface.ViewModels
+{
+ public class BrokerSettingsViewModel : ViewModelBase
+ {
+ public string Host { get; set; }
+ public string Username { get; set; }
+ public string Password { get; set; }
+ }
+}
diff --git a/UserInterface/ViewModels/MainWindowViewModel.cs b/UserInterface/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..667fc49
--- /dev/null
+++ b/UserInterface/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace UserInterface.ViewModels
+{
+ public class MainWindowViewModel : ViewModelBase
+ {
+ public string Greeting => "Welcome to Avalonia!";
+ }
+}
diff --git a/UserInterface/ViewModels/ViewModelBase.cs b/UserInterface/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..987b7c8
--- /dev/null
+++ b/UserInterface/ViewModels/ViewModelBase.cs
@@ -0,0 +1,11 @@
+using ReactiveUI;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace UserInterface.ViewModels
+{
+ public class ViewModelBase : ReactiveObject
+ {
+ }
+}
diff --git a/UserInterface/Views/BrokerSettings/BrokerSettings.axaml b/UserInterface/Views/BrokerSettings/BrokerSettings.axaml
new file mode 100644
index 0000000..110b651
--- /dev/null
+++ b/UserInterface/Views/BrokerSettings/BrokerSettings.axaml
@@ -0,0 +1,17 @@
+
+
+ Mqtt broker
+ IP or hostname
+
+ Username
+
+ Password
+
+
+
+
diff --git a/UserInterface/Views/BrokerSettings/BrokerSettings.axaml.cs b/UserInterface/Views/BrokerSettings/BrokerSettings.axaml.cs
new file mode 100644
index 0000000..75ff0f3
--- /dev/null
+++ b/UserInterface/Views/BrokerSettings/BrokerSettings.axaml.cs
@@ -0,0 +1,56 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Microsoft.Extensions.DependencyInjection;
+using hass_workstation_service.Communication.NamedPipe;
+using JKang.IpcServiceFramework.Client;
+using System.Threading.Tasks;
+using Avalonia.Interactivity;
+using System.Reactive.Linq;
+using UserInterface.ViewModels;
+using System.Security;
+
+namespace UserInterface.Views
+{
+ public class BrokerSettings : UserControl
+ {
+ private readonly IIpcClient client;
+ private string _host { get; set; }
+ private string _username { get; set; }
+ private string _password { get; set; }
+ public BrokerSettings()
+ {
+ DataContext = new BrokerSettingsViewModel();
+ this.InitializeComponent();
+ // register IPC clients
+ ServiceProvider serviceProvider = new ServiceCollection()
+ .AddNamedPipeIpcClient("client1", pipeName: "pipeinternal")
+ .BuildServiceProvider();
+
+ // resolve IPC client factory
+ IIpcClientFactory clientFactory = serviceProvider
+ .GetRequiredService>();
+
+ // create client
+ this.client = clientFactory.CreateClient("client1");
+
+ }
+ public void Ping(object sender, RoutedEventArgs args) {
+ var result = this.client.InvokeAsync(x => x.Ping("ping")).Result;
+ }
+
+ public void Configure(object sender, RoutedEventArgs args)
+ {
+ var model = (BrokerSettingsViewModel)this.DataContext;
+ var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettings(model.Host, model.Username, model.Password));
+ }
+
+
+
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UserInterface/Views/MainWindow.axaml b/UserInterface/Views/MainWindow.axaml
new file mode 100644
index 0000000..f154da4
--- /dev/null
+++ b/UserInterface/Views/MainWindow.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UserInterface/Views/MainWindow.axaml.cs b/UserInterface/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..55de57f
--- /dev/null
+++ b/UserInterface/Views/MainWindow.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace UserInterface.Views
+{
+ public class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UserInterface/Win/Interop/BalloonFlags.cs b/UserInterface/Win/Interop/BalloonFlags.cs
new file mode 100644
index 0000000..119e0fe
--- /dev/null
+++ b/UserInterface/Win/Interop/BalloonFlags.cs
@@ -0,0 +1,61 @@
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// Flags that define the icon that is shown on a balloon
+ /// tooltip.
+ ///
+ public enum BalloonFlags
+ {
+ ///
+ /// No icon is displayed.
+ ///
+ None = 0x00,
+
+ ///
+ /// An information icon is displayed.
+ ///
+ Info = 0x01,
+
+ ///
+ /// A warning icon is displayed.
+ ///
+ Warning = 0x02,
+
+ ///
+ /// An error icon is displayed.
+ ///
+ Error = 0x03,
+
+ ///
+ /// Windows XP Service Pack 2 (SP2) and later.
+ /// Use a custom icon as the title icon.
+ ///
+ User = 0x04,
+
+ ///
+ /// Windows XP (Shell32.dll version 6.0) and later.
+ /// Do not play the associated sound. Applies only to balloon ToolTips.
+ ///
+ NoSound = 0x10,
+
+ ///
+ /// Windows Vista (Shell32.dll version 6.0.6) and later. The large version
+ /// of the icon should be used as the balloon icon. This corresponds to the
+ /// icon with dimensions SM_CXICON x SM_CYICON. If this flag is not set,
+ /// the icon with dimensions XM_CXSMICON x SM_CYSMICON is used.
+ /// - This flag can be used with all stock icons.
+ /// - Applications that use older customized icons (NIIF_USER with hIcon) must
+ /// provide a new SM_CXICON x SM_CYICON version in the tray icon (hIcon). These
+ /// icons are scaled down when they are displayed in the System Tray or
+ /// System Control Area (SCA).
+ /// - New customized icons (NIIF_USER with hBalloonIcon) must supply an
+ /// SM_CXICON x SM_CYICON version in the supplied icon (hBalloonIcon).
+ ///
+ LargeIcon = 0x20,
+
+ ///
+ /// Windows 7 and later.
+ ///
+ RespectQuietTime = 0x80
+ }
+}
diff --git a/UserInterface/Win/Interop/IconDataMembers.cs b/UserInterface/Win/Interop/IconDataMembers.cs
new file mode 100644
index 0000000..fb92b20
--- /dev/null
+++ b/UserInterface/Win/Interop/IconDataMembers.cs
@@ -0,0 +1,70 @@
+using System;
+
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// Indicates which members of a structure
+ /// were set, and thus contain valid data or provide additional information
+ /// to the ToolTip as to how it should display.
+ ///
+ [Flags]
+ public enum IconDataMembers
+ {
+ ///
+ /// The message ID is set.
+ ///
+ Message = 0x01,
+
+ ///
+ /// The notification icon is set.
+ ///
+ Icon = 0x02,
+
+ ///
+ /// The tooltip is set.
+ ///
+ Tip = 0x04,
+
+ ///
+ /// State information () is set. This
+ /// applies to both and
+ /// .
+ ///
+ State = 0x08,
+
+ ///
+ /// The balloon ToolTip is set. Accordingly, the following
+ /// members are set: ,
+ /// , ,
+ /// and .
+ ///
+ Info = 0x10,
+
+ // Internal identifier is set. Reserved, thus commented out.
+ //Guid = 0x20,
+
+ ///
+ /// Windows Vista (Shell32.dll version 6.0.6) and later. If the ToolTip
+ /// cannot be displayed immediately, discard it.
+ /// Use this flag for ToolTips that represent real-time information which
+ /// would be meaningless or misleading if displayed at a later time.
+ /// For example, a message that states "Your telephone is ringing."
+ /// This modifies and must be combined with the flag.
+ ///
+ Realtime = 0x40,
+
+ ///
+ /// Windows Vista (Shell32.dll version 6.0.6) and later.
+ /// Use the standard ToolTip. Normally, when uVersion is set
+ /// to NOTIFYICON_VERSION_4, the standard ToolTip is replaced
+ /// by the application-drawn pop-up user interface (UI).
+ /// If the application wants to show the standard tooltip
+ /// in that case, regardless of whether the on-hover UI is showing,
+ /// it can specify NIF_SHOWTIP to indicate the standard tooltip
+ /// should still be shown.
+ /// Note that the NIF_SHOWTIP flag is effective until the next call
+ /// to Shell_NotifyIcon.
+ ///
+ UseLegacyToolTips = 0x80
+ }
+}
diff --git a/UserInterface/Win/Interop/IconState.cs b/UserInterface/Win/Interop/IconState.cs
new file mode 100644
index 0000000..7e1dc20
--- /dev/null
+++ b/UserInterface/Win/Interop/IconState.cs
@@ -0,0 +1,22 @@
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// The state of the icon - can be set to
+ /// hide the icon.
+ ///
+ public enum IconState
+ {
+ ///
+ /// The icon is visible.
+ ///
+ Visible = 0x00,
+
+ ///
+ /// Hide the icon.
+ ///
+ Hidden = 0x01,
+
+ // The icon is shared - currently not supported, thus commented out.
+ //Shared = 0x02
+ }
+}
diff --git a/UserInterface/Win/Interop/MouseEvent.cs b/UserInterface/Win/Interop/MouseEvent.cs
new file mode 100644
index 0000000..a0e5b7a
--- /dev/null
+++ b/UserInterface/Win/Interop/MouseEvent.cs
@@ -0,0 +1,54 @@
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// Event flags for clicked events.
+ ///
+ public enum MouseEvent
+ {
+ ///
+ /// The mouse was moved withing the
+ /// taskbar icon's area.
+ ///
+ MouseMove,
+
+ ///
+ /// The right mouse button was clicked.
+ ///
+ IconRightMouseDown,
+
+ ///
+ /// The left mouse button was clicked.
+ ///
+ IconLeftMouseDown,
+
+ ///
+ /// The right mouse button was released.
+ ///
+ IconRightMouseUp,
+
+ ///
+ /// The left mouse button was released.
+ ///
+ IconLeftMouseUp,
+
+ ///
+ /// The middle mouse button was clicked.
+ ///
+ IconMiddleMouseDown,
+
+ ///
+ /// The middle mouse button was released.
+ ///
+ IconMiddleMouseUp,
+
+ ///
+ /// The taskbar icon was double clicked.
+ ///
+ IconDoubleClick,
+
+ ///
+ /// The balloon tip was clicked.
+ ///
+ BalloonToolTipClicked
+ }
+}
diff --git a/UserInterface/Win/Interop/NotifyCommand.cs b/UserInterface/Win/Interop/NotifyCommand.cs
new file mode 100644
index 0000000..b0ebe00
--- /dev/null
+++ b/UserInterface/Win/Interop/NotifyCommand.cs
@@ -0,0 +1,41 @@
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// Main operations performed on the
+ /// function.
+ ///
+ public enum NotifyCommand
+ {
+ ///
+ /// The taskbar icon is being created.
+ ///
+ Add = 0x00,
+
+ ///
+ /// The settings of the taskbar icon are being updated.
+ ///
+ Modify = 0x01,
+
+ ///
+ /// The taskbar icon is deleted.
+ ///
+ Delete = 0x02,
+
+ ///
+ /// Focus is returned to the taskbar icon. Currently not in use.
+ ///
+ SetFocus = 0x03,
+
+ ///
+ /// Shell32.dll version 5.0 and later only. Instructs the taskbar
+ /// to behave according to the version number specified in the
+ /// uVersion member of the structure pointed to by lpdata.
+ /// This message allows you to specify whether you want the version
+ /// 5.0 behavior found on Microsoft Windows 2000 systems, or the
+ /// behavior found on earlier Shell versions. The default value for
+ /// uVersion is zero, indicating that the original Windows 95 notify
+ /// icon behavior should be used.
+ ///
+ SetVersion = 0x04
+ }
+}
diff --git a/UserInterface/Win/Interop/NotifyIconData.cs b/UserInterface/Win/Interop/NotifyIconData.cs
new file mode 100644
index 0000000..37ba684
--- /dev/null
+++ b/UserInterface/Win/Interop/NotifyIconData.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// A struct that is submitted in order to configure
+ /// the taskbar icon. Provides various members that
+ /// can be configured partially, according to the
+ /// values of the
+ /// that were defined.
+ ///
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct NotifyIconData
+ {
+ ///
+ /// Size of this structure, in bytes.
+ ///
+ public uint cbSize;
+
+ ///
+ /// Handle to the window that receives notification messages associated with an icon in the
+ /// taskbar status area. The Shell uses hWnd and uID to identify which icon to operate on
+ /// when Shell_NotifyIcon is invoked.
+ ///
+ public IntPtr WindowHandle;
+
+ ///
+ /// Application-defined identifier of the taskbar icon. The Shell uses hWnd and uID to identify
+ /// which icon to operate on when Shell_NotifyIcon is invoked. You can have multiple icons
+ /// associated with a single hWnd by assigning each a different uID. This feature, however
+ /// is currently not used.
+ ///
+ public uint TaskbarIconId;
+
+ ///
+ /// Flags that indicate which of the other members contain valid data. This member can be
+ /// a combination of the NIF_XXX constants.
+ ///
+ public IconDataMembers ValidMembers;
+
+ ///
+ /// Application-defined message identifier. The system uses this identifier to send
+ /// notifications to the window identified in hWnd.
+ ///
+ public uint CallbackMessageId;
+
+ ///
+ /// A handle to the icon that should be displayed. Just
+ /// Icon.Handle.
+ ///
+ public IntPtr IconHandle;
+
+ ///
+ /// String with the text for a standard ToolTip. It can have a maximum of 64 characters including
+ /// the terminating NULL. For Version 5.0 and later, szTip can have a maximum of
+ /// 128 characters, including the terminating NULL.
+ ///
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ public string ToolTipText;
+
+
+ ///
+ /// State of the icon. Remember to also set the .
+ ///
+ public IconState IconState;
+
+ ///
+ /// A value that specifies which bits of the state member are retrieved or modified.
+ /// For example, setting this member to
+ /// causes only the item's hidden
+ /// state to be retrieved.
+ ///
+ public IconState StateMask;
+
+ ///
+ /// String with the text for a balloon ToolTip. It can have a maximum of 255 characters.
+ /// To remove the ToolTip, set the NIF_INFO flag in uFlags and set szInfo to an empty string.
+ ///
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
+ public string BalloonText;
+
+ ///
+ /// Mainly used to set the version when is invoked
+ /// with . However, for legacy operations,
+ /// the same member is also used to set timeouts for balloon ToolTips.
+ ///
+ public uint VersionOrTimeout;
+
+ ///
+ /// String containing a title for a balloon ToolTip. This title appears in boldface
+ /// above the text. It can have a maximum of 63 characters.
+ ///
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
+ public string BalloonTitle;
+
+ ///
+ /// Adds an icon to a balloon ToolTip, which is placed to the left of the title. If the
+ /// member is zero-length, the icon is not shown.
+ ///
+ public BalloonFlags BalloonFlags;
+
+ ///
+ /// Windows XP (Shell32.dll version 6.0) and later.
+ /// - Windows 7 and later: A registered GUID that identifies the icon.
+ /// This value overrides uID and is the recommended method of identifying the icon.
+ /// - Windows XP through Windows Vista: Reserved.
+ ///
+ public Guid TaskbarIconGuid;
+
+ ///
+ /// Windows Vista (Shell32.dll version 6.0.6) and later. The handle of a customized
+ /// balloon icon provided by the application that should be used independently
+ /// of the tray icon. If this member is non-NULL and the
+ /// flag is set, this icon is used as the balloon icon.
+ /// If this member is NULL, the legacy behavior is carried out.
+ ///
+ public IntPtr CustomBalloonIconHandle;
+
+
+ ///
+ /// Creates a default data structure that provides
+ /// a hidden taskbar icon without the icon being set.
+ ///
+ ///
+ /// NotifyIconData
+ public static NotifyIconData CreateDefault(IntPtr handle)
+ {
+ var data = new NotifyIconData();
+
+ //use the current size
+ data.cbSize = (uint) Marshal.SizeOf(data);
+
+ data.WindowHandle = handle;
+ data.TaskbarIconId = 0x0;
+ data.CallbackMessageId = WindowMessageSink.CallbackMessageId;
+ data.VersionOrTimeout = (uint) NotifyIconVersion.Vista;
+
+ data.IconHandle = IntPtr.Zero;
+
+ //hide initially
+ data.IconState = IconState.Hidden;
+ data.StateMask = IconState.Hidden;
+
+ //set flags
+ data.ValidMembers = IconDataMembers.Message | IconDataMembers.Icon | IconDataMembers.Tip | IconDataMembers.UseLegacyToolTips;
+
+ //reset strings
+ data.ToolTipText = data.BalloonText = data.BalloonTitle = string.Empty;
+
+ return data;
+ }
+ }
+}
diff --git a/UserInterface/Win/Interop/NotifyIconVersion.cs b/UserInterface/Win/Interop/NotifyIconVersion.cs
new file mode 100644
index 0000000..349ac75
--- /dev/null
+++ b/UserInterface/Win/Interop/NotifyIconVersion.cs
@@ -0,0 +1,15 @@
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// The notify icon version that is used. The higher
+ /// the version, the more capabilities are available.
+ ///
+ public enum NotifyIconVersion
+ {
+ ///
+ /// Extended tooltip support, which is available for Vista and later.
+ /// Detailed information about what the different versions do, can be found here
+ ///
+ Vista = 0x4
+ }
+}
diff --git a/UserInterface/Win/Interop/WinApi.cs b/UserInterface/Win/Interop/WinApi.cs
new file mode 100644
index 0000000..d0580e1
--- /dev/null
+++ b/UserInterface/Win/Interop/WinApi.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// Win32 API imports.
+ ///
+ internal static class WinApi
+ {
+ private const string User32 = "user32.dll";
+
+ ///
+ /// Creates, updates or deletes the taskbar icon.
+ ///
+ [DllImport("shell32.Dll", CharSet = CharSet.Unicode)]
+ public static extern bool Shell_NotifyIcon(NotifyCommand cmd, [In] ref NotifyIconData data);
+
+
+ ///
+ /// Creates the helper window that receives messages from the taskar icon.
+ ///
+ [DllImport(User32, EntryPoint = "CreateWindowExW", SetLastError = true)]
+ public static extern IntPtr CreateWindowEx(int dwExStyle, [MarshalAs(UnmanagedType.LPWStr)] string lpClassName,
+ [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, int dwStyle, int x, int y,
+ int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance,
+ IntPtr lpParam);
+
+
+ ///
+ /// Processes a default windows procedure.
+ ///
+ [DllImport(User32)]
+ public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wparam, IntPtr lparam);
+
+ ///
+ /// Registers the helper window class.
+ ///
+ [DllImport(User32, EntryPoint = "RegisterClassW", SetLastError = true)]
+ public static extern short RegisterClass(ref WindowClass lpWndClass);
+
+ ///
+ /// Registers a listener for a window message.
+ ///
+ ///
+ /// uint
+ [DllImport(User32, EntryPoint = "RegisterWindowMessageW")]
+ public static extern uint RegisterWindowMessage([MarshalAs(UnmanagedType.LPWStr)] string lpString);
+
+ ///
+ /// Used to destroy the hidden helper window that receives messages from the
+ /// taskbar icon.
+ ///
+ ///
+ /// bool
+ [DllImport(User32, SetLastError = true)]
+ public static extern bool DestroyWindow(IntPtr hWnd);
+ }
+}
diff --git a/UserInterface/Win/Interop/WindowClass.cs b/UserInterface/Win/Interop/WindowClass.cs
new file mode 100644
index 0000000..b6840a0
--- /dev/null
+++ b/UserInterface/Win/Interop/WindowClass.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// Callback delegate which is used by the Windows API to
+ /// submit window messages.
+ ///
+ public delegate IntPtr WindowProcedureHandler(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
+
+
+ ///
+ /// Win API WNDCLASS struct - represents a single window.
+ /// Used to receive window messages.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct WindowClass
+ {
+ #pragma warning disable 1591
+
+ public uint style;
+ public WindowProcedureHandler lpfnWndProc;
+ public int cbClsExtra;
+ public int cbWndExtra;
+ public IntPtr hInstance;
+ public IntPtr hIcon;
+ public IntPtr hCursor;
+ public IntPtr hbrBackground;
+ [MarshalAs(UnmanagedType.LPWStr)] public string lpszMenuName;
+ [MarshalAs(UnmanagedType.LPWStr)] public string lpszClassName;
+
+ #pragma warning restore 1591
+ }
+}
diff --git a/UserInterface/Win/Interop/WindowMessageSink.cs b/UserInterface/Win/Interop/WindowMessageSink.cs
new file mode 100644
index 0000000..f4ef6d7
--- /dev/null
+++ b/UserInterface/Win/Interop/WindowMessageSink.cs
@@ -0,0 +1,357 @@
+// hardcodet.net NotifyIcon for WPF
+// Copyright (c) 2009 - 2013 Philipp Sumi
+// Contact and Information: http://www.hardcodet.net
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the Code Project Open License (CPOL);
+// either version 1.0 of the License, or (at your option) any later
+// version.
+//
+// The above copyright notice and this permission notice 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.
+//
+// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE
+
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// Receives messages from the taskbar icon through
+ /// window messages of an underlying helper window.
+ ///
+ public class WindowMessageSink : IDisposable
+ {
+ #region members
+
+ ///
+ /// The ID of messages that are received from the the
+ /// taskbar icon.
+ ///
+ public const int CallbackMessageId = 0x400;
+
+ ///
+ /// The ID of the message that is being received if the
+ /// taskbar is (re)started.
+ ///
+ private uint taskbarRestartMessageId;
+
+ ///
+ /// Used to track whether a mouse-up event is just
+ /// the aftermath of a double-click and therefore needs
+ /// to be suppressed.
+ ///
+ private bool isDoubleClick;
+
+ ///
+ /// A delegate that processes messages of the hidden
+ /// native window that receives window messages. Storing
+ /// this reference makes sure we don't loose our reference
+ /// to the message window.
+ ///
+ private WindowProcedureHandler messageHandler;
+
+ ///
+ /// Window class ID.
+ ///
+ internal string WindowId { get; private set; }
+
+ ///
+ /// Handle for the message window.
+ ///
+ internal IntPtr MessageWindowHandle { get; private set; }
+
+ ///
+ /// The version of the underlying icon. Defines how
+ /// incoming messages are interpreted.
+ ///
+ public NotifyIconVersion Version { get; set; }
+
+ #endregion
+
+ #region events
+
+ ///
+ /// The custom tooltip should be closed or hidden.
+ ///
+ public event Action ChangeToolTipStateRequest;
+
+ ///
+ /// Fired in case the user clicked or moved within
+ /// the taskbar icon area.
+ ///
+ public event Action MouseEventReceived;
+
+ ///
+ /// Fired if a balloon ToolTip was either displayed
+ /// or closed (indicated by the boolean flag).
+ ///
+ public event Action BalloonToolTipChanged;
+
+ ///
+ /// Fired if the taskbar was created or restarted. Requires the taskbar
+ /// icon to be reset.
+ ///
+ public event Action TaskbarCreated;
+
+ #endregion
+
+ #region construction
+
+ ///
+ /// Creates a new message sink that receives message from
+ /// a given taskbar icon.
+ ///
+ ///
+ public WindowMessageSink(NotifyIconVersion version)
+ {
+ Version = version;
+ CreateMessageWindow();
+ }
+
+ #endregion
+
+ #region CreateMessageWindow
+
+ ///
+ /// Creates the helper message window that is used
+ /// to receive messages from the taskbar icon.
+ ///
+ private void CreateMessageWindow()
+ {
+ //generate a unique ID for the window
+ WindowId = "WPFTaskbarIcon_" + Guid.NewGuid();
+
+ //register window message handler
+ messageHandler = OnWindowMessageReceived;
+
+ // Create a simple window class which is reference through
+ //the messageHandler delegate
+ WindowClass wc;
+
+ wc.style = 0;
+ wc.lpfnWndProc = messageHandler;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = IntPtr.Zero;
+ wc.hIcon = IntPtr.Zero;
+ wc.hCursor = IntPtr.Zero;
+ wc.hbrBackground = IntPtr.Zero;
+ wc.lpszMenuName = string.Empty;
+ wc.lpszClassName = WindowId;
+
+ // Register the window class
+ WinApi.RegisterClass(ref wc);
+
+ // Get the message used to indicate the taskbar has been restarted
+ // This is used to re-add icons when the taskbar restarts
+ taskbarRestartMessageId = WinApi.RegisterWindowMessage("TaskbarCreated");
+
+ // Create the message window
+ MessageWindowHandle = WinApi.CreateWindowEx(0, WindowId, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero,
+ IntPtr.Zero, IntPtr.Zero);
+
+ if (MessageWindowHandle == IntPtr.Zero)
+ {
+ throw new Win32Exception("Message window handle was not a valid pointer");
+ }
+ }
+
+ #endregion
+
+ #region Handle Window Messages
+
+ ///
+ /// Callback method that receives messages from the taskbar area.
+ ///
+ private IntPtr OnWindowMessageReceived(IntPtr hWnd, uint messageId, IntPtr wParam, IntPtr lParam)
+ {
+ if (messageId == taskbarRestartMessageId)
+ {
+ //recreate the icon if the taskbar was restarted (e.g. due to Win Explorer shutdown)
+ var listener = TaskbarCreated;
+ listener?.Invoke();
+ }
+
+ //forward message
+ ProcessWindowMessage(messageId, wParam, lParam);
+
+ // Pass the message to the default window procedure
+ return WinApi.DefWindowProc(hWnd, messageId, wParam, lParam);
+ }
+
+
+ ///
+ /// Processes incoming system messages.
+ ///
+ /// Callback ID.
+ /// If the version is
+ /// or higher, this parameter can be used to resolve mouse coordinates.
+ /// Currently not in use.
+ /// Provides information about the event.
+ private void ProcessWindowMessage(uint msg, IntPtr wParam, IntPtr lParam)
+ {
+ if (msg != CallbackMessageId) return;
+
+ var message = (WindowsMessages) lParam.ToInt32();
+ switch (message)
+ {
+ case WindowsMessages.WM_CONTEXTMENU:
+ // TODO: Handle WM_CONTEXTMENU, see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw
+ Debug.WriteLine("Unhandled WM_CONTEXTMENU");
+ break;
+
+ case WindowsMessages.WM_MOUSEMOVE:
+ MouseEventReceived?.Invoke(MouseEvent.MouseMove);
+ break;
+
+ case WindowsMessages.WM_LBUTTONDOWN:
+ MouseEventReceived?.Invoke(MouseEvent.IconLeftMouseDown);
+ break;
+
+ case WindowsMessages.WM_LBUTTONUP:
+ if (!isDoubleClick)
+ {
+ MouseEventReceived?.Invoke(MouseEvent.IconLeftMouseUp);
+ }
+ isDoubleClick = false;
+ break;
+
+ case WindowsMessages.WM_LBUTTONDBLCLK:
+ isDoubleClick = true;
+ MouseEventReceived?.Invoke(MouseEvent.IconDoubleClick);
+ break;
+
+ case WindowsMessages.WM_RBUTTONDOWN:
+ MouseEventReceived?.Invoke(MouseEvent.IconRightMouseDown);
+ break;
+
+ case WindowsMessages.WM_RBUTTONUP:
+ MouseEventReceived?.Invoke(MouseEvent.IconRightMouseUp);
+ break;
+
+ case WindowsMessages.WM_RBUTTONDBLCLK:
+ //double click with right mouse button - do not trigger event
+ break;
+
+ case WindowsMessages.WM_MBUTTONDOWN:
+ MouseEventReceived?.Invoke(MouseEvent.IconMiddleMouseDown);
+ break;
+
+ case WindowsMessages.WM_MBUTTONUP:
+ MouseEventReceived?.Invoke(MouseEvent.IconMiddleMouseUp);
+ break;
+
+ case WindowsMessages.WM_MBUTTONDBLCLK:
+ //double click with middle mouse button - do not trigger event
+ break;
+
+ case WindowsMessages.NIN_BALLOONSHOW:
+ BalloonToolTipChanged?.Invoke(true);
+ break;
+
+ case WindowsMessages.NIN_BALLOONHIDE:
+ case WindowsMessages.NIN_BALLOONTIMEOUT:
+ BalloonToolTipChanged?.Invoke(false);
+ break;
+
+ case WindowsMessages.NIN_BALLOONUSERCLICK:
+ MouseEventReceived?.Invoke(MouseEvent.BalloonToolTipClicked);
+ break;
+
+ case WindowsMessages.NIN_POPUPOPEN:
+ ChangeToolTipStateRequest?.Invoke(true);
+ break;
+
+ case WindowsMessages.NIN_POPUPCLOSE:
+ ChangeToolTipStateRequest?.Invoke(false);
+ break;
+
+ case WindowsMessages.NIN_SELECT:
+ // TODO: Handle NIN_SELECT see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw
+ Debug.WriteLine("Unhandled NIN_SELECT");
+ break;
+
+ case WindowsMessages.NIN_KEYSELECT:
+ // TODO: Handle NIN_KEYSELECT see https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw
+ Debug.WriteLine("Unhandled NIN_KEYSELECT");
+ break;
+
+ default:
+ Debug.WriteLine("Unhandled NotifyIcon message ID: " + lParam);
+ break;
+ }
+ }
+
+ #endregion
+
+ #region Dispose
+
+ ///
+ /// Set to true as soon as Dispose has been invoked.
+ ///
+ public bool IsDisposed { get; private set; }
+
+
+ ///
+ /// Disposes the object.
+ ///
+ /// This method is not virtual by design. Derived classes
+ /// should override .
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+
+ // This object will be cleaned up by the Dispose method.
+ // Therefore, you should call GC.SuppressFinalize to
+ // take this object off the finalization queue
+ // and prevent finalization code for this object
+ // from executing a second time.
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// This destructor will run only if the
+ /// method does not get called. This gives this base class the
+ /// opportunity to finalize.
+ ///
+ /// Important: Do not provide destructor in types derived from
+ /// this class.
+ ///
+ ///
+ ~WindowMessageSink()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Removes the windows hook that receives window
+ /// messages and closes the underlying helper window.
+ ///
+ private void Dispose(bool disposing)
+ {
+ //don't do anything if the component is already disposed
+ if (IsDisposed) return;
+ IsDisposed = true;
+
+ //always destroy the unmanaged handle (even if called from the GC)
+ WinApi.DestroyWindow(MessageWindowHandle);
+ messageHandler = null;
+ }
+
+ #endregion
+ }
+}
diff --git a/UserInterface/Win/Interop/WindowsMessages.cs b/UserInterface/Win/Interop/WindowsMessages.cs
new file mode 100644
index 0000000..80bdc71
--- /dev/null
+++ b/UserInterface/Win/Interop/WindowsMessages.cs
@@ -0,0 +1,190 @@
+// hardcodet.net NotifyIcon for WPF
+// Copyright (c) 2009 - 2013 Philipp Sumi
+// Contact and Information: http://www.hardcodet.net
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the Code Project Open License (CPOL);
+// either version 1.0 of the License, or (at your option) any later
+// version.
+//
+// The above copyright notice and this permission notice 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.
+//
+// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE
+
+// ReSharper disable InconsistentNaming
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace MangaReader.Avalonia.Platform.Win.Interop
+{
+ ///
+ /// This enum defines the windows messages we respond to.
+ /// See more on Windows messages here
+ ///
+ [SuppressMessage("ReSharper", "IdentifierTypo")]
+ public enum WindowsMessages : uint
+ {
+ ///
+ /// Notifies a window that the user clicked the right mouse button (right-clicked) in the window.
+ /// See WM_CONTEXTMENU message
+ ///
+ /// In case of a notify icon:
+ /// If a user selects a notify icon's shortcut menu with the keyboard, the Shell now sends the associated application a WM_CONTEXTMENU message. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
+ /// See Shell_NotifyIcon function
+ ///
+ WM_CONTEXTMENU = 0x007b,
+
+ ///
+ /// Posted to a window when the cursor moves.
+ /// If the mouse is not captured, the message is posted to the window that contains the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_MOUSEMOVE message
+ ///
+ WM_MOUSEMOVE = 0x0200,
+
+ ///
+ /// Posted when the user presses the left mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_LBUTTONDOWN message
+ ///
+ WM_LBUTTONDOWN = 0x0201,
+
+ ///
+ /// Posted when the user releases the left mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_LBUTTONUP message
+ ///
+ WM_LBUTTONUP = 0x0202,
+
+ ///
+ /// Posted when the user double-clicks the left mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_LBUTTONDBLCLK message
+ ///
+ WM_LBUTTONDBLCLK = 0x0203,
+
+ ///
+ /// Posted when the user presses the right mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_RBUTTONDOWN message
+ ///
+ WM_RBUTTONDOWN = 0x0204,
+
+ ///
+ /// Posted when the user releases the right mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_RBUTTONUP message
+ ///
+ WM_RBUTTONUP = 0x0205,
+
+ ///
+ /// Posted when the user double-clicks the right mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_RBUTTONDBLCLK message
+ ///
+ WM_RBUTTONDBLCLK = 0x0206,
+
+ ///
+ /// Posted when the user presses the middle mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_MBUTTONDOWN message
+ ///
+ WM_MBUTTONDOWN = 0x0207,
+
+ ///
+ /// Posted when the user releases the middle mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_MBUTTONUP message
+ ///
+ WM_MBUTTONUP = 0x0208,
+
+ ///
+ /// Posted when the user double-clicks the middle mouse button while the cursor is in the client area of a window.
+ /// If the mouse is not captured, the message is posted to the window beneath the cursor.
+ /// Otherwise, the message is posted to the window that has captured the mouse.
+ ///
+ /// See WM_MBUTTONDBLCLK message
+ ///
+ WM_MBUTTONDBLCLK = 0x0209,
+
+ ///
+ /// Used to define private messages for use by private window classes, usually of the form WM_USER+x, where x is an integer value.
+ ///
+ WM_USER = 0x0400,
+
+ ///
+ /// This message is only send when using NOTIFYICON_VERSION_4, the Shell now sends the associated application an NIN_SELECT notification.
+ /// Send when a notify icon is activated with mouse or ENTER key.
+ /// Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
+ ///
+ NIN_SELECT = WM_USER,
+
+ ///
+ /// This message is only send when using NOTIFYICON_VERSION_4, the Shell now sends the associated application an NIN_SELECT notification.
+ /// Send when a notify icon is activated with SPACEBAR or ENTER key.
+ /// Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
+ ///
+ NIN_KEYSELECT = WM_USER + 1,
+
+ ///
+ /// Sent when the balloon is shown (balloons are queued).
+ ///
+ NIN_BALLOONSHOW = WM_USER + 2,
+
+ ///
+ /// Sent when the balloon disappears. For example, when the icon is deleted.
+ /// This message is not sent if the balloon is dismissed because of a timeout or if the user clicks the mouse.
+ ///
+ /// As of Windows 7, NIN_BALLOONHIDE is also sent when a notification with the NIIF_RESPECT_QUIET_TIME flag set attempts to display during quiet time (a user's first hour on a new computer).
+ /// In that case, the balloon is never displayed at all.
+ ///
+ NIN_BALLOONHIDE = WM_USER + 3,
+
+ ///
+ /// Sent when the balloon is dismissed because of a timeout.
+ ///
+ NIN_BALLOONTIMEOUT = WM_USER + 4,
+
+ ///
+ /// Sent when the balloon is dismissed because the user clicked the mouse.
+ ///
+ NIN_BALLOONUSERCLICK = WM_USER + 5,
+
+ ///
+ /// Sent when the user hovers the cursor over an icon to indicate that the richer pop-up UI should be used in place of a standard textual tooltip.
+ ///
+ NIN_POPUPOPEN = WM_USER + 6,
+
+ ///
+ /// Sent when a cursor no longer hovers over an icon to indicate that the rich pop-up UI should be closed.
+ ///
+ NIN_POPUPCLOSE = WM_USER + 7
+ }
+}
diff --git a/UserInterface/Win/NotifyIcon.cs b/UserInterface/Win/NotifyIcon.cs
new file mode 100644
index 0000000..c0c2cbd
--- /dev/null
+++ b/UserInterface/Win/NotifyIcon.cs
@@ -0,0 +1,403 @@
+// hardcodet.net NotifyIcon for WPF
+// Copyright (c) 2009 - 2013 Philipp Sumi
+// Contact and Information: http://www.hardcodet.net
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the Code Project Open License (CPOL);
+// either version 1.0 of the License, or (at your option) any later
+// version.
+//
+// The above copyright notice and this permission notice 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.
+//
+// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE
+
+
+using System;
+using System.Drawing;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using MangaReader.Avalonia.Platform.Win.Interop;
+
+namespace MangaReader.Avalonia.Platform.Win
+{
+ ///
+ /// A WPF proxy to for a taskbar icon (NotifyIcon) that sits in the system's
+ /// taskbar notification area ("system tray").
+ ///
+ public class TaskBarIcon : IDisposable
+ {
+ private readonly object lockObject = new object();
+
+ #region Members
+
+ ///
+ /// Represents the current icon data.
+ ///
+ private NotifyIconData iconData;
+
+ ///
+ /// Receives messages from the taskbar icon.
+ ///
+ private readonly WindowMessageSink messageSink;
+
+ ///
+ /// Indicates whether the taskbar icon has been created or not.
+ ///
+ public bool IsTaskbarIconCreated { get; private set; }
+
+ public Icon Icon { get; }
+
+ public event EventHandler MouseEventHandler;
+
+ #endregion
+
+ #region Construction
+
+ ///
+ /// Initializes the taskbar icon and registers a message listener
+ /// in order to receive events from the taskbar area.
+ ///
+ public TaskBarIcon(Icon icon)
+ {
+ Icon = icon;
+
+ // using dummy sink in design mode
+ messageSink = new WindowMessageSink(NotifyIconVersion.Vista);
+
+ // init icon data structure
+ iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle);
+ iconData.IconHandle = Icon?.Handle ?? IntPtr.Zero;
+ iconData.ToolTipText = nameof(MangaReader);
+
+ // create the taskbar icon
+ CreateTaskbarIcon();
+
+ // register event listeners
+ messageSink.MouseEventReceived += OnMouseEvent;
+ messageSink.TaskbarCreated += OnTaskbarCreated;
+
+ // register listener in order to get notified when the application closes
+ if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
+ {
+ lifetime.Exit += OnExit;
+ }
+ }
+
+ #endregion
+
+ #region Process Incoming Mouse Events
+
+ ///
+ /// Processes mouse events, which are bubbled
+ /// through the class' routed events, trigger
+ /// certain actions (e.g. show a popup), or
+ /// both.
+ ///
+ /// Event flag.
+ private void OnMouseEvent(MouseEvent me)
+ {
+ if (IsDisposed)
+ return;
+
+ switch (me)
+ {
+ case MouseEvent.MouseMove:
+ // immediately return - there's nothing left to evaluate
+ return;
+ case MouseEvent.IconRightMouseDown:
+ case MouseEvent.IconLeftMouseDown:
+ case MouseEvent.IconRightMouseUp:
+ case MouseEvent.IconLeftMouseUp:
+ case MouseEvent.IconMiddleMouseDown:
+ case MouseEvent.IconMiddleMouseUp:
+ case MouseEvent.BalloonToolTipClicked:
+ case MouseEvent.IconDoubleClick:
+ MouseEventHandler?.Invoke(this, me);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(me), "Missing handler for mouse event flag: " + me);
+ }
+ }
+
+ #endregion
+
+ #region Balloon Tips
+
+ ///
+ /// Displays a balloon tip with the specified title,
+ /// text, and icon in the taskbar for the specified time period.
+ ///
+ /// The title to display on the balloon tip.
+ /// The text to display on the balloon tip.
+ /// A symbol that indicates the severity.
+ public void ShowBalloonTip(string title, string message, BalloonFlags symbol)
+ {
+ lock (lockObject)
+ {
+ ShowBalloonTip(title, message, symbol, IntPtr.Zero);
+ }
+ }
+
+
+ ///
+ /// Invokes in order to display
+ /// a given balloon ToolTip.
+ ///
+ /// The title to display on the balloon tip.
+ /// The text to display on the balloon tip.
+ /// Indicates what icon to use.
+ /// A handle to a custom icon, if any, or
+ /// .
+ private void ShowBalloonTip(string title, string message, BalloonFlags flags, IntPtr balloonIconHandle)
+ {
+ EnsureNotDisposed();
+
+ iconData.BalloonText = message ?? string.Empty;
+ iconData.BalloonTitle = title ?? string.Empty;
+
+ iconData.BalloonFlags = flags;
+ iconData.CustomBalloonIconHandle = balloonIconHandle;
+ WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info | IconDataMembers.Icon);
+ }
+
+
+ ///
+ /// Hides a balloon ToolTip, if any is displayed.
+ ///
+ public void HideBalloonTip()
+ {
+ EnsureNotDisposed();
+
+ // reset balloon by just setting the info to an empty string
+ iconData.BalloonText = iconData.BalloonTitle = string.Empty;
+ WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info);
+ }
+
+ #endregion
+
+ #region Create / Remove Taskbar Icon
+
+ ///
+ /// Recreates the taskbar icon if the whole taskbar was
+ /// recreated (e.g. because Explorer was shut down).
+ ///
+ private void OnTaskbarCreated()
+ {
+ IsTaskbarIconCreated = false;
+ CreateTaskbarIcon();
+ }
+
+
+ ///
+ /// Creates the taskbar icon. This message is invoked during initialization,
+ /// if the taskbar is restarted, and whenever the icon is displayed.
+ ///
+ private void CreateTaskbarIcon()
+ {
+ lock (lockObject)
+ {
+ if (IsTaskbarIconCreated)
+ {
+ return;
+ }
+
+ const IconDataMembers members = IconDataMembers.Message | IconDataMembers.Icon | IconDataMembers.Tip;
+
+ //write initial configuration
+ var status = WriteIconData(ref iconData, NotifyCommand.Add, members);
+ if (!status)
+ {
+ // couldn't create the icon - we can assume this is because explorer is not running (yet!)
+ // -> try a bit later again rather than throwing an exception. Typically, if the windows
+ // shell is being loaded later, this method is being re-invoked from OnTaskbarCreated
+ // (we could also retry after a delay, but that's currently YAGNI)
+ return;
+ }
+
+ messageSink.Version = (NotifyIconVersion)iconData.VersionOrTimeout;
+
+ IsTaskbarIconCreated = true;
+ }
+ }
+
+ ///
+ /// Closes the taskbar icon if required.
+ ///
+ private void RemoveTaskbarIcon()
+ {
+ lock (lockObject)
+ {
+ // make sure we didn't schedule a creation
+
+ if (!IsTaskbarIconCreated)
+ {
+ return;
+ }
+
+ WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message);
+ IsTaskbarIconCreated = false;
+ }
+ }
+
+ #endregion
+
+ #region Dispose / Exit
+
+ ///
+ /// Set to true as soon as Dispose has been invoked.
+ ///
+ public bool IsDisposed { get; private set; }
+
+
+ ///
+ /// Checks if the object has been disposed and
+ /// raises a in case
+ /// the flag is true.
+ ///
+ private void EnsureNotDisposed()
+ {
+ if (IsDisposed)
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+
+
+ ///
+ /// Disposes the class if the application exits.
+ ///
+ private void OnExit(object sender, EventArgs e)
+ {
+ Dispose();
+ }
+
+
+ ///
+ /// This destructor will run only if the
+ /// method does not get called. This gives this base class the
+ /// opportunity to finalize.
+ ///
+ /// Important: Do not provide destructor in types derived from this class.
+ ///
+ ///
+ ~TaskBarIcon()
+ {
+ Dispose(false);
+ }
+
+
+ ///
+ /// Disposes the object.
+ ///
+ /// This method is not virtual by design. Derived classes
+ /// should override .
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+
+ // This object will be cleaned up by the Dispose method.
+ // Therefore, you should call GC.SuppressFinalize to
+ // take this object off the finalization queue
+ // and prevent finalization code for this object
+ // from executing a second time.
+ GC.SuppressFinalize(this);
+ }
+
+
+ ///
+ /// Closes the tray and releases all resources.
+ ///
+ ///
+ /// Dispose(bool disposing) executes in two distinct scenarios.
+ /// If disposing equals true, the method has been called directly
+ /// or indirectly by a user's code. Managed and unmanaged resources
+ /// can be disposed.
+ ///
+ /// If disposing equals false, the method
+ /// has been called by the runtime from inside the finalizer and you
+ /// should not reference other objects. Only unmanaged resources can
+ /// be disposed.
+ /// Check the property to determine whether
+ /// the method has already been called.
+ private void Dispose(bool disposing)
+ {
+ // don't do anything if the component is already disposed
+ if (IsDisposed || !disposing)
+ return;
+
+ lock (lockObject)
+ {
+ IsDisposed = true;
+
+ // de-register application event listener
+ if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
+ {
+ lifetime.Exit -= OnExit;
+ }
+
+ // dispose message sink
+ messageSink.Dispose();
+
+ // remove icon
+ RemoveTaskbarIcon();
+ }
+ }
+
+ #endregion
+
+ #region WriteIconData
+
+ ///
+ /// Updates the taskbar icons with data provided by a given
+ /// instance.
+ ///
+ /// Configuration settings for the NotifyIcon.
+ /// Operation on the icon (e.g. delete the icon).
+ /// Defines which members of the
+ /// structure are set.
+ /// True if the data was successfully written.
+ /// See Shell_NotifyIcon documentation on MSDN for details.
+ private bool WriteIconData(ref NotifyIconData data, NotifyCommand command, IconDataMembers flags)
+ {
+ data.ValidMembers |= flags;
+ lock (lockObject)
+ {
+ return WinApi.Shell_NotifyIcon(command, ref data);
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Reads a given image resource into a WinForms icon.
+ ///
+ /// Image source pointing to
+ /// an icon file (*.ico).
+ /// An icon object that can be used with the
+ /// taskbar area.
+ public static Icon ToIcon(string imageSource)
+ {
+ if (imageSource == null)
+ return null;
+
+ var executingAssembly = System.Reflection.Assembly.GetExecutingAssembly();
+ if (executingAssembly.GetManifestResourceNames().Contains(imageSource))
+ {
+ var stream = executingAssembly.GetManifestResourceStream(imageSource);
+ return new Icon(stream);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/UserInterface/Win/WindowsTrayIcon.cs b/UserInterface/Win/WindowsTrayIcon.cs
new file mode 100644
index 0000000..532ce8b
--- /dev/null
+++ b/UserInterface/Win/WindowsTrayIcon.cs
@@ -0,0 +1,62 @@
+using System.Runtime.InteropServices;
+using System.Windows.Input;
+using MangaReader.Avalonia.Platform.Win.Interop;
+
+namespace MangaReader.Avalonia.Platform.Win
+{
+ public class WindowsTrayIcon : ITrayIcon
+ {
+ public ICommand DoubleClickCommand { get; set; }
+
+ public ICommand BalloonClickedCommand { get; set; }
+
+ private TaskBarIcon taskBarIcon;
+
+ private object lastBalloonState;
+
+ public void SetIcon()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var iconSource = "MangaReader.Avalonia.Assets.main.ico";
+ var icon = TaskBarIcon.ToIcon(iconSource);
+ taskBarIcon = new TaskBarIcon(icon);
+ taskBarIcon.MouseEventHandler += TaskBarIconOnMouseEventHandler;
+ }
+ }
+
+ public void ShowBalloon(string text, object state)
+ {
+ this.lastBalloonState = state;
+ taskBarIcon?.ShowBalloonTip(nameof(MangaReader), text, BalloonFlags.Info);
+ }
+
+ private void TaskBarIconOnMouseEventHandler(object sender, MouseEvent e)
+ {
+ if (e == MouseEvent.IconDoubleClick)
+ {
+ var command = this.DoubleClickCommand;
+ if (command != null && command.CanExecute(null))
+ {
+ command.Execute(null);
+ }
+ }
+
+ if (e == MouseEvent.BalloonToolTipClicked)
+ {
+ var command = this.BalloonClickedCommand;
+ if (command != null && command.CanExecute(lastBalloonState))
+ {
+ command.Execute(lastBalloonState);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (taskBarIcon != null)
+ taskBarIcon.MouseEventHandler -= TaskBarIconOnMouseEventHandler;
+ taskBarIcon?.Dispose();
+ }
+ }
+}
diff --git a/UserInterface/nuget.config b/UserInterface/nuget.config
new file mode 100644
index 0000000..6c273ab
--- /dev/null
+++ b/UserInterface/nuget.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/hass-workstation-service.sln b/hass-workstation-service.sln
index 18edd60..8815dcc 100644
--- a/hass-workstation-service.sln
+++ b/hass-workstation-service.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hass-workstation-service", "hass-workstation-service\hass-workstation-service.csproj", "{78EC7ACA-8826-4A0A-AA8E-664D03ACBE88}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserInterface", "UserInterface\UserInterface.csproj", "{8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,14 @@ Global
{78EC7ACA-8826-4A0A-AA8E-664D03ACBE88}.Release|Any CPU.Build.0 = Release|Any CPU
{78EC7ACA-8826-4A0A-AA8E-664D03ACBE88}.Release|x86.ActiveCfg = Release|Any CPU
{78EC7ACA-8826-4A0A-AA8E-664D03ACBE88}.Release|x86.Build.0 = Release|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Debug|x86.Build.0 = Debug|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Release|x86.ActiveCfg = Release|Any CPU
+ {8ECB6FEE-1AD2-40E3-897D-E75EDB637BB5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs
new file mode 100644
index 0000000..8750f31
--- /dev/null
+++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace hass_workstation_service.Communication.NamedPipe
+{
+ public interface ServiceContractInterfaces
+ {
+ public string Ping(string str);
+ void WriteMqttBrokerSettings(string host, string username, string password);
+ }
+}
diff --git a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs
index 95d4894..2b1dd92 100644
--- a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs
+++ b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs
@@ -17,7 +17,7 @@ namespace hass_workstation_service.Communication
{
private readonly IMqttClient _mqttClient;
private readonly ILogger _logger;
- private readonly ConfigurationService _configurationService;
+ private readonly IConfigurationService _configurationService;
public DateTime LastConfigAnnounce { get; private set; }
public DeviceConfigModel DeviceConfigModel { get; private set; }
public bool IsConnected
@@ -38,7 +38,7 @@ namespace hass_workstation_service.Communication
public MqttPublisher(
ILogger logger,
DeviceConfigModel deviceConfigModel,
- ConfigurationService configurationService)
+ IConfigurationService configurationService)
{
this._logger = logger;
@@ -46,6 +46,7 @@ namespace hass_workstation_service.Communication
this._configurationService = configurationService;
var options = _configurationService.ReadMqttSettings().Result;
+ _configurationService.MqqtConfigChangedHandler = this.ReplaceMqttClient;
var factory = new MqttFactory();
this._mqttClient = factory.CreateMqttClient();
@@ -54,17 +55,21 @@ namespace hass_workstation_service.Communication
// configure what happens on disconnect
this._mqttClient.UseDisconnectedHandler(async e =>
{
- _logger.LogWarning("Disconnected from server");
- await Task.Delay(TimeSpan.FromSeconds(5));
-
- try
- {
- await this._mqttClient.ConnectAsync(options, CancellationToken.None);
- }
- catch (Exception ex)
+ if (e.ReasonCode != MQTTnet.Client.Disconnecting.MqttClientDisconnectReason.NormalDisconnection)
{
- _logger.LogError(ex, "Reconnecting failed");
+ _logger.LogWarning("Disconnected from server");
+ await Task.Delay(TimeSpan.FromSeconds(5));
+
+ try
+ {
+ await this._mqttClient.ConnectAsync(options, CancellationToken.None);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Reconnecting failed");
+ }
}
+
});
}
@@ -76,7 +81,7 @@ namespace hass_workstation_service.Communication
}
else
{
- this._logger.LogInformation($"message dropped because mqtt not connected: {message}");
+ this._logger.LogInformation($"Message dropped because mqtt not connected: {message}");
}
}
@@ -99,5 +104,24 @@ namespace hass_workstation_service.Communication
LastConfigAnnounce = DateTime.UtcNow;
}
}
+
+ public async void ReplaceMqttClient(IMqttClientOptions options)
+ {
+ this._logger.LogInformation($"Replacing Mqtt client with new config");
+ await _mqttClient.DisconnectAsync();
+ try
+ {
+ await _mqttClient.ConnectAsync(options);
+ }
+ catch (Exception ex)
+ {
+ Log.Logger.Error("Could not connect to broker: " + ex.Message);
+ }
+ finally
+ {
+ Log.Logger.Information("Connected to new broker");
+ }
+
+ }
}
}
diff --git a/hass-workstation-service/Data/ConfigurationService.cs b/hass-workstation-service/Data/ConfigurationService.cs
index 834f032..dcb4f0b 100644
--- a/hass-workstation-service/Data/ConfigurationService.cs
+++ b/hass-workstation-service/Data/ConfigurationService.cs
@@ -2,9 +2,11 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
+using System.Security;
using System.Text.Json;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
+using hass_workstation_service.Communication.NamedPipe;
using hass_workstation_service.Domain.Sensors;
using Microsoft.Extensions.Configuration;
using MQTTnet;
@@ -14,9 +16,11 @@ using Serilog;
namespace hass_workstation_service.Data
{
- public class ConfigurationService
+ public class ConfigurationService : ServiceContractInterfaces, IConfigurationService
{
public ICollection ConfiguredSensors { get; private set; }
+ public Action MqqtConfigChangedHandler { get; set; }
+
private readonly IsolatedStorageFile _fileStorage;
public ConfigurationService()
@@ -64,8 +68,9 @@ namespace hass_workstation_service.Data
configuredBroker = await JsonSerializer.DeserializeAsync(stream);
}
stream.Close();
- if (configuredBroker != null)
+ if (configuredBroker != null && configuredBroker.Host != null)
{
+
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer(configuredBroker.Host)
// .WithTls()
@@ -110,5 +115,38 @@ namespace hass_workstation_service.Data
sensors.ForEach((sensor) => this.ConfiguredSensors.Add(sensor));
WriteSettings();
}
+
+ public async void WriteMqttBrokerSettings(string host, string username, string password)
+ {
+ IsolatedStorageFileStream stream = this._fileStorage.OpenFile("mqttbroker.json", FileMode.OpenOrCreate);
+ Log.Logger.Information($"writing configured mqttbroker to: {stream.Name}");
+ ConfiguredMqttBroker configuredBroker = new ConfiguredMqttBroker()
+ {
+ Host = host,
+ Username = username,
+ Password = password
+ };
+
+ await JsonSerializer.SerializeAsync(stream, configuredBroker);
+ stream.Close();
+
+ this.MqqtConfigChangedHandler.Invoke(await this.ReadMqttSettings());
+ }
+
+
+
+ ///
+ /// You can use this to check if the application responds.
+ ///
+ ///
+ ///
+ public string Ping(string str)
+ {
+ if (str == "ping")
+ {
+ return "pong";
+ }
+ return "what?";
+ }
}
}
\ No newline at end of file
diff --git a/hass-workstation-service/Data/ConfiguredMqttBroker.cs b/hass-workstation-service/Data/ConfiguredMqttBroker.cs
index 77afbc2..a86e249 100644
--- a/hass-workstation-service/Data/ConfiguredMqttBroker.cs
+++ b/hass-workstation-service/Data/ConfiguredMqttBroker.cs
@@ -7,6 +7,6 @@ namespace hass_workstation_service.Data
{
public string Host { get; set; }
public string Username { get; set; }
- public SecureString Password { get; set; }
+ public string Password { get; set; }
}
}
\ No newline at end of file
diff --git a/hass-workstation-service/Data/IConfigurationService.cs b/hass-workstation-service/Data/IConfigurationService.cs
new file mode 100644
index 0000000..7dac53c
--- /dev/null
+++ b/hass-workstation-service/Data/IConfigurationService.cs
@@ -0,0 +1,24 @@
+using hass_workstation_service.Communication;
+using hass_workstation_service.Domain.Sensors;
+using MQTTnet.Client.Options;
+using System;
+using System.Collections.Generic;
+using System.Security;
+using System.Threading.Tasks;
+
+namespace hass_workstation_service.Data
+{
+ public interface IConfigurationService
+ {
+ ICollection ConfiguredSensors { get; }
+ Action MqqtConfigChangedHandler { get; set; }
+
+ void AddConfiguredSensor(AbstractSensor sensor);
+ void AddConfiguredSensors(List sensors);
+ string Ping(string str);
+ Task ReadMqttSettings();
+ void ReadSensorSettings(MqttPublisher publisher);
+ void WriteMqttBrokerSettings(string host, string username, string password);
+ void WriteSettings();
+ }
+}
\ No newline at end of file
diff --git a/hass-workstation-service/Program.cs b/hass-workstation-service/Program.cs
index 7b3eac7..b5223bc 100644
--- a/hass-workstation-service/Program.cs
+++ b/hass-workstation-service/Program.cs
@@ -16,6 +16,8 @@ using System.IO.IsolatedStorage;
using System.Reflection;
using System.IO;
using Microsoft.Win32;
+using JKang.IpcServiceFramework.Hosting;
+using hass_workstation_service.Communication.NamedPipe;
namespace hass_workstation_service
{
@@ -86,9 +88,15 @@ namespace hass_workstation_service
Sw_version = GetVersion()
};
services.AddSingleton(deviceConfig);
- services.AddSingleton();
+ ConfigurationService configurationService = new ConfigurationService();
+ services.AddSingleton(configurationService);
+ services.AddSingleton(configurationService);
services.AddSingleton();
services.AddHostedService();
+ }).ConfigureIpcHost(builder =>
+ {
+ // configure IPC endpoints
+ builder.AddNamedPipeEndpoint(pipeName: "pipeinternal");
});
static internal string GetVersion()
{
diff --git a/hass-workstation-service/Properties/PublishProfiles/ClickOnceProfile.pubxml b/hass-workstation-service/Properties/PublishProfiles/ClickOnceProfile.pubxml
index e801e01..9a49144 100644
--- a/hass-workstation-service/Properties/PublishProfiles/ClickOnceProfile.pubxml
+++ b/hass-workstation-service/Properties/PublishProfiles/ClickOnceProfile.pubxml
@@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
-->
- 33
+ 341.0.0.*TrueRelease
diff --git a/hass-workstation-service/Worker.cs b/hass-workstation-service/Worker.cs
index 8e09eeb..f054334 100644
--- a/hass-workstation-service/Worker.cs
+++ b/hass-workstation-service/Worker.cs
@@ -15,11 +15,11 @@ namespace hass_workstation_service
public class Worker : BackgroundService
{
private readonly ILogger _logger;
- private readonly ConfigurationService _configurationService;
+ private readonly IConfigurationService _configurationService;
private readonly MqttPublisher _mqttPublisher;
public Worker(ILogger logger,
- ConfigurationService configuredSensorsService,
+ IConfigurationService configuredSensorsService,
MqttPublisher mqttPublisher)
{
_logger = logger;
diff --git a/hass-workstation-service/hass-workstation-service.csproj b/hass-workstation-service/hass-workstation-service.csproj
index ee48ed1..f61dee6 100644
--- a/hass-workstation-service/hass-workstation-service.csproj
+++ b/hass-workstation-service/hass-workstation-service.csproj
@@ -4,7 +4,7 @@
netcoreapp3.1dotnet-hass_workstation_service-C65C2EBE-1977-4C24-AC6B-6921877E1390hass_workstation_service
- WinExe
+ ExeSleevezipperhttps://github.com/sleevezipper/hass-workstation-servicefalse
@@ -30,7 +30,8 @@
-
+
+