diff --git a/src/HASS.Agent.Installer/InstallerScript-Service-x86.iss b/src/HASS.Agent.Installer/InstallerScript-Service-x86.iss index 9b632fc5..d34a5ec6 100644 --- a/src/HASS.Agent.Installer/InstallerScript-Service-x86.iss +++ b/src/HASS.Agent.Installer/InstallerScript-Service-x86.iss @@ -9,7 +9,7 @@ ; Standard installation constants #define MyAppName "HASS.Agent Satellite Service" -#define MyAppVersion "2.2.0" +#define MyAppVersion "2.2.1" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.Satellite.Service.exe" diff --git a/src/HASS.Agent.Installer/InstallerScript-Service.iss b/src/HASS.Agent.Installer/InstallerScript-Service.iss index f2825143..f9c44adb 100644 --- a/src/HASS.Agent.Installer/InstallerScript-Service.iss +++ b/src/HASS.Agent.Installer/InstallerScript-Service.iss @@ -9,7 +9,7 @@ ; Standard installation constants #define MyAppName "HASS.Agent Satellite Service" -#define MyAppVersion "2.2.0" +#define MyAppVersion "2.2.1" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.Satellite.Service.exe" diff --git a/src/HASS.Agent.Installer/InstallerScript-x86.iss b/src/HASS.Agent.Installer/InstallerScript-x86.iss index 62f11d9e..75927faa 100644 --- a/src/HASS.Agent.Installer/InstallerScript-x86.iss +++ b/src/HASS.Agent.Installer/InstallerScript-x86.iss @@ -9,7 +9,7 @@ ; Standard installation constants #define MyAppName "HASS.Agent" -#define MyAppVersion "2.2.0" +#define MyAppVersion "2.2.1" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.exe" diff --git a/src/HASS.Agent.Installer/InstallerScript.iss b/src/HASS.Agent.Installer/InstallerScript.iss index 4519dba4..335f0338 100644 --- a/src/HASS.Agent.Installer/InstallerScript.iss +++ b/src/HASS.Agent.Installer/InstallerScript.iss @@ -9,7 +9,7 @@ ; Standard installation constants #define MyAppName "HASS.Agent" -#define MyAppVersion "2.2.0" +#define MyAppVersion "2.2.1" #define MyAppPublisher "HASS.Agent Team" #define MyAppURL "https://hass-agent.io" #define MyAppExeName "HASS.Agent.exe" diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj index 1e3dfd54..4464d5b6 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/HASS.Agent.Satellite.Service.csproj @@ -8,7 +8,7 @@ dotnet-HASSAgentSatelliteService-6E4FA50A-3AC9-4E66-8671-9FAB92372154 anycpu x64;x86;AnyCPU - 2.2.0 + 2.2.1 HASS.Agent Team HASS.Agent Satellite Service HASS.Agent.Satellite.Service @@ -17,9 +17,9 @@ https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent hass.png - 2.2.0 + 2.2.1 hass.ico - 2.2.0 + 2.2.1 10.0.17763.0 false diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs index 0673e775..de838048 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredCommands.cs @@ -1,4 +1,4 @@ -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Models.Config; using HASS.Agent.Shared.HomeAssistant.Commands; using HASS.Agent.Shared.HomeAssistant.Commands.CustomCommands; @@ -56,7 +56,10 @@ internal static async Task LoadAsync() // convert to abstract commands await Task.Run(delegate { - foreach (var abstractCommand in configuredCommands.Select(ConvertConfiguredToAbstract)) Variables.Commands.Add(abstractCommand!); + foreach (var abstractCommand in configuredCommands.Select(ConvertConfiguredToAbstract)) + { + if (abstractCommand != null) Variables.Commands.Add(abstractCommand); + } }); // all good diff --git a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs index ea81e57a..556c8086 100644 --- a/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs +++ b/src/HASS.Agent/HASS.Agent.Satellite.Service/Settings/StoredSensors.cs @@ -1,4 +1,4 @@ -using HASS.Agent.Shared.Enums; +using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Models.Config; using HASS.Agent.Satellite.Service.Extensions; using HASS.Agent.Shared.HomeAssistant.Sensors; @@ -61,8 +61,16 @@ await Task.Run(delegate { foreach (var sensor in configuredSensors) { - if (sensor.IsSingleValue()) Variables.SingleValueSensors.Add(ConvertConfiguredToAbstractSingleValue(sensor)!); - else Variables.MultiValueSensors.Add(ConvertConfiguredToAbstractMultiValue(sensor)!); + if (sensor.IsSingleValue()) + { + var abstractSensor = ConvertConfiguredToAbstractSingleValue(sensor); + if (abstractSensor != null) Variables.SingleValueSensors.Add(abstractSensor); + } + else + { + var abstractSensor = ConvertConfiguredToAbstractMultiValue(sensor); + if (abstractSensor != null) Variables.MultiValueSensors.Add(abstractSensor); + } } }); diff --git a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj index 0fae9b41..31336c84 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj +++ b/src/HASS.Agent/HASS.Agent.Shared/HASS.Agent.Shared.csproj @@ -10,9 +10,9 @@ Shared functions and models for the HASS.Agent platform. https://github.com/hass-agent/HASS.Agent https://github.com/hass-agent/HASS.Agent - 2.2.0 - 2.2.0 - 2.2.0 + 2.2.1 + 2.2.1 + 2.2.1 logo_128.png True hassagent.ico diff --git a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs index 7796990c..5d7de85e 100644 --- a/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs +++ b/src/HASS.Agent/HASS.Agent.Shared/HomeAssistant/Sensors/GeneralSensors/SingleValue/LastActiveSensor.cs @@ -86,12 +86,12 @@ private static DateTime GetLastInputTime() lastInputInfo.cbSize = Marshal.SizeOf(lastInputInfo); lastInputInfo.dwTime = 0; - var envTicks = Environment.TickCount; + var envTicks = Environment.TickCount64; if (!GetLastInputInfo(ref lastInputInfo)) return DateTime.Now; - var lastInputTick = Convert.ToDouble(lastInputInfo.dwTime); + var lastInputTick = (long)lastInputInfo.dwTime; var idleTime = envTicks - lastInputTick; return idleTime > 0 ? DateTime.Now - TimeSpan.FromMilliseconds(idleTime) : DateTime.Now; diff --git a/src/HASS.Agent/HASS.Agent/Controls/Onboarding/Onboarding-6-HotKey.cs b/src/HASS.Agent/HASS.Agent/Controls/Onboarding/Onboarding-6-HotKey.cs index d0313c97..a8c556f7 100644 --- a/src/HASS.Agent/HASS.Agent/Controls/Onboarding/Onboarding-6-HotKey.cs +++ b/src/HASS.Agent/HASS.Agent/Controls/Onboarding/Onboarding-6-HotKey.cs @@ -1,12 +1,13 @@ using HASS.Agent.Functions; -using WK.Libraries.HotkeyListenerNS; +using System.Windows.Forms; namespace HASS.Agent.Controls.Onboarding { public partial class OnboardingHotKey : UserControl { - private readonly HotkeySelector _hotkeySelector = new(); - + private Keys _key = Keys.None; + private Keys _modifiers = Keys.None; + public OnboardingHotKey() { InitializeComponent(); @@ -14,19 +15,18 @@ public OnboardingHotKey() private void OnboardingHotKey_Load(object sender, EventArgs e) { - // config quick actions hotkey selector - _hotkeySelector.Enable(TbQuickActionsHotkey); - + TbQuickActionsHotkey.ReadOnly = true; + TbQuickActionsHotkey.KeyDown += TbQuickActionsHotkey_KeyDown; if (string.IsNullOrEmpty(Variables.AppSettings.QuickActionsHotKey)) { // if nothing set, load default LoadDefault(); } - else if (Variables.AppSettings.QuickActionsHotKey == _hotkeySelector.EmptyHotkeyText) + else if (Variables.AppSettings.QuickActionsHotKey == string.Empty) { // if set to empty, show empty - TbQuickActionsHotkey.Text = _hotkeySelector.EmptyHotkeyText; + TbQuickActionsHotkey.Text = string.Empty; } else { @@ -81,13 +81,61 @@ private void LoadSetValue() internal bool Store() { Variables.AppSettings.QuickActionsHotKey = TbQuickActionsHotkey.Text; - _hotkeySelector.Dispose(); + TbQuickActionsHotkey.KeyDown -= TbQuickActionsHotkey_KeyDown; return true; } private void BtnClear_Click(object sender, EventArgs e) { - TbQuickActionsHotkey.Text = _hotkeySelector.EmptyHotkeyText; + TbQuickActionsHotkey.Text = string.Empty; + } + + private void TbQuickActionsHotkey_KeyDown(object sender, KeyEventArgs e) + { + e.SuppressKeyPress = true; + + var key = e.KeyCode; + + if (key is Keys.LControlKey or Keys.RControlKey + or Keys.LShiftKey or Keys.RShiftKey + or Keys.LWin or Keys.RWin + or Keys.Alt) + { + key = Keys.None; + } + + if (key == Keys.Escape) + { + _key = Keys.None; + _modifiers = Keys.None; + TbQuickActionsHotkey.Text = string.Empty; + + return; + } + + _key = key; + TbQuickActionsHotkey.Text = FormatHotkey(_key, e.Modifiers); + } + + private string FormatHotkey(Keys key, Keys modifiers) + { + var parts = new List(); + if ((modifiers & Keys.Shift) != 0) + { + parts.Add(nameof(Keys.Shift)); + } + + if ((modifiers & Keys.Control) != 0) + { + parts.Add(nameof(Keys.Control)); + } + + if ((modifiers & Keys.Alt) != 0) + { + parts.Add(nameof(Keys.Alt)); + } + + return parts.Count > 0 ? string.Join(", ", parts) + " + " + key : key.ToString(); } } } diff --git a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandConfig/WebViewCommandConfig.Designer.cs b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandConfig/WebViewCommandConfig.Designer.cs index 718b80ee..cba67b64 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandConfig/WebViewCommandConfig.Designer.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandConfig/WebViewCommandConfig.Designer.cs @@ -243,11 +243,8 @@ private void InitializeComponent() this.NumLocationX.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.NumLocationX.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); this.NumLocationX.Location = new System.Drawing.Point(34, 160); - this.NumLocationX.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); + this.NumLocationX.Maximum = new decimal(Int32.MaxValue); + this.NumLocationX.Minimum = new decimal(Int32.MinValue); this.NumLocationX.MaxLength = 10; this.NumLocationX.MetroColor = System.Drawing.SystemColors.WindowFrame; this.NumLocationX.Name = "NumLocationX"; @@ -269,11 +266,8 @@ private void InitializeComponent() this.NumLocationY.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.NumLocationY.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(241)))), ((int)(((byte)(241)))), ((int)(((byte)(241))))); this.NumLocationY.Location = new System.Drawing.Point(149, 160); - this.NumLocationY.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); + this.NumLocationY.Maximum = new decimal(Int32.MaxValue); + this.NumLocationY.Minimum = new decimal(Int32.MinValue); this.NumLocationY.MaxLength = 10; this.NumLocationY.MetroColor = System.Drawing.SystemColors.WindowFrame; this.NumLocationY.Name = "NumLocationY"; diff --git a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs index a14d80bc..de248bda 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Commands/CommandsMod.cs @@ -28,7 +28,9 @@ public partial class CommandsMod : MetroForm private bool _interfaceLockedWrongType; private bool _loading = true; - private readonly Dictionary _commandEntityTypes = new(); + private bool _escapeLock = true; + + private readonly Dictionary _commandEntityTypes = new(); private readonly Dictionary _radioDevices = new(); public CommandsMod(ConfiguredCommand command, bool serviceMode = false, string serviceDeviceName = "") @@ -977,11 +979,21 @@ private void LblIntegrityInfo_Click(object sender, EventArgs e) private void CommandsMod_KeyUp(object sender, KeyEventArgs e) { - if (e.KeyCode != Keys.Escape) - return; + if (e.KeyCode != Keys.Escape) + { + _escapeLock = true; + return; + } - Close(); - } + if (_escapeLock) + { + _escapeLock = false; + } + else + { + Close(); + } + } private void CommandsMod_Layout(object sender, LayoutEventArgs e) { diff --git a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs index 8c081535..cf1f6baa 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Configuration.cs @@ -8,7 +8,6 @@ using HASS.Agent.Settings; using HASS.Agent.Shared; using HASS.Agent.Shared.Functions; -using WK.Libraries.HotkeyListenerNS; using Task = System.Threading.Tasks.Task; using ConfigSatelliteService = HASS.Agent.Controls.Configuration.ConfigService; using HASS.Agent.MQTT; @@ -17,8 +16,7 @@ namespace HASS.Agent.Forms { public partial class Configuration : MetroForm { - private readonly HotkeySelector _hotkeySelector = new(); - private readonly Hotkey _previousHotkey = Variables.QuickActionsHotKey; + private readonly string _previousHotkey = Variables.QuickActionsHotKey; private readonly string _previousDeviceName = Variables.AppSettings.DeviceName; private readonly int _previousLocalApiPort = Variables.AppSettings.LocalApiPort; @@ -41,6 +39,9 @@ public partial class Configuration : MetroForm private bool _initializing = true; + private Keys _key = Keys.None; + private Keys _modifiers = Keys.None; + public Configuration() { InitializeComponent(); @@ -52,7 +53,7 @@ private void Configuration_Load(object sender, EventArgs e) KeyPreview = true; // suspend global hotkeys - Variables.HotKeyListener.Suspend(); + Variables.HotKeyListener.IsEnabled = false; // load controls TabGeneral.Controls.Add(_general); @@ -79,18 +80,21 @@ private void Configuration_Load(object sender, EventArgs e) LoadSettings(); // config quick actions hotkey selector - if (Variables.QuickActionsHotKey != null) _hotkeySelector.Enable(_hotKey.TbQuickActionsHotkey, Variables.QuickActionsHotKey); - else _hotkeySelector.Enable(_hotKey.TbQuickActionsHotkey); + _hotKey.TbQuickActionsHotkey.ReadOnly = true; + _hotKey.TbQuickActionsHotkey.KeyDown += TbQuickActionsHotkey_KeyDown; + if (Variables.QuickActionsHotKey != null) + { + _hotKey.TbQuickActionsHotkey.Text = Variables.QuickActionsHotKey; + } } private void Configuration_FormClosing(object sender, FormClosingEventArgs e) { // resume global hotkeys - Variables.HotKeyListener.Resume(); + Variables.HotKeyListener.IsEnabled = true; - // remove hotkey selector - _hotkeySelector?.Disable(_hotKey.TbQuickActionsHotkey); - _hotkeySelector?.Dispose(); + // clean hotkey selector + _hotKey.TbQuickActionsHotkey.KeyDown -= TbQuickActionsHotkey_KeyDown; // dispose controls _general.Dispose(); @@ -110,14 +114,62 @@ private void Configuration_FormClosing(object sender, FormClosingEventArgs e) _nfc.Dispose(); } + private void TbQuickActionsHotkey_KeyDown(object sender, KeyEventArgs e) + { + e.SuppressKeyPress = true; + + var key = e.KeyCode; + + if (key is Keys.LControlKey or Keys.RControlKey + or Keys.LShiftKey or Keys.RShiftKey + or Keys.LWin or Keys.RWin + or Keys.Alt) + { + key = Keys.None; + } + + if (key == Keys.Escape) + { + _key = Keys.None; + _modifiers = Keys.None; + _hotKey.TbQuickActionsHotkey.Text = string.Empty; + + return; + } + + _key = key; + _hotKey.TbQuickActionsHotkey.Text = FormatHotkey(_key, e.Modifiers); + } + + private string FormatHotkey(Keys key, Keys modifiers) + { + var parts = new List(); + if ((modifiers & Keys.Shift) != 0) + { + parts.Add(nameof(Keys.Shift)); + } + + if ((modifiers & Keys.Control) != 0) + { + parts.Add(nameof(Keys.Control)); + } + + if ((modifiers & Keys.Alt) != 0) + { + parts.Add(nameof(Keys.Alt)); + } + + return parts.Count > 0 ? string.Join(", ", parts) + " + " + key : key.ToString(); + } + private void BindEvents() { // hass _homeAssistantApi.CbHassAutoClientCertificate.CheckedChanged += CbHassAutoClientCertificate_CheckedChanged; - + // mqtt _mqtt.CbMqttTls.CheckedChanged += CbMqttTls_CheckedChanged; - + // hotkey _hotKey.BtnClearHotKey.Click += BtnClearHotKey_Click; } @@ -148,7 +200,8 @@ private async void ProcessChanges() BtnStore.Text = Languages.Configuration_BtnStore_Busy; // optionally sanitize device name - if (_general.CbEnableDeviceNameSanitation.Checked) _general.TbDeviceName.Text = SharedHelperFunctions.GetSafeValue(_general.TbDeviceName.Text); + if (_general.CbEnableDeviceNameSanitation.Checked) + _general.TbDeviceName.Text = SharedHelperFunctions.GetSafeValue(_general.TbDeviceName.Text); // store settings await StoreSettingsAsync(); @@ -198,28 +251,32 @@ private async void ProcessChanges() // disconnect mqtt so we don't get announced again await Task.Run(Variables.MqttManager.Disconnect); - + forceRestart = true; } // reserve the new local api's port if it's changed if (Variables.AppSettings.LocalApiPort != _previousLocalApiPort) { - MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox2, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox2, Variables.MessageBoxTitle, + MessageBoxButtons.OK, MessageBoxIcon.Information); // try to reserve elevated if (!ApiManager.ExecuteElevatedPortReservation()) { // failed, copy the command onto the clipboard - Clipboard.SetText($"netsh http add urlacl url=http://+:{Variables.AppSettings.LocalApiPort}/ user=\"{SharedHelperFunctions.EveryoneLocalizedAccountName()}\""); + Clipboard.SetText( + $"netsh http add urlacl url=http://+:{Variables.AppSettings.LocalApiPort}/ user=\"{SharedHelperFunctions.EveryoneLocalizedAccountName()}\""); // notify the user - MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox3, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox3, + Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // notify the user - MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox4, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox4, + Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); // we need to restart, so go ahead, otherwise it's starting to look like popup-spam .. forceRestart = true; @@ -230,17 +287,22 @@ private async void ProcessChanges() { // prepare the restart without asking var restartPrepared = HelperFunctions.Restart(); - if (!restartPrepared) MessageBoxAdv.Show(this, Languages.Configuration_MessageBox_RestartManually, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + if (!restartPrepared) + MessageBoxAdv.Show(this, Languages.Configuration_MessageBox_RestartManually, + Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // ask the user if they want to restart - var question = MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox5, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + var question = MessageBoxAdv.Show(this, Languages.Configuration_ProcessChanges_MessageBox5, + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (question == DialogResult.Yes) { // prepare the restart var restartPrepared = HelperFunctions.Restart(); - if (!restartPrepared) MessageBoxAdv.Show(this, Languages.Configuration_MessageBox_RestartManually, Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + if (!restartPrepared) + MessageBoxAdv.Show(this, Languages.Configuration_MessageBox_RestartManually, + Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); } } @@ -260,7 +322,8 @@ private bool CheckValues() { if (!SharedHelperFunctions.CheckHomeAssistantApiToken(hassApi)) { - var q = MessageBoxAdv.Show(this, Languages.Configuration_CheckValues_MessageBox1, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); + var q = MessageBoxAdv.Show(this, Languages.Configuration_CheckValues_MessageBox1, + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); if (q != DialogResult.Yes) return false; } } @@ -271,7 +334,8 @@ private bool CheckValues() { if (!SharedHelperFunctions.CheckHomeAssistantUri(hassUri)) { - var q = MessageBoxAdv.Show(this, Languages.Configuration_CheckValues_MessageBox2, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); + var q = MessageBoxAdv.Show(this, Languages.Configuration_CheckValues_MessageBox2, + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); if (q != DialogResult.Yes) return false; } } @@ -282,7 +346,8 @@ private bool CheckValues() { if (!SharedHelperFunctions.CheckMqttBrokerUri(mqttUri)) { - var q = MessageBoxAdv.Show(this, Languages.Configuration_CheckValues_MessageBox3, Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); + var q = MessageBoxAdv.Show(this, Languages.Configuration_CheckValues_MessageBox3, + Variables.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); if (q != DialogResult.Yes) return false; } } @@ -298,28 +363,42 @@ private void LoadSettings() { // general _general.TbDeviceName.Text = Variables.AppSettings.DeviceName; - _general.CbEnableDeviceNameSanitation.CheckState = Variables.AppSettings.SanitizeName ? CheckState.Checked : CheckState.Unchecked; + _general.CbEnableDeviceNameSanitation.CheckState = + Variables.AppSettings.SanitizeName ? CheckState.Checked : CheckState.Unchecked; _general.NumDisconnectGrace.Value = Variables.AppSettings.DisconnectedGracePeriodSeconds; - _general.CbEnableStateNotifications.CheckState = Variables.AppSettings.EnableStateNotifications ? CheckState.Checked : CheckState.Unchecked; + _general.CbEnableStateNotifications.CheckState = Variables.AppSettings.EnableStateNotifications + ? CheckState.Checked + : CheckState.Unchecked; // startup settings Task.Run(_startup.DetermineStartOnLoginStatus); // local api _localApi.NumLocalApiPort.Value = Variables.AppSettings.LocalApiPort; - _localApi.CbLocalApiActive.CheckState = Variables.AppSettings.LocalApiEnabled ? CheckState.Checked : CheckState.Unchecked; + _localApi.CbLocalApiActive.CheckState = + Variables.AppSettings.LocalApiEnabled ? CheckState.Checked : CheckState.Unchecked; // notifications - _notifications.CbAcceptNotifications.CheckState = Variables.AppSettings.NotificationsEnabled ? CheckState.Checked : CheckState.Unchecked; - _notifications.CbNotificationsIgnoreImageCertErrors.CheckState = Variables.AppSettings.NotificationsIgnoreImageCertificateErrors ? CheckState.Checked : CheckState.Unchecked; - _notifications.CbNotificationsOpenActionUri.CheckState = Variables.AppSettings.NotificationsOpenActionUri ? CheckState.Checked : CheckState.Unchecked; + _notifications.CbAcceptNotifications.CheckState = Variables.AppSettings.NotificationsEnabled + ? CheckState.Checked + : CheckState.Unchecked; + _notifications.CbNotificationsIgnoreImageCertErrors.CheckState = + Variables.AppSettings.NotificationsIgnoreImageCertificateErrors + ? CheckState.Checked + : CheckState.Unchecked; + _notifications.CbNotificationsOpenActionUri.CheckState = Variables.AppSettings.NotificationsOpenActionUri + ? CheckState.Checked + : CheckState.Unchecked; // hass settings _homeAssistantApi.TbHassIp.Text = Variables.AppSettings.HassUri; _homeAssistantApi.TbHassApiToken.Text = Variables.AppSettings.HassToken; _homeAssistantApi.TbHassClientCertificate.Text = Variables.AppSettings.HassClientCertificate; - _homeAssistantApi.CbHassAutoClientCertificate.CheckState = Variables.AppSettings.HassAutoClientCertificate ? CheckState.Checked : CheckState.Unchecked; - _homeAssistantApi.CbHassAllowUntrustedCertificates.CheckState = Variables.AppSettings.HassAllowUntrustedCertificates ? CheckState.Checked : CheckState.Unchecked; + _homeAssistantApi.CbHassAutoClientCertificate.CheckState = Variables.AppSettings.HassAutoClientCertificate + ? CheckState.Checked + : CheckState.Unchecked; + _homeAssistantApi.CbHassAllowUntrustedCertificates.CheckState = + Variables.AppSettings.HassAllowUntrustedCertificates ? CheckState.Checked : CheckState.Unchecked; if (Variables.AppSettings.HassAutoClientCertificate) { _homeAssistantApi.TbHassClientCertificate.Text = string.Empty; @@ -327,10 +406,13 @@ private void LoadSettings() } // hotkey - _hotKey.CbEnableQuickActionsHotkey.CheckState = Variables.AppSettings.QuickActionsHotKeyEnabled ? CheckState.Checked : CheckState.Unchecked; + _hotKey.CbEnableQuickActionsHotkey.CheckState = Variables.AppSettings.QuickActionsHotKeyEnabled + ? CheckState.Checked + : CheckState.Unchecked; // mqtt - _mqtt.CbEnableMqtt.CheckState = Variables.AppSettings.MqttEnabled ? CheckState.Checked : CheckState.Unchecked; + _mqtt.CbEnableMqtt.CheckState = + Variables.AppSettings.MqttEnabled ? CheckState.Checked : CheckState.Unchecked; _mqtt.TbMqttAddress.Text = Variables.AppSettings.MqttAddress; _mqtt.NumMqttPort.Value = Variables.AppSettings.MqttPort; _mqtt.CbMqttTls.CheckState = Variables.AppSettings.MqttUseTls ? CheckState.Checked : CheckState.Unchecked; @@ -340,15 +422,25 @@ private void LoadSettings() _mqtt.TbMqttClientId.Text = Variables.AppSettings.MqttClientId; _mqtt.TbMqttRootCertificate.Text = Variables.AppSettings.MqttRootCertificate; _mqtt.TbMqttClientCertificate.Text = Variables.AppSettings.MqttClientCertificate; - _mqtt.CbAllowUntrustedCertificates.CheckState = Variables.AppSettings.MqttAllowUntrustedCertificates ? CheckState.Checked : CheckState.Unchecked; - _mqtt.CbUseRetainFlag.CheckState = Variables.AppSettings.MqttUseRetainFlag ? CheckState.Checked : CheckState.Unchecked; - _mqtt.CbUseWebSocket.CheckState = Variables.AppSettings.MqttUseWebSocket ? CheckState.Checked : CheckState.Unchecked; - _mqtt.CbIgnoreGracePeriod.CheckState = Variables.AppSettings.MqttIgnoreGracePeriod ? CheckState.Checked : CheckState.Unchecked; + _mqtt.CbAllowUntrustedCertificates.CheckState = Variables.AppSettings.MqttAllowUntrustedCertificates + ? CheckState.Checked + : CheckState.Unchecked; + _mqtt.CbUseRetainFlag.CheckState = + Variables.AppSettings.MqttUseRetainFlag ? CheckState.Checked : CheckState.Unchecked; + _mqtt.CbUseWebSocket.CheckState = + Variables.AppSettings.MqttUseWebSocket ? CheckState.Checked : CheckState.Unchecked; + _mqtt.CbIgnoreGracePeriod.CheckState = Variables.AppSettings.MqttIgnoreGracePeriod + ? CheckState.Checked + : CheckState.Unchecked; // updates - _updates.CbUpdates.CheckState = Variables.AppSettings.CheckForUpdates ? CheckState.Checked : CheckState.Unchecked; - _updates.CbBetaUpdates.CheckState = Variables.AppSettings.ShowBetaUpdates ? CheckState.Checked : CheckState.Unchecked; - _updates.CbExecuteUpdater.CheckState = Variables.AppSettings.EnableExecuteUpdateInstaller ? CheckState.Checked : CheckState.Unchecked; + _updates.CbUpdates.CheckState = + Variables.AppSettings.CheckForUpdates ? CheckState.Checked : CheckState.Unchecked; + _updates.CbBetaUpdates.CheckState = + Variables.AppSettings.ShowBetaUpdates ? CheckState.Checked : CheckState.Unchecked; + _updates.CbExecuteUpdater.CheckState = Variables.AppSettings.EnableExecuteUpdateInstaller + ? CheckState.Checked + : CheckState.Unchecked; // cache _localStorage.TbImageCacheLocation.Text = Variables.ImageCachePath; @@ -359,7 +451,9 @@ private void LoadSettings() _localStorage.NumWebViewRetention.Value = Variables.AppSettings.WebViewCacheRetentionDays; // logging - _logging.CbExtendedLogging.CheckState = SettingsManager.GetExtendedLoggingSetting() ? CheckState.Checked : CheckState.Unchecked; + _logging.CbExtendedLogging.CheckState = SettingsManager.GetExtendedLoggingSetting() + ? CheckState.Checked + : CheckState.Unchecked; // external tools _externalTools.TbExternalBrowserName.Text = Variables.AppSettings.BrowserName; @@ -369,20 +463,31 @@ private void LoadSettings() _externalTools.TbExternalExecutorBinary.Text = Variables.AppSettings.CustomExecutorBinary; // mediaplayer - _mediaPlayer.CbEnableMediaPlayer.CheckState = Variables.AppSettings.MediaPlayerEnabled ? CheckState.Checked : CheckState.Unchecked; + _mediaPlayer.CbEnableMediaPlayer.CheckState = Variables.AppSettings.MediaPlayerEnabled + ? CheckState.Checked + : CheckState.Unchecked; // tray icon - _trayIcon.CbUseModernIcon.CheckState = Variables.AppSettings.TrayIconUseModern ? CheckState.Checked : CheckState.Unchecked; - _trayIcon.CbDefaultMenu.CheckState = Variables.AppSettings.TrayIconShowDefaultMenu ? CheckState.Checked : CheckState.Unchecked; - _trayIcon.CbShowWebView.CheckState = Variables.AppSettings.TrayIconShowWebView ? CheckState.Checked : CheckState.Unchecked; + _trayIcon.CbUseModernIcon.CheckState = + Variables.AppSettings.TrayIconUseModern ? CheckState.Checked : CheckState.Unchecked; + _trayIcon.CbDefaultMenu.CheckState = Variables.AppSettings.TrayIconShowDefaultMenu + ? CheckState.Checked + : CheckState.Unchecked; + _trayIcon.CbShowWebView.CheckState = + Variables.AppSettings.TrayIconShowWebView ? CheckState.Checked : CheckState.Unchecked; _trayIcon.NumWebViewWidth.Value = Variables.AppSettings.TrayIconWebViewWidth; _trayIcon.NumWebViewHeight.Value = Variables.AppSettings.TrayIconWebViewHeight; - _trayIcon.SelectedScreen = Variables.AppSettings.TrayIconWebViewScreen; + _trayIcon.SelectedScreen = Variables.AppSettings.TrayIconWebViewScreen; _trayIcon.TbWebViewUrl.Text = Variables.AppSettings.TrayIconWebViewUrl; - _trayIcon.CbWebViewKeepLoaded.CheckState = Variables.AppSettings.TrayIconWebViewBackgroundLoading ? CheckState.Checked : CheckState.Unchecked; - _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState = Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick ? CheckState.Checked : CheckState.Unchecked; + _trayIcon.CbWebViewKeepLoaded.CheckState = Variables.AppSettings.TrayIconWebViewBackgroundLoading + ? CheckState.Checked + : CheckState.Unchecked; + _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState = Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick + ? CheckState.Checked + : CheckState.Unchecked; - _nfc.CbEnableNfc.CheckState = Variables.AppSettings.NfcScanningEnabled ? CheckState.Checked : CheckState.Unchecked; + _nfc.CbEnableNfc.CheckState = + Variables.AppSettings.NfcScanningEnabled ? CheckState.Checked : CheckState.Unchecked; // done _initializing = false; @@ -394,10 +499,13 @@ private void LoadSettings() private async Task StoreSettingsAsync() { // general - var deviceName = string.IsNullOrEmpty(_general.TbDeviceName.Text) ? SharedHelperFunctions.GetSafeDeviceName() : _general.TbDeviceName.Text; + var deviceName = string.IsNullOrEmpty(_general.TbDeviceName.Text) + ? SharedHelperFunctions.GetSafeDeviceName() + : _general.TbDeviceName.Text; Variables.AppSettings.DeviceName = deviceName; Variables.AppSettings.SanitizeName = _general.CbEnableDeviceNameSanitation.CheckState == CheckState.Checked; - Variables.AppSettings.EnableStateNotifications = _general.CbEnableStateNotifications.CheckState == CheckState.Checked; + Variables.AppSettings.EnableStateNotifications = + _general.CbEnableStateNotifications.CheckState == CheckState.Checked; var uiLanguage = Variables.SupportedUILanguages.Find(x => x.DisplayName == _general.CbLanguage.Text); Variables.AppSettings.InterfaceLanguage = uiLanguage?.Name ?? "en"; @@ -409,32 +517,38 @@ private async Task StoreSettingsAsync() Variables.AppSettings.LocalApiEnabled = _localApi.CbLocalApiActive.CheckState == CheckState.Checked; // notifications - Variables.AppSettings.NotificationsEnabled = _notifications.CbAcceptNotifications.CheckState == CheckState.Checked; - Variables.AppSettings.NotificationsIgnoreImageCertificateErrors = _notifications.CbNotificationsIgnoreImageCertErrors.CheckState == CheckState.Checked; - Variables.AppSettings.NotificationsOpenActionUri = _notifications.CbNotificationsOpenActionUri.CheckState == CheckState.Checked; + Variables.AppSettings.NotificationsEnabled = + _notifications.CbAcceptNotifications.CheckState == CheckState.Checked; + Variables.AppSettings.NotificationsIgnoreImageCertificateErrors = + _notifications.CbNotificationsIgnoreImageCertErrors.CheckState == CheckState.Checked; + Variables.AppSettings.NotificationsOpenActionUri = + _notifications.CbNotificationsOpenActionUri.CheckState == CheckState.Checked; // hass settings Variables.AppSettings.HassUri = _homeAssistantApi.TbHassIp.Text; Variables.AppSettings.HassToken = _homeAssistantApi.TbHassApiToken.Text; Variables.AppSettings.HassClientCertificate = _homeAssistantApi.TbHassClientCertificate.Text; - Variables.AppSettings.HassAutoClientCertificate = _homeAssistantApi.CbHassAutoClientCertificate.CheckState == CheckState.Checked; - Variables.AppSettings.HassAllowUntrustedCertificates = _homeAssistantApi.CbHassAllowUntrustedCertificates.CheckState == CheckState.Checked; + Variables.AppSettings.HassAutoClientCertificate = + _homeAssistantApi.CbHassAutoClientCertificate.CheckState == CheckState.Checked; + Variables.AppSettings.HassAllowUntrustedCertificates = + _homeAssistantApi.CbHassAllowUntrustedCertificates.CheckState == CheckState.Checked; // hotkey config - Variables.AppSettings.QuickActionsHotKeyEnabled = _hotKey.CbEnableQuickActionsHotkey.CheckState == CheckState.Checked; + Variables.AppSettings.QuickActionsHotKeyEnabled = + _hotKey.CbEnableQuickActionsHotkey.CheckState == CheckState.Checked; if (Variables.AppSettings.QuickActionsHotKeyEnabled) { // hotkey enabled, store and activate - Variables.QuickActionsHotKey = new Hotkey(_hotKey.TbQuickActionsHotkey.Text); + Variables.QuickActionsHotKey = _hotKey.TbQuickActionsHotkey.Text; Variables.AppSettings.QuickActionsHotKey = Variables.QuickActionsHotKey.ToString(); - Variables.HotKeyManager.QuickActionsHotKeyChanged(_previousHotkey); + Variables.InternalHotKeyManager.QuickActionsHotKeyChanged(_previousHotkey); } else { // hotkey disabled, remove and deactivate Variables.QuickActionsHotKey = null; Variables.AppSettings.QuickActionsHotKey = string.Empty; - Variables.HotKeyManager.QuickActionsHotKeyChanged(_previousHotkey, false); + Variables.InternalHotKeyManager.QuickActionsHotKeyChanged(_previousHotkey, false); } // mqtt @@ -448,7 +562,8 @@ private async Task StoreSettingsAsync() Variables.AppSettings.MqttClientId = _mqtt.TbMqttClientId.Text; Variables.AppSettings.MqttRootCertificate = _mqtt.TbMqttRootCertificate.Text; Variables.AppSettings.MqttClientCertificate = _mqtt.TbMqttClientCertificate.Text; - Variables.AppSettings.MqttAllowUntrustedCertificates = _mqtt.CbAllowUntrustedCertificates.CheckState == CheckState.Checked; + Variables.AppSettings.MqttAllowUntrustedCertificates = + _mqtt.CbAllowUntrustedCertificates.CheckState == CheckState.Checked; Variables.AppSettings.MqttUseRetainFlag = _mqtt.CbUseRetainFlag.CheckState == CheckState.Checked; Variables.AppSettings.MqttUseWebSocket = _mqtt.CbUseWebSocket.CheckState == CheckState.Checked; Variables.AppSettings.MqttIgnoreGracePeriod = _mqtt.CbIgnoreGracePeriod.CheckState == CheckState.Checked; @@ -459,7 +574,8 @@ private async Task StoreSettingsAsync() // updates Variables.AppSettings.CheckForUpdates = _updates.CbUpdates.CheckState == CheckState.Checked; Variables.AppSettings.ShowBetaUpdates = _updates.CbBetaUpdates.CheckState == CheckState.Checked; - Variables.AppSettings.EnableExecuteUpdateInstaller = _updates.CbExecuteUpdater.CheckState == CheckState.Checked; + Variables.AppSettings.EnableExecuteUpdateInstaller = + _updates.CbExecuteUpdater.CheckState == CheckState.Checked; // cache Variables.AppSettings.ImageCacheRetentionDays = (int)_localStorage.NumImageRetention.Value; @@ -480,7 +596,8 @@ private async Task StoreSettingsAsync() AgentSharedBase.SetCustomExecutorBinary(Variables.AppSettings.CustomExecutorBinary); // mediaplayer - Variables.AppSettings.MediaPlayerEnabled = _mediaPlayer.CbEnableMediaPlayer.CheckState == CheckState.Checked; + Variables.AppSettings.MediaPlayerEnabled = + _mediaPlayer.CbEnableMediaPlayer.CheckState == CheckState.Checked; // tray icon Variables.AppSettings.TrayIconUseModern = _trayIcon.CbUseModernIcon.CheckState == CheckState.Checked; @@ -490,12 +607,16 @@ private async Task StoreSettingsAsync() Variables.AppSettings.TrayIconWebViewHeight = (int)_trayIcon.NumWebViewHeight.Value; Variables.AppSettings.TrayIconWebViewScreen = _trayIcon.NumWebViewScreen.SelectedIndex; Variables.AppSettings.TrayIconWebViewUrl = _trayIcon.TbWebViewUrl.Text; - Variables.AppSettings.TrayIconWebViewBackgroundLoading = _trayIcon.CbWebViewKeepLoaded.CheckState == CheckState.Checked; - Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick = _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState == CheckState.Checked; + Variables.AppSettings.TrayIconWebViewBackgroundLoading = + _trayIcon.CbWebViewKeepLoaded.CheckState == CheckState.Checked; + Variables.AppSettings.TrayIconWebViewShowMenuOnLeftClick = + _trayIcon.CbWebViewShowMenuOnLeftClick.CheckState == CheckState.Checked; // nfc Variables.AppSettings.NfcScanningEnabled = _nfc.CbEnableNfc.CheckState == CheckState.Checked; - Variables.AppSettings.NfcSelectedScanner = _nfc.CbNfcScanner.SelectedItem == null ? string.Empty : _nfc.CbNfcScanner.SelectedItem.ToString(); + Variables.AppSettings.NfcSelectedScanner = _nfc.CbNfcScanner.SelectedItem == null + ? string.Empty + : _nfc.CbNfcScanner.SelectedItem.ToString(); // save to file SettingsManager.StoreAppSettings(); @@ -532,7 +653,7 @@ private void Configuration_KeyUp(object sender, KeyEventArgs e) private void BtnClearHotKey_Click(object sender, EventArgs e) { - _hotKey.TbQuickActionsHotkey.Text = _hotkeySelector.EmptyHotkeyText; + _hotKey.TbQuickActionsHotkey.Text = string.Empty; } private void Configuration_ResizeEnd(object sender, EventArgs e) @@ -571,4 +692,4 @@ private void CbHassAutoClientCertificate_CheckedChanged(object sender, EventArgs private void BtnClose_Click(object sender, EventArgs e) => Close(); } -} +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Forms/Main.cs b/src/HASS.Agent/HASS.Agent/Forms/Main.cs index e79059ec..5bde6719 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/Main.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/Main.cs @@ -24,11 +24,12 @@ using Serilog; using Syncfusion.Windows.Forms; using WindowsDesktop; -using WK.Libraries.HotkeyListenerNS; using NativeMethods = HASS.Agent.Functions.NativeMethods; using QuickActionsConfig = HASS.Agent.Forms.QuickActions.QuickActionsConfig; using Task = System.Threading.Tasks.Task; using Microsoft.Win32; +using NHotkey; +using NHotkey.WindowsForms; namespace HASS.Agent.Forms { @@ -64,11 +65,11 @@ private async void Main_Load(object sender, EventArgs e) // exception handlers Application.ThreadException += Application_ThreadException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - } - - if (SharedHelperFunctions.RunningElevated()) - { - Log.Warning("[MAIN] Running with elevated privileges, this might cause issues for example with notifications!"); + } + + if (SharedHelperFunctions.RunningElevated()) + { + Log.Warning("[MAIN] Running with elevated privileges, this might cause issues for example with notifications!"); } // catch all key presses @@ -88,7 +89,7 @@ private async void Main_Load(object sender, EventArgs e) SetMqttStatus(ComponentStatus.Loading); // create a hotkey listener - Variables.HotKeyListener = new HotkeyListener(); + Variables.HotKeyListener = HotkeyManager.Current; // check for dpi scaling CheckDpiScalingFactor(); @@ -124,7 +125,7 @@ private async void Main_Load(object sender, EventArgs e) // prepare the tray icon config SystemEvents.DisplaySettingsChanged += (s, a) => RefreshTrayIcon(); - ProcessTrayIcon(); + ProcessTrayIcon(); InitializeHotkeys(); @@ -304,10 +305,10 @@ private static void CurrentDomain_UnhandledException(object sender, UnhandledExc private void InitializeHotkeys() { // prepare listener - Variables.HotKeyListener.HotkeyPressed += HotkeyListener_HotkeyPressed; + Variables.InternalHotKeyManager.HotkeyActivated += HotkeyListener_HotkeyPressed; // bind quick actions hotkey (if configured) - Variables.HotKeyManager.InitializeQuickActionsHotKeys(); + Variables.InternalHotKeyManager.InitializeQuickActionsHotKeys(); } /// @@ -317,10 +318,10 @@ private void InitializeHotkeys() /// private void HotkeyListener_HotkeyPressed(object sender, HotkeyEventArgs e) { - if (e.Hotkey == Variables.QuickActionsHotKey) + if (e.Name == Variables.QuickActionsHotKey) ShowQuickActions(); else - HotKeyManager.ProcessQuickActionHotKey(e.Hotkey.ToString()); + InternalHotKeyManager.ProcessQuickActionHotKey(e.Name); } /// diff --git a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsConfig.cs b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsConfig.cs index 9a8285c4..7c86317c 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsConfig.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsConfig.cs @@ -163,7 +163,7 @@ private void BtnStore_Click(object sender, EventArgs e) StoredQuickActions.Store(); // reload hotkey bindings - Variables.HotKeyManager.ReloadQuickActionsHotKeys(); + Variables.InternalHotKeyManager.ReloadQuickActionsHotKeys(); // done Close(); diff --git a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs index e6dcff81..3b4008e0 100644 --- a/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs +++ b/src/HASS.Agent/HASS.Agent/Forms/QuickActions/QuickActionsMod.cs @@ -6,17 +6,18 @@ using HASS.Agent.Shared.Enums; using HASS.Agent.Shared.Functions; using Syncfusion.Windows.Forms; -using WK.Libraries.HotkeyListenerNS; namespace HASS.Agent.Forms.QuickActions { public partial class QuickActionsMod : MetroForm { - private readonly HotkeySelector _hotkeySelector = new(); internal readonly QuickAction QuickAction; private readonly Dictionary _hassDomainEntityTypes = new(); private readonly Dictionary _hassActionEntityTypes = new(); + + private Keys _key = Keys.None; + private Keys _modifiers = Keys.None; public QuickActionsMod(QuickAction quickAction) { @@ -90,15 +91,17 @@ private async void QuickActionsMod_Load(object sender, EventArgs e) } LvDomain.EndUpdate(); + TbHotkey.ReadOnly = true; + TbHotkey.KeyDown += TbQuickActionsHotkey_KeyDown; + // load or new quickaction? if (QuickAction.Id == Guid.Empty) { // new quickaction Text = Languages.QuickActionsMod_Title_New; QuickAction.Id = Guid.NewGuid(); - - _hotkeySelector.Enable(TbHotkey); - TbHotkey.Text = _hotkeySelector.EmptyHotkeyText; + + TbHotkey.Text = string.Empty; LvDomain.Items[0].Selected = true; if (CbEntity.Items.Count > 0) CbEntity.SelectedIndex = 0; @@ -211,12 +214,7 @@ private void LoadQuickAction() if (!string.IsNullOrWhiteSpace(TbDescription.Text)) TbDescription.SelectionStart = TbDescription.Text.Length; // load the hotkey - if (!string.IsNullOrEmpty(QuickAction.HotKey)) _hotkeySelector.Enable(TbHotkey, new Hotkey(QuickAction.HotKey)); - else - { - _hotkeySelector.Enable(TbHotkey); - TbHotkey.Text = _hotkeySelector.EmptyHotkeyText; - } + TbHotkey.Text = !string.IsNullOrEmpty(QuickAction.HotKey) ? QuickAction.HotKey : string.Empty; } /// @@ -426,16 +424,13 @@ private void CbEntity_SelectedIndexChanged(object sender, EventArgs e) private void QuickActionsMod_FormClosing(object sender, FormClosingEventArgs e) { - // stop and dispose selector - _hotkeySelector?.Disable(TbHotkey); - _hotkeySelector?.Dispose(); + // clean selector + TbHotkey.KeyDown -= TbQuickActionsHotkey_KeyDown; } private void TbHotkey_TextChanged(object sender, EventArgs e) { - if (string.IsNullOrWhiteSpace(TbHotkey.Text) - || TbHotkey.Text == _hotkeySelector?.EmptyHotkeyText - || TbHotkey.Text == _hotkeySelector?.InvalidHotkeyText) + if (string.IsNullOrWhiteSpace(TbHotkey.Text)) { CbEnableHotkey.CheckState = CheckState.Unchecked; return; @@ -481,5 +476,53 @@ private void QuickActionsMod_Layout(object sender, LayoutEventArgs e) // hide the pesky horizontal scrollbar ListViewTheme.ShowScrollBar(LvDomain.Handle, ListViewTheme.SB_HORZ, false); } + + private void TbQuickActionsHotkey_KeyDown(object sender, KeyEventArgs e) + { + e.SuppressKeyPress = true; + + var key = e.KeyCode; + + if (key is Keys.LControlKey or Keys.RControlKey + or Keys.LShiftKey or Keys.RShiftKey + or Keys.LWin or Keys.RWin + or Keys.Alt) + { + key = Keys.None; + } + + if (key == Keys.Escape) + { + _key = Keys.None; + _modifiers = Keys.None; + TbHotkey.Text = string.Empty; + + return; + } + + _key = key; + TbHotkey.Text = FormatHotkey(_key, e.Modifiers); + } + + private string FormatHotkey(Keys key, Keys modifiers) + { + var parts = new List(); + if ((modifiers & Keys.Shift) != 0) + { + parts.Add(nameof(Keys.Shift)); + } + + if ((modifiers & Keys.Control) != 0) + { + parts.Add(nameof(Keys.Control)); + } + + if ((modifiers & Keys.Alt) != 0) + { + parts.Add(nameof(Keys.Alt)); + } + + return parts.Count > 0 ? string.Join(", ", parts) + " + " + key : key.ToString(); + } } } diff --git a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs index 5f604d43..30796038 100644 --- a/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs +++ b/src/HASS.Agent/HASS.Agent/Functions/HelperFunctions.cs @@ -172,8 +172,8 @@ internal static async Task ShutdownAsync(TimeSpan waitBeforeClosing) // stop hotkey Variables.MainForm?.Invoke(new MethodInvoker(delegate { - Variables.HotKeyListener?.RemoveAll(); - Variables.HotKeyListener?.Dispose(); + Variables.InternalHotKeyManager?.ReloadQuickActionsHotKeys(); + //Variables.HotKeyListener?.Dispose(); })); // stop bt listener @@ -371,9 +371,9 @@ internal static void OpenLocalFolder(string path) /// /// /// - internal static void LaunchUrl(string url, bool incognito = false) + internal static void LaunchUrl(string url, bool incognito = false, bool explicitUrl = false) { - var targetUrl = StorageManager.GetElementUrl(url); + var targetUrl = explicitUrl ? url : StorageManager.GetElementUrl(url); // did the user provide a browser? if (string.IsNullOrEmpty(Variables.AppSettings.BrowserBinary)) diff --git a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj index 0ce5be7e..f3fc5f33 100644 --- a/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj +++ b/src/HASS.Agent/HASS.Agent/HASS.Agent.csproj @@ -17,7 +17,7 @@ anycpu x64;x86;AnyCPU full - 2.2.0 + 2.2.1 HASS.Agent Team HASS.Agent Team Windows-based client for Home Assistant. Provides notifications, quick actions, commands, sensors and more. @@ -27,8 +27,8 @@ https://github.com/hass-agent/HASS.Agent MIT app.manifest - 2.2.0 - 2.2.0 + 2.2.1 + 2.2.1 HASS.Agent None true @@ -55,6 +55,7 @@ + all @@ -67,11 +68,13 @@ + + @@ -89,15 +92,6 @@ - - - Libraries\HADotNet.Core.dll - - - Libraries\HotkeyListener.dll - - - UserControl diff --git a/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/LaunchUrlCommand.cs b/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/LaunchUrlCommand.cs index b20b53d2..b8884225 100644 --- a/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/LaunchUrlCommand.cs +++ b/src/HASS.Agent/HASS.Agent/HomeAssistant/Commands/InternalCommands/LaunchUrlCommand.cs @@ -39,7 +39,7 @@ public override void TurnOn() return; } - HelperFunctions.LaunchUrl(_url, _incognito); + HelperFunctions.LaunchUrl(_url, _incognito, true); State = "OFF"; } @@ -59,7 +59,7 @@ public override void TurnOnWithAction(string action) // prepare command var command = string.IsNullOrWhiteSpace(_url) ? action : $"{_url} {action}"; - HelperFunctions.LaunchUrl(command, _incognito); + HelperFunctions.LaunchUrl(command, _incognito, true); State = "OFF"; } diff --git a/src/HASS.Agent/HASS.Agent/Libraries/HADotNet.Core.dll b/src/HASS.Agent/HASS.Agent/Libraries/HADotNet.Core.dll deleted file mode 100644 index 2e825960..00000000 Binary files a/src/HASS.Agent/HASS.Agent/Libraries/HADotNet.Core.dll and /dev/null differ diff --git a/src/HASS.Agent/HASS.Agent/Libraries/HADotNet.Core.xml b/src/HASS.Agent/HASS.Agent/Libraries/HADotNet.Core.xml deleted file mode 100644 index 4515185e..00000000 --- a/src/HASS.Agent/HASS.Agent/Libraries/HADotNet.Core.xml +++ /dev/null @@ -1,1729 +0,0 @@ - - - - HADotNet.Core - - - - - Represents the base client from which all other API clients derive. - - - - - Gets the static HttpClient instance. - - - - - Initializes a new instance. - - The preconfigured to communicate with a Home Assistant instance. - - - - Performs a GET request on the specified path. - - The type of data to deserialize and return. - The relative API endpoint path. - The deserialized data of type . - - - - Performs a POST request on the specified path. - - The type of object expected back. - The path to post to. - The body contents to serialize and include. - if the body should be interpereted as a pre-built JSON string, or if it should be serialized. - - - - - Performs a DELETE request on the specified path. - - The type of data to deserialize and return. - The relative API endpoint path. - The deserialized data of type . - - - - Performs a DELETE request on the specified path. - - The relative API endpoint path. - - - - Provides a factory which can instantiate API clients (useful in DI scenarios, for example). - - - - - Gets whether or not the Client Factory has been initialized. - - - - - Gets the instance configured for this ClientFactory. To reconfigure the HttpClient, call again. - - - - - Initializes the client factory with the specified and which are forwarded to clients instantiated from this factory. - - The Home Assistant base instance address (do not include /api/). - The Home Assistant long-lived access token. - - - - Initializes the client factory with the specified and which are forwarded to clients instantiated from this factory. - Allows specifying the to allow further configuring. - - - - - - - - Initializes the client factory with the specified and which are forwarded to clients instantiated from this factory. - - The Home Assistant base instance address (do not include /api/). - The Home Assistant long-lived access token. - - - - Initializes the client factory with the specified and which are forwarded to clients instantiated from this factory. - Allows specifying the to allow further configuring. - - - - - - - - Resets the Client Factory to its initial state (not initialized). - - - - - Retrieves a new instance of a client by type, preconfigured with the same as this (from the last time was called). - - The type of client to get. - Thrown if this is not initialized (call first). - A new instance of the specified type. - - - - Provides access to the automations API for working with automations. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Create the . - - The . - The . - - - - Read the . - - The automation id. - The . - - - - Update the . - - The . - The . - - - - Delete the . - - The automation id. - The . - - - - Provides access to the calendar API for retrieving information about calendar entries. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of current and future calendar items, from now until the specified . The maximum number of results is driven by the "max_results" configuration option in the calendar config. - - The full name of the calendar entity. If this paramter does not start with "calendar.", it will be prepended automatically. - Optional, defaults to 30. The number of days from the current point in time to retrieve calendar items for. - A representing the calendar items found. - - - - Retrieves a list of current and future calendar items, between the and parameters. The maximum number of results is driven by the "max_results" configuration option in the calendar config. - - The full name of the calendar entity. If this paramter does not start with "calendar.", it will be prepended automatically. - The start date/time to search. - The end date/time to search. - A representing the calendar items found. - - - - Provides access to the camera proxy API which allows fetching of the current image from a camera entity. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves the most recently available (still) image data from the specified . - - The camera entity ID to reteive the image for. - A byte array containing the still image, typically in JPEG format. - - - - Retrieves the most recently available (still) image data from the specified . - - The camera entity ID to reteive the image for. - true to include the prefix "data:image/jpg;base64,", false to omit. Defaults to true. - A web-friendly Base64-encoded still image, in JPEG format. - - - - Provides access to the configuration API for retrieving the current Home Assistant configuration. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves the current Home Assistant configuration object. - - A representing the current Home Assistant configuration. - - - - Performs a configuration check and returns the result. - - A containing the results of the check, and any errors that occurred. - - - - Provides access to the discovery API for retrieving the current Home Assistant instance information. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves the current Home Assistant discovery object. - - A representing the current Home Assistant instance information. - - - - Provides a wrapper around the States endpoint for retrieving entity info. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of all current entity names (that have state) in the format "domain.name". - - An of strings of all known entities (with state) at the time. - - - - Retrieves a list of entity names for a particular domain (that have state) in the format "domain.name". - - A domain name to filter the entity list to (e.g. "light"). - An of strings of all known entities (with state) at the time. - - - - Provides access to the error log API for retrieving the current error log messages. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of error log entries. - - An containing error log entries. - - - - Provides access to the event API for retrieving information about events and firing events. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of event types from the current Home Assistant instance. - - A list of representing the available event types. - - - - Fires an event of type on the event bus. - - An with a message on if the event fired successfully or not. - - - - Provides access to the history API for retrieving and querying for historical state information. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of ALL historical states for all entities for the past 1 day. WARNING: On larger HA installs, this can return 300+ entities, over 4 MB of data, and take 20+ seconds. - - A representing a 24-hour history snapshot for all entities. - - - - Retrieves a list of ALL historical states for all entities for the specified day ( + 24 hours). WARNING: On larger HA installs, this can return 300+ entities, over 4 MB of data, and take 20+ seconds. - - A representing a 24-hour history snapshot starting from for all entities. - - - - Retrieves a list of ALL historical states for all entities for the specified time range, from to . WARNING: On larger HA installs, for multiple days, this can return A LOT of data and potentially take a LONG time to return. Use with caution! - - A representing a 24-hour history snapshot, from to , for all entities. - - - - Retrieves a list of ALL historical states for all entities for the specified time range, from , for the specified . WARNING: On larger HA installs, for multiple days, this can return A LOT of data and potentially take a LONG time to return. Use with caution! - - A representing a 24-hour history snapshot, from , for the specified , for all entities. - - - - Retrieves a list of historical states for the specified for the specified time range, from to . - - The entity ID to filter on. - The earliest history entry to retrieve. - The most recent history entry to retrieve. - A of history snapshots for the specified , from to . - - - - Retrieves a list of historical states for the specified for the past 1 day. - - The entity ID to retrieve state history for. - A representing a 24-hour history snapshot for the specified . - - - - Provides access to the info API for retrieving information about Supervisor, Core and Host. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves Supervisor information. - - A representing Supervisor informatio. - - - - Retrieves Host information. - - A representing Host informatio. - - - - Retrieves Core information. - - A representing Host informatio. - - - - Provides access to the Logbook API for retrieving and querying for change events. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of ALL logbook states for all entities for the past 1 day. - - A representing a 24-hour history snapshot for all entities. - - - - Retrieves a list of ALL historical states for all entities for the specified day ( + 24 hours). WARNING: On larger HA installs, this can return 300+ entities, over 4 MB of data, and take 20+ seconds. - - A representing a 24-hour history snapshot starting from for all entities. - - - - Retrieves a list of ALL historical states for all entities for the specified time range, from to . WARNING: On larger HA installs, for multiple days, this can return A LOT of data and potentially take a LONG time to return. Use with caution! - - A representing a 24-hour history snapshot, from to , for all entities. - - - - Retrieves a list of ALL historical states for all entities for the specified time range, from , for the specified . WARNING: On larger HA installs, for multiple days, this can return A LOT of data and potentially take a LONG time to return. Use with caution! - - A representing a 24-hour history snapshot, from , for the specified , for all entities. - - - - Retrieves a list of historical states for the specified for the specified time range, from to . - - The entity ID to filter on. - The earliest history entry to retrieve. - The most recent history entry to retrieve. - A of history snapshots for the specified , from to . - - - - Retrieves a list of historical states for the specified for the past 1 day. - - The entity ID to retrieve state history for. - A representing a 24-hour history snapshot for the specified . - - - - Provides access to the root API call (located at /api/) to ensure the API is working normally. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves the API status message for the Home Assistant instance, to ensure it is running. - - A indicating the status of the connected instance. - - - - Provides access to the service API for retrieving information about services and calling services. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of current services, separated into service domains. - - A representing available services grouped by domain. - - - - Calls a service using the given , , and optionally, . - - The domain of the service (e.g. "light"). - The name of the service (e.g. "turn_on"). - Optional. An object representing the fields/parameters to pass to the service. Can be an anonymous type, or a Dictionary<string, object>. - - - - - Calls a service using the given fully-qualified , and optionally, . - - The fully-qualified service name (e.g. "light.turn_on"). - Optional. An object representing the fields/parameters to pass to the service. Can be an anonymous type, or a Dictionary<string, object>. - - - - - Calls a service using the given , , and optionally, . - - The domain of the service (e.g. "light"). - The name of the service (e.g. "turn_on"). - Optional. A JSON string representing the fields/parameters to pass to the service. Ensure the JSON is a well-formatted object. - - - - - Calls a service using the given fully-qualified , and optionally, . - - The fully-qualified service name (e.g. "light.turn_on"). - Optional. A JSON string representing the fields/parameters to pass to the service. Ensure the JSON is a well-formatted object. - - - - - Calls a service using the given fully-qualified and one or more . - - The fully-qualified service name (e.g. "light.turn_on"). - The entity IDs to pass to the service (using the entity_ids parameter). - - - - - Provides access to the states API for retrieving information about the current state of entities. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves a list of current entities and their states. - - A representing the current state. - - - - Retrieves the state of an entity by its ID. - - A representing the current state of the requested . - - - - Sets the state of an entity. If the entity does not exist, it will be created. - - The entity ID of the state to change. - The new state value. - Optional. The attributes to set. - A representing the updated state of the updated . - - - - Provides access to the info API for retrieving statistics about Supervisor and Core. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Retrieves Supervisor information. - - A representing Supervisor stats. - - - - Retrieves Core stats. - - A representing Core stats. - - - - Provides access to the template API for rendering Home Assistant templates. - - - - - Initializes a new instance of the . - - The preconfigured to communicate with a Home Assistant instance. - - - - Renders a template and returns the resulting output as a string. - - A string of the rendered template output. - - - - Represents a failed HTTP call to a Home Assistant endpoint. - - - - - Gets the status code for the HTTP response. - - - - - Gets the network description, if the error was at the network level. - - - - - Gets the original request path. - - - - - Gets the error response body. - - - - - Initializes a new HttpResponseException. - - - - - Initializes a new HttpResponseException. - - - - - Initializes a new HttpResponseException. - - - - - The exception that occurs when a Supervisor-only API call is made to a non-Supervisor environment. - - - - - Initializes a new instance of the SupervisorNotFoundException. - - - - - Initializes a new instance of the SupervisorNotFoundException. - - - - - Initializes a new instance of the SupervisorNotFoundException. - - - - - Represents Add-on object. - - - - - Gets or sets the description. - - - - - True if icon is available, otherwise False. - - - - - True if logo is available, otherwise False. - - - - - Gets or sets the name. - - - - - Gets or sets the repository. - - - - - Gets or sets the slug. - - - - - Gets or sets the state. - - - - - True if update is available, otherwise False. - - - - - Gets or sets the version. - - - - - Gets or sets the latest version. - - - - - Represents automation object. - - - - - Gets or sets the ID. - - - - - Gets or sets the alias. - - - - - Gets or sets the description. - - - - - Gets or sets the actions. - - - - - Gets or sets the conditions. - - - - - Gets or sets the triggers. - - - - - Represents the automation result. - - - - - Gets or sets the automation result. - - - - - Represents a calendar item from the Calendar API. - - - - - Gets or sets the Attendee list, if applicable. - - - - - Gets or sets the Created time of the event. - - - - - Gets or sets the creator of the event. - - - - - Gets or sets the end time of the event. - - - - - Gets or sets the eTag for this object. - - - - - Gets or sets the HTML link for this calendar item. - - - - - Gets or sets the iCal UID for this calendar item. - - - - - Gets or sets the unique ID of this calendar item. - - - - - Gets or sets the kind of item (e.g. "calendar#event"). - - - - - Gets or sets the location of the event. - - - - - Gets or sets the organizer of the event. - - - - - Gets or sets the original start time of the event. - - - - - Gets or sets the recurring ID, if this is a recurring item. - - - - - Gets or sets if there are any reminders for this event. - - - - - Gets or sets the sequence number of this event, if applicable. - - - - - Gets or sets the start time of the event. - - - - - Gets or sets the status of this event (e.g. "confirmed"). - - - - - Gets or sets the summary, or title, of this event. - - - - - Gets or sets the transparency of this event (e.g. "transparent"). - - - - - Gets or sets the timestamp when this item was last updated. - - - - - Represents a person (an organizer, an attendee, etc) for an event. - - - - - Gets or sets the display name for this person. - - - - - Gets or sets the person's e-mail address. - - - - - Gets or sets the response if this is an attendee person (e.g. "accepted"). - - - - - Gets or sets if this is the same person as the owner of this calendar (yourself). - - - - - Gets or sets if this is the organizer of the event, for attendees. - - - - - Represents a date/time with a timezone. - - - - - The date of the event, if the event is all-day. - - - - - The date/time of the event, in local time (with an offset). - - - - - The timezone of this date/time. Not always present. - - - - - Represents a reminder override notification. - - - - - The method of notification. - - - - - The number of minutes before the event starts. - - - - - Represents a set of reminders for an event. - - - - - Whether or not to use the account's default reminder period for this event. - - - - - A list of specific notifications or reminders for this event only. - - - - - Represents the Home Assistant configuration object. - - - - - Gets the errors that occurred. This value is if is valid. - - - - - Gets the result of the configuration check. Valid values are valid and invalid. - - - - - Gets a string representation of this object. - - - - - Represents the Home Assistant configuration object. - - - - - Gets or sets the list of components loaded, in the [domain] or [domain].[component] format. - - - - - Gets or sets the relative path to the config directory (usually "/config"). - - - - - Gets or sets the config source, or type of configuration file (usually "yaml"). - - - - - Gets or sets the elevation (in meters) of the current location. - - - - - Gets or sets the latitude of the current location. - - - - - Gets or sets the longitude of the current location. - - - - - Gets or sets the location's friendly name. - - - - - Gets or sets the time zone name (in "tz database" name format, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). - - - - - Gets or sets information about the various measurement unit preferences. - - - - - Gets or sets the version of Home Assistant that is currently running (e.g. 0.96.1). - - - - - Gets or sets a list of relative paths that are approved to be exposed externally (e.g. /config/www). - - - - - Gets a string representation of this object. - - - - - Represents an entity state's context. - - - - - Gets or sets the ID of this context. - - - - - Gets or sets the Parent Context ID if this element is a child of another context, otherwise . - - - - - Gets or sets the User ID of this element, or for the default user or no user. - - - - - Gets a string representation of this object. - - - - - Represents Core info object. - - - - - Gets or sets the processor architecture. - - - - - Gets or sets the audio input. - - - - - Gets or sets the audio output. - - - - - True if booted, otherwise False. - - - - - Gets or sets the image. - - - - - Gets or sets the IP address. - - - - - Gets or sets the last_version. - - - - - Gets or sets the machine. - - - - - Gets or sets the port. - - - - - True if SSL is enabled, otherwise False. - - - - - True if update is available, otherwise False. - - - - - Gets or sets the version. - - - - - Gets or sets the latest version. - - - - - Gets or sets the wait boot timeout. - - - - - True if watchdog is enabled, otherwise False. - - - - - Represents a discovery info object, used to convey basic information about the HA instance. - - - - - Gets or sets the Base URL for the instance (e.g. http://192.168.0.2:8123). - - - - - Gets or sets the location name for this instance (e.g. "Home"). - - - - - Gets or sets whether or not a password is required to use the API. (should most always be "true"). - - - - - Gets or sets the current version of Home Assistant (e.g. 0.96.1). - - - - - Gets a string representation of this object. - - - - - Represents an error log entry. - - - - - Gets a Regex object representing a new log line. - - - - - Gets a Regex object representing an error line. - - - - - Gets a Regex object representing a warning line. - - - - - Gets the full, raw log text. - - - - - Gets the list of log entries. - - - - - Gets only log lines with a type of "ERROR". - - - - - Gets only log lines with a type of "WARNING". - - - - - Gets the most recent of entries, sorted newest first. - - The number of entries to retrieve. - A list of log entries of the specified , newest first. - - - - Initializes a new instance of the error log object with the specified data. - - The raw log data to parse. - - - - Parses the raw log text and splits each entry out based on whether or not the line starts with a date. - - The log text to parse. - A parsed list of log entries. - - - - Represents the result of an event firing. - - - - - Gets the resulting message from the event fire command. - - - - - Represents an event definition in Home Assistant. - - - - - Gets the global event string (*). - - - - - Gets the event's name. - - - - - Gets the listener count for this event. - - - - - Gets a string representation of this object. - - - - - Represents a list of one or more historical states for an entity. - - - - - Gets the EntityId for the state objects in this list. - - - - - Gets the earliest point in time represented by this history list. - - - - - Gets the most recent point in time represented by this history list. - - - - - Gets a string representation of this object. - - - - - Represents Host info object. - - - - - Gets or sets the chassis type. - - - - - Gets or sets the CPE string. - - - - - Gets or sets the deployment type (e.g. production). - - - - - Gets or sets the disk free, expressed in GB. - - - - - Gets or sets the disk total, expressed in GB. - - - - - Gets or sets the disk used, expressed in GB. - - - - - Gets or sets the feature list. - - - - - Gets or sets the hostname. - - - - - Gets or sets the kernel version. - - - - - Gets or sets the operating system. - - - - - Represents a logbook entry object. The only consistently available properties are , , and . - - - - - Gets or sets the entity ID for this entry. - - - - - Gets or sets the domain. - - - - - Gets or sets the context user ID associated with this entry. - - - - - Gets or sets the context entity ID associated with this entry. (For example, which automation triggered this change?) - - - - - Gets or sets the friendly name for the . This is sometimes the same as . - - - - - Gets or sets the type of associated change, for example, if an automation triggered this event, the value is 'automation_triggered'. - - - - - Gets or sets the domain associated with the context entity. For example, if an automation triggered this change, the value is 'automation'. - - - - - Gets or sets the name associated with the context entity (e.g. the name of the automation). This is sometimes the same as . - - - - - Gets or sets the new state that the entity transitioned to. - - - - - Gets or sets the source for this logbook entry (i.e. what triggered this change). - - - - - Gets or sets the message, a brief description of what happened. - - - - - Gets or sets the category name for this entry. - - - - - Gets or sets the timestmap when this entry occurred.. - - - - - Represents a basic message object returned from the server (e.g. an object with a "message" property). - - - - - Gets or sets the message for this message object. - - - - - Represents Home Assistant response. - - - - - - Gets or sets the result. - - - - - Gets or sets the data. - - - - - Represents a service domain definition in Home Assistant. - - - - - Gets the service domain's name. - - - - - Gets the list of services in this domain. - - - - - Gets a flat, fully-qualified list of services in this service domain. - - - - - Retrieves a service object from this domain by its name, or returns if the service does not exist. - - The service name to retrieve. - The , if the name exists in this domain, otherwise . - - - - If the specified exists, populates the with the - fully qualified service name (domain.service), and returns . Otherwise, if the service does not exist, - returns . - - The relative name of the service to look up in this service domain. - Upon successful match, will be set to the fullly qualified service name (domain.service). Otherwise, . - if a match was found, otherwise . - - - - Gets a string representation of this object. - - - - - Represents a signle field in a service call. - - - - - Gets or sets the description of this field. - - - - - Gets or sets the example text for this field (may be ). - - - - - Represents a signle service definition. - - - - - The description of the service object. - - - - - The fields/parameters that the service supports. - - - - - Represents a single entity's state. - - - - - Gets or sets the Entity ID that this state represents. - - - - - Gets or sets the string representation of the state that this entity is currently in. - - - - - Gets or sets the entity's current attributes and values. - - - - - Gets or sets the context for this entity's state. - - - - - Gets or sets the UTC date and time that this state was last changed. - - - - - Gets or sets the UTC date and time that this state was last updated. - - - - - Attempts to get the value of the specified attribute by , and cast the value to type . - - Thrown when the specified type cannot be cast to the attribute's current value. - The desired type to cast the attribute value to. - The name of the attribute to retrieve the value for. - The attribute's current value, cast to type . - - - - Gets a string representation of this entity's state. - - - - - Represents the stats object. - - - - - Gets or sets the number of bulk reads. - - - - - Gets or sets the number of bulk writes. - - - - - Gets or sets the CPU usage percent. - - - - - Gets or sets the memory limit. - - - - - Gets or sets the memory usage percent. - - - - - Gets or sets the memory usage. - - - - - Gets or sets the network rx. - - - - - Gets or sets the network tx. - - - - - Represents Supervisor info object. - - - - - Gets or sets the addons. - - - - - Gets or sets the addons repositories. - - - - - Gets or sets the processor architecture. - - - - - Gets or sets the processor channel. - - - - - True if debug, otherwise False. - - - - - True if debug block, otherwise False. - - - - - True if diagnostics is available, otherwise False. - - - - - True if healthy, otherwise False. - - - - - Gets or sets the IP address. - - - - - Gets or sets the logging. - - - - - True if supported, otherwise False. - - - - - Gets or sets the timezone. - - - - - True if update is available, otherwise False. - - - - - Gets or sets the version. - - - - - Gets or sets the latest version. - - - - - Gets or sets the wait boot timeout. - - - - - Represents the Unit System, part of the . - - - - - The length unit (e.g. mi, km). - - - - - The mass unit (e.g. lb, kg). - - - - - The pressure unit (e.g. psi, bar). - - - - - The temperature unit including degree symbol (e.g. °F, °C). - - - - - The volume unit (e.g. gal, L). - - - - - Deserializes the "Example" field into a string regardless of its type. - - - - - Always attempt to deserialize examples. - - - - - Read the JSON into a string. - - - - - Read-only, no writing. - - - - diff --git a/src/HASS.Agent/HASS.Agent/Libraries/HotkeyListener.dll b/src/HASS.Agent/HASS.Agent/Libraries/HotkeyListener.dll deleted file mode 100644 index 6907c33c..00000000 Binary files a/src/HASS.Agent/HASS.Agent/Libraries/HotkeyListener.dll and /dev/null differ diff --git a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs index 67cfb13b..43eae3a5 100644 --- a/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs +++ b/src/HASS.Agent/HASS.Agent/MQTT/MqttManager.cs @@ -917,7 +917,7 @@ internal static async Task TestConnection(string address, int port, bool u clientOptionsBuilder.WithTcpServer(address, port); } - if (!string.IsNullOrEmpty(Variables.AppSettings.MqttUsername)) + if (!string.IsNullOrEmpty(username)) { clientOptionsBuilder.WithCredentials(username, password); } diff --git a/src/HASS.Agent/HASS.Agent/Managers/HotKeyManager.cs b/src/HASS.Agent/HASS.Agent/Managers/InternalHotKeyManager.cs similarity index 51% rename from src/HASS.Agent/HASS.Agent/Managers/HotKeyManager.cs rename to src/HASS.Agent/HASS.Agent/Managers/InternalHotKeyManager.cs index b2d9b2cb..9db0495c 100644 --- a/src/HASS.Agent/HASS.Agent/Managers/HotKeyManager.cs +++ b/src/HASS.Agent/HASS.Agent/Managers/InternalHotKeyManager.cs @@ -1,13 +1,15 @@ using HASS.Agent.Commands; using HASS.Agent.HomeAssistant; using HASS.Agent.Shared.Enums; +using NHotkey; using Serilog; -using WK.Libraries.HotkeyListenerNS; namespace HASS.Agent.Managers { - internal class HotKeyManager + internal class InternalHotKeyManager { + internal event EventHandler HotkeyActivated; + /// /// Initializes the quickaction hotkeys /// @@ -31,7 +33,7 @@ internal void ReloadQuickActionsHotKeys() Variables.MainForm?.BeginInvoke(new MethodInvoker(delegate { // remove all bindings - Variables.HotKeyListener?.RemoveAll(); + RemoveAllRegisteredHotkeys(); // reload InitializeQuickActionsHotKeys(); @@ -80,38 +82,56 @@ internal static void ProcessQuickActionHotKey(string hotkey) } } - private static void InitializeGlobalQuickActionsHotKey() + internal void RemoveAllRegisteredHotkeys() + { + foreach (var quickAction in Variables.QuickActions) + { + Variables.HotKeyListener?.Remove(quickAction.HotKey); + } + } + + private void InitializeGlobalQuickActionsHotKey() { Variables.MainForm?.BeginInvoke(new MethodInvoker(delegate { - // check if the quick action hotkey's active - if (!Variables.AppSettings.QuickActionsHotKeyEnabled) return; - - // check if it's configured - if (Variables.QuickActionsHotKey == null || Variables.QuickActionsHotKey.ToString() == "None") return; + // check if it's enabled and configured + if (!Variables.AppSettings.QuickActionsHotKeyEnabled || string.IsNullOrWhiteSpace(Variables.QuickActionsHotKey) || Variables.QuickActionsHotKey == "None") + { + return; + } // all good, bind - Variables.HotKeyListener?.Add(Variables.QuickActionsHotKey); - - Log.Information("[HOTKEY] Completed bind for global quickaction hotkey"); + var globalHotkey = HotkeyFromString(Variables.QuickActionsHotKey); + if (globalHotkey.Item1 != Keys.None) + { + Variables.HotKeyListener?.AddOrReplace(Variables.QuickActionsHotKey, globalHotkey.Item1 | globalHotkey.Item2, OnHotkeyActivated); + Log.Information("[HOTKEY] Completed bind for global quickaction hotkey"); + } + else + { + Log.Warning("[HOTKEY] Could not bind for global quickaction hotkey"); + } })); } - private static void InitializeIndividualQuickActionsHotKeys() + private void InitializeIndividualQuickActionsHotKeys() { Variables.MainForm?.BeginInvoke(new MethodInvoker(delegate { var count = 0; - foreach (var quickAcion in Variables.QuickActions.Where(x => x.HotKeyEnabled && !string.IsNullOrWhiteSpace(x.HotKey))) + foreach (var quickAcion in Variables.QuickActions.Where(x => + x.HotKeyEnabled && !string.IsNullOrWhiteSpace(x.HotKey))) { try { - Variables.HotKeyListener?.Add(new Hotkey(quickAcion.HotKey)); + var hotkey = HotkeyFromString(quickAcion.HotKey); + Variables.HotKeyListener?.AddOrReplace(quickAcion.HotKey, hotkey.Item1 | hotkey.Item2, OnHotkeyActivated); count++; } catch (Exception ex) { - Log.Fatal(ex, "[HOTKEYS] Unable to bind individual quickaction hotkey '{hotkey}': {msg}", quickAcion.HotKey, ex.Message); + Log.Fatal(ex, "[HOTKEYS] Unable to bind individual quickaction hotkey '{hotkey}': {msg}", + quickAcion.HotKey, ex.Message); } } @@ -123,15 +143,56 @@ private static void InitializeIndividualQuickActionsHotKeys() /// /// Process a changed quickactions hotkey /// - /// + /// /// - internal void QuickActionsHotKeyChanged(Hotkey previousKey, bool register = true) + internal void QuickActionsHotKeyChanged(string previousHotkey, bool register = true) { Variables.MainForm?.BeginInvoke(new MethodInvoker(delegate { - Variables.HotKeyListener?.Remove(previousKey); - if (register && Variables.QuickActionsHotKey != null && Variables.QuickActionsHotKey.KeyCode != Keys.None) Variables.HotKeyListener?.Add(Variables.QuickActionsHotKey); + Variables.HotKeyListener?.Remove(previousHotkey); + if (!register || Variables.QuickActionsHotKey == null + || !string.IsNullOrWhiteSpace(Variables.QuickActionsHotKey)) + { + return; + } + + var parsedHotkey = HotkeyFromString(previousHotkey); + if (parsedHotkey.Item1 != Keys.None) + { + Variables.HotKeyListener?.AddOrReplace(Variables.QuickActionsHotKey, + parsedHotkey.Item1 | parsedHotkey.Item2, OnHotkeyActivated); + } })); } + + private void OnHotkeyActivated(object sender, HotkeyEventArgs e) + { + HotkeyActivated?.Invoke(sender, e); + } + + private static (Keys, Keys) HotkeyFromString(string stringHotkey) + { + if (string.IsNullOrWhiteSpace(stringHotkey)) + { + return (Keys.None, Keys.None); + } + + var parts = stringHotkey.Split("+", 2, StringSplitOptions.TrimEntries); + var modifiersString = parts.Length == 2 ? parts[0] : string.Empty; + var keyString = parts.Length == 2 ? parts[1] : parts[0]; + + var modifiers = Keys.None; + foreach (var modKey in modifiersString.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + if (!Enum.TryParse(modKey, out var parsedModifiers)) + { + continue; + } + + modifiers |= parsedModifiers; + } + + return Enum.TryParse(keyString, out var parsedKey) ? (parsedKey, modifiers) : (Keys.None, Keys.None); + } } -} +} \ No newline at end of file diff --git a/src/HASS.Agent/HASS.Agent/Settings/SettingsManager.cs b/src/HASS.Agent/HASS.Agent/Settings/SettingsManager.cs index 01c525ac..546f2c31 100644 --- a/src/HASS.Agent/HASS.Agent/Settings/SettingsManager.cs +++ b/src/HASS.Agent/HASS.Agent/Settings/SettingsManager.cs @@ -14,7 +14,6 @@ using Newtonsoft.Json; using Serilog; using Syncfusion.Windows.Forms; -using WK.Libraries.HotkeyListenerNS; namespace HASS.Agent.Settings { @@ -139,7 +138,7 @@ private static bool LoadAppSettings() AgentSharedBase.SetCustomExecutorBinary(Variables.AppSettings.CustomExecutorBinary); // load the hotkey - Variables.QuickActionsHotKey = string.IsNullOrEmpty(Variables.AppSettings.QuickActionsHotKey) ? null : HotkeyListener.Convert(Variables.AppSettings.QuickActionsHotKey); + Variables.QuickActionsHotKey = string.IsNullOrWhiteSpace(Variables.AppSettings.QuickActionsHotKey) ? string.Empty : Variables.AppSettings.QuickActionsHotKey; // done Log.Information("[SETTINGS] Configuration loaded"); diff --git a/src/HASS.Agent/HASS.Agent/Variables.cs b/src/HASS.Agent/HASS.Agent/Variables.cs index f2d9e850..e38a904d 100644 --- a/src/HASS.Agent/HASS.Agent/Variables.cs +++ b/src/HASS.Agent/HASS.Agent/Variables.cs @@ -18,7 +18,7 @@ using MQTTnet; using Serilog.Core; using Microsoft.Web.WebView2.Core; -using WK.Libraries.HotkeyListenerNS; +using NHotkey.WindowsForms; namespace HASS.Agent { @@ -51,9 +51,9 @@ public static class Variables /// internal static Main MainForm { get; set; } internal static HttpClient HttpClient { get; set; } = new(); - internal static Hotkey QuickActionsHotKey { get; set; } = new(Keys.Control | Keys.Alt, Keys.Q); - internal static HotKeyManager HotKeyManager { get; } = new(); - internal static HotkeyListener HotKeyListener { get; set; } + internal static string QuickActionsHotKey { get; set; } = "Control, Alt + Q"; + internal static InternalHotKeyManager InternalHotKeyManager { get; } = new(); + internal static HotkeyManager HotKeyListener { get; set; } internal static Random Rnd { get; } = new(); internal static Font DefaultFont { get; } = new("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); internal static WebView TrayIconWebView { get; set; } = null;