2021-04-23 08:11:32 -07:00
// 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 Espresso.Shell.Core ;
2021-05-03 08:04:53 -07:00
using ManagedCommon ;
2021-05-09 22:25:52 -07:00
using Microsoft.PowerToys.Settings.UI.Library ;
2021-04-20 08:31:18 -07:00
using Newtonsoft.Json ;
2021-04-29 08:02:24 -07:00
using NLog ;
2021-04-08 15:12:09 -07:00
using System ;
using System.CommandLine ;
using System.CommandLine.Invocation ;
2021-04-29 08:02:24 -07:00
using System.Diagnostics ;
2021-04-20 07:54:03 -07:00
using System.IO ;
2021-04-28 18:27:42 -07:00
using System.Linq ;
using System.Reactive.Concurrency ;
using System.Reactive.Linq ;
2021-04-29 08:02:24 -07:00
using System.Reflection ;
2021-04-08 15:12:09 -07:00
using System.Threading ;
2021-05-01 11:04:47 -07:00
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
2021-04-08 15:12:09 -07:00
namespace Espresso.Shell
{
class Program
{
2021-05-01 11:04:47 -07:00
private static Mutex ? mutex = null ;
2021-04-08 15:12:09 -07:00
private const string appName = "Espresso" ;
2021-05-01 11:04:47 -07:00
private static FileSystemWatcher ? watcher = null ;
2021-05-09 22:25:52 -07:00
private static SettingsUtils ? settingsUtils = null ;
2021-05-01 11:04:47 -07:00
2021-04-23 08:11:32 -07:00
public static Mutex Mutex { get = > mutex ; set = > mutex = value ; }
2021-04-22 08:14:07 -07:00
2021-05-01 11:04:47 -07:00
private static Logger ? log ;
2021-04-29 08:02:24 -07:00
2021-04-08 15:12:09 -07:00
static int Main ( string [ ] args )
{
bool instantiated ;
2021-04-23 08:11:32 -07:00
Mutex = new Mutex ( true , appName , out instantiated ) ;
2021-04-08 15:12:09 -07:00
if ( ! instantiated )
{
2021-04-20 08:31:18 -07:00
ForceExit ( appName + " is already running! Exiting the application." , 1 ) ;
2021-04-08 15:12:09 -07:00
}
2021-04-29 08:02:24 -07:00
log = LogManager . GetCurrentClassLogger ( ) ;
2021-05-09 22:25:52 -07:00
settingsUtils = new SettingsUtils ( ) ;
2021-04-29 08:02:24 -07:00
log . Info ( "Launching Espresso..." ) ;
log . Info ( FileVersionInfo . GetVersionInfo ( Assembly . GetExecutingAssembly ( ) . Location ) . FileVersion ) ;
2021-04-29 08:18:35 -07:00
log . Debug ( $"OS: {Environment.OSVersion}" ) ;
log . Debug ( $"OS Build: {APIHelper.GetOperatingSystemBuild()}" ) ;
2021-04-08 15:12:09 -07:00
2021-04-20 07:54:03 -07:00
var configOption = new Option < string > (
aliases : new [ ] { "--config" , "-c" } ,
getDefaultValue : ( ) = > string . Empty ,
description : "Pointer to a PowerToys configuration file that the tool will be watching for changes. All other options are disregarded if config is used." )
{
Argument = new Argument < string > ( ( ) = > string . Empty )
{
Arity = ArgumentArity . ZeroOrOne ,
} ,
} ;
configOption . Required = false ;
2021-04-08 16:09:59 -07:00
var displayOption = new Option < bool > (
aliases : new [ ] { "--display-on" , "-d" } ,
getDefaultValue : ( ) = > true ,
description : "Determines whether the display should be kept awake." )
2021-04-08 15:12:09 -07:00
{
2021-04-08 16:09:59 -07:00
Argument = new Argument < bool > ( ( ) = > false )
{
Arity = ArgumentArity . ZeroOrOne ,
} ,
} ;
displayOption . Required = false ;
var timeOption = new Option < long > (
aliases : new [ ] { "--time-limit" , "-t" } ,
2021-04-08 15:12:09 -07:00
getDefaultValue : ( ) = > 0 ,
description : "Determines the interval, in seconds, during which the computer is kept awake." )
2021-04-08 16:09:59 -07:00
{
Argument = new Argument < long > ( ( ) = > 0 )
{
Arity = ArgumentArity . ExactlyOne ,
} ,
} ;
timeOption . Required = false ;
2021-05-04 08:21:58 -07:00
var pidOption = new Option < int > (
aliases : new [ ] { "--pid" , "-p" } ,
2021-05-03 08:04:53 -07:00
getDefaultValue : ( ) = > 0 ,
2021-05-04 08:21:58 -07:00
description : "Bind the execution of Espresso to another process." )
2021-05-03 08:04:53 -07:00
{
Argument = new Argument < int > ( ( ) = > 0 )
{
Arity = ArgumentArity . ZeroOrOne ,
} ,
} ;
2021-05-04 08:21:58 -07:00
pidOption . Required = false ;
2021-05-03 08:04:53 -07:00
2021-04-08 16:09:59 -07:00
var rootCommand = new RootCommand
{
2021-04-20 07:54:03 -07:00
configOption ,
2021-04-08 16:09:59 -07:00
displayOption ,
2021-05-03 08:04:53 -07:00
timeOption ,
2021-05-04 08:21:58 -07:00
pidOption
2021-04-08 15:12:09 -07:00
} ;
rootCommand . Description = appName ;
2021-05-03 08:04:53 -07:00
rootCommand . Handler = CommandHandler . Create < string , bool , long , int > ( HandleCommandLineArguments ) ;
2021-04-08 15:12:09 -07:00
return rootCommand . InvokeAsync ( args ) . Result ;
}
2021-04-20 08:31:18 -07:00
private static void ForceExit ( string message , int exitCode )
{
2021-04-29 08:02:24 -07:00
log . Debug ( message ) ;
log . Info ( message ) ;
2021-04-20 08:31:18 -07:00
Console . ReadKey ( ) ;
Environment . Exit ( exitCode ) ;
}
2021-05-04 08:21:58 -07:00
private static void HandleCommandLineArguments ( string config , bool displayOn , long timeLimit , int pid )
2021-04-08 15:12:09 -07:00
{
2021-04-29 08:18:35 -07:00
log . Info ( $"The value for --config is: {config}" ) ;
2021-04-29 08:02:24 -07:00
log . Info ( $"The value for --display-on is: {displayOn}" ) ;
log . Info ( $"The value for --time-limit is: {timeLimit}" ) ;
2021-05-04 08:21:58 -07:00
log . Info ( $"The value for --pid is: {pid}" ) ;
2021-04-20 07:54:03 -07:00
if ( ! string . IsNullOrWhiteSpace ( config ) )
2021-04-08 15:12:09 -07:00
{
2021-04-20 07:54:03 -07:00
// Configuration file is used, therefore we disregard any other command-line parameter
// and instead watch for changes in the file.
2021-04-20 08:31:18 -07:00
try
{
2021-04-23 08:47:18 -07:00
watcher = new FileSystemWatcher
2021-04-20 08:31:18 -07:00
{
Path = Path . GetDirectoryName ( config ) ,
EnableRaisingEvents = true ,
2021-04-24 11:06:13 -07:00
NotifyFilter = NotifyFilters . LastWrite ,
2021-04-20 08:31:18 -07:00
Filter = Path . GetFileName ( config )
} ;
2021-04-24 11:06:13 -07:00
2021-04-28 18:27:42 -07:00
Observable . FromEventPattern < FileSystemEventHandler , FileSystemEventArgs > (
h = > watcher . Changed + = h ,
h = > watcher . Changed - = h
)
. SubscribeOn ( TaskPoolScheduler . Default )
. Select ( e = > e . EventArgs )
. Throttle ( TimeSpan . FromMilliseconds ( 25 ) )
. Subscribe ( HandleEspressoConfigChange ) ;
2021-04-22 08:14:07 -07:00
// Initially the file might not be updated, so we need to start processing
// settings right away.
2021-05-09 22:25:52 -07:00
ProcessSettings ( ) ;
2021-04-20 08:31:18 -07:00
}
catch ( Exception ex )
2021-04-08 15:12:09 -07:00
{
2021-04-29 08:02:24 -07:00
var errorString = $"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}" ;
log . Info ( errorString ) ;
log . Debug ( errorString ) ;
2021-04-20 08:31:18 -07:00
}
2021-04-08 15:12:09 -07:00
}
2021-04-08 16:30:56 -07:00
else
{
2021-04-20 07:54:03 -07:00
if ( timeLimit < = 0 )
2021-04-08 16:30:56 -07:00
{
2021-05-09 22:25:52 -07:00
SetupIndefiniteKeepAwake ( displayOn ) ;
2021-04-08 16:30:56 -07:00
}
else
{
2021-04-20 07:54:03 -07:00
// Timed keep-awake.
2021-05-09 22:25:52 -07:00
SetupTimedKeepAwake ( timeLimit , displayOn ) ;
2021-04-08 16:30:56 -07:00
}
}
2021-04-08 15:12:09 -07:00
2021-05-04 08:21:58 -07:00
if ( pid ! = 0 )
{
RunnerHelper . WaitForPowerToysRunner ( pid , ( ) = >
{
Environment . Exit ( 0 ) ;
} ) ;
}
2021-05-09 22:25:52 -07:00
#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.
2021-05-05 07:52:08 -07:00
2021-04-08 15:12:09 -07:00
new ManualResetEvent ( false ) . WaitOne ( ) ;
}
2021-04-20 07:54:03 -07:00
2021-05-09 22:25:52 -07:00
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 ) ;
}
}
2021-04-28 18:27:42 -07:00
private static void HandleEspressoConfigChange ( FileSystemEventArgs fileEvent )
2021-04-22 08:14:07 -07:00
{
2021-04-29 08:02:24 -07:00
log . Info ( "Detected a settings file change. Updating configuration..." ) ;
log . Info ( "Resetting keep-awake to normal state due to settings change." ) ;
2021-04-24 09:59:52 -07:00
ResetNormalPowerState ( ) ;
2021-05-09 22:25:52 -07:00
ProcessSettings ( ) ;
2021-04-24 11:06:13 -07:00
}
2021-05-09 22:25:52 -07:00
private static void ProcessSettings ( )
2021-04-20 07:54:03 -07:00
{
2021-04-20 08:31:18 -07:00
try
{
2021-05-09 22:25:52 -07:00
EspressoSettings settings = settingsUtils . GetSettings < EspressoSettings > ( appName ) ;
2021-04-20 08:31:18 -07:00
2021-05-09 22:25:52 -07:00
if ( settings ! = null )
2021-04-20 08:31:18 -07:00
{
2021-05-09 22:25:52 -07:00
// If the settings were successfully processed, we need to set the right mode of operation.
// INDEFINITE = 0
// TIMED = 1
switch ( settings . Properties . Mode )
2021-04-22 08:14:07 -07:00
{
2021-05-09 22:25:52 -07:00
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 ) ;
break ;
}
default :
{
var errorMessage = "Unknown mode of operation. Check config file." ;
log . Info ( errorMessage ) ;
log . Debug ( errorMessage ) ;
break ;
}
2021-04-22 08:14:07 -07:00
}
2021-05-09 22:25:52 -07:00
TrayHelper . SetTray ( appName , settings ) ;
2021-04-22 08:14:07 -07:00
}
else
{
2021-05-09 22:25:52 -07:00
var errorMessage = "Settings are null." ;
2021-04-29 08:02:24 -07:00
log . Info ( errorMessage ) ;
log . Debug ( errorMessage ) ;
2021-04-20 08:31:18 -07:00
}
}
catch ( Exception ex )
{
2021-04-29 08:18:35 -07:00
var errorMessage = $"There was a problem reading the configuration file. Error: {ex.Message}" ;
2021-04-29 08:02:24 -07:00
log . Info ( errorMessage ) ;
log . Debug ( errorMessage ) ;
2021-04-20 08:31:18 -07:00
}
}
2021-05-09 22:25:52 -07:00
private static void SetupTimedKeepAwake ( long time , bool displayOn )
{
log . Info ( $"Timed keep-awake. Expected runtime: {time} seconds." ) ;
APIHelper . SetTimedKeepAwake ( time , LogTimedKeepAwakeCompletion , LogUnexpectedOrCancelledKeepAwakeCompletion , displayOn ) ;
}
2021-04-24 10:07:42 -07:00
private static void LogUnexpectedOrCancelledKeepAwakeCompletion ( )
{
2021-04-29 08:02:24 -07:00
var errorMessage = "The keep-awake thread was terminated early." ;
log . Info ( errorMessage ) ;
log . Debug ( errorMessage ) ;
2021-04-24 10:07:42 -07:00
}
2021-04-24 09:59:52 -07:00
private static void LogTimedKeepAwakeCompletion ( bool result )
{
2021-04-29 08:02:24 -07:00
log . Info ( $"Completed timed keep-awake successfully: {result}" ) ;
2021-04-24 09:59:52 -07:00
}
2021-04-20 08:31:18 -07:00
private static void ResetNormalPowerState ( )
{
bool success = APIHelper . SetNormalKeepAwake ( ) ;
if ( success )
{
2021-04-29 08:02:24 -07:00
log . Info ( "Returned to normal keep-awake state." ) ;
2021-04-20 08:31:18 -07:00
}
else
{
2021-04-29 08:02:24 -07:00
var errorMessage = "Could not return to normal keep-awake state." ;
log . Info ( errorMessage ) ;
log . Debug ( errorMessage ) ;
2021-04-20 08:31:18 -07:00
}
2021-04-20 07:54:03 -07:00
}
2021-04-08 15:12:09 -07:00
}
}