using System.Diagnostics; using System.Net.Http.Json; using System.Text; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace WindowWatcher; public class Worker( IHttpClientFactory httpClientFactory, IOptions options, ILogger logger) : BackgroundService { private string _lastAppName = string.Empty; private string _lastWindowTitle = string.Empty; private DateTime _lastChangeTime = DateTime.MinValue; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var config = options.Value; logger.LogInformation("WindowWatcher started. Polling every {Interval}ms, debounce {Debounce}ms", config.PollIntervalMs, config.DebounceMs); while (!stoppingToken.IsCancellationRequested) { try { var hwnd = NativeMethods.GetForegroundWindow(); if (hwnd == IntPtr.Zero) { await Task.Delay(config.PollIntervalMs, stoppingToken); continue; } // Get window title var sb = new StringBuilder(512); NativeMethods.GetWindowText(hwnd, sb, sb.Capacity); var windowTitle = sb.ToString(); // Get process name NativeMethods.GetWindowThreadProcessId(hwnd, out var pid); string appName; try { var process = Process.GetProcessById((int)pid); appName = process.ProcessName; } catch { appName = "Unknown"; } // Check if changed if (appName != _lastAppName || windowTitle != _lastWindowTitle) { var now = DateTime.UtcNow; // Debounce: only report if last change was more than DebounceMs ago if ((now - _lastChangeTime).TotalMilliseconds >= config.DebounceMs && !string.IsNullOrWhiteSpace(windowTitle)) { await ReportContextEvent(appName, windowTitle, stoppingToken); } _lastAppName = appName; _lastWindowTitle = windowTitle; _lastChangeTime = now; } } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; } catch (Exception ex) { logger.LogError(ex, "Error polling foreground window"); } await Task.Delay(config.PollIntervalMs, stoppingToken); } } private async Task ReportContextEvent(string appName, string windowTitle, CancellationToken ct) { try { var client = httpClientFactory.CreateClient("TaskTrackerApi"); var payload = new { source = "WindowWatcher", appName, windowTitle }; var response = await client.PostAsJsonAsync("/api/context", payload, ct); if (response.IsSuccessStatusCode) { logger.LogDebug("Reported: {App} - {Title}", appName, windowTitle); } else { logger.LogWarning("API returned {StatusCode} for context event", response.StatusCode); } } catch (Exception ex) { logger.LogWarning(ex, "Failed to report context event to API"); } } }