diff --git a/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs b/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs index 6d26b77..8ae64f9 100644 --- a/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs +++ b/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs @@ -19,7 +19,9 @@ public sealed class MockedFlowExecutorApiFixture : WebApplicationFactory.ModifyJsonSerializerOptions(SystemTextJsonSerializerConfig.Options); + WebApplicationWrapper.ModifyJsonSerializerOptions( + SystemTextJsonSerializerConfig.Options + ); } public ValueTask InitializeAsync() => ValueTask.CompletedTask; diff --git a/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs b/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs index 23fb18b..840c2e4 100644 --- a/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs +++ b/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs @@ -35,7 +35,7 @@ public FlowError(string message, bool isExceptional) : this(message, isException [System.Diagnostics.Contracts.Pure] public static FlowError NoCommandExecutorFound(ICommand command) => new( - $"No transformation executor found that handles transformation type {command.GetType().FullName}.", + $"No command executor found that handles transformation type {command.GetType().FullName}.", isExceptional: false ); diff --git a/Oma.WndwCtrl.Api/CtrlApiProgram.cs b/Oma.WndwCtrl.Api/CtrlApiProgram.cs index 4fdce18..f3291d4 100644 --- a/Oma.WndwCtrl.Api/CtrlApiProgram.cs +++ b/Oma.WndwCtrl.Api/CtrlApiProgram.cs @@ -12,9 +12,13 @@ public async static Task Main(string[] args) MessageBus = new NoOpMessageBus(), }; + IConfiguration configuration = new ConfigurationBuilder() + .Build(); + CtrlApiService apiService = new( await ComponentConfigurationAccessor.FromFileAsync(), - messageBusAccessor + messageBusAccessor, + configuration ); await apiService.StartAsync(CancellationToken.None, args); diff --git a/Oma.WndwCtrl.Api/CtrlApiService.cs b/Oma.WndwCtrl.Api/CtrlApiService.cs index 9568290..6c489a7 100644 --- a/Oma.WndwCtrl.Api/CtrlApiService.cs +++ b/Oma.WndwCtrl.Api/CtrlApiService.cs @@ -17,9 +17,10 @@ namespace Oma.WndwCtrl.Api; public class CtrlApiService( ComponentConfigurationAccessor configurationAccessor, - MessageBusAccessor messageBusAccessor + MessageBusAccessor messageBusAccessor, + IConfiguration rootConfiguration ) - : WebApplicationWrapper(messageBusAccessor) + : WebApplicationWrapper(messageBusAccessor, rootConfiguration) { private readonly MessageBusAccessor _messageBusAccessor = messageBusAccessor; diff --git a/Oma.WndwCtrl.Configuration/component-configuration-windows.json b/Oma.WndwCtrl.Configuration/component-configuration-windows.json index 4e8c2e4..c7170f9 100644 --- a/Oma.WndwCtrl.Configuration/component-configuration-windows.json +++ b/Oma.WndwCtrl.Configuration/component-configuration-windows.json @@ -11,7 +11,7 @@ }, "oma-service-status": { "type": "sensor", - "active": false, + "active": true, "queryCommand": { "type": "cli", "fileName": "sc.exe", @@ -65,6 +65,7 @@ }, "ping-google": { "type": "sensor", + "active": false, "queryCommand": { "type": "cli", "fileName": "ping", diff --git a/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs b/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs index c7b7159..cf276fb 100644 --- a/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs +++ b/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using JetBrains.Annotations; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Oma.WndwCtrl.Abstractions; @@ -13,6 +15,7 @@ using Oma.WndwCtrl.Core.Logger; using Oma.WndwCtrl.Core.Metrics; using Oma.WndwCtrl.Core.Model; +using Oma.WndwCtrl.Core.Model.Settings; namespace Oma.WndwCtrl.Core.Extensions; @@ -24,6 +27,8 @@ public static IServiceCollection AddCommandExecutors( IConfiguration configuration ) { + services.AddOptions(); + IConfigurationSection coreConfig = configuration.GetSection("Core"); // TODO: Will cause problems when called multiple times. @@ -35,6 +40,13 @@ IConfiguration configuration coreConfig.GetSection(CliParserLoggerOptions.SectionName) ); + ExtensionSettings extensions = []; + coreConfig.GetSection(ExtensionSettings.SectionName).Bind(extensions); + + List extensionAssemblies = extensions.GetAssemblies(); + JsonExtensions.AddAssembliesToLoad(extensionAssemblies.ToList()); + AddExtensionExecutors(services, extensionAssemblies); + services .AddSingleton() .AddSingleton() @@ -47,4 +59,32 @@ IConfiguration configuration return services; } + + private static void AddExtensionExecutors( + IServiceCollection services, + List extensionAssemblies + ) + { + foreach (Assembly assembly in extensionAssemblies) + { + AddAllFromAssembly(services, assembly); + AddAllFromAssembly(services, assembly); + } + } + + [PublicAPI] + public static IServiceCollection AddAllFromAssembly( + IServiceCollection services, + Assembly assembly, + ServiceLifetime serviceLifetime = ServiceLifetime.Scoped + ) + { + Type baseType = typeof(T); + + foreach (Type implType in assembly.GetTypes() + .Where(t => t is { IsAbstract: false, } && t.IsAssignableTo(baseType))) + services.Add(new ServiceDescriptor(baseType, implType, serviceLifetime)); + + return services; + } } \ No newline at end of file diff --git a/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs b/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs index a4d6e55..b804e36 100644 --- a/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs +++ b/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs @@ -7,12 +7,24 @@ namespace Oma.WndwCtrl.Core.Extensions; public static class JsonExtensions { - private static readonly Assembly AssembliesToSearch = typeof(BaseTransformation).Assembly; + private static IEnumerable _assembliesToInspect = []; + + internal static void AddAssembliesToLoad(List assembliesToLoad) + { + if (assembliesToLoad.Count == 0) + { + return; + } + + _assembliesToInspect = _assembliesToInspect.Union(assembliesToLoad).Distinct(); + } public static Action GetPolymorphismModifierFor( Func typeToDiscriminatorTransform ) { + List assembliesToSearch = [.._assembliesToInspect, typeof(BaseTransformation).Assembly,]; + return jsonTypeInfo => { Type baseType = typeof(T); @@ -29,7 +41,7 @@ Func typeToDiscriminatorTransform UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization, }; - IEnumerable types = AssembliesToSearch.GetTypes() + IEnumerable types = assembliesToSearch.SelectMany(a => a.GetTypes()) .Where(t => t is { IsClass: true, IsAbstract: false, } && t.IsAssignableTo(baseType)); foreach (Type t in types) diff --git a/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs b/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs new file mode 100644 index 0000000..bb51297 --- /dev/null +++ b/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs @@ -0,0 +1,10 @@ +using JetBrains.Annotations; + +namespace Oma.WndwCtrl.Core.Model.Settings; + +[UsedImplicitly] +[PublicAPI] +public record AssemblyInfo2 +{ + public string AssemblyName { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs b/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs new file mode 100644 index 0000000..2d0cd6a --- /dev/null +++ b/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs @@ -0,0 +1,14 @@ +using System.Reflection; + +namespace Oma.WndwCtrl.Core.Model.Settings; + +public class ExtensionSettings : List +{ + public const string SectionName = "Extensions"; + + public List GetAssemblies() + { + // TODO: [Required for MVP] Obvious security concerns.. + return this.Select(assemblyInfo => Assembly.LoadFrom($"{assemblyInfo.AssemblyName}")).ToList(); + } +} \ No newline at end of file diff --git a/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs b/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs index f5b759b..273d88c 100644 --- a/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs +++ b/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs @@ -16,11 +16,20 @@ public class BackgroundServiceWrapper(IConfiguration config protected readonly string RunningInOs = configuration.GetValue("ACaaD:OS") ?? "windows"; + private IConfiguration? _configuration; + protected static IServiceProvider ServiceProvider => _serviceProvider ?? throw new InvalidOperationException( "The WebApplicationWrapper has not been initialized properly." ); + protected IConfiguration Configuration + { + get => _configuration ?? + throw new InvalidOperationException($"{nameof(Configuration)} is not populated."); + private set => _configuration = value; + } + [PublicAPI] protected IHost? Host { get; private set; } @@ -37,6 +46,7 @@ public async Task StartAsync(CancellationToken cancelToken = default, params str Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); #endif + Configuration = configuration; HostBuilder hostBuilder = new(); hostBuilder.ConfigureHostConfiguration(builder => builder.AddConfiguration(configuration)); diff --git a/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs b/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs index d07883a..ca324fb 100644 --- a/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs +++ b/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.FileProviders; using Oma.WndwCtrl.Abstractions; using Oma.WndwCtrl.Core.Extensions; +using Oma.WndwCtrl.Core.Model.Settings; using Oma.WndwCtrl.CoreAsp.Api.Filters; using Oma.WndwCtrl.CoreAsp.Conventions; using Oma.WndwCtrl.Messaging.Bus; @@ -14,7 +15,10 @@ namespace Oma.WndwCtrl.CoreAsp; -public class WebApplicationWrapper(MessageBusAccessor? messageBusAccessor) : IApiService +public class WebApplicationWrapper( + MessageBusAccessor? messageBusAccessor, + IConfiguration? rootConfiguration +) : IApiService where TAssemblyDescriptor : class, IApiService { [SuppressMessage( @@ -24,6 +28,8 @@ public class WebApplicationWrapper(MessageBusAccessor? mess )] private static IServiceProvider? _serviceProvider; + private static readonly string _serviceName = typeof(TAssemblyDescriptor).Name; + private IConfiguration? _configuration; private IWebHostEnvironment? _environment; @@ -74,8 +80,20 @@ public async Task StartAsync(CancellationToken cancelToken = default, params str Environment = builder.Environment; Configuration = builder.Configuration; + IConfiguration coreConfig = Configuration.GetSection("Core"); + ExtensionSettings extensions = []; + coreConfig.GetSection(ExtensionSettings.SectionName).Bind(extensions); + ConfigurationConfiguration(builder.Configuration); + builder.WebHost.ConfigureKestrel( + (context, options) => + { + IConfiguration configToUse = context.Configuration.GetSection(_serviceName).GetSection("Kestrel"); + options.Configure(configToUse); + } + ); + IMvcCoreBuilder mvcBuilder = builder.Services .AddMvcCore( opts => @@ -154,7 +172,9 @@ public Task ForceStopAsync(CancellationToken cancelToken) => Application is not ? Application.StopAsync(cancelToken) : Task.CompletedTask; - public static void ModifyJsonSerializerOptions(JsonSerializerOptions jsonSerializerOptions) + public static void ModifyJsonSerializerOptions( + JsonSerializerOptions jsonSerializerOptions + ) { jsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver() .WithAddedModifier( @@ -179,7 +199,10 @@ protected virtual IConfigurationBuilder ConfigurationConfiguration( IConfigurationBuilder configurationBuilder ) { - string serviceName = typeof(TAssemblyDescriptor).Name; + if (rootConfiguration is not null) + { + configurationBuilder.AddConfiguration(rootConfiguration); + } IFileProvider standardFileProvider = configurationBuilder.GetFileProvider(); @@ -190,12 +213,12 @@ IConfigurationBuilder configurationBuilder configurationBuilder.SetFileProvider(compositeFileProvider); - configurationBuilder.AddJsonFile($"{serviceName}.config.json", optional: true, reloadOnChange: false); + configurationBuilder.AddJsonFile($"{_serviceName}.config.json", optional: true, reloadOnChange: false); if (Environment.IsDevelopment()) { configurationBuilder.AddJsonFile( - $"{serviceName}.config.development.json", + $"{_serviceName}.config.development.json", optional: true, reloadOnChange: false ); diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs new file mode 100644 index 0000000..c93b2e5 --- /dev/null +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs @@ -0,0 +1,58 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using JetBrains.Annotations; +using LanguageExt; +using Oma.WndwCtrl.Abstractions; +using Oma.WndwCtrl.Abstractions.Errors; +using Oma.WndwCtrl.Abstractions.Model; +using Oma.WndwCtrl.Ext.Windows.Media.Model.Commands; +using static LanguageExt.Prelude; + +namespace Oma.WndwCtrl.Ext.Windows.Media.Executors.Commands; + +[UsedImplicitly] +public partial class MediaCommandExecutor : ICommandExecutor +{ + private const int KEYEVENTF_EXTENTEDKEY = 1; + + private const int VK_MEDIA_NEXT_TRACK = 0xB0; + private const int VK_MEDIA_PREV_TRACK = 0xB1; + private const int VK_MEDIA_STOP = 0xB2; + private const int VK_MEDIA_PLAY_PAUSE = 0xB3; + + [SuppressMessage( + "Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "Must be disposed by caller." + )] + [MustDisposeResource] + [SuppressMessage("ReSharper", "NotDisposedResource", Justification = "Method flagged as must-dispose.")] + public Task> ExecuteAsync( + MediaCommand command, + CancellationToken cancelToken = default + ) + { + byte keyToSend = command.Action switch + { + MediaAction.Play or MediaAction.Pause => VK_MEDIA_PLAY_PAUSE, + MediaAction.Next => VK_MEDIA_NEXT_TRACK, + MediaAction.Previous => VK_MEDIA_PREV_TRACK, + MediaAction.Stop => VK_MEDIA_STOP, + var err => throw new InvalidOperationException($"Unhandled media action {err.ToString()}."), + }; + + keybd_event(keyToSend, scanCode: 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); + + return Task.FromResult>( + Right( + new CommandOutcome + { + Success = true, + } + ) + ); + } + + [LibraryImport("user32.dll")] + private static partial void keybd_event(byte virtualKey, byte scanCode, uint flags, IntPtr extraInfo); +} \ No newline at end of file diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs new file mode 100644 index 0000000..ad816cf --- /dev/null +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; +using Oma.WndwCtrl.Core.Model.Commands; + +namespace Oma.WndwCtrl.Ext.Windows.Media.Model.Commands; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum MediaAction +{ + Play, + Pause, + Stop, + Next, + Previous, +} + +[PublicAPI] +public class MediaCommand : BaseCommand +{ + public override string Category => "Media"; + + public MediaAction Action { get; init; } +} \ No newline at end of file diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj new file mode 100644 index 0000000..aa306cc --- /dev/null +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj @@ -0,0 +1,14 @@ + + + + true + net9.0 + enable + enable + + + + + + + diff --git a/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json b/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json index 196353d..b927333 100644 --- a/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json +++ b/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json @@ -3,12 +3,5 @@ "LogLevel": { "Default": "Warning" } - }, - "Kestrel": { - "Endpoints": { - "MetricsApi": { - "Url": "http://192.168.178.22:19090" - } - } } } \ No newline at end of file diff --git a/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs b/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs index 200d6d6..6b57cb8 100644 --- a/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs +++ b/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Oma.WndwCtrl.CoreAsp; using Oma.WndwCtrl.Messaging.Bus; @@ -8,8 +9,8 @@ namespace Oma.WndwCtrl.MetricsApi; -public class MetricsApiService(MessageBusAccessor messageBusAccessor) - : WebApplicationWrapper(messageBusAccessor) +public class MetricsApiService(MessageBusAccessor messageBusAccessor, IConfiguration rootConfiguration) + : WebApplicationWrapper(messageBusAccessor, rootConfiguration) { protected override IServiceCollection ConfigureServices(IServiceCollection services) { diff --git a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json index eed5140..884a904 100644 --- a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json +++ b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json @@ -6,11 +6,6 @@ "Oma.WndwCtrl.Scheduling": "Information" } }, - "SchedulingService": { - "Enabled": true, - "Active": true, - "CheckInterval": "00:00:00.001" - }, "Core": { "CliParser": { "Silent": true diff --git a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json index 035168a..5be049e 100644 --- a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json +++ b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json @@ -15,14 +15,39 @@ "Enabled": false }, "Core": { + "Extensions": [ + { + "AssemblyName": "Z:\\git\\wndw.ctl\\Oma.WndwCtrl.Extensions\\Windows\\Oma.WndwCtrl.Ext.Windows.Media\\bin\\Debug\\net9.0\\Oma.WndwCtrl.Ext.Windows.Media.dll" + } + ], "CliParser": { "Silent": true } }, - "Kestrel": { - "Endpoints": { - "ManagementApi": { - "Url": "http://localhost:8443" + "MgmtApiService": { + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:8443" + } + } + } + }, + "CtrlApiService": { + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5000" + } + } + } + }, + "MetricsApiService": { + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:19090" + } } } } diff --git a/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs b/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs index 7dec9aa..e49c5fa 100644 --- a/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs +++ b/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs @@ -11,8 +11,8 @@ namespace Oma.WndwCtrl.MgmtApi; -public class MgmtApiService(MessageBusAccessor? messageBusAccessor) - : WebApplicationWrapper(messageBusAccessor) +public class MgmtApiService(MessageBusAccessor? messageBusAccessor, IConfiguration? rootConfiguration) + : WebApplicationWrapper(messageBusAccessor, rootConfiguration) { protected override IServiceCollection ConfigureServices(IServiceCollection services) { diff --git a/Oma.WndwCtrl.MgmtApi/Program.cs b/Oma.WndwCtrl.MgmtApi/Program.cs index 011c1da..db41b56 100644 --- a/Oma.WndwCtrl.MgmtApi/Program.cs +++ b/Oma.WndwCtrl.MgmtApi/Program.cs @@ -1,5 +1,5 @@ using Oma.WndwCtrl.MgmtApi; -MgmtApiService apiService = new(null); +MgmtApiService apiService = new(messageBusAccessor: null, rootConfiguration: null); await apiService.StartAsync(); await apiService.WaitForShutdownAsync(); \ No newline at end of file diff --git a/Oma.WndwCtrl.sln b/Oma.WndwCtrl.sln index 8cc5f5a..084c554 100644 --- a/Oma.WndwCtrl.sln +++ b/Oma.WndwCtrl.sln @@ -66,6 +66,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installation", "Installatio Documentation\Installation\Linux.md = Documentation\Installation\Linux.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Oma.WndwCtrl.Extensions", "Oma.WndwCtrl.Extensions", "{9AE4447A-201F-4DCF-8715-803610028379}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{4A56D8FD-7965-4FE2-B989-B72A3CB14FB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oma.WndwCtrl.Ext.Windows.Media", "Oma.WndwCtrl.Extensions\Windows\Oma.WndwCtrl.Ext.Windows.Media\Oma.WndwCtrl.Ext.Windows.Media.csproj", "{418214AE-8597-4FAB-A511-C4194AD96E4F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +146,10 @@ Global {59CE4E31-306C-48EC-AD4D-EBB8F9000FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU {59CE4E31-306C-48EC-AD4D-EBB8F9000FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU {59CE4E31-306C-48EC-AD4D-EBB8F9000FFD}.Release|Any CPU.Build.0 = Release|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {E7217C2E-5C78-4106-977D-3091CFA63F43} = {307F90ED-24AD-4725-960B-0A7AAFFE8C6D} @@ -152,5 +162,7 @@ Global {94B42F55-3A25-4022-B02B-28F4F945CB00} = {FC063003-91F3-415C-AB68-D1C08337FAC2} {C0FC9E76-5113-4E14-8061-4A9EE8CA1507} = {FC063003-91F3-415C-AB68-D1C08337FAC2} {41D1E3C4-0AC3-4245-BB9C-68F8D13481ED} = {94B42F55-3A25-4022-B02B-28F4F945CB00} + {4A56D8FD-7965-4FE2-B989-B72A3CB14FB6} = {9AE4447A-201F-4DCF-8715-803610028379} + {418214AE-8597-4FAB-A511-C4194AD96E4F} = {4A56D8FD-7965-4FE2-B989-B72A3CB14FB6} EndGlobalSection EndGlobal diff --git a/Oma.WndwCtrl.sln.DotSettings b/Oma.WndwCtrl.sln.DotSettings index 3aeed5b..d8bd1dc 100644 --- a/Oma.WndwCtrl.sln.DotSettings +++ b/Oma.WndwCtrl.sln.DotSettings @@ -117,5 +117,6 @@ True True True + True True True \ No newline at end of file