added sending closing event message to settings windows (#7)
Release / release (push) Successful in 5m7s
Release / release (push) Successful in 5m7s
Reviewed-on: #7 Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #7.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user