Skip to content

Commit

Permalink
✨ Start adding embedded monitors to the UI.
Browse files Browse the repository at this point in the history
  • Loading branch information
hexawyz committed Jan 31, 2025
1 parent c5d8aba commit fa7f0b1
Show file tree
Hide file tree
Showing 17 changed files with 378 additions and 28 deletions.
55 changes: 44 additions & 11 deletions src/Exo/Service/Exo.Service.Grpc/GrpcConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,20 @@
using GrpcSensorCapabilities = Exo.Contracts.Ui.Settings.SensorCapabilities;
using GrpcSensorDeviceInformation = Exo.Contracts.Ui.Settings.SensorDeviceInformation;
using GrpcSensorInformation = Exo.Contracts.Ui.Settings.SensorInformation;
using GrpcEmbeddedMonitorDeviceInformation = Exo.Contracts.Ui.Settings.EmbeddedMonitorDeviceInformation;
using GrpcEmbeddedMonitorInformation = Exo.Contracts.Ui.Settings.EmbeddedMonitorInformation;
using GrpcMonitorShape = Exo.Contracts.Ui.Settings.MonitorShape;
using GrpcVendorIdSource = Exo.Contracts.Ui.Settings.VendorIdSource;
using GrpcWatchNotificationKind = Exo.Contracts.Ui.WatchNotificationKind;
using GrpcImageInformation = Exo.Contracts.Ui.Settings.ImageInformation;
using GrpcImageFormat = Exo.Contracts.Ui.Settings.ImageFormat;
using GrpcSize = Exo.Contracts.Ui.Settings.Size;
using VendorIdSource = DeviceTools.VendorIdSource;
using Exo.Contracts.Ui.Settings.Cooling;
using Exo.Cooling.Configuration;
using System.Numerics;
using Exo.Images;
using Exo.Monitors;

namespace Exo.Service.Grpc;

Expand Down Expand Up @@ -127,17 +132,6 @@ public static GrpcSensorDeviceInformation ToGrpc(this SensorDeviceInformation se
Sensors = ImmutableArray.CreateRange(sensorDeviceInformation.Sensors, ToGrpc),
};

public static GrpcImageInformation ToGrpc(this ImageInformation imageInformation)
=> new()
{
ImageName = imageInformation.ImageName,
FileName = imageInformation.FileName,
Width = imageInformation.Width,
Height = imageInformation.Height,
Format = imageInformation.Format.ToGrpc(),
IsAnimated = imageInformation.IsAnimated,
};

public static GrpcSensorInformation ToGrpc(this SensorInformation sensorInformation)
=> new()
{
Expand All @@ -149,6 +143,45 @@ public static GrpcSensorInformation ToGrpc(this SensorInformation sensorInformat
ScaleMaximumValue = sensorInformation.ScaleMaximumValue is not null ? Convert.ToDouble(sensorInformation.ScaleMaximumValue) : null,
};

public static GrpcEmbeddedMonitorDeviceInformation ToGrpc(this EmbeddedMonitorDeviceInformation embeddedMonitorDeviceInformation)
=> new()
{
DeviceId = embeddedMonitorDeviceInformation.DeviceId,
EmbeddedMonitors = ImmutableArray.CreateRange(embeddedMonitorDeviceInformation.EmbeddedMonitors, ToGrpc),
};

public static GrpcEmbeddedMonitorInformation ToGrpc(this EmbeddedMonitorInformation embeddedMonitorInformation)
=> new()
{
MonitorId = embeddedMonitorInformation.MonitorId,
Shape = embeddedMonitorInformation.Shape.ToGrpc(),
ImageSize = embeddedMonitorInformation.ImageSize.ToGrpc(),
Capabilities = (Contracts.Ui.Settings.EmbeddedMonitorCapabilities)embeddedMonitorInformation.Capabilities,
SupportedImageFormats = (Contracts.Ui.Settings.ImageFormats)embeddedMonitorInformation.SupportedImageFormats,
};

public static GrpcMonitorShape ToGrpc(this MonitorShape shape)
=> shape switch
{
MonitorShape.Rectangle => GrpcMonitorShape.Rectangle,
MonitorShape.Square => GrpcMonitorShape.Square,
MonitorShape.Circle => GrpcMonitorShape.Circle,
_ => throw new NotImplementedException()
};

public static GrpcSize ToGrpc(this Size size) => new() { Width = size.Width, Height = size.Height };

public static GrpcImageInformation ToGrpc(this ImageInformation imageInformation)
=> new()
{
ImageName = imageInformation.ImageName,
FileName = imageInformation.FileName,
Width = imageInformation.Width,
Height = imageInformation.Height,
Format = imageInformation.Format.ToGrpc(),
IsAnimated = imageInformation.IsAnimated,
};

public static GrpcCoolingDeviceInformation ToGrpc(this CoolingDeviceInformation coolingDeviceInformation)
=> new()
{
Expand Down
6 changes: 1 addition & 5 deletions src/Exo/Service/Exo.Service.Grpc/GrpcDeviceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,19 @@

namespace Exo.Service.Grpc;

internal sealed class GrpcDeviceService : IDeviceService, IAsyncDisposable
internal sealed class GrpcDeviceService : IDeviceService
{
private readonly ConfigurationService _configurationService;
private readonly ILogger<GrpcDeviceService> _logger;
private readonly DeviceRegistry _driverRegistry;
private readonly PowerService _powerService;

public GrpcDeviceService(ILogger<GrpcDeviceService> logger, ConfigurationService configurationService, DeviceRegistry driverRegistry, PowerService powerService)
{
_logger = logger;
_configurationService = configurationService;
_driverRegistry = driverRegistry;
_powerService = powerService;
}

public ValueTask DisposeAsync() => _powerService.DisposeAsync();

public async IAsyncEnumerable<WatchNotification<Contracts.Ui.Settings.DeviceInformation>> WatchDevicesAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
_logger.GrpcDeviceServiceWatchStart();
Expand Down
34 changes: 34 additions & 0 deletions src/Exo/Service/Exo.Service.Grpc/GrpcEmbeddedMonitorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Exo.Contracts.Ui.Settings;
using Microsoft.Extensions.Logging;

namespace Exo.Service.Grpc;

internal sealed class GrpcEmbeddedMonitorService : IEmbeddedMonitorService
{
private readonly ConfigurationService _configurationService;
private readonly ILogger<GrpcDeviceService> _logger;
private readonly EmbeddedMonitorService _embeddedMonitorService;

public GrpcEmbeddedMonitorService(ILogger<GrpcDeviceService> logger, ConfigurationService configurationService, EmbeddedMonitorService embeddedMonitorService)
{
_logger = logger;
_configurationService = configurationService;
_embeddedMonitorService = embeddedMonitorService;
}

public async IAsyncEnumerable<Contracts.Ui.Settings.EmbeddedMonitorDeviceInformation> WatchEmbeddedMonitorDevicesAsync(CancellationToken cancellationToken)
{
_logger.GrpcSpecializedDeviceServiceWatchStart(GrpcService.EmbeddedMonitor);
try
{
await foreach (var notification in _embeddedMonitorService.WatchDevicesAsync(cancellationToken).ConfigureAwait(false))
{
yield return notification.ToGrpc();
}
}
finally
{
_logger.GrpcSpecializedDeviceServiceWatchStop(GrpcService.EmbeddedMonitor);
}
}
}
20 changes: 20 additions & 0 deletions src/Exo/Service/Exo.Service.Grpc/LoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@ namespace Exo.Service;

internal static partial class LoggerExtensions
{
// TODO: Reorganize Event IDs (They are currently not used but the plan is to log events to the event log at some point…)

[LoggerMessage(EventId = 10_101, EventName = "GrpcDeviceServiceWatchStart", Level = LogLevel.Debug, Message = "Started watching devices.")]
public static partial void GrpcDeviceServiceWatchStart(this ILogger logger);

[LoggerMessage(EventId = 10_102, EventName = "GrpcDeviceServiceWatchStop", Level = LogLevel.Debug, Message = "Stopped watching devices.")]
public static partial void GrpcDeviceServiceWatchStop(this ILogger logger);

// These should have more sensical IDs and replace all the other specialized messages.
[LoggerMessage(EventId = 10_901, EventName = "GrpcSpecializedDeviceServiceWatchStart", Level = LogLevel.Debug, Message = "Started watching {DeviceService} devices.")]
public static partial void GrpcSpecializedDeviceServiceWatchStart(this ILogger logger, GrpcService deviceService);

[LoggerMessage(EventId = 10_902, EventName = "GrpcSpecializedDeviceServiceWatchStop", Level = LogLevel.Debug, Message = "Stopped watching {DeviceService} devices.")]
public static partial void GrpcSpecializedDeviceServiceWatchStop(this ILogger logger, GrpcService deviceService);


[LoggerMessage(EventId = 10_103, EventName = "GrpcDeviceServiceWatchNotification", Level = LogLevel.Trace, Message = "Device notification: {NotificationKind} for {DeviceId} ({DeviceFriendlyName}). Availability: {IsAvailable}.")]
public static partial void GrpcDeviceServiceWatchNotification(this ILogger logger, WatchNotificationKind notificationKind, Guid deviceId, string deviceFriendlyName, bool isAvailable);

Expand Down Expand Up @@ -103,5 +113,15 @@ internal static partial class LoggerExtensions

[LoggerMessage(EventId = 10_806, EventName = "GrpcImageServiceAddImageRequestSuccess", Level = LogLevel.Information, Message = "Success of request to add image {ImageName}.")]
public static partial void GrpcImageServiceAddImageRequestSuccess(this ILogger logger, string imageName);
}

public enum GrpcService
{
Device = 0,
Power = 1,
Battery = 2,
Lighting = 3,
EmbeddedMonitor = 4,
Sensor = 5,
Cooling = 6,
}
2 changes: 2 additions & 0 deletions src/Exo/Service/Exo.Service/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<GrpcDeviceService>();
services.AddSingleton<GrpcPowerService>();
services.AddSingleton<GrpcLightingService>();
services.AddSingleton<GrpcEmbeddedMonitorService>();
services.AddSingleton<GrpcImageService>();
services.AddSingleton<GrpcSensorService>();
services.AddSingleton<GrpcCoolingService>();
Expand Down Expand Up @@ -305,6 +306,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
endpoints.MapGrpcService<GrpcDeviceService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcPowerService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcLightingService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcEmbeddedMonitorService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcImageService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcSensorService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcCoolingService>().AddEndpointFilter(settingsEndpointFilter);
Expand Down
2 changes: 2 additions & 0 deletions src/Exo/Ui/Exo.Settings.Ui/ChangedProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ internal static class ChangedProperty
public static readonly PropertyChangedEventArgs HasWirelessBrightness = new (nameof(HasWirelessBrightness));
public static readonly PropertyChangedEventArgs LoadedImageName = new (nameof(LoadedImageName));
public static readonly PropertyChangedEventArgs LoadedImageData = new (nameof(LoadedImageData));
public static readonly PropertyChangedEventArgs Shape = new (nameof(Shape));
public static readonly PropertyChangedEventArgs ImageSize = new (nameof(ImageSize));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Exo.Settings.Ui.ViewModels;
using Exo.Contracts.Ui.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace Exo.Settings.Ui.DataTemplateSelectors;

internal sealed class EmbeddedMonitorSettingTemplateSelector : DataTemplateSelector
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public DataTemplate SingleMonitorTemplate { get; set; }
public DataTemplate MonitorMatrixTemplate { get; set; }
public DataTemplate MultiMonitorTemplate { get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
=> SelectTemplateCore(item);

protected override DataTemplate SelectTemplateCore(object item)
{
// NB: This can't detect changes. If we ever need to update the display mode dynamically, it will have to be driven externally. (For example triggering a PropertyChanged event somewhere)
if (item is EmbeddedMonitorFeaturesViewModel s)
{
if (s.EmbeddedMonitors.Count == 1) return SingleMonitorTemplate;
// TODO: This is hardcoded for StreamDeck. Have this less hardcoded. Should probably be driven by the ViewModel + metadata.
else if (s.EmbeddedMonitors.Count == 32) return MonitorMatrixTemplate;
}
return MultiMonitorTemplate;
}
}
14 changes: 14 additions & 0 deletions src/Exo/Ui/Exo.Settings.Ui/DevicePage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@
</Expander.Header>
<local:MousePerformanceSettingsControl DataContext="{Binding MouseFeatures, Mode=OneWay}" />
</Expander>
<Expander
Margin="{StaticResource VerticalItemSpacing}"
Visibility="{Binding EmbeddedMonitorFeatures, Converter={StaticResource NullabilityToVisibilityConverter}, Mode=OneWay}"
IsExpanded="{Binding EmbeddedMonitorFeatures.IsExpanded, Mode=TwoWay}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<FontIcon Glyph="&#xE8B9;" />
<TextBlock x:Uid="EmbeddedMonitorSettingsSectionHeader" Margin="{StaticResource TitleTextIconMargin}" />
</StackPanel>
</Expander.Header>
<local:EmbeddedMonitorSettingsControl DataContext="{Binding EmbeddedMonitorFeatures, Mode=OneWay}" />
</Expander>
<Expander
Margin="{StaticResource VerticalItemSpacing}"
Visibility="{Binding MonitorFeatures, Converter={StaticResource NullabilityToVisibilityConverter}, Mode=OneWay}"
Expand Down
48 changes: 48 additions & 0 deletions src/Exo/Ui/Exo.Settings.Ui/EmbeddedMonitorSettingsControl.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<UserControl
x:Class="Exo.Settings.Ui.EmbeddedMonitorSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Exo.Settings.Ui"
xmlns:lconverters="using:Exo.Settings.Ui.Converters"
xmlns:lts="using:Exo.Settings.Ui.DataTemplateSelectors"
xmlns:vm="using:Exo.Settings.Ui.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:EmbeddedMonitorFeaturesViewModel, IsDesignTimeCreatable=False}">
<ContentPresenter Content="{Binding}" HorizontalAlignment="Stretch">
<ContentPresenter.ContentTemplateSelector>
<lts:EmbeddedMonitorSettingTemplateSelector>
<lts:EmbeddedMonitorSettingTemplateSelector.SingleMonitorTemplate>
<DataTemplate x:DataType="vm:EmbeddedMonitorFeaturesViewModel">
<Grid HorizontalAlignment="Stretch" DataContext="{Binding EmbeddedMonitors[0]}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource PropertyLabelColumnWidth}" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
<Image Grid.Row="1" Width="{Binding ImageSize.Width}" Height="{Binding ImageSize.Height}" />
</Grid>
</DataTemplate>
</lts:EmbeddedMonitorSettingTemplateSelector.SingleMonitorTemplate>
<lts:EmbeddedMonitorSettingTemplateSelector.MonitorMatrixTemplate>
<DataTemplate x:DataType="vm:EmbeddedMonitorFeaturesViewModel">
<Grid></Grid>
</DataTemplate>
</lts:EmbeddedMonitorSettingTemplateSelector.MonitorMatrixTemplate>
<lts:EmbeddedMonitorSettingTemplateSelector.MultiMonitorTemplate>
<DataTemplate x:DataType="vm:EmbeddedMonitorFeaturesViewModel">
<Grid></Grid>
</DataTemplate>
</lts:EmbeddedMonitorSettingTemplateSelector.MultiMonitorTemplate>
</lts:EmbeddedMonitorSettingTemplateSelector>
</ContentPresenter.ContentTemplateSelector>
</ContentPresenter>
</UserControl>
26 changes: 26 additions & 0 deletions src/Exo/Ui/Exo.Settings.Ui/EmbeddedMonitorSettingsControl.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace Exo.Settings.Ui;
public sealed partial class EmbeddedMonitorSettingsControl : UserControl
{
public EmbeddedMonitorSettingsControl()
{
this.InitializeComponent();
}
}
7 changes: 7 additions & 0 deletions src/Exo/Ui/Exo.Settings.Ui/Exo.Settings.Ui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<None Remove="DevicePage.xaml" />
<None Remove="DevicesPage.xaml" />
<None Remove="EditionToolbar.xaml" />
<None Remove="EmbeddedMonitorSettingsControl.xaml" />
<None Remove="HomePage.xaml" />
<None Remove="ImagesPage.xaml" />
<None Remove="LightingPage.xaml" />
Expand Down Expand Up @@ -89,6 +90,12 @@
<ProjectReference Include="..\..\Core\Exo.Utils\Exo.Utils.csproj" />
</ItemGroup>

<ItemGroup>
<Page Update="EmbeddedMonitorSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>

<ItemGroup>
<Page Update="ImagesPage.xaml">
<Generator>MSBuild:Compile</Generator>
Expand Down
2 changes: 1 addition & 1 deletion src/Exo/Ui/Exo.Settings.Ui/LightingZoneControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<DataTemplate>
<Grid Margin="0,6,0,6" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="{StaticResource PropertyLabelColumnWidth}" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
Expand Down
Loading

0 comments on commit fa7f0b1

Please sign in to comment.