Skip to content

Commit

Permalink
Merge pull request #5 from MV10/popup_formatting
Browse files Browse the repository at this point in the history
popup formatting tweaks
  • Loading branch information
MV10 authored Apr 30, 2023
2 parents 9bbb7fd + c63f9c8 commit bb709ed
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 55 deletions.
2 changes: 1 addition & 1 deletion UPSMonitor/HistoryForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ public HistoryForm()
private void lstMessages_SelectedIndexChanged(object sender, EventArgs e)
=> txtDetails.Text = messages[lstMessages.SelectedIndex].Content
.Replace("\n", "\r\n")
.Replace(Program.SeparatorControlCode, "\r\n");
.Replace(Program.TitleSeparator, "\r\n");
}
}
24 changes: 19 additions & 5 deletions UPSMonitor/MessageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static async Task RunServer(CancellationToken cancellationToken)
received = true;

// look for and strip the no-popup / log-only control code
var noPopUp = message.StartsWith(Program.NoPopupControlCode);
var noPopUp = message.StartsWith(Program.NoPopupPrefix);
if (noPopUp) message = message.Substring(1);

// store the raw message to history
Expand All @@ -39,10 +39,24 @@ public static async Task RunServer(CancellationToken cancellationToken)
// display the pop-up
if(!noPopUp)
{
message = message.Replace(Program.SeparatorControlCode, "\n");
new ToastContentBuilder()
.AddText(message)
.Show();
var sep = message.IndexOf(Program.TitleSeparator);
if(sep == -1)
{
new ToastContentBuilder()
.AddText("Message")
.AddText(message)
.Show();
}
else
{
var title = message.Substring(0, sep);
var detail = message.Substring(sep + 1);
new ToastContentBuilder()
.AddText(title)
.AddText(detail)
.Show();
}

}
}

Expand Down
9 changes: 7 additions & 2 deletions UPSMonitor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ internal static class Program
{
public static MessageBuffer MessageHistory = new();

// the Windows Service connects to this
internal static readonly string PipeServerName = "UPSMonitor";
internal static readonly string SeparatorControlCode = "\u0014";
internal static readonly string NoPopupControlCode = "\u0020";

// optional; separates the pop-up title line (in boldface) from the detail text
internal static readonly string TitleSeparator = "~";

// a message prefixed with this is only stored to history
internal static readonly string NoPopupPrefix = "@";

private static SystemTrayApp trayApp = null;
private static CancellationTokenSource ctsMessageServer = new();
Expand Down
30 changes: 15 additions & 15 deletions UPSMonitorService/InjectedServices/BatteryState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public async Task InitializeAsync()
{
if (!string.IsNullOrEmpty(batt.Health))
{
await notify.Send(EventLogEntryType.Error, "Service initialization may have failed.", batt.Health);
await notify.Send(EventLogEntryType.Error, "Invalid BatteryState Data", "Service initialization may have failed.");
return;
}

await notify.Send(EventLogEntryType.Information, "Service has started, but no battery was found.");
await notify.Send(EventLogEntryType.Information, "No Battery", "Service has started, but no battery was found.");
return;
}

Expand Down Expand Up @@ -60,11 +60,11 @@ public async Task Poll()
{
if (newBatt.Health.Equals("OK"))
{
await notify.Send(EventLogEntryType.Information, "Battery health has returned to normal.");
await notify.Send(EventLogEntryType.Information, "Battery Health", $"Battery health has returned to normal.\nHealth changed from {Battery.Health} to {newBatt.Health}.");
}
else
{
await notify.Send(EventLogEntryType.Warning, $"Battery health has changed from {Battery.Health} to {newBatt.Health}. Service may be required.");
await notify.Send(EventLogEntryType.Warning, "Battery Health", $"Battery health has changed from {Battery.Health} to {newBatt.Health}.\nService may be required.");
}
}

Expand All @@ -77,19 +77,19 @@ public async Task Poll()
// Charge level notifications, most severe to least severe
if (Battery.ChargePct > config.ChargeLevels.Critical && newBatt.ChargePct <= config.ChargeLevels.Critical)
{
await ChargeNotification(newBatt, EventLogEntryType.Warning, "Battery at CRITICAL charge and falling.");
await ChargeNotification(newBatt, EventLogEntryType.Warning, "Battery Charge CRITICAL, Final Warning");
}
else if (Battery.ChargePct > config.ChargeLevels.Reserve && newBatt.ChargePct <= config.ChargeLevels.Reserve)
{
await ChargeNotification(newBatt, EventLogEntryType.Warning, "Battery at RESERVE charge and falling.");
await ChargeNotification(newBatt, EventLogEntryType.Warning, "Battery Charge RESERVE");
}
else if (Battery.ChargePct > config.ChargeLevels.Advisory && newBatt.ChargePct <= config.ChargeLevels.Advisory)
{
await ChargeNotification(newBatt, EventLogEntryType.Warning, "Battery at LOW charge and falling.");
await ChargeNotification(newBatt, EventLogEntryType.Warning, "Battery Charge LOW");
}
else if (Battery.ChargePct > config.ChargeLevels.Advisory && newBatt.ChargePct <= config.ChargeLevels.Advisory)
{
await ChargeNotification(newBatt, EventLogEntryType.Information, "Battery charge is falling.");
await ChargeNotification(newBatt, EventLogEntryType.Information, "Battery Charge Advisory");
}

// Update the stored battery info
Expand All @@ -101,21 +101,21 @@ private async Task BatteryChanged(BatteryData batt)
// output summary
if(string.IsNullOrEmpty(Battery?.Name))
{
await notify.Send(EventLogEntryType.Information, "Service has started monitoring this battery:", batt.Summary);
await notify.Send(EventLogEntryType.Information, "Monitoring Started", batt.Summary);
}
else if(string.IsNullOrEmpty(batt.Name))
{
await notify.Send(EventLogEntryType.Information, "Service is running, but is no longer monitoring any battery.");
await notify.Send(EventLogEntryType.Information, "Monitoring Ended", "Service is running, but is no longer monitoring any battery.");
}
else
{
await notify.Send(EventLogEntryType.Information, "Service has changed to monitor this battery:", batt.Summary);
await notify.Send(EventLogEntryType.Information, "Monitoring Changed", batt.Summary);
}

// warn if battery is not healthy
if (!string.IsNullOrEmpty(batt.Name) && !batt.Health.Equals("OK"))
{
await notify.Send(EventLogEntryType.Warning, "Battery may require service.", $"Health: {batt.Health}");
await notify.Send(EventLogEntryType.Warning, "Battery Health", $"Battery may require service.\nHealth: {batt.Health}");
}

Battery = batt;
Expand All @@ -124,12 +124,12 @@ private async Task BatteryChanged(BatteryData batt)
private async Task StatusChanged(BatteryData batt)
{
// It appears only values 1 and 2 are used (Discharging and AC Power).
await notify.Send(EventLogEntryType.Information, $"Battery state changed from {BatteryData.BatteryStatusValues[Battery.Status]} to {BatteryData.BatteryStatusValues[batt.Status]}.");
await notify.Send(EventLogEntryType.Information, "Battery State", $"State changed from {BatteryData.BatteryStatusValues[Battery.Status]} to {BatteryData.BatteryStatusValues[batt.Status]}.");
}

private async Task ChargeNotification(BatteryData batt, EventLogEntryType eventType, string message)
private async Task ChargeNotification(BatteryData batt, EventLogEntryType eventType, string title)
{
await notify.Send(eventType, message, $"Charge: {Battery.ChargePct}%, {Battery.Runtime}");
await notify.Send(eventType, title, $"Charge: {Battery.ChargePct}%, {Battery.Runtime}");
}
}
}
78 changes: 46 additions & 32 deletions UPSMonitorService/InjectedServices/Notify.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,60 @@ public class Notify
{
private readonly Config config = null;

private static readonly string pipeServerName = "UPSMonitor";
private static readonly string separatorControlCode = "\u0014";
internal static readonly string NoPopupControlCode = "\u0020";
private static readonly int pipeTimeoutMS = 100; // high, 10 is probably OK for local machine...
// provided by UPS Monitor system tray app
private static readonly string PipeServerName = "UPSMonitor";

// optional; separates the pop-up title line (in boldface) from the detail text
private static readonly string TitleSeparator = "~";

// a message prefixed with this is only stored to history
internal static readonly string NoPopupPrefix = "@";

// high, 10 is probably OK for local machine...
private static readonly int pipeTimeoutMS = 100;

public Notify(Config appConfig)
{
config = appConfig;
}

/// <summary>
/// Sends a message and details to all configured notification channels.
/// Sends a message to all configured notification channels. For pop-ups, the
/// details field may contain four lines separated by \n newline characters. If
/// only a title is provided, for pop-up purposes it will be shown as the detail
/// text and a title of "Message" will be shown. (Pop-up titles are in boldface.)
/// </summary>
public async Task Send(EventLogEntryType eventType, string message, string details = "")
public async Task Send(EventLogEntryType eventType, string title, string details = "")
{
// popup has limited space, don't prefix the eventType
await SendPopup(message, details);
await SendPopup(title, details);

// for everything else, prefix the eventType (Information, Warning, Error)
var flaggedMessage = $"{eventType.ToString().ToUpper()} - {title}";

var flaggedMessage = $"{eventType.ToString().ToUpper()} - {message}";
SendConsole(flaggedMessage, details);

SendEventLog(eventType, flaggedMessage, details);

await SendEmail(flaggedMessage, details);
}

/// <summary>
/// Sends a message and details to the Windows Application Event Log, if enabled.
/// Sends a message to the Windows Application Event Log, if enabled.
/// </summary>
public void SendEventLog(EventLogEntryType eventType, string message, string details = "")
public void SendEventLog(EventLogEntryType eventType, string title, string details = "")
{
if (!config.Settings.NotificationEventLog) return;

Task.Run(() =>
EventLog.WriteEntry("Application", $"UPSMonitor\n{message}\n{details}", eventType, 9001)
EventLog.WriteEntry("Application", $"UPSMonitor\n{title}\n{details}", eventType, 9001)
).FireAndForget();
}

/// <summary>
/// Sends a message and details via Email, if enabled.
/// Sends a message via email, if enabled.
/// </summary>
public async Task SendEmail(string message, string details = "")
public async Task SendEmail(string title, string details = "")
{
// MS says don't use this mail client...
// https://github.com/dotnet/platform-compat/blob/master/docs/DE0005.md
Expand All @@ -66,7 +80,7 @@ public async Task SendEmail(string message, string details = "")
var recipients = config.Email.RecipientList.Split(',');
foreach (var addr in recipients) email.To.Add(addr);
email.Subject = config.Email.Subject;
email.Body = $"UPSMonitor update from {Environment.MachineName} at {DateTimeOffset.Now}\n\n{message}\n{details}";
email.Body = $"UPSMonitor update from {Environment.MachineName} at {DateTimeOffset.Now}\n\n{title}\n{details}";

using var smtp = new SmtpClient(config.Email.MailServerDomain, config.Email.MailServerPort);
smtp.EnableSsl = config.Email.UseTLS;
Expand All @@ -85,50 +99,50 @@ public async Task SendEmail(string message, string details = "")
}

/// <summary>
/// Sends a message and details via Windows "toast" pop-up, if enabled. When running
/// Sends a message shown as a Windows "toast" pop-up, if enabled. When running
/// non-interactively (e.g. as a Windows Service), this will send a Named Pipes message
/// to the UPSMonitor system tray service.
/// </summary>
public async Task SendPopup(string message, string details = "")
public async Task SendPopup(string title, string details = "")
{
if (!config.Settings.NotificationPopups) return;

if(Environment.UserInteractive && !config.Settings.RemotePopupOnly)
{
new ToastContentBuilder()
.AddText(message)
.AddText(title)
.AddText(details)
.Show();
}
else
{
await SendNamedPipePopup(message, details);
await SendNamedPipePopup(title, details);
}
}

/// <summary>
/// Sends a message and details to the Console when running interactively.
/// Sends a message to the Console when running interactively.
/// </summary>
public void SendConsole(string message, string details = "")
public void SendConsole(string title, string details = "")
{
if (!Environment.UserInteractive) return;

if(!string.IsNullOrEmpty(details))
{
Console.WriteLine($"{message}\n{details}");
Console.WriteLine($"{title}\n{details}");
}
else
{
Console.WriteLine(message);
Console.WriteLine(title);
}
}

/// <summary>
/// Sends a message and details via Named Pipe to the UPSMonitor system tray service.
/// </summary>
public async Task SendNamedPipePopup(string message, string details = "", bool noPopUp = false)
public async Task SendNamedPipePopup(string title, string details = "", bool noPopUp = false)
{
using var client = new NamedPipeClientStream(".", pipeServerName, PipeDirection.Out);
using var client = new NamedPipeClientStream(".", PipeServerName, PipeDirection.Out);

// try to connect to the server
try
Expand All @@ -142,26 +156,26 @@ public async Task SendNamedPipePopup(string message, string details = "", bool n
}

// build the message
var popupFlag = noPopUp ? NoPopupControlCode: string.Empty;
var popupFlag = noPopUp ? NoPopupPrefix: string.Empty;

var msg = string.IsNullOrEmpty(details)
? $"{popupFlag}{message}"
: $"{popupFlag}{message}{separatorControlCode}{details}";
var content = string.IsNullOrEmpty(details)
? $"{popupFlag}{title}"
: $"{popupFlag}{title}{TitleSeparator}{details}";

// send it!
await WriteString(client, msg);
await WriteString(client, content);
}

private async Task WriteString(PipeStream stream, string message)
private async Task WriteString(PipeStream stream, string content)
{
try
{
var messageBuffer = Encoding.ASCII.GetBytes(message);
var messageBuffer = Encoding.ASCII.GetBytes(content);

var sizeBuffer = BitConverter.GetBytes(messageBuffer.Length);
await stream.WriteAsync(sizeBuffer, 0, sizeBuffer.Length);

if (message.Length > 0)
if (content.Length > 0)
await stream.WriteAsync(messageBuffer, 0, messageBuffer.Length);

stream.WaitForPipeDrain();
Expand Down

0 comments on commit bb709ed

Please sign in to comment.