Making sure the app can run well without a console window

Only applicable when a PID is specified.
This commit is contained in:
Den Delimarsky 2021-05-11 20:50:02 -07:00
parent 95bac54c0e
commit ff486e5a61
No known key found for this signature in database
GPG key ID: E1BE1355085F0BCF
4 changed files with 66 additions and 19 deletions

View file

@ -3,12 +3,15 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using NLog;
#pragma warning disable SA1116 // Split parameters should start on line after declaration
namespace Espresso.Shell.Core
{
[Flags]
@ -27,6 +30,11 @@ namespace Espresso.Shell.Core
public class APIHelper
{
private const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
public const int StdOutputHandle = -11;
public const int StdInputHandle = -10;
public const int StdErrorHandle = -12;
public const uint GenericWrite = 0x40000000;
public const uint GenericRead = 0x80000000;
private static readonly Logger _log;
private static CancellationTokenSource _tokenSource;
@ -36,12 +44,45 @@ namespace Espresso.Shell.Core
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile([MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] uint access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
static APIHelper()
{
_log = LogManager.GetCurrentClassLogger();
_tokenSource = new CancellationTokenSource();
}
public static void AllocateConsole()
{
AllocConsole();
var outputHandle = GetStdHandle(StdOutputHandle);
var outputFilePointer = CreateFile("CONOUT$", GenericRead | GenericWrite, FileShare.Write, IntPtr.Zero, FileMode.OpenOrCreate, 0, IntPtr.Zero);
if (outputFilePointer != outputHandle)
{
SetStdHandle(StdOutputHandle, outputFilePointer);
Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true });
}
}
/// <summary>
/// Sets the computer awake state using the native Win32 SetThreadExecutionState API. This
/// function is just a nice-to-have wrapper that helps avoid tracking the success or failure of

View file

@ -63,45 +63,45 @@ namespace Espresso.Shell.Core
};
}
private static Action KeepDisplayOnCallback(string text)
private static Action KeepDisplayOnCallback(string moduleName)
{
return () =>
{
// Just changing the display mode.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(moduleName);
currentSettings.Properties.KeepDisplayOn.Value = !currentSettings.Properties.KeepDisplayOn.Value;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
};
}
private static Action<int, int> TimedKeepAwakeCallback(string text)
private static Action<int, int> TimedKeepAwakeCallback(string moduleName)
{
return (hours, minutes) =>
{
// Set timed keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(moduleName);
currentSettings.Properties.Mode = EspressoMode.TIMED;
currentSettings.Properties.Hours.Value = hours;
currentSettings.Properties.Minutes.Value = minutes;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
};
}
private static Action IndefiniteKeepAwakeCallback(string text)
private static Action IndefiniteKeepAwakeCallback(string moduleName)
{
return () =>
{
// Set indefinite keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(moduleName);
currentSettings.Properties.Mode = EspressoMode.INDEFINITE;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
};
}
internal static void SetTray(string text, bool keepDisplayOn, EspressoMode mode, Action indefiniteKeepAwakeCallback, Action<int, int> timedKeepAwakeCallback, Action keepDisplayOnCallback, Action exitCallback)
public static void SetTray(string text, bool keepDisplayOn, EspressoMode mode, Action indefiniteKeepAwakeCallback, Action<int, int> timedKeepAwakeCallback, Action keepDisplayOnCallback, Action exitCallback)
{
var contextMenuStrip = new ContextMenuStrip();

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\Espresso</OutputPath>
<Nullable>enable</Nullable>

View file

@ -55,10 +55,10 @@ namespace Espresso.Shell
var configOption = new Option<bool>(
aliases: new[] { "--use-pt-config", "-c" },
getDefaultValue: () => true,
getDefaultValue: () => false,
description: "Specifies whether Espresso will be using the PowerToys configuration file for managing the state.")
{
Argument = new Argument<bool>(() => true)
Argument = new Argument<bool>(() => false)
{
Arity = ArgumentArity.ZeroOrOne,
},
@ -130,17 +130,22 @@ namespace Espresso.Shell
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, long timeLimit, int pid)
{
if (pid == 0)
{
APIHelper.AllocateConsole();
}
_log.Info($"The value for --use-pt-config is: {usePtConfig}");
_log.Info($"The value for --display-on is: {displayOn}");
_log.Info($"The value for --time-limit is: {timeLimit}");
_log.Info($"The value for --pid is: {pid}");
#pragma warning disable CS8604 // Possible null reference argument.
TrayHelper.InitializeTray(AppName, new Icon(Application.GetResourceStream(new Uri("/Images/Espresso.ico", UriKind.Relative)).Stream));
#pragma warning restore CS8604 // Possible null reference argument.
if (usePtConfig)
{
#pragma warning disable CS8604 // Possible null reference argument.
TrayHelper.InitializeTray(AppName, new Icon(Application.GetResourceStream(new Uri("/Images/Espresso.ico", UriKind.Relative)).Stream));
#pragma warning restore CS8604 // Possible null reference argument.
// Configuration file is used, therefore we disregard any other command-line parameter
// and instead watch for changes in the file.
try
@ -177,13 +182,14 @@ namespace Espresso.Shell
}
else
{
if (timeLimit <= 0)
var mode = timeLimit <= 0 ? EspressoMode.INDEFINITE : EspressoMode.TIMED;
if (mode == EspressoMode.INDEFINITE)
{
SetupIndefiniteKeepAwake(displayOn);
}
else
{
// Timed keep-awake.
SetupTimedKeepAwake(timeLimit, displayOn);
}
}