1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2024-05-18 13:32:53 +02:00
commit 7052ee38b6
18 changed files with 407 additions and 56 deletions

View File

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using RGB.NET.Core; using RGB.NET.Core;
namespace Artemis.Core.DeviceProviders; namespace Artemis.Core.DeviceProviders;
@ -15,7 +16,7 @@ public abstract class DeviceProvider : PluginFeature
/// The RGB.NET device provider backing this Artemis device provider /// The RGB.NET device provider backing this Artemis device provider
/// </summary> /// </summary>
public abstract IRGBDeviceProvider RgbDeviceProvider { get; } public abstract IRGBDeviceProvider RgbDeviceProvider { get; }
/// <summary> /// <summary>
/// A boolean indicating whether this device provider detects the physical layout of connected keyboards. /// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
/// <para> /// <para>
@ -48,6 +49,11 @@ public abstract class DeviceProvider : PluginFeature
/// </summary> /// </summary>
public bool RemoveExcessiveLedsSupported { get; protected set; } = true; public bool RemoveExcessiveLedsSupported { get; protected set; } = true;
/// <summary>
/// Gets or sets a boolean indicating whether suspending the device provider is supported
/// </summary>
public bool SuspendSupported { get; protected set; }
/// <summary> /// <summary>
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" /> /// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
/// </summary> /// </summary>
@ -109,4 +115,12 @@ public abstract class DeviceProvider : PluginFeature
return fileName + ".xml"; return fileName + ".xml";
} }
/// <summary>
/// Called when the device provider is being suspended, like when the system is going to sleep.
/// Note: This will be called while the plugin is disabled.
/// </summary>
public virtual void Suspend()
{
}
} }

View File

@ -2,13 +2,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers; using Artemis.Core.Providers;
using Artemis.Core.Services.Models; using Artemis.Core.Services.Models;
using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using DryIoc;
using RGB.NET.Core; using RGB.NET.Core;
using Serilog; using Serilog;
@ -21,8 +21,10 @@ internal class DeviceService : IDeviceService
private readonly IDeviceRepository _deviceRepository; private readonly IDeviceRepository _deviceRepository;
private readonly Lazy<IRenderService> _renderService; private readonly Lazy<IRenderService> _renderService;
private readonly Func<List<ILayoutProvider>> _getLayoutProviders; private readonly Func<List<ILayoutProvider>> _getLayoutProviders;
private readonly List<ArtemisDevice> _enabledDevices = new(); private readonly List<ArtemisDevice> _enabledDevices = [];
private readonly List<ArtemisDevice> _devices = new(); private readonly List<ArtemisDevice> _devices = [];
private readonly List<DeviceProvider> _suspendedDeviceProviders = [];
private readonly object _suspensionLock = new();
public DeviceService(ILogger logger, public DeviceService(ILogger logger,
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
@ -69,7 +71,7 @@ internal class DeviceService : IDeviceService
OnDeviceRemoved(new DeviceEventArgs(device)); OnDeviceRemoved(new DeviceEventArgs(device));
} }
List<Exception> providerExceptions = new(); List<Exception> providerExceptions = [];
void DeviceProviderOnException(object? sender, ExceptionEventArgs e) void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{ {
@ -95,7 +97,7 @@ internal class DeviceService : IDeviceService
return; return;
} }
List<ArtemisDevice> addedDevices = new(); List<ArtemisDevice> addedDevices = [];
foreach (IRGBDevice rgbDevice in rgbDeviceProvider.Devices) foreach (IRGBDevice rgbDevice in rgbDeviceProvider.Devices)
{ {
ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice); ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice);
@ -184,7 +186,7 @@ internal class DeviceService : IDeviceService
device.ApplyLayout(null, false, false); device.ApplyLayout(null, false, false);
else else
provider?.ApplyLayout(device, layout); provider?.ApplyLayout(device, layout);
UpdateLeds(); UpdateLeds();
} }
catch (Exception e) catch (Exception e)
@ -241,6 +243,83 @@ internal class DeviceService : IDeviceService
UpdateLeds(); UpdateLeds();
} }
/// <inheritdoc />
public void SuspendDeviceProviders()
{
lock (_suspensionLock)
{
_logger.Information("Suspending all device providers");
bool wasPaused = _renderService.Value.IsPaused;
try
{
_renderService.Value.IsPaused = true;
foreach (DeviceProvider deviceProvider in _pluginManagementService.GetFeaturesOfType<DeviceProvider>().Where(d => d.SuspendSupported))
SuspendDeviceProvider(deviceProvider);
}
finally
{
_renderService.Value.IsPaused = wasPaused;
}
}
}
/// <inheritdoc />
public void ResumeDeviceProviders()
{
lock (_suspensionLock)
{
_logger.Information("Resuming all device providers");
bool wasPaused = _renderService.Value.IsPaused;
try
{
_renderService.Value.IsPaused = true;
foreach (DeviceProvider deviceProvider in _suspendedDeviceProviders.ToList())
ResumeDeviceProvider(deviceProvider);
}
finally
{
_renderService.Value.IsPaused = wasPaused;
}
}
}
private void SuspendDeviceProvider(DeviceProvider deviceProvider)
{
if (_suspendedDeviceProviders.Contains(deviceProvider))
{
_logger.Warning("Device provider {DeviceProvider} is already suspended", deviceProvider.Info.Name);
return;
}
try
{
_pluginManagementService.DisablePluginFeature(deviceProvider, false);
deviceProvider.Suspend();
_suspendedDeviceProviders.Add(deviceProvider);
_logger.Information("Device provider {DeviceProvider} suspended", deviceProvider.Info.Name);
}
catch (Exception e)
{
_logger.Error(e, "Device provider {DeviceProvider} failed to suspend", deviceProvider.Info.Name);
}
}
private void ResumeDeviceProvider(DeviceProvider deviceProvider)
{
try
{
_pluginManagementService.EnablePluginFeature(deviceProvider, false, true);
_suspendedDeviceProviders.Remove(deviceProvider);
_logger.Information("Device provider {DeviceProvider} resumed", deviceProvider.Info.Name);
}
catch (Exception e)
{
_logger.Error(e, "Device provider {DeviceProvider} failed to resume", deviceProvider.Info.Name);
}
}
private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice) private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice)
{ {
string deviceIdentifier = rgbDevice.GetDeviceIdentifier(); string deviceIdentifier = rgbDevice.GetDeviceIdentifier();

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
namespace Artemis.Core.Services; namespace Artemis.Core.Services;
@ -71,6 +72,16 @@ public interface IDeviceService : IArtemisService
/// </summary> /// </summary>
void SaveDevices(); void SaveDevices();
/// <summary>
/// Suspends all active device providers
/// </summary>
void SuspendDeviceProviders();
/// <summary>
/// Resumes all previously active device providers
/// </summary>
void ResumeDeviceProviders();
/// <summary> /// <summary>
/// Occurs when a single device was added. /// Occurs when a single device was added.
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
@ -69,12 +70,19 @@ public class DataModelPicker : TemplatedControl
public static readonly StyledProperty<bool> IsEventPickerProperty = public static readonly StyledProperty<bool> IsEventPickerProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(IsEventPicker)); AvaloniaProperty.Register<DataModelPicker, bool>(nameof(IsEventPicker));
private readonly ObservableCollection<DataModelVisualizationViewModel> _searchResults = [];
private TextBlock? _currentPathDescription; private TextBlock? _currentPathDescription;
private TextBlock? _currentPathDisplay; private TextBlock? _currentPathDisplay;
private MaterialIcon? _currentPathIcon; private MaterialIcon? _currentPathIcon;
private TreeView? _dataModelTreeView; private TreeView? _dataModelTreeView;
private ListBox? _searchListBox;
private TextBox? _searchBox;
private Panel? _searchContainer;
private StackPanel? _searchEmpty;
private DispatcherTimer? _updateTimer; private DispatcherTimer? _updateTimer;
private string? _lastSearch;
private bool _firstUpdate;
static DataModelPicker() static DataModelPicker()
{ {
@ -200,7 +208,14 @@ public class DataModelPicker : TemplatedControl
if (DataModelUIService == null) if (DataModelUIService == null)
return; return;
DataModelViewModel?.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker)); DataModelViewModel?.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker, !string.IsNullOrEmpty(_searchBox?.Text)));
ApplySearch();
if (_firstUpdate)
{
_firstUpdate = false;
UpdateCurrentPath(true);
}
} }
private void GetDataModel() private void GetDataModel()
@ -229,14 +244,15 @@ public class DataModelPicker : TemplatedControl
if (DataModelUIService == null || DataModelViewModel == null) if (DataModelUIService == null || DataModelViewModel == null)
return; return;
DataModelViewModel.Update(DataModelUIService, new DataModelUpdateConfiguration(IsEventPicker)); DataModelViewModel.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker, !string.IsNullOrEmpty(_searchBox?.Text)));
DataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); DataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
ApplySearch();
} }
private void DataModelTreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e) private void TreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{ {
// Multi-select isn't a think so grab the first one // Multi-select isn't a thing so grab the first one
object? selected = _dataModelTreeView?.SelectedItems[0]; object? selected = e.AddedItems[0];
if (selected == null) if (selected == null)
return; return;
@ -255,12 +271,48 @@ public class DataModelPicker : TemplatedControl
treeViewItem.IsExpanded = !treeViewItem.IsExpanded; treeViewItem.IsExpanded = !treeViewItem.IsExpanded;
} }
private void ApplySearch()
{
if (DataModelViewModel == null || string.IsNullOrWhiteSpace(_searchBox?.Text))
{
if (_dataModelTreeView != null)
_dataModelTreeView.IsVisible = true;
if (_searchContainer != null)
_searchContainer.IsVisible = false;
return;
}
if (_dataModelTreeView != null)
_dataModelTreeView.IsVisible = false;
if (_searchContainer != null)
_searchContainer.IsVisible = true;
if (_searchBox.Text != _lastSearch)
{
_searchResults.Clear();
foreach (DataModelVisualizationViewModel searchResult in DataModelViewModel.GetSearchResults(_searchBox.Text).DistinctBy(r => r.Path).Take(20))
_searchResults.Add(searchResult);
_lastSearch = _searchBox.Text;
}
if (_searchListBox != null)
{
_searchListBox.IsVisible = _searchResults.Count > 0;
_searchListBox.SelectedItem = _searchResults.FirstOrDefault(r => r.DataModelPath?.Path == DataModelPath?.Path);
}
if (_searchEmpty != null)
_searchEmpty.IsVisible = _searchResults.Count == 0;
}
private void UpdateCurrentPath(bool selectCurrentPath) private void UpdateCurrentPath(bool selectCurrentPath)
{ {
if (DataModelPath == null) if (DataModelPath == null || _dataModelTreeView == null)
return; return;
if (_dataModelTreeView != null && selectCurrentPath) if (selectCurrentPath)
{ {
// Expand the path // Expand the path
DataModel? start = DataModelPath.Target; DataModel? start = DataModelPath.Target;
@ -286,7 +338,7 @@ public class DataModelPicker : TemplatedControl
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
if (_dataModelTreeView != null) if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged; _dataModelTreeView.SelectionChanged -= TreeViewOnSelectionChanged;
if (_dataModelTreeView != null) if (_dataModelTreeView != null)
_dataModelTreeView.DoubleTapped -= DataModelTreeViewOnDoubleTapped; _dataModelTreeView.DoubleTapped -= DataModelTreeViewOnDoubleTapped;
@ -294,11 +346,22 @@ public class DataModelPicker : TemplatedControl
_currentPathDisplay = e.NameScope.Find<TextBlock>("CurrentPathDisplay"); _currentPathDisplay = e.NameScope.Find<TextBlock>("CurrentPathDisplay");
_currentPathDescription = e.NameScope.Find<TextBlock>("CurrentPathDescription"); _currentPathDescription = e.NameScope.Find<TextBlock>("CurrentPathDescription");
_dataModelTreeView = e.NameScope.Find<TreeView>("DataModelTreeView"); _dataModelTreeView = e.NameScope.Find<TreeView>("DataModelTreeView");
_searchBox = e.NameScope.Find<TextBox>("SearchBox");
_searchContainer = e.NameScope.Find<Panel>("SearchContainer");
_searchListBox = e.NameScope.Find<ListBox>("SearchListBox");
_searchEmpty = e.NameScope.Find<StackPanel>("SearchEmpty");
if (_dataModelTreeView != null) if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged; {
if (_dataModelTreeView != null) _dataModelTreeView.SelectionChanged += TreeViewOnSelectionChanged;
_dataModelTreeView.DoubleTapped += DataModelTreeViewOnDoubleTapped; _dataModelTreeView.DoubleTapped += DataModelTreeViewOnDoubleTapped;
}
if (_searchListBox != null)
{
_searchListBox.ItemsSource = _searchResults;
_searchListBox.SelectionChanged += TreeViewOnSelectionChanged;
}
} }
#region Overrides of Visual #region Overrides of Visual
@ -307,17 +370,23 @@ public class DataModelPicker : TemplatedControl
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
GetDataModel(); GetDataModel();
UpdateCurrentPath(true);
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Background, Update); _updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Background, Update);
_updateTimer.Start(); _updateTimer.Start();
_firstUpdate = true;
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
DataModelViewModel?.Dispose(); DataModelViewModel?.Dispose();
_updateTimer?.Stop(); _updateTimer?.Stop();
_updateTimer = null; _updateTimer = null;
_lastSearch = null;
if (_searchBox != null)
_searchBox.Text = null;
} }
#endregion #endregion

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
@ -41,13 +42,21 @@ public class DataModelEventViewModel : DataModelVisualizationViewModel
} }
// Only update children if the parent is expanded // Only update children if the parent is expanded
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded) if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded && (configuration == null || !configuration.UpdateAllChildren))
return; return;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children) foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration); dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
} }
/// <inheritdoc />
public override IEnumerable<DataModelVisualizationViewModel> GetSearchResults(string search)
{
if (PropertyDescription?.Name != null && PropertyDescription.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
return [this];
return [];
}
/// <summary> /// <summary>
/// Always returns <see langword="null" /> for data model events /// Always returns <see langword="null" /> for data model events
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using ReactiveUI; using ReactiveUI;
@ -62,6 +63,12 @@ public class DataModelListItemViewModel : DataModelVisualizationViewModel
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value); internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
} }
/// <inheritdoc />
public override IEnumerable<DataModelVisualizationViewModel> GetSearchResults(string search)
{
return [];
}
/// <inheritdoc /> /// <inheritdoc />
public override object? GetCurrentValue() public override object? GetCurrentValue()
{ {

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
@ -119,6 +120,17 @@ public class DataModelListViewModel : DataModelVisualizationViewModel
CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}"; CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}";
} }
/// <inheritdoc />
public override IEnumerable<DataModelVisualizationViewModel> GetSearchResults(string search)
{
if (PropertyDescription?.Name != null && PropertyDescription.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
return [this];
if (PropertyDescription?.Description != null && PropertyDescription.Description.Contains(search, StringComparison.OrdinalIgnoreCase))
return [this];
return [];
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -52,14 +54,20 @@ public class DataModelPropertiesViewModel : DataModelVisualizationViewModel
// Always populate properties // Always populate properties
PopulateProperties(dataModelUIService, configuration); PopulateProperties(dataModelUIService, configuration);
// Only update children if the parent is expanded // Only update children if the parent is expanded or when searching
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded) if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded && (configuration == null || !configuration.UpdateAllChildren))
return; return;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children) foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration); dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
} }
/// <inheritdoc />
public override IEnumerable<DataModelVisualizationViewModel> GetSearchResults(string search)
{
return Children.SelectMany(c => c.GetSearchResults(search));
}
/// <inheritdoc /> /// <inheritdoc />
public override object? GetCurrentValue() public override object? GetCurrentValue()
{ {

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -71,6 +72,17 @@ public class DataModelPropertyViewModel : DataModelVisualizationViewModel
DisplayViewModel?.UpdateValue(DisplayValue); DisplayViewModel?.UpdateValue(DisplayValue);
} }
/// <inheritdoc />
public override IEnumerable<DataModelVisualizationViewModel> GetSearchResults(string search)
{
if (PropertyDescription?.Name != null && PropertyDescription.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
return [this];
if (DisplayValue != null && DisplayValue.ToString()?.Contains(search, StringComparison.OrdinalIgnoreCase) == true)
return [this];
return [];
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {

View File

@ -8,14 +8,21 @@ public class DataModelUpdateConfiguration
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="DataModelUpdateConfiguration" /> class /// Creates a new instance of the <see cref="DataModelUpdateConfiguration" /> class
/// </summary> /// </summary>
/// <param name="createEventChildren">A boolean indicating whether or not event children should be created</param> /// <param name="createEventChildren">A boolean indicating whether event children should be created</param>
public DataModelUpdateConfiguration(bool createEventChildren) /// <param name="updateAllChildren">A boolean indicating whether all children should be updated</param>
public DataModelUpdateConfiguration(bool createEventChildren, bool updateAllChildren)
{ {
CreateEventChildren = createEventChildren; CreateEventChildren = createEventChildren;
UpdateAllChildren = updateAllChildren;
} }
/// <summary> /// <summary>
/// Gets a boolean indicating whether or not event children should be created /// Gets a boolean indicating whether event children should be created
/// </summary> /// </summary>
public bool CreateEventChildren { get; } public bool CreateEventChildren { get; }
/// <summary>
/// Gets a boolean indicating whether all children should be updated
/// </summary>
public bool UpdateAllChildren { get; }
} }

View File

@ -144,6 +144,13 @@ public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposa
/// <param name="configuration">The configuration to apply while updating</param> /// <param name="configuration">The configuration to apply while updating</param>
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration); public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration);
/// <summary>
/// Gets the search results for the provided search string
/// </summary>
/// <param name="search">The search string</param>
/// <returns>The search results</returns>
public abstract IEnumerable<DataModelVisualizationViewModel> GetSearchResults(string search);
/// <summary> /// <summary>
/// Gets the current value of the property being visualized /// Gets the current value of the property being visualized
/// </summary> /// </summary>
@ -281,7 +288,7 @@ public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposa
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken)) foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
{ {
string childPath = AppendToPath(propertyInfo.Name); string childPath = AppendToPath(propertyInfo.Name);
if (!ShouldIncludePath(childPath, propertyInfo)) if (!ShouldIncludePath(childPath, propertyInfo))
continue; continue;

View File

@ -11,38 +11,43 @@
<Style Selector="dataModelPicker|DataModelPicker"> <Style Selector="dataModelPicker|DataModelPicker">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid RowDefinitions="Auto,Auto,*" Width="600" Height="400" Margin="10"> <Grid RowDefinitions="Auto,Auto,*" MinWidth="600" MaxHeight="1200" Height="400" Margin="10">
<TextBox Grid.Row="0" Watermark="Search - not yet implemented 😱" Name="SearchBox" IsEnabled="False" /> <TextBox Grid.Row="0" Watermark="Search" Name="SearchBox" Classes="search-box" />
<Border Grid.Row="1" Classes="card card-condensed" Margin="0 10"> <Border Grid.Row="1" Classes="card card-condensed" Margin="0 10">
<Panel> <Panel>
<Grid ColumnDefinitions="Auto,*" <Grid ColumnDefinitions="Auto,*"
RowDefinitions="*" RowDefinitions="*"
MinHeight="38" MinHeight="38"
IsVisible="{CompiledBinding DataModelPath, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNotNull}}"> IsVisible="{CompiledBinding ElementName=CurrentPathDisplay, Path=Text, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<avalonia:MaterialIcon Grid.Column="0" <avalonia:MaterialIcon Grid.Column="0"
Grid.Row="0" Grid.Row="0"
Name="CurrentPathIcon" Name="CurrentPathIcon"
Kind="QuestionMarkCircle" Kind="QuestionMarkCircle"
Height="22" Height="22"
Width="22" Width="22"
Margin="5 0 15 0" Margin="5 0 15 0"
IsVisible="{CompiledBinding !IsEventPicker, RelativeSource={RelativeSource TemplatedParent}}"/> IsVisible="{CompiledBinding !IsEventPicker, RelativeSource={RelativeSource TemplatedParent}}" />
<avalonia:MaterialIcon Grid.Column="0" <avalonia:MaterialIcon Grid.Column="0"
Grid.Row="0" Grid.Row="0"
Name="EventIcon" Name="EventIcon"
Kind="LightningBolt" Kind="LightningBolt"
Height="22" Height="22"
Width="22" Width="22"
Margin="5 0 15 0" Margin="5 0 15 0"
IsVisible="{CompiledBinding IsEventPicker, RelativeSource={RelativeSource TemplatedParent}}"/> IsVisible="{CompiledBinding IsEventPicker, RelativeSource={RelativeSource TemplatedParent}}" />
<StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Center"> <StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Center">
<TextBlock Name="CurrentPathDisplay" Classes="BodyStrongTextBlockStyle" MaxHeight="50" /> <TextBlock Name="CurrentPathDisplay" Classes="BodyStrongTextBlockStyle" MaxHeight="50" />
<TextBlock Name="CurrentPathDescription" Classes="BodyTextBlockStyle" Foreground="{DynamicResource TextFillColorSecondary}" MaxHeight="50" /> <TextBlock Name="CurrentPathDescription"
IsVisible="{CompiledBinding ElementName=CurrentPathDescription, Path=Text, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Classes="BodyTextBlockStyle"
Foreground="{DynamicResource TextFillColorSecondary}"
MaxHeight="50" />
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid MinHeight="38" <Grid MinHeight="38"
IsVisible="{CompiledBinding DataModelPath, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNull}}" ColumnDefinitions="*,Auto" IsVisible="{CompiledBinding ElementName=CurrentPathDisplay, Path=Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
ColumnDefinitions="*,Auto"
RowDefinitions="*,*"> RowDefinitions="*,*">
<TextBlock Grid.Column="0" Grid.Row="0" Classes="BodyStrongTextBlockStyle">Welcome to the data model picker</TextBlock> <TextBlock Grid.Column="0" Grid.Row="0" Classes="BodyStrongTextBlockStyle">Welcome to the data model picker</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Foreground="{DynamicResource TextFillColorSecondary}">Select a value from the data model below</TextBlock> <TextBlock Grid.Column="0" Grid.Row="1" Foreground="{DynamicResource TextFillColorSecondary}">Select a value from the data model below</TextBlock>
@ -53,7 +58,8 @@
<TreeView Grid.Row="2" <TreeView Grid.Row="2"
Name="DataModelTreeView" Name="DataModelTreeView"
ItemsSource="{CompiledBinding DataModelViewModel.Children, RelativeSource={RelativeSource TemplatedParent}}"> ItemsSource="{CompiledBinding DataModelViewModel.Children, RelativeSource={RelativeSource TemplatedParent}}"
IsVisible="False">
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem"> <Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{CompiledBinding IsVisualizationExpanded, Mode=TwoWay, DataType=dataModel:DataModelVisualizationViewModel}" /> <Setter Property="IsExpanded" Value="{CompiledBinding IsVisualizationExpanded, Mode=TwoWay, DataType=dataModel:DataModelVisualizationViewModel}" />
@ -69,7 +75,7 @@
FontFamily="{StaticResource RobotoMono}" FontFamily="{StaticResource RobotoMono}"
FontSize="13" FontSize="13"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0 0 10 0" /> Margin="10 0"/>
</Grid> </Grid>
</TreeDataTemplate> </TreeDataTemplate>
@ -79,10 +85,10 @@
<TextBlock Text="{CompiledBinding PropertyDescription.Name}" ToolTip.Tip="{CompiledBinding PropertyDescription.Description}" /> <TextBlock Text="{CompiledBinding PropertyDescription.Name}" ToolTip.Tip="{CompiledBinding PropertyDescription.Description}" />
<TextBlock Text=" changed" <TextBlock Text=" changed"
ToolTip.Tip="{CompiledBinding PropertyDescription.Description}" ToolTip.Tip="{CompiledBinding PropertyDescription.Description}"
IsVisible="{CompiledBinding IsEventPicker, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dataModelPicker:DataModelPicker}}}"/> IsVisible="{CompiledBinding IsEventPicker, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dataModelPicker:DataModelPicker}}}" />
</StackPanel> </StackPanel>
<ContentControl Grid.Column="1" Content="{CompiledBinding DisplayViewModel}" FontFamily="{StaticResource RobotoMono}" FontSize="13" Margin="0 0 10 0" /> <ContentControl Grid.Column="1" Content="{CompiledBinding DisplayViewModel}" FontFamily="{StaticResource RobotoMono}" FontSize="13" Margin="10 0" />
</Grid> </Grid>
</TreeDataTemplate> </TreeDataTemplate>
@ -94,7 +100,7 @@
FontFamily="{StaticResource RobotoMono}" FontFamily="{StaticResource RobotoMono}"
FontSize="13" FontSize="13"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0 0 10 0" /> Margin="10 0" />
</Grid> </Grid>
</TreeDataTemplate> </TreeDataTemplate>
@ -103,10 +109,46 @@
</TreeDataTemplate> </TreeDataTemplate>
</TreeView.DataTemplates> </TreeView.DataTemplates>
</TreeView> </TreeView>
<StackPanel Grid.Row="2" VerticalAlignment="Center" Spacing="20" IsVisible="False">
<avalonia:MaterialIcon Kind="CloseCircle" Width="64" Height="64"></avalonia:MaterialIcon> <Panel Name="SearchContainer" Grid.Row="2">
<TextBlock Classes="h4" TextAlignment="Center">No parts of the data model match your search</TextBlock> <ListBox Name="SearchListBox">
</StackPanel> <ListBox.DataTemplates>
<DataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
<Grid ColumnDefinitions="Auto,*">
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="{CompiledBinding DisplayPath}" ToolTip.Tip="{CompiledBinding PropertyDescription.Description}" />
<TextBlock Text=" changed"
ToolTip.Tip="{CompiledBinding PropertyDescription.Description}"
IsVisible="{CompiledBinding IsEventPicker, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dataModelPicker:DataModelPicker}}}" />
</StackPanel>
<ContentControl Grid.Column="1" Content="{CompiledBinding DisplayViewModel}" FontFamily="{StaticResource RobotoMono}" FontSize="13" Margin="10 0"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type dataModel:DataModelListViewModel}">
<Grid ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" Text="{CompiledBinding DisplayPath}" ToolTip.Tip="{CompiledBinding PropertyDescription.Description}" />
<TextBlock Grid.Column="1"
Text="{CompiledBinding CountDisplay, Mode=OneWay}"
FontFamily="{StaticResource RobotoMono}"
FontSize="13"
HorizontalAlignment="Right"
Margin="10 0"/>
</Grid>
</DataTemplate>
<TreeDataTemplate DataType="{x:Type dataModel:DataModelEventViewModel}" ItemsSource="{CompiledBinding Children}">
<TextBlock Text="{CompiledBinding DisplayPath}" ToolTip.Tip="{CompiledBinding PropertyDescription.Description}" />
</TreeDataTemplate>
</ListBox.DataTemplates>
</ListBox>
<StackPanel Name="SearchEmpty" VerticalAlignment="Center" Spacing="20" IsVisible="False">
<avalonia:MaterialIcon Kind="CloseCircle" Width="64" Height="64"></avalonia:MaterialIcon>
<TextBlock Classes="h4" TextAlignment="Center">No parts of the data model match your search</TextBlock>
</StackPanel>
</Panel>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>

View File

@ -35,10 +35,10 @@ public class App : Application
} }
_container = ArtemisBootstrapper.Bootstrap(this, c => c.RegisterProviders()); _container = ArtemisBootstrapper.Bootstrap(this, c => c.RegisterProviders());
Program.CreateLogger(_container); Program.CreateLogger(_container);
LegacyMigrationService.MigrateToSqlite(_container); LegacyMigrationService.MigrateToSqlite(_container);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
@ -47,10 +47,13 @@ public class App : Application
{ {
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown) if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
return; return;
if (_container == null)
_applicationStateManager = new ApplicationStateManager(_container!, desktop.Args ?? Array.Empty<string>()); throw new InvalidOperationException("Container is null");
_applicationStateManager = new ApplicationStateManager(_container, desktop.Args ?? Array.Empty<string>());
_suspensionManager = new SuspensionManager(_container);
ArtemisBootstrapper.Initialize(); ArtemisBootstrapper.Initialize();
RegisterProviders(_container!); RegisterProviders(_container);
} }
private void RegisterProviders(IContainer container) private void RegisterProviders(IContainer container)
@ -117,8 +120,10 @@ public class App : Application
} }
// ReSharper disable NotAccessedField.Local // ReSharper disable NotAccessedField.Local
private ApplicationStateManager? _applicationStateManager;
private ApplicationStateManager? _applicationStateManager;
private SuspensionManager? _suspensionManager;
private Mutex? _artemisMutex; private Mutex? _artemisMutex;
// ReSharper restore NotAccessedField.Local // ReSharper restore NotAccessedField.Local
} }

View File

@ -24,6 +24,7 @@
<PackageReference Include="Avalonia.Win32" /> <PackageReference Include="Avalonia.Win32" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" />
<PackageReference Include="Microsoft.Win32" /> <PackageReference Include="Microsoft.Win32" />
<PackageReference Include="Microsoft.Win32.SystemEvents" />
<PackageReference Include="Microsoft.Windows.Compatibility" /> <PackageReference Include="Microsoft.Windows.Compatibility" />
<PackageReference Include="RawInput.Sharp" /> <PackageReference Include="RawInput.Sharp" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" /> <PackageReference Include="SkiaSharp.Vulkan.SharpVk" />

View File

@ -0,0 +1,67 @@
using System;
using System.Threading.Tasks;
using Artemis.Core.Services;
using DryIoc;
using Microsoft.Win32;
using Serilog;
namespace Artemis.UI.Windows;
public class SuspensionManager
{
private readonly ILogger _logger;
private readonly IDeviceService _deviceService;
public SuspensionManager(IContainer container)
{
_logger = container.Resolve<ILogger>();
_deviceService = container.Resolve<IDeviceService>();
try
{
SystemEvents.PowerModeChanged += SystemEventsOnPowerModeChanged;
SystemEvents.SessionSwitch += SystemEventsOnSessionSwitch;
}
catch (Exception e)
{
_logger.Warning(e, "Could not subscribe to system events");
}
}
private void SystemEventsOnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
if (e.Mode == PowerModes.Suspend)
Task.Run(() => SetDeviceSuspension(true));
else if (e.Mode == PowerModes.Resume)
Task.Run(() => SetDeviceSuspension(false));
}
private void SystemEventsOnSessionSwitch(object sender, SessionSwitchEventArgs e)
{
if (e.Reason is SessionSwitchReason.SessionLock or SessionSwitchReason.SessionLogoff)
Task.Run(() => SetDeviceSuspension(true));
else if (e.Reason is SessionSwitchReason.SessionUnlock or SessionSwitchReason.SessionLogon)
Task.Run(() => SetDeviceSuspension(false));
}
private async Task SetDeviceSuspension(bool suspend)
{
try
{
if (suspend)
{
// Suspend instantly, system is going into sleep at any moment
_deviceService.SuspendDeviceProviders();
}
else
{
await Task.Delay(TimeSpan.FromSeconds(2));
_deviceService.ResumeDeviceProviders();
}
}
catch (Exception e)
{
_logger.Error(e, "An error occurred while setting provider suspension to {Suspension}", suspend);
}
}
}

View File

@ -100,7 +100,7 @@ public partial class DataModelDebugViewModel : ActivatableViewModelBase
lock (MainDataModel) lock (MainDataModel)
{ {
MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true)); MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true, false));
} }
} }

View File

@ -21,7 +21,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
{ {
private const string CLIENT_ID = "artemis.desktop"; private const string CLIENT_ID = "artemis.desktop";
private readonly IAuthenticationRepository _authenticationRepository; private readonly IAuthenticationRepository _authenticationRepository;
private readonly SemaphoreSlim _authLock = new(1); private readonly SemaphoreSlim _authLock = new(1, 1);
private readonly SourceList<Claim> _claims; private readonly SourceList<Claim> _claims;
private readonly IDiscoveryCache _discoveryCache; private readonly IDiscoveryCache _discoveryCache;

View File

@ -14,6 +14,7 @@
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.0.9" /> <PackageVersion Include="Avalonia.ReactiveUI" Version="11.0.9" />
<PackageVersion Include="Avalonia.Skia.Lottie" Version="11.0.0" /> <PackageVersion Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageVersion Include="Avalonia.Win32" Version="11.0.9" /> <PackageVersion Include="Avalonia.Win32" Version="11.0.9" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="8.0.0" />
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.0.6" /> <PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.0.6" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.0.6" /> <PackageVersion Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" /> <PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />