Release / release (push) Successful in 5m7s
Reviewed-on: #7 Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
99 lines
3.7 KiB
C#
99 lines
3.7 KiB
C#
namespace CursorLang.Core.Services;
|
|
|
|
/// <summary>
|
|
/// Tells the settings window that the agent is quitting and it is to close with it.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The one thing the agent says to the settings window, and the mirror image of
|
|
/// <see cref="SettingsSignal"/>. "Exit" in the tray menu means the application is done
|
|
/// with, and a settings window left alone on the screen after it is a window belonging
|
|
/// to nothing: the tray icon it was opened from is gone, and closing it would be the
|
|
/// user's only remaining move.
|
|
///
|
|
/// A named event rather than a window message, because the agent has no handle to send
|
|
/// one to: the settings window lives in a process the agent starts and deliberately
|
|
/// does not keep hold of. The name has no <c>Global</c> prefix, so it lives in the
|
|
/// session namespace — same reasoning as <see cref="SingleInstanceGate"/>, and the same
|
|
/// consequence: with fast user switching each user's halves talk to their own.
|
|
///
|
|
/// Only the settings window creates the object; the agent opens what is already there
|
|
/// and stays silent when there is nothing. Were it the other way round, the request
|
|
/// would sit in an auto-reset event waiting for the next settings window to open and
|
|
/// close it the moment it did.
|
|
/// </remarks>
|
|
internal sealed class SettingsCloseSignal : IDisposable
|
|
{
|
|
private const string EventName = "CursorLang.CloseSettings";
|
|
|
|
private readonly string _eventName;
|
|
|
|
private EventWaitHandle? _request;
|
|
private RegisteredWaitHandle? _wait;
|
|
|
|
/// <summary>
|
|
/// Listens on the name the two halves agree on.
|
|
/// </summary>
|
|
/// <param name="nameSuffix">
|
|
/// A namespace of its own. Empty for the application; the tests pass one so that
|
|
/// they do not answer for — or worse, close — a settings window someone is using.
|
|
/// </param>
|
|
internal SettingsCloseSignal(string nameSuffix = "") => _eventName = EventName + nameSuffix;
|
|
|
|
/// <summary>The agent asks for the window to be closed.</summary>
|
|
/// <remarks>
|
|
/// Raised on a thread pool thread, wherever the wait happened to be answered — a
|
|
/// window obeys only its own, so the handler has to get back to it.
|
|
/// </remarks>
|
|
internal event EventHandler? CloseRequested;
|
|
|
|
/// <summary>
|
|
/// Asks the settings window of this session, if one is open, to close. Silence is
|
|
/// a normal answer: most of the time the user quits with no window on the screen.
|
|
/// </summary>
|
|
/// <returns>Whether there was anybody to hear it.</returns>
|
|
internal static bool RequestClose(string nameSuffix = "")
|
|
{
|
|
if (!EventWaitHandle.TryOpenExisting(EventName + nameSuffix, out EventWaitHandle? request))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
using (request)
|
|
{
|
|
return request.Set();
|
|
}
|
|
}
|
|
|
|
/// <summary>Starts waiting for the request. Called once, by the settings window.</summary>
|
|
internal void Listen()
|
|
{
|
|
if (_request is not null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_request = new EventWaitHandle(false, EventResetMode.AutoReset, _eventName);
|
|
|
|
// As in the gate: the thread pool holds the wait, there is no reason to keep a
|
|
// thread of our own for a request that may never come
|
|
_wait = ThreadPool.RegisterWaitForSingleObject(
|
|
_request,
|
|
OnCloseSignalled,
|
|
state: null,
|
|
Timeout.Infinite,
|
|
executeOnlyOnce: false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_wait?.Unregister(null);
|
|
_wait = null;
|
|
|
|
_request?.Dispose();
|
|
_request = null;
|
|
}
|
|
|
|
private void OnCloseSignalled(object? state, bool timedOut) =>
|
|
CloseRequested?.Invoke(this, EventArgs.Empty);
|
|
}
|