using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.Drawing; using System.IO; using System.Runtime; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Configuration; using NetSparkleUpdater.Enums; using NetSparkleUpdater.SignatureVerifiers; using NetSparkleUpdater.UI.WinForms; using NetSparkleUpdater; using Serilog; using Zzz.Action; using Zzz.Clients; using Zzz.Idle; using Zzz.Properties; using Zzz.Utilities; using Zzz.Utilities.Serialization; using System.Reflection; using System.Net; using MQTTnet.Client; using MqttClient = Zzz.Clients.MqttClient; namespace Zzz { public partial class MainForm : Form { #region Public Enums, Properties and Fields public bool MemorySinkEnabled { get; set; } public Configuration.Configuration Configuration { get; set; } public ScheduledContinuation ChangedConfigurationContinuation { get; set; } #endregion #region Private Delegates, Events, Enums, Properties, Indexers and Fields private Idler _defaultIdler; private MqttClient _mqttClient; private readonly ActionTrigger _trigger; private AboutForm _aboutForm; private SettingsForm _settingsForm; private Idler _hibernateIdler; private LogMemorySink _memorySink; private readonly object _memorySinkLock; private LogViewForm _logViewForm; private SparkleUpdater _sparkle; private readonly CancellationToken _cancellationToken; private readonly CancellationTokenSource _cancellationTokenSource; #endregion #region Constructors, Destructors and Finalizers public MainForm(Mutex mutex) : this() { InitializeComponent(); _memorySink = new LogMemorySink(); _memorySinkLock = new object(); lock (_memorySinkLock) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Conditional(condition => MemorySinkEnabled, configureSink => configureSink.Sink(_memorySink)) .WriteTo.File( Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"), rollingInterval: RollingInterval.Day) .CreateLogger(); } // Initialize the sleeper. _trigger = new ActionTrigger(Handle); _trigger.Action += Trigger_Action; // Start application update. var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName; var icon = Icon.ExtractAssociatedIcon(manifestModuleName); _sparkle = new SparkleUpdater("https://zzz.grimore.org/update/appcast.xml", new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY=")) { UIFactory = new UIFactory(icon), RelaunchAfterUpdate = true, SecurityProtocolType = SecurityProtocolType.Tls12 }; _sparkle.StartLoop(true, true); } private MainForm() { _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; ChangedConfigurationContinuation = new ScheduledContinuation(); } /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing) { components?.Dispose(); } base.Dispose(disposing); } #endregion #region Event Handlers private async void MainForm_Load(object sender, EventArgs e) { Configuration = await LoadConfiguration(); // Initialize the idle timer. _defaultIdler = new Idler(Configuration, Resources.Default); if (Configuration.Enabled) { _defaultIdler.Start(TimeSpan.FromMinutes((int)Configuration.Timeout)); } // Bind to the idle notification. _defaultIdler.Idle += DefaultIdler_Idle; _defaultIdler.IdleImminent += DefaultIdler_IdleImminent; _hibernateIdler = new Idler(Configuration, Resources.Hibernate); if (Configuration.HibernateEnabled) { _hibernateIdler.Start(TimeSpan.FromMinutes((int)Configuration.HibernateTimeout)); } _hibernateIdler.Idle += HibernateIdler_Idle; _hibernateIdler.IdleImminent += HibernateIdler_IdleImminent; toolStripEnabledMenuItem.Checked = Configuration.Enabled; hibernateToolStripMenuItem.Checked = Configuration.HibernateEnabled; // Initialize MQTT client. _mqttClient = new MqttClient(Configuration); _mqttClient.MqttSubscribeSucceeded += MqttClient_MqttSubscribeSucceeded; _mqttClient.MqttSubscribeFailed += MqttClient_MqttSubscribeFailed; _mqttClient.MqttStateReceived += MqttClient_MqttStateReceived; _mqttClient.MqttActionReceived += MqttClient_MqttActionReceived; #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed _mqttClient.Start(); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } private void LogViewToolStripMenuItem_Click(object sender, EventArgs e) { if (_logViewForm != null) { return; } lock (_memorySinkLock) { _logViewForm = new LogViewForm(this, _memorySink, _memorySinkLock); _logViewForm.Closing += LogViewFormClosing; _logViewForm.Show(); } } private void LogViewFormClosing(object sender, CancelEventArgs e) { if (_logViewForm == null) { return; } _logViewForm.Closing -= LogViewFormClosing; _logViewForm.Close(); _logViewForm = null; } private async void Trigger_Action(object sender, ActionEventArgs e) { if (Configuration.MqttEnable) { await _mqttClient.Publish(e.Action); } } private void MqttClient_MqttActionReceived(object sender, MqttActionReceivedEventArgs e) { Log.Information($"MQTT action {e.Action} received."); _trigger.Execute(e.Action); } private void MqttClient_MqttStateReceived(object sender, MqttStateReceivedEventArgs e) { Log.Information($"MQTT state {e.ZzzState.State} received."); switch (e.ZzzState.State) { case State.State.Enabled: Configuration.Enabled = true; this.InvokeIfRequired(form => { form.toolStripEnabledMenuItem.Checked = true; form.toolStripEnabledMenuItem.CheckState = CheckState.Checked; form.notifyIcon1.BalloonTipText = Resources.Zzz_enabled; form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Info; form.notifyIcon1.BalloonTipTitle = Resources.Enabled; form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds); }); break; case State.State.Disabled: Configuration.Enabled = false; this.InvokeIfRequired(form => { form.toolStripEnabledMenuItem.Checked = false; form.toolStripEnabledMenuItem.CheckState = CheckState.Unchecked; form.notifyIcon1.BalloonTipText = Resources.Zzz_disabled; form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Info; form.notifyIcon1.BalloonTipTitle = Resources.Disabled; form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds); }); break; } } private void MqttClient_MqttSubscribeFailed(object sender, MqttClientSubscribeResultCode e) { Log.Warning(Resources.Unable_to_subscribe_to_MQTT_topic); } private void MqttClient_MqttSubscribeSucceeded(object sender, MqttClientSubscribeResultCode e) { Log.Information(Resources.Subscribed_to_MQTT_topic_and_waiting_for_messages); } private void DefaultIdler_IdleImminent(object sender, IdleImminentEventArgs e) { this.InvokeIfRequired(form => { form.notifyIcon1.BalloonTipText = Resources.Your_computer_is_becoming_idle_and_will_go_to_sleep_in_a_minute; form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; form.notifyIcon1.BalloonTipTitle = Resources.Sleeping_soon; form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds); }); } private void DefaultIdler_Idle(object sender, IdleEventArgs e) { Log.Information(Resources.Sleeping); // Sleep! _trigger.Execute(new ZzzAction(Configuration.Action)); } private void HibernateIdler_IdleImminent(object sender, IdleImminentEventArgs e) { this.InvokeIfRequired(form => { form.notifyIcon1.BalloonTipText = Resources.Your_computer_will_enter_hiberation; form.notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; form.notifyIcon1.BalloonTipTitle = Resources.Hibernating_soon; form.notifyIcon1.ShowBalloonTip(TimeSpan.MaxValue.Milliseconds); }); } private void HibernateIdler_Idle(object sender, IdleEventArgs e) { Log.Information(Resources.Hibernating); _trigger.Execute(new ZzzAction(Action.Action.Hibernate)); } private void OnContextMenuQuitClick(object sender, EventArgs e) { Environment.Exit(0); } private void OnContextMenuAboutClick(object sender, EventArgs e) { if (_aboutForm != null) { return; } // Show the about form. _aboutForm = new AboutForm(); _aboutForm.Closing += AboutForm_Closing; _aboutForm.Show(); } private void AboutForm_Closing(object sender, CancelEventArgs e) { if (_aboutForm == null) { return; } _aboutForm.Closing -= AboutForm_Closing; _aboutForm.Dispose(); _aboutForm = null; } private void ToolStripMenuEnabledMenuItem_CheckedChanged(object sender, EventArgs e) { Configuration.Enabled = ((ToolStripMenuItem) sender).Checked; switch(Configuration.Enabled) { case true: if(!_defaultIdler.IsRunning) { _defaultIdler.Start(TimeSpan.FromMinutes((int)Configuration.HibernateTimeout)); } break; default: _defaultIdler.Stop(); break; } } private void HibernateToolStripMenuItem_CheckedChanged(object sender, EventArgs e) { Configuration.HibernateEnabled = ((ToolStripMenuItem)sender).Checked; switch (Configuration.HibernateEnabled) { case true: if (!_hibernateIdler.IsRunning) { _hibernateIdler.Start(TimeSpan.FromMinutes((int)Configuration.HibernateTimeout)); } break; default: _hibernateIdler.Stop(); break; } } private void OnNotifyIconMouseDoubleClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { return; } Task.Delay((int)Configuration.ClickActionDelay).ContinueWith(task => { _trigger.Execute(new ZzzAction(Configuration.ActionDoubleClick)); }); } private void OnNotifyIconMouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { return; } Task.Delay((int)Configuration.ClickActionDelay).ContinueWith(task => { _trigger.Execute(new ZzzAction(Configuration.ActionClick)); }); } private async void UpdateToolStripMenuItem_Click(object sender, EventArgs e) { // Manually check for updates, this will not show a ui var result = await _sparkle.CheckForUpdatesQuietly(); if (result.Status == UpdateStatus.UpdateAvailable) { // if update(s) are found, then we have to trigger the UI to show it gracefully _sparkle.ShowUpdateNeededUI(); return; } MessageBox.Show(Resources.No_updates_available_at_this_time, Resources.Zzz, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false); } private void ToolStripMenuItem4_Click(object sender, EventArgs e) { if (_settingsForm != null) { return; } _settingsForm = new SettingsForm(_mqttClient, Configuration); _settingsForm.Closing += SettingsForm_Closing; _settingsForm.Show(); } private async void SettingsForm_Closing(object sender, CancelEventArgs e) { if (_settingsForm == null) { return; } _settingsForm.Closing -= SettingsForm_Closing; _settingsForm.Dispose(); _settingsForm = null; // Commit the configuration. ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), async () => { await SaveConfiguration(); }, _cancellationToken); Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot); // Update idle timer parameters. _defaultIdler.IdleTimeout = TimeSpan.FromMinutes((int)Configuration.Timeout); _hibernateIdler.IdleTimeout = TimeSpan.FromMinutes((int)Configuration.HibernateTimeout); // Restart MQTT client. try { await _mqttClient.Restart(); } catch (Exception ex) { Log.Warning(ex, "Unable to restart MQTT client."); } } #endregion #region Public Methods public async Task SaveConfiguration() { if (!Directory.Exists(Constants.UserApplicationDirectory)) { Directory.CreateDirectory(Constants.UserApplicationDirectory); } switch (await Serialization.Serialize(Configuration, Constants.ConfigurationFile, "Configuration", "", CancellationToken.None)) { case SerializationSuccess _: Log.Information("Serialized configuration."); break; case SerializationFailure serializationFailure: Log.Warning(serializationFailure.Exception.Message, "Failed to serialize configuration."); break; } } public static async Task LoadConfiguration() { if (!Directory.Exists(Constants.UserApplicationDirectory)) { Directory.CreateDirectory(Constants.UserApplicationDirectory); } var deserializationResult = await Serialization.Deserialize(Constants.ConfigurationFile, Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None); switch (deserializationResult) { case SerializationSuccess serializationSuccess: return serializationSuccess.Result; case SerializationFailure serializationFailure: Log.Warning(serializationFailure.Exception, "Failed to load configuration."); return new Configuration.Configuration(); default: return new Configuration.Configuration(); } } #endregion } }