| File: src\Aspire.Tray\Linux\LinuxTrayIcons.cs | Web Access |
| Project: src\tests\Aspire.Tray.Tests\Aspire.Tray.Tests.csproj (Aspire.Tray.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Aspire.Tray; /// <summary> /// Keeps status artwork alive while a desktop panel may still be reading it. /// </summary> internal sealed class LinuxTrayIcons(string source) : IDisposable { private readonly Dictionary<TrayIconStatus, string> _paths = []; private DirectoryInfo? _directory; internal string GetPath(TrayIconStatus status) { if (_paths.TryGetValue(status, out var path)) { return path; } if (status == TrayIconStatus.Idle) { return source; } _directory ??= Directory.CreateTempSubdirectory("aspire-tray-icons-"); path = Path.Combine(_directory.FullName, $"{status}.png"); Render(source, path, status); _paths.Add(status, path); return path; } internal static void Render(string source, string destination, TrayIconStatus status) { var logo = Gtk.cairo_image_surface_create_from_png(source); nint surface = 0; nint context = 0; try { if (Gtk.cairo_surface_status(logo) != 0) { throw new InvalidOperationException("Could not read the Aspire tray icon."); } var width = Gtk.cairo_image_surface_get_width(logo); var height = Gtk.cairo_image_surface_get_height(logo); surface = Gtk.cairo_image_surface_create(0, width, height); context = Gtk.cairo_create(surface); Gtk.cairo_set_source_surface(context, logo, 0, 0); Gtk.cairo_paint_with_alpha(context, TrayIconStatusRules.IsDimmed(status) ? 0.4 : 1); if (TrayIconStatusRules.HasBadge(status)) { var radius = Math.Min(width, height) * 0.16; var x = width - radius * 1.15; var y = height - radius * 1.15; // Clear a gap around the badge so it contrasts on any panel theme. // https://www.cairographics.org/operators/ Gtk.cairo_set_operator(context, 0); // CAIRO_OPERATOR_CLEAR Gtk.cairo_arc(context, x, y, radius * 1.25, 0, Math.Tau); Gtk.cairo_fill(context); Gtk.cairo_set_operator(context, 2); // CAIRO_OPERATOR_OVER var color = status == TrayIconStatus.Running ? 0x269653 : 0xE58A00; Gtk.cairo_set_source_rgb(context, ((color >> 16) & 255) / 255d, ((color >> 8) & 255) / 255d, (color & 255) / 255d); Gtk.cairo_arc(context, x, y, radius, 0, Math.Tau); Gtk.cairo_fill(context); } if (Gtk.cairo_status(context) != 0 || Gtk.cairo_surface_status(surface) != 0 || Gtk.cairo_surface_write_to_png(surface, destination) != 0) { throw new InvalidOperationException("Could not render the Aspire tray status icon."); } } finally { if (context != 0) { Gtk.cairo_destroy(context); } if (surface != 0) { Gtk.cairo_surface_destroy(surface); } Gtk.cairo_surface_destroy(logo); } } public void Dispose() => _directory?.Delete(recursive: true); }