Change how settings and tray are handled.

This commit is contained in:
Den Delimarsky
2021-05-09 22:25:52 -07:00
parent 1347223707
commit af4ec9ad8b
5 changed files with 152 additions and 184 deletions

View File

@@ -1,41 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace Espresso.Shell.Core
{
public class SettingsHelper
{
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
public static FileStream? GetSettingsFile(string path, int retries)
{
for (int i = 0; i < retries; i++)
{
try
{
FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None);
return fileStream;
}
catch (IOException ex)
{
var errorCode = Marshal.GetHRForException(ex) & ((1 << 16) - 1);
if (errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION)
{
Console.WriteLine("There was another process using the file, so couldn't pick the settings up.");
}
Thread.Sleep(50);
}
}
return null;
}
}
}

View File

@@ -2,36 +2,76 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using Espresso.Shell.Models; using Microsoft.PowerToys.Settings.UI.Library;
using Newtonsoft.Json;
using System; using System;
using System.Drawing; using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
namespace Espresso.Shell.Core namespace Espresso.Shell.Core
{ {
internal static class TrayHelper internal static class TrayHelper
{ {
static NotifyIcon trayIcon; static NotifyIcon? trayIcon;
private static NotifyIcon TrayIcon { get => trayIcon; set => trayIcon = value; }
static SettingsUtils? moduleSettings;
private static SettingsUtils ModuleSettings { get => moduleSettings; set => moduleSettings = value; }
static TrayHelper() static TrayHelper()
{ {
trayIcon = new NotifyIcon(); TrayIcon = new NotifyIcon();
ModuleSettings = new SettingsUtils();
} }
private static void InitializeTrayIcon(string text, Icon icon, ContextMenuStrip contextMenu) public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu)
{ {
trayIcon.Text = text; System.Threading.Tasks.Task.Factory.StartNew((tray) =>
trayIcon.Icon = icon; {
trayIcon.ContextMenuStrip = contextMenu; ((NotifyIcon?)tray).Text = text;
trayIcon.Visible = true; ((NotifyIcon?)tray).Icon = icon;
((NotifyIcon?)tray).ContextMenuStrip = contextMenu;
((NotifyIcon?)tray).Visible = true;
Application.Run(); Application.Run();
}, TrayIcon);
} }
internal static void InitializeEspressoTray(string text, EspressoMode mode, bool keepDisplayOn, Action indefiniteSelectionCallback, Action timedSelectionCallback) internal static void SetTray(string text, EspressoSettings settings)
{
SetTray(text, settings.Properties.KeepDisplayOn.Value, settings.Properties.Mode, settings.Properties.Hours.Value, settings.Properties.Minutes.Value,
() => {
// Set indefinite keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.Mode = EspressoMode.INDEFINITE;
ModuleSettings.SaveSettings(JsonConvert.SerializeObject(currentSettings), text);
},
() => {
// Set timed keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.Mode = EspressoMode.TIMED;
currentSettings.Properties.Hours.Value = settings.Properties.Hours.Value;
currentSettings.Properties.Minutes.Value = settings.Properties.Minutes.Value;
ModuleSettings.SaveSettings(JsonConvert.SerializeObject(currentSettings), text);
},
() =>
{
// Just changing the display mode.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.KeepDisplayOn = settings.Properties.KeepDisplayOn;
ModuleSettings.SaveSettings(JsonConvert.SerializeObject(currentSettings), text);
});
}
internal static void SetTray(string text, bool keepDisplayOn, EspressoMode mode, int hours, int minutes, Action indefiniteKeepAwakeCallback, Action timedKeepAwakeCallback, Action keepDisplayOnCallback)
{ {
var contextMenuStrip = new ContextMenuStrip(); var contextMenuStrip = new ContextMenuStrip();
// Main toolstrip. // Main toolstrip.
var operationContextMenu = new ToolStripMenuItem(); var operationContextMenu = new ToolStripMenuItem();
operationContextMenu.Text = "Mode"; operationContextMenu.Text = "Mode";
@@ -39,10 +79,28 @@ namespace Espresso.Shell.Core
// Indefinite keep-awake menu item. // Indefinite keep-awake menu item.
var indefiniteMenuItem = new ToolStripMenuItem(); var indefiniteMenuItem = new ToolStripMenuItem();
indefiniteMenuItem.Text = "Indefinite"; indefiniteMenuItem.Text = "Indefinite";
if (mode == EspressoMode.INDEFINITE) if (mode == EspressoMode.INDEFINITE)
{ {
indefiniteMenuItem.Checked = true; indefiniteMenuItem.Checked = true;
} }
indefiniteMenuItem.Click += (e, s) =>
{
// User opted to set the mode to indefinite, so we need to write new settings.
indefiniteKeepAwakeCallback();
};
var displayOnMenuItem = new ToolStripMenuItem();
displayOnMenuItem.Text = "Keep display on";
if (keepDisplayOn)
{
displayOnMenuItem.Checked = true;
}
displayOnMenuItem.Click += (e, s) =>
{
// User opted to set the display mode directly.
keepDisplayOnCallback();
};
// Timed keep-awake menu item // Timed keep-awake menu item
var timedMenuItem = new ToolStripMenuItem(); var timedMenuItem = new ToolStripMenuItem();
@@ -50,12 +108,27 @@ namespace Espresso.Shell.Core
var halfHourMenuItem = new ToolStripMenuItem(); var halfHourMenuItem = new ToolStripMenuItem();
halfHourMenuItem.Text = "30 minutes"; halfHourMenuItem.Text = "30 minutes";
halfHourMenuItem.Click += (e, s) =>
{
// User is setting the keep-awake to 30 minutes.
timedKeepAwakeCallback();
};
var oneHourMenuItem = new ToolStripMenuItem(); var oneHourMenuItem = new ToolStripMenuItem();
oneHourMenuItem.Text = "1 hour"; oneHourMenuItem.Text = "1 hour";
oneHourMenuItem.Click += (e, s) =>
{
// User is setting the keep-awake to 1 hour.
timedKeepAwakeCallback();
};
var twoHoursMenuItem = new ToolStripMenuItem(); var twoHoursMenuItem = new ToolStripMenuItem();
twoHoursMenuItem.Text = "2 hours"; twoHoursMenuItem.Text = "2 hours";
twoHoursMenuItem.Click += (e, s) =>
{
// User is setting the keep-awake to 2 hours.
timedKeepAwakeCallback();
};
timedMenuItem.DropDownItems.Add(halfHourMenuItem); timedMenuItem.DropDownItems.Add(halfHourMenuItem);
timedMenuItem.DropDownItems.Add(oneHourMenuItem); timedMenuItem.DropDownItems.Add(oneHourMenuItem);
@@ -63,12 +136,12 @@ namespace Espresso.Shell.Core
operationContextMenu.DropDownItems.Add(indefiniteMenuItem); operationContextMenu.DropDownItems.Add(indefiniteMenuItem);
operationContextMenu.DropDownItems.Add(timedMenuItem); operationContextMenu.DropDownItems.Add(timedMenuItem);
operationContextMenu.DropDownItems.Add(new ToolStripSeparator());
operationContextMenu.DropDownItems.Add(displayOnMenuItem);
contextMenuStrip.Items.Add(operationContextMenu); contextMenuStrip.Items.Add(operationContextMenu);
#pragma warning disable CS8604 // Possible null reference argument. TrayIcon.ContextMenuStrip = contextMenuStrip;
Task.Factory.StartNew(() => InitializeTrayIcon(text, APIHelper.Extract("shell32.dll", 12, true), contextMenuStrip));
#pragma warning restore CS8604 // Possible null reference argument.
} }
} }
} }

View File

@@ -49,6 +49,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" /> <ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Microsoft.PowerToys.Settings.UI.Library\Microsoft.PowerToys.Settings.UI.Library.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,52 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
namespace Espresso.Shell.Models
{
public enum EspressoMode
{
INDEFINITE = 0,
TIMED = 1,
}
public class EspressoSettingsModel
{
[JsonProperty("properties")]
public Properties? Properties { get; set; }
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("version")]
public string? Version { get; set; }
}
public class Properties
{
[JsonProperty("espresso_keep_display_on")]
public KeepDisplayOn? KeepDisplayOn { get; set; }
[JsonProperty("espresso_mode")]
public EspressoMode Mode { get; set; }
[JsonProperty("espresso_hours")]
public Hours? Hours { get; set; }
[JsonProperty("espresso_minutes")]
public Minutes? Minutes { get; set; }
}
public class KeepDisplayOn
{
public bool Value { get; set; }
}
public class Hours
{
public int Value { get; set; }
}
public class Minutes
{
public int Value { get; set; }
}
}

View File

@@ -3,8 +3,8 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using Espresso.Shell.Core; using Espresso.Shell.Core;
using Espresso.Shell.Models;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; using NLog;
using System; using System;
@@ -19,7 +19,6 @@ using System.Reflection;
using System.Threading; using System.Threading;
#pragma warning disable CS8602 // Dereference of a possibly null reference. #pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8603 // Possible null reference return. #pragma warning disable CS8603 // Possible null reference return.
namespace Espresso.Shell namespace Espresso.Shell
@@ -29,6 +28,7 @@ namespace Espresso.Shell
private static Mutex? mutex = null; private static Mutex? mutex = null;
private const string appName = "Espresso"; private const string appName = "Espresso";
private static FileSystemWatcher? watcher = null; private static FileSystemWatcher? watcher = null;
private static SettingsUtils? settingsUtils = null;
public static Mutex Mutex { get => mutex; set => mutex = value; } public static Mutex Mutex { get => mutex; set => mutex = value; }
@@ -45,7 +45,8 @@ namespace Espresso.Shell
} }
log = LogManager.GetCurrentClassLogger(); log = LogManager.GetCurrentClassLogger();
settingsUtils = new SettingsUtils();
log.Info("Launching Espresso..."); log.Info("Launching Espresso...");
log.Info(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); log.Info(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
log.Debug($"OS: {Environment.OSVersion}"); log.Debug($"OS: {Environment.OSVersion}");
@@ -158,7 +159,7 @@ namespace Espresso.Shell
// Initially the file might not be updated, so we need to start processing // Initially the file might not be updated, so we need to start processing
// settings right away. // settings right away.
ProcessSettings(config); ProcessSettings();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -171,23 +172,12 @@ namespace Espresso.Shell
{ {
if (timeLimit <= 0) if (timeLimit <= 0)
{ {
// Indefinite keep awake. SetupIndefiniteKeepAwake(displayOn);
bool success = APIHelper.SetIndefiniteKeepAwake(displayOn);
if (success)
{
log.Info($"Currently in indefinite keep awake. Display always on: {displayOn}");
}
else
{
var errorMessage = "Could not set up the state to be indefinite keep awake.";
log.Info(errorMessage);
log.Debug(errorMessage);
}
} }
else else
{ {
// Timed keep-awake. // Timed keep-awake.
APIHelper.SetTimedKeepAwake(timeLimit, LogTimedKeepAwakeCompletion, LogUnexpectedOrCancelledKeepAwakeCompletion, displayOn); SetupTimedKeepAwake(timeLimit, displayOn);
} }
} }
@@ -199,87 +189,78 @@ namespace Espresso.Shell
}); });
} }
TrayHelper.InitializeEspressoTray("Espresso", EspressoMode.INDEFINITE, true, new Action(()=>Console.WriteLine("test")), new Action(() => Console.WriteLine("test"))); #pragma warning disable CS8604 // Possible null reference argument.
TrayHelper.InitializeTray(appName, APIHelper.Extract("shell32.dll", 21, true), null);
#pragma warning restore CS8604 // Possible null reference argument.
new ManualResetEvent(false).WaitOne(); new ManualResetEvent(false).WaitOne();
} }
private static void SetupIndefiniteKeepAwake(bool displayOn)
{
// Indefinite keep awake.
bool success = APIHelper.SetIndefiniteKeepAwake(displayOn);
if (success)
{
log.Info($"Currently in indefinite keep awake. Display always on: {displayOn}");
}
else
{
var errorMessage = "Could not set up the state to be indefinite keep awake.";
log.Info(errorMessage);
log.Debug(errorMessage);
}
}
private static void HandleEspressoConfigChange(FileSystemEventArgs fileEvent) private static void HandleEspressoConfigChange(FileSystemEventArgs fileEvent)
{ {
log.Info("Detected a settings file change. Updating configuration..."); log.Info("Detected a settings file change. Updating configuration...");
log.Info("Resetting keep-awake to normal state due to settings change."); log.Info("Resetting keep-awake to normal state due to settings change.");
ResetNormalPowerState(); ResetNormalPowerState();
ProcessSettings(fileEvent.FullPath); ProcessSettings();
} }
private static void ProcessSettings(string fullPath) private static void ProcessSettings()
{ {
try try
{ {
EspressoSettingsModel settings = null; EspressoSettings settings = settingsUtils.GetSettings<EspressoSettings>(appName);
var fileStream = SettingsHelper.GetSettingsFile(fullPath, 3); if (settings != null)
if (fileStream != null)
{ {
using (fileStream) // If the settings were successfully processed, we need to set the right mode of operation.
// INDEFINITE = 0
// TIMED = 1
switch (settings.Properties.Mode)
{ {
using StreamReader reader = new StreamReader(fileStream); case EspressoMode.INDEFINITE:
{
// Indefinite keep awake.
SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn.Value);
break;
}
case EspressoMode.TIMED:
{
// Timed keep-awake.
long computedTime = (settings.Properties.Hours.Value * 60 * 60) + (settings.Properties.Minutes.Value * 60);
SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn.Value);
settings = JsonConvert.DeserializeObject<EspressoSettingsModel>(reader.ReadToEnd()); break;
}
default:
{
var errorMessage = "Unknown mode of operation. Check config file.";
log.Info(errorMessage);
log.Debug(errorMessage);
break;
}
} }
if (settings != null) TrayHelper.SetTray(appName, settings);
{
// If the settings were successfully processed, we need to set the right mode of operation.
// INDEFINITE = 0
// TIMED = 1
switch (settings.Properties.Mode)
{
case EspressoMode.INDEFINITE:
{
// Indefinite keep awake.
bool success = APIHelper.SetIndefiniteKeepAwake(settings.Properties.KeepDisplayOn.Value);
if (success)
{
log.Info($"Indefinite keep-awake. Display always on: {settings.Properties.KeepDisplayOn.Value}");
}
else
{
var errorMessage = "Could not set up the state to be indefinite keep-awake.";
log.Info(errorMessage);
log.Debug(errorMessage);
}
break;
}
case EspressoMode.TIMED:
{
// Timed keep-awake.
long computedTime = (settings.Properties.Hours.Value * 60 * 60) + (settings.Properties.Minutes.Value * 60);
log.Info($"Timed keep-awake. Expected runtime: {computedTime} seconds.");
APIHelper.SetTimedKeepAwake(computedTime, LogTimedKeepAwakeCompletion, LogUnexpectedOrCancelledKeepAwakeCompletion, settings.Properties.KeepDisplayOn.Value);
break;
}
default:
{
var errorMessage= "Unknown mode of operation. Check config file.";
log.Info(errorMessage);
log.Debug(errorMessage);
break;
}
}
}
else
{
var errorMessage = "Settings are null.";
log.Info(errorMessage);
log.Debug(errorMessage);
}
} }
else else
{ {
var errorMessage = "Could not get handle on file."; var errorMessage = "Settings are null.";
log.Info(errorMessage); log.Info(errorMessage);
log.Debug(errorMessage); log.Debug(errorMessage);
} }
@@ -289,10 +270,16 @@ namespace Espresso.Shell
var errorMessage = $"There was a problem reading the configuration file. Error: {ex.Message}"; var errorMessage = $"There was a problem reading the configuration file. Error: {ex.Message}";
log.Info(errorMessage); log.Info(errorMessage);
log.Debug(errorMessage); log.Debug(errorMessage);
log.Debug($"Configuration path: {fullPath}");
} }
} }
private static void SetupTimedKeepAwake(long time, bool displayOn)
{
log.Info($"Timed keep-awake. Expected runtime: {time} seconds.");
APIHelper.SetTimedKeepAwake(time, LogTimedKeepAwakeCompletion, LogUnexpectedOrCancelledKeepAwakeCompletion, displayOn);
}
private static void LogUnexpectedOrCancelledKeepAwakeCompletion() private static void LogUnexpectedOrCancelledKeepAwakeCompletion()
{ {
var errorMessage = "The keep-awake thread was terminated early."; var errorMessage = "The keep-awake thread was terminated early.";