Skip to content

Commit

Permalink
[WtqService] Simplified orchestration of app toggling
Browse files Browse the repository at this point in the history
  • Loading branch information
flyingpie committed Oct 30, 2024
1 parent 0444ece commit b523a11
Showing 1 changed file with 91 additions and 102 deletions.
193 changes: 91 additions & 102 deletions src/10-Core/Wtq/WtqService.cs
Original file line number Diff line number Diff line change
@@ -1,134 +1,123 @@
using Microsoft.Extensions.Hosting;
using Wtq.Events;
using Wtq.Services;

namespace Wtq;

public sealed class WtqService(
ILogger<WtqService> log,
IOptionsMonitor<WtqOptions> opts,
IWtqAppRepo appRepo,
IWtqBus bus,
IWtqFocusTracker focusTracker)
: IDisposable, IHostedService
/// <summary>
/// Orchestrates toggling on- and off of apps, and sending focus to the right window.
/// </summary>
// TODO: Better name.
public sealed class WtqService : IDisposable, IAsyncInitializable
{
private readonly ILogger<WtqService> _log = Guard.Against.Null(log);
private readonly IOptionsMonitor<WtqOptions> _opts = Guard.Against.Null(opts);
private readonly IWtqAppRepo _appRepo = Guard.Against.Null(appRepo);
private readonly IWtqBus _bus = Guard.Against.Null(bus);
private readonly IWtqFocusTracker _focusTracker = Guard.Against.Null(focusTracker);
private readonly SemaphoreSlim _lock = new(1);
private readonly ILogger<WtqService> _log;
private readonly IOptionsMonitor<WtqOptions> _opts;
private readonly IWtqAppRepo _appRepo;
private readonly IWtqBus _bus;
private readonly WtqSemaphoreSlim _lock = new(1, 1);

private WtqWindow? _lastNonWtqWindow;

public WtqService(
ILogger<WtqService> log,
IOptionsMonitor<WtqOptions> opts,
IWtqAppRepo appRepo,
IWtqBus bus)
{
_log = Guard.Against.Null(log);
_opts = Guard.Against.Null(opts);
_appRepo = Guard.Against.Null(appRepo);
_bus = Guard.Against.Null(bus);

private WtqApp? _lastOpen;
private WtqApp? _open;
_bus.OnEvent<WtqAppToggledEvent>(OnAppToggledEventAsync);
_bus.OnEvent<WtqWindowFocusChangedEvent>(OnWindowFocusChangedEventAsync);
}

public Task InitializeAsync()
{
// TODO: Currently necessary to make sure this service is constructed.
return Task.CompletedTask;
}

public void Dispose()
{
_lock.Dispose();
}

public Task StartAsync(CancellationToken cancellationToken)
/// <summary>
/// Handles "toggle" events, e.g. where the user pressed a hotkey.
/// </summary>
private async Task OnAppToggledEventAsync(WtqAppToggledEvent ev)
{
_log.LogInformation("Starting");

_bus.OnEvent<WtqToggleAppEvent>(HandleToggleAppEventAsync);
_bus.OnEvent<WtqAppFocusEvent>(HandleAppFocusEventAsync);
// Wait for service-wide lock.
using var l = await _lock.WaitOneSecondAsync().NoCtx();

return Task.CompletedTask;
}
// "Switching apps"
// If a previously toggled app (that is not the to-be-toggled app) is still open, close it first.
var open = _appRepo.GetOpen();
if (open != null && open != ev.App)
{
_log.LogInformation("Closing app '{AppClosing}', opening app '{AppOpening}'", open, ev.App);
await open.CloseAsync(ToggleModifiers.SwitchingApps).NoCtx();
await ev.App.OpenAsync(ToggleModifiers.SwitchingApps).NoCtx();
return;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_log.LogInformation("Stopping");
// "Toggling app"
if (ev.App.IsOpen)
{
_log.LogInformation("Closing previously open app '{App}'", ev.App);

return Task.CompletedTask;
}
// Close app.
ev.App.CloseAsync().NoCtx();

private async Task HandleAppFocusEventAsync(WtqAppFocusEvent ev)
{
// If focus moved to a different window, toggle out the current one (if there is an active app, and it's configured as such).
if (ev.App != null &&
ev.App == _open &&
!ev.GainedFocus &&
_opts.CurrentValue.GetHideOnFocusLostForApp(ev.App.Options))
// Bring focus back to last non-WTQ app.
await (_lastNonWtqWindow?.BringToForegroundAsync() ?? Task.CompletedTask).NoCtx();
}
else
{
await _open.CloseAsync().ConfigureAwait(false);
_lastOpen = _open;
_open = null;
_log.LogInformation("Opening previously closed app '{App}'", ev.App);

// Open app.
ev.App.OpenAsync().NoCtx();
}
}

private async Task HandleToggleAppEventAsync(WtqToggleAppEvent ev)
/// <summary>
/// Handles events where the focus moved to another window.
/// </summary>
private async Task OnWindowFocusChangedEventAsync(WtqWindowFocusChangedEvent ev)
{
try
{
await _lock.WaitAsync().ConfigureAwait(false);
Guard.Against.Null(ev);

var app = ev.App;
// Wait for service-wide lock.
using var l = await _lock.WaitOneSecondAsync().NoCtx();

// If the action does not point to a single app, toggle the most recent one.
if (app == null)
{
// If we still have an app open, close it now.
if (_open != null)
{
await _open.CloseAsync().ConfigureAwait(false);
_lastOpen = _open;
_open = null;
await _focusTracker.FocusLastNonWtqAppAsync().NoCtx();
return;
}

// If we don't yet have an app open, open either the most recently used one, or the first one.
if (_lastOpen == null)
{
// TODO
var first = _appRepo.Apps.FirstOrDefault();
if (first != null)
{
await first.OpenAsync().ConfigureAwait(false);
}

_open = first;
_lastOpen = first;
return;
}

_open = _lastOpen;
await _open.OpenAsync().ConfigureAwait(false);
return;
}
// Look for apps that are attached to the windows that got- and lost focus, respectively.
var appGotFocus = ev.GotFocusWindow != null ? _appRepo.GetByWindow(ev.GotFocusWindow) : null;
var appLostFocus = ev.LostFocusWindow != null ? _appRepo.GetByWindow(ev.LostFocusWindow) : null;

if (_open != null)
{
if (_open == app)
{
await app.CloseAsync().ConfigureAwait(false);
_lastOpen = _open;
_open = null;
await _focusTracker.FocusLastNonWtqAppAsync().NoCtx();
}
else
{
await _open.CloseAsync(ToggleModifiers.SwitchingApps).ConfigureAwait(false);
await app.OpenAsync(ToggleModifiers.SwitchingApps).ConfigureAwait(false);

_open = app;
}
// If the window that just LOST focus is NOT managed by WTQ, store it for giving back focus to later.
if (ev.LostFocusWindow != null && appLostFocus == null)
{
_lastNonWtqWindow = ev.LostFocusWindow;
}

return;
}
// If the app that GOT focus is a WTQ app, toggling will be done in the "app toggled" event handler.
if (appGotFocus != null)
{
return;
}

// Open the specified app.
_log.LogInformation("Toggling app {App}", app);
if (await app.OpenAsync().ConfigureAwait(false))
// If the app that LOST focus is a WTQ app, toggle it off (depending on configuration).
if (appLostFocus != null)
{
// If the app has "hide on focus lost" set to FALSE, well, don't hide.
if (!_opts.CurrentValue.GetHideOnFocusLostForApp(appLostFocus.Options))
{
_open = app;
return;
}
}
finally
{
_lock.Release();

_log.LogInformation("App '{App}' lost focus, closing", appLostFocus);
await appLostFocus.CloseAsync().NoCtx();
}
}
}

0 comments on commit b523a11

Please sign in to comment.