Merge pull request #42 from That-One-Nerd/shell-systems
Added the ability to cancel stuff.
This commit is contained in:
commit
21b7436f0e
@ -3,6 +3,7 @@ global using SharpCompress.Archives.Rar;
|
|||||||
global using SharpCompress.Archives.SevenZip;
|
global using SharpCompress.Archives.SevenZip;
|
||||||
global using SharpCompress.Readers;
|
global using SharpCompress.Readers;
|
||||||
global using SrcMod.Shell;
|
global using SrcMod.Shell;
|
||||||
|
global using SrcMod.Shell.Interop;
|
||||||
global using SrcMod.Shell.Modules.ObjectModels;
|
global using SrcMod.Shell.Modules.ObjectModels;
|
||||||
global using System;
|
global using System;
|
||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
|
|||||||
25
SrcMod/Shell/Interop/Winmm.cs
Normal file
25
SrcMod/Shell/Interop/Winmm.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace SrcMod.Shell.Interop;
|
||||||
|
|
||||||
|
internal static partial class Winmm
|
||||||
|
{
|
||||||
|
[LibraryImport("winmm.dll")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static partial bool PlaySound([MarshalAs(UnmanagedType.LPStr)] string pszSound, nint hMod, uint fdwSound);
|
||||||
|
|
||||||
|
public enum PlaySoundFlags : uint
|
||||||
|
{
|
||||||
|
Sync = 0x00000000,
|
||||||
|
Async = 0x00000001,
|
||||||
|
NoDefault = 0x00000002,
|
||||||
|
Memory = 0x00000004,
|
||||||
|
Loop = 0x00000008,
|
||||||
|
NoStop = 0x00000010,
|
||||||
|
Purge = 0x00000040,
|
||||||
|
Application = 0x00000080,
|
||||||
|
NoWait = 0x00002000,
|
||||||
|
Alias = 0x00010000,
|
||||||
|
FileName = 0x00020000,
|
||||||
|
Resource = 0x00040000,
|
||||||
|
AliasId = 0x00100000,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,4 @@
|
|||||||
using SrcMod.Shell.Interop;
|
namespace SrcMod.Shell.Modules;
|
||||||
|
|
||||||
namespace SrcMod.Shell.Modules;
|
|
||||||
|
|
||||||
[Module("clipboard")]
|
[Module("clipboard")]
|
||||||
public static class ClipboardModule
|
public static class ClipboardModule
|
||||||
|
|||||||
12
SrcMod/Shell/Modules/ObjectModels/CanCancelAttribute.cs
Normal file
12
SrcMod/Shell/Modules/ObjectModels/CanCancelAttribute.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace SrcMod.Shell.Modules.ObjectModels;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
||||||
|
public class CanCancelAttribute : Attribute
|
||||||
|
{
|
||||||
|
public readonly bool CanCancel;
|
||||||
|
|
||||||
|
public CanCancelAttribute(bool canCancel)
|
||||||
|
{
|
||||||
|
CanCancel = canCancel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
public class CommandInfo
|
public class CommandInfo
|
||||||
{
|
{
|
||||||
|
public bool CanBeCancelled { get; private set; }
|
||||||
public required ModuleInfo Module { get; init; }
|
public required ModuleInfo Module { get; init; }
|
||||||
public required MethodInfo Method { get; init; }
|
public required MethodInfo Method { get; init; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
@ -11,6 +12,7 @@ public class CommandInfo
|
|||||||
|
|
||||||
private CommandInfo()
|
private CommandInfo()
|
||||||
{
|
{
|
||||||
|
CanBeCancelled = false;
|
||||||
Name = string.Empty;
|
Name = string.Empty;
|
||||||
NameId = string.Empty;
|
NameId = string.Empty;
|
||||||
Parameters = Array.Empty<ParameterInfo>();
|
Parameters = Array.Empty<ParameterInfo>();
|
||||||
@ -34,10 +36,13 @@ public class CommandInfo
|
|||||||
CommandAttribute[] attributes = info.GetCustomAttributes<CommandAttribute>().ToArray();
|
CommandAttribute[] attributes = info.GetCustomAttributes<CommandAttribute>().ToArray();
|
||||||
if (attributes.Length <= 0) return Array.Empty<CommandInfo>();
|
if (attributes.Length <= 0) return Array.Empty<CommandInfo>();
|
||||||
|
|
||||||
|
CanCancelAttribute? cancel = info.GetCustomAttribute<CanCancelAttribute>();
|
||||||
|
|
||||||
foreach (CommandAttribute attribute in attributes)
|
foreach (CommandAttribute attribute in attributes)
|
||||||
{
|
{
|
||||||
commands.Add(new()
|
commands.Add(new()
|
||||||
{
|
{
|
||||||
|
CanBeCancelled = cancel is null || cancel.CanCancel,
|
||||||
Method = info,
|
Method = info,
|
||||||
Module = parentModule,
|
Module = parentModule,
|
||||||
Name = info.Name,
|
Name = info.Name,
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
namespace SrcMod.Shell;
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace SrcMod.Shell;
|
||||||
|
|
||||||
public class Shell
|
public class Shell
|
||||||
{
|
{
|
||||||
public const string Author = "That_One_Nerd";
|
public const string Author = "That_One_Nerd";
|
||||||
public const string Name = "SrcMod";
|
public const string Name = "SrcMod";
|
||||||
public const string Version = "Alpha 0.3.0";
|
public const string Version = "Alpha 0.3.1";
|
||||||
|
|
||||||
public readonly string? ShellDirectory;
|
public readonly string? ShellDirectory;
|
||||||
|
|
||||||
@ -16,6 +18,11 @@ public class Shell
|
|||||||
public List<HistoryItem> History;
|
public List<HistoryItem> History;
|
||||||
public string WorkingDirectory;
|
public string WorkingDirectory;
|
||||||
|
|
||||||
|
private bool lastCancel;
|
||||||
|
private bool printedCancel;
|
||||||
|
|
||||||
|
private BackgroundWorker? activeCommand;
|
||||||
|
|
||||||
public Shell()
|
public Shell()
|
||||||
{
|
{
|
||||||
Console.CursorVisible = false;
|
Console.CursorVisible = false;
|
||||||
@ -79,6 +86,10 @@ public class Shell
|
|||||||
Write(" by ", ConsoleColor.White, false);
|
Write(" by ", ConsoleColor.White, false);
|
||||||
Write($"{Author}", ConsoleColor.DarkYellow);
|
Write($"{Author}", ConsoleColor.DarkYellow);
|
||||||
|
|
||||||
|
lastCancel = false;
|
||||||
|
activeCommand = null;
|
||||||
|
Console.CancelKeyPress += HandleCancel;
|
||||||
|
|
||||||
ActiveGame = null;
|
ActiveGame = null;
|
||||||
|
|
||||||
ReloadDirectoryInfo();
|
ReloadDirectoryInfo();
|
||||||
@ -106,8 +117,6 @@ public class Shell
|
|||||||
|
|
||||||
public string ReadLine()
|
public string ReadLine()
|
||||||
{
|
{
|
||||||
Console.CursorVisible = true;
|
|
||||||
|
|
||||||
Write($"\n{WorkingDirectory}", ConsoleColor.DarkGreen, false);
|
Write($"\n{WorkingDirectory}", ConsoleColor.DarkGreen, false);
|
||||||
if (ActiveGame is not null) Write($" {ActiveGame}", ConsoleColor.DarkYellow, false);
|
if (ActiveGame is not null) Write($" {ActiveGame}", ConsoleColor.DarkYellow, false);
|
||||||
if (ActiveMod is not null) Write($" {ActiveMod}", ConsoleColor.Magenta, false);
|
if (ActiveMod is not null) Write($" {ActiveMod}", ConsoleColor.Magenta, false);
|
||||||
@ -116,17 +125,52 @@ public class Shell
|
|||||||
Write($" {Name}", ConsoleColor.DarkCyan, false);
|
Write($" {Name}", ConsoleColor.DarkCyan, false);
|
||||||
Write(" > ", ConsoleColor.White, false);
|
Write(" > ", ConsoleColor.White, false);
|
||||||
|
|
||||||
|
bool printed = false;
|
||||||
|
|
||||||
|
if (lastCancel && !printedCancel)
|
||||||
|
{
|
||||||
|
// Print the warning. A little bit of mess because execution must
|
||||||
|
// continue without funny printing errors but it's alright I guess.
|
||||||
|
|
||||||
|
int originalLeft = Console.CursorLeft;
|
||||||
|
|
||||||
|
Console.CursorTop -= 3;
|
||||||
|
Write("Press ^C again to exit the shell.", ConsoleColor.Red);
|
||||||
|
PlayWarningSound();
|
||||||
|
|
||||||
|
printedCancel = true;
|
||||||
|
Console.CursorTop += 2;
|
||||||
|
|
||||||
|
Console.CursorLeft = originalLeft;
|
||||||
|
printed = true;
|
||||||
|
}
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.White;
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
Console.CursorVisible = true;
|
||||||
string message = Console.ReadLine()!;
|
string message = Console.ReadLine()!;
|
||||||
|
Console.CursorVisible = false;
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
|
||||||
Console.CursorVisible = false;
|
if (!printed)
|
||||||
|
{
|
||||||
|
lastCancel = false;
|
||||||
|
printedCancel = false;
|
||||||
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvokeCommand(string cmd)
|
public void InvokeCommand(string cmd)
|
||||||
{
|
{
|
||||||
|
if (cmd is null)
|
||||||
|
{
|
||||||
|
// This usually won't happen, but might if for example
|
||||||
|
// the shell cancel interrupt is called. This probably
|
||||||
|
// happens for other shell interrupts are called.
|
||||||
|
Write(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<string> parts = new();
|
List<string> parts = new();
|
||||||
string active = string.Empty;
|
string active = string.Empty;
|
||||||
|
|
||||||
@ -171,24 +215,41 @@ public class Shell
|
|||||||
int start = module.NameIsPrefix ? 2 : 1;
|
int start = module.NameIsPrefix ? 2 : 1;
|
||||||
string[] args = parts.GetRange(start, parts.Count - start).ToArray();
|
string[] args = parts.GetRange(start, parts.Count - start).ToArray();
|
||||||
|
|
||||||
|
void runCommand(object? sender, DoWorkEventArgs e)
|
||||||
|
{
|
||||||
#if RELEASE
|
#if RELEASE
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
command.Invoke(args);
|
command.Invoke(args);
|
||||||
#if RELEASE
|
#if RELEASE
|
||||||
}
|
}
|
||||||
catch (TargetInvocationException ex)
|
catch (TargetInvocationException ex)
|
||||||
{
|
{
|
||||||
Write($"[ERROR] {ex.InnerException!.Message}", ConsoleColor.Red);
|
Write($"[ERROR] {ex.InnerException!.Message}", ConsoleColor.Red);
|
||||||
if (LoadingBarEnabled) LoadingBarEnd();
|
if (LoadingBarEnabled) LoadingBarEnd();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Write($"[ERROR] {ex.Message}", ConsoleColor.Red);
|
Write($"[ERROR] {ex.Message}", ConsoleColor.Red);
|
||||||
if (LoadingBarEnabled) LoadingBarEnd();
|
if (LoadingBarEnabled) LoadingBarEnd();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
activeCommand = new();
|
||||||
|
activeCommand.DoWork += runCommand;
|
||||||
|
activeCommand.RunWorkerAsync();
|
||||||
|
|
||||||
|
activeCommand.WorkerSupportsCancellation = command.CanBeCancelled;
|
||||||
|
|
||||||
|
while (activeCommand is not null && activeCommand.IsBusy) Thread.Yield();
|
||||||
|
|
||||||
|
if (activeCommand is not null)
|
||||||
|
{
|
||||||
|
activeCommand.Dispose();
|
||||||
|
activeCommand = null;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,6 +257,17 @@ public class Shell
|
|||||||
Write($"[ERROR] Could not find command \"{cmd}\".", ConsoleColor.Red);
|
Write($"[ERROR] Could not find command \"{cmd}\".", ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void PlayErrorSound()
|
||||||
|
{
|
||||||
|
Winmm.PlaySound("SystemHand", nint.Zero,
|
||||||
|
(uint)(Winmm.PlaySoundFlags.Alias | Winmm.PlaySoundFlags.Async));
|
||||||
|
}
|
||||||
|
private static void PlayWarningSound()
|
||||||
|
{
|
||||||
|
Winmm.PlaySound("SystemAsterisk", nint.Zero,
|
||||||
|
(uint)(Winmm.PlaySoundFlags.Alias | Winmm.PlaySoundFlags.Async));
|
||||||
|
}
|
||||||
|
|
||||||
public void ReloadDirectoryInfo()
|
public void ReloadDirectoryInfo()
|
||||||
{
|
{
|
||||||
ActiveMod = Mod.ReadDirectory(WorkingDirectory);
|
ActiveMod = Mod.ReadDirectory(WorkingDirectory);
|
||||||
@ -205,4 +277,44 @@ public class Shell
|
|||||||
if (ActiveMod is not null) title += $" - {ActiveMod.Name}";
|
if (ActiveMod is not null) title += $" - {ActiveMod.Name}";
|
||||||
Console.Title = title;
|
Console.Title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleCancel(object? sender, ConsoleCancelEventArgs args)
|
||||||
|
{
|
||||||
|
if (activeCommand is not null && activeCommand.IsBusy)
|
||||||
|
{
|
||||||
|
if (activeCommand.WorkerSupportsCancellation)
|
||||||
|
{
|
||||||
|
// Kill the active command.
|
||||||
|
activeCommand.CancelAsync();
|
||||||
|
activeCommand.Dispose();
|
||||||
|
activeCommand = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Command doesn't support cancellation.
|
||||||
|
// Warn the user.
|
||||||
|
PlayErrorSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCancel = false;
|
||||||
|
printedCancel = false;
|
||||||
|
args.Cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to some funny multithreading issues, we want to make the warning label
|
||||||
|
// single-threaded on the shell.
|
||||||
|
if (!lastCancel)
|
||||||
|
{
|
||||||
|
// Enable the warning. The "ReadLine" method will do the rest.
|
||||||
|
lastCancel = true;
|
||||||
|
args.Cancel = true; // "Cancel" referring to the cancellation of the cancel operation.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually kill the shell. We do still have to worry about some multithreaded
|
||||||
|
// nonsense, but a bearable amount of it.
|
||||||
|
Console.ResetColor();
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user