diff --git a/windows-terminal-quake/Native/TerminalProcess.cs b/windows-terminal-quake/Native/TerminalProcess.cs new file mode 100644 index 00000000..7326f473 --- /dev/null +++ b/windows-terminal-quake/Native/TerminalProcess.cs @@ -0,0 +1,110 @@ +using Polly; +using Polly.Retry; +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace WindowsTerminalQuake.Native +{ + public static class TerminalProcess + { + private static readonly RetryPolicy Retry = Policy + .Handle() + .WaitAndRetry(new[] + { + TimeSpan.FromMilliseconds(25), + TimeSpan.FromMilliseconds(50), + TimeSpan.FromMilliseconds(100), + TimeSpan.FromMilliseconds(250), + TimeSpan.FromMilliseconds(500) + }, + onRetry: (ex, t) => Log.Error($"Error creating process: '{ex.Message}'")); + + private static Process? _process; + + private static List _onExit = new List(); + + private static bool _isExitting; + + public static void OnExit(Action action) + { + _onExit.Add(action); + } + + private static void FireOnExit() + { + _isExitting = true; + + _onExit.ForEach(a => a()); + } + + public static Process Get() + { + return Retry.Execute(() => + { + if (_isExitting) return _process!; + + if (_process == null || _process.HasExited) + { + _process = GetOrCreate(); + } + + return _process; + }); + } + + private static Process GetOrCreate() + { + const string existingProcessName = "WindowsTerminal"; + const string newProcessName = "wt.exe"; + + var process = Process.GetProcessesByName(existingProcessName).FirstOrDefault(); + if (process == null) + { + process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = newProcessName, + WindowStyle = ProcessWindowStyle.Maximized + } + }; + + process.Start(); + process.WaitForInputIdle(); + + // After starting the process, just throw an exception so the process search gets restarted. + // The "wt.exe" process does some stuff to ultimately fire up a "WindowsTerminal" process, so we can't actually use the Process instance we just created. + throw new Exception($"Started process"); + } + + // Try the "nice way" of waiting for the process to become ready + process.Refresh(); + process.WaitForInputIdle(); + + Log.Information( + $"Got process with id '{process.Id}' and name '{process.ProcessName}' and title '{process.MainWindowTitle}'."); + + // Make sure the process has not exited + if (process.HasExited) throw new Exception($"Process existing."); + + // Make sure we can access the main window handle + // Note: Accessing mainWindowHandle already throws "Process has exited, so the requested information is not available." + if (process.MainWindowHandle == IntPtr.Zero) throw new Exception("Main window handle no accessible."); + + // Make sure the process name equals "WindowsTerminal", otherwise WT might still be starting + if (process.ProcessName != "WindowsTerminal") throw new Exception("Process name is not 'WindowsTerminal' yet."); + + // This is a way-too-specific check to further ensure the WT process is ready + if (process.MainWindowTitle == "DesktopWindowXamlSource") + throw new Exception($"Process still has temporary 'DesktopWindowXamlSource' window title."); + + process.EnableRaisingEvents = true; + process.Exited += (s, a) => FireOnExit(); + + return process; + } + } +} diff --git a/windows-terminal-quake/Program.cs b/windows-terminal-quake/Program.cs index 77f4527e..6c25aa47 100644 --- a/windows-terminal-quake/Program.cs +++ b/windows-terminal-quake/Program.cs @@ -1,10 +1,9 @@ -using System; -using System.Diagnostics; +using Serilog; +using System; using System.Linq; using System.Windows.Forms; using WindowsTerminalQuake.Native; using WindowsTerminalQuake.UI; -using Serilog; namespace WindowsTerminalQuake { @@ -21,18 +20,14 @@ public static void Main(string[] args) try { - var process = GetOrCreateWindowsTerminalProcess(); - process.EnableRaisingEvents = true; - process.Exited += (sender, e) => - { - Close(); - }; - _toggler = new Toggler(process); + TerminalProcess.OnExit(() => Close()); + + _toggler = new Toggler(); // Transparency Settings.Get(s => { - TransparentWindow.SetTransparent(process, s.Opacity); + TransparentWindow.SetTransparent(TerminalProcess.Get(), s.Opacity); }); var hks = string.Join(" or ", Settings.Instance.Hotkeys.Select(hk => $"{hk.Modifiers}+{hk.Key}")); @@ -48,43 +43,6 @@ public static void Main(string[] args) } } - private static Process GetOrCreateWindowsTerminalProcess() - { - var process = Process.GetProcessesByName("WindowsTerminal").FirstOrDefault(); - - if (process == null) - { - process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "wt", - UseShellExecute = false, - RedirectStandardOutput = true, - WindowStyle = ProcessWindowStyle.Maximized - } - }; - process.Start(); - - try - { - // Note: Accessing mainWindowHandle already throws "Process has exited, so the requested information is not available." - if (process.MainWindowHandle == IntPtr.Zero) - { - throw new Exception("Can not access newly started process."); - } - } - catch (Exception) - { - process = Process.GetProcessesByName("WindowsTerminal").First(); - // _process.WaitForInputIdle(); - process.Refresh(); - } - } - - return process; - } - private static void Close() { _toggler?.Dispose(); diff --git a/windows-terminal-quake/Toggler.cs b/windows-terminal-quake/Toggler.cs index 33f011af..cb941411 100644 --- a/windows-terminal-quake/Toggler.cs +++ b/windows-terminal-quake/Toggler.cs @@ -12,22 +12,20 @@ namespace WindowsTerminalQuake { public class Toggler : IDisposable { - private Process _process; + private Process _process => TerminalProcess.Get(); private readonly List _registeredHotKeys = new List(); - public Toggler(Process process) + public Toggler() { - _process = process; - // Hide from taskbar User32.SetWindowLong(_process.MainWindowHandle, User32.GWL_EX_STYLE, (User32.GetWindowLong(_process.MainWindowHandle, User32.GWL_EX_STYLE) | User32.WS_EX_TOOLWINDOW) & ~User32.WS_EX_APPWINDOW); User32.Rect rect = default; - var ok = User32.GetWindowRect(_process.MainWindowHandle, ref rect); - var isOpen = rect.Top >= GetScreenWithCursor().Bounds.Y; User32.ShowWindow(_process.MainWindowHandle, NCmdShow.MAXIMIZE); + var isOpen = true; + // Register hotkeys Settings.Get(s => {