diff --git a/src/Artemis.Core/Extensions/ProcessExtensions.cs b/src/Artemis.Core/Extensions/ProcessExtensions.cs new file mode 100644 index 000000000..3353ed60d --- /dev/null +++ b/src/Artemis.Core/Extensions/ProcessExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Artemis.Core.Extensions +{ + public static class ProcessExtensions + { + public static string GetProcessFilename(this Process p) + { + var capacity = 2000; + var builder = new StringBuilder(capacity); + var ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id); + if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) return string.Empty; + + return builder.ToString(); + } + + [DllImport("kernel32.dll")] + private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize); + + [DllImport("kernel32.dll")] + private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId); + + [Flags] + private enum ProcessAccessFlags : uint + { + QueryLimitedInformation = 0x00001000 + } + } +} diff --git a/src/Artemis.Core/Plugins/Abstract/Module.cs b/src/Artemis.Core/Plugins/Abstract/Module.cs index 5592004e9..c0a26a264 100644 --- a/src/Artemis.Core/Plugins/Abstract/Module.cs +++ b/src/Artemis.Core/Plugins/Abstract/Module.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; using Artemis.Core.Plugins.Abstract.ViewModels; +using Artemis.Core.Plugins.ModuleActivationRequirements; using SkiaSharp; namespace Artemis.Core.Plugins.Abstract @@ -26,7 +28,7 @@ namespace Artemis.Core.Plugins.Abstract /// Gets or sets whether this module must also expand the main data model /// /// Note: If expanding the main data model is all you want your plugin to do, create a - /// plugin instead. + /// plugin instead. /// /// public bool ExpandsDataModel @@ -66,9 +68,11 @@ namespace Artemis.Core.Plugins.Abstract /// public abstract class Module : Plugin { - internal DataModel InternalDataModel { get; set; } - - internal bool InternalExpandsMainDataModel { get; set; } + protected Module() + { + ActivationRequirements = new List(); + ActivationRequirementMode = ActivationRequirementType.Any; + } /// /// The modules display name that's shown in the menu @@ -81,6 +85,27 @@ namespace Artemis.Core.Plugins.Abstract /// public string DisplayIcon { get; set; } + /// + /// Gets whether this module is activated. A module can only be active while its + /// are met + /// + public bool IsActivated { get; internal set; } + + /// + /// A list of activation requirements + /// Note: if empty the module is always activated + /// + public List ActivationRequirements { get; } + + /// + /// Gets or sets the activation requirement mode, defaults to + /// + public ActivationRequirementType ActivationRequirementMode { get; set; } + + internal DataModel InternalDataModel { get; set; } + + internal bool InternalExpandsMainDataModel { get; set; } + /// /// Called each frame when the module must update /// @@ -101,5 +126,62 @@ namespace Artemis.Core.Plugins.Abstract /// /// public abstract IEnumerable GetViewModels(); + + /// + /// Called when the are met + /// + public abstract void ModuleActivated(); + + /// + /// Called when the are no longer met + /// + public abstract void ModuleDeactivated(); + + /// + /// Evaluates the activation requirements following the and returns the result + /// + /// The evaluated result of the activation requirements + public bool EvaluateActivationRequirements() + { + if (!ActivationRequirements.Any()) + return true; + if (ActivationRequirementMode == ActivationRequirementType.All) + return ActivationRequirements.All(r => r.Evaluate()); + if (ActivationRequirementMode == ActivationRequirementType.Any) + return ActivationRequirements.Any(r => r.Evaluate()); + + return false; + } + + internal virtual void Activate() + { + if (IsActivated) + return; + + ModuleActivated(); + IsActivated = true; + } + + internal virtual void Deactivate() + { + if (!IsActivated) + return; + + IsActivated = false; + ModuleDeactivated(); + } + } + + public enum ActivationRequirementType + { + /// + /// Any activation requirement must be met for the module to activate + /// + Any, + + /// + /// All activation requirements must be met for the module to activate + /// + All } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index 55965ca63..1fa60785b 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -143,7 +143,8 @@ namespace Artemis.Core.Plugins.Abstract { if (profile != null && profile.Module != this) throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {PluginInfo}."); - + if (!IsActivated) + throw new ArtemisCoreException("Cannot activate a profile on a deactivated module"); if (profile == ActiveProfile || AnimatingProfileChange) return; @@ -164,6 +165,9 @@ namespace Artemis.Core.Plugins.Abstract { if (profile != null && profile.Module != this) throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {PluginInfo}."); + if (!IsActivated) + throw new ArtemisCoreException("Cannot activate a profile on a deactivated module"); + lock (this) { if (profile == ActiveProfile) @@ -188,6 +192,15 @@ namespace Artemis.Core.Plugins.Abstract /// public bool AnimatingProfileChange { get; private set; } + internal override void Deactivate() + { + base.Deactivate(); + + var profile = ActiveProfile; + ActiveProfile = null; + profile?.Dispose(); + } + #region Events public event EventHandler ActiveProfileChanged; diff --git a/src/Artemis.Core/Plugins/ModuleActivationRequirements/ProcessActivationRequirement.cs b/src/Artemis.Core/Plugins/ModuleActivationRequirements/ProcessActivationRequirement.cs new file mode 100644 index 000000000..87cd32dbf --- /dev/null +++ b/src/Artemis.Core/Plugins/ModuleActivationRequirements/ProcessActivationRequirement.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Artemis.Core.Extensions; + +namespace Artemis.Core.Plugins.ModuleActivationRequirements +{ + public class ProcessActivationRequirement : IModuleActivationRequirement + { + public string ProcessName { get; set; } + public string Location { get; set; } + + public ProcessActivationRequirement(string processName, string location = null) + { + ProcessName = processName; + Location = location; + } + + + public bool Evaluate() + { + if (ProcessName == null && Location == null) + return false; + + var processes = ProcessName != null ? Process.GetProcessesByName(ProcessName).Where(p => !p.HasExited) : Process.GetProcesses().Where(p => !p.HasExited); + return Location != null + ? processes.Any(p => Path.GetDirectoryName(p.GetProcessFilename()) == Location) + : processes.Any(); + } + } + + public interface IModuleActivationRequirement + { + bool Evaluate(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index ec0654995..5193b45d1 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -39,6 +39,7 @@ namespace Artemis.Core.Services private List _dataModelExpansions; private List _modules; private IntroAnimation _introAnimation; + private DateTime _lastModuleActivationUpdate; // ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else internal CoreService(ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService, @@ -95,8 +96,6 @@ namespace Artemis.Core.Services _logger.Information("Initialized without an active surface entity"); PlayIntroAnimation(); - _profileService.ActivateLastActiveProfiles(); - OnInitialized(); } @@ -170,6 +169,11 @@ namespace Artemis.Core.Services try { _frameStopWatch.Restart(); + + // Only run the module activation update every 2 seconds + if (DateTime.Now - _lastModuleActivationUpdate > TimeSpan.FromSeconds(2)) + ModuleActivationUpdate(); + lock (_dataModelExpansions) { // Update all active modules @@ -177,13 +181,16 @@ namespace Artemis.Core.Services dataModelExpansion.Update(args.DeltaTime); } + List modules; lock (_modules) { - // Update all active modules - foreach (var module in _modules) - module.Update(args.DeltaTime); + modules = _modules.Where(m => m.IsActivated).ToList(); } + // Update all active modules + foreach (var module in modules) + module.Update(args.DeltaTime); + // If there is no ready bitmap brush, skip the frame if (_rgbService.BitmapBrush == null) return; @@ -194,20 +201,15 @@ namespace Artemis.Core.Services return; // Render all active modules - using (var canvas = new SKCanvas(_rgbService.BitmapBrush.Bitmap)) + using var canvas = new SKCanvas(_rgbService.BitmapBrush.Bitmap); + canvas.Clear(new SKColor(0, 0, 0)); + if (!ModuleRenderingDisabled) { - canvas.Clear(new SKColor(0, 0, 0)); - if (!ModuleRenderingDisabled) - { - lock (_modules) - { - foreach (var module in _modules) - module.Render(args.DeltaTime, _surfaceService.ActiveSurface, canvas, _rgbService.BitmapBrush.Bitmap.Info); - } - } - - OnFrameRendering(new FrameRenderingEventArgs(_modules, canvas, args.DeltaTime, _rgbService.Surface)); + foreach (var module in modules) + module.Render(args.DeltaTime, _surfaceService.ActiveSurface, canvas, _rgbService.BitmapBrush.Bitmap.Info); } + + OnFrameRendering(new FrameRenderingEventArgs(modules, canvas, args.DeltaTime, _rgbService.Surface)); } } catch (Exception e) @@ -221,6 +223,33 @@ namespace Artemis.Core.Services } } + private void ModuleActivationUpdate() + { + _lastModuleActivationUpdate = DateTime.Now; + var stopwatch = new Stopwatch(); + stopwatch.Start(); + lock (_modules) + { + foreach (var module in _modules) + { + var shouldBeActivated = module.EvaluateActivationRequirements(); + if (shouldBeActivated && !module.IsActivated) + { + module.Activate(); + // If this is a profile module, activate the last active profile after module activation + if (module is ProfileModule profileModule) + _profileService.ActivateLastProfile(profileModule); + } + else if (!shouldBeActivated && module.IsActivated) + module.Deactivate(); + } + } + + stopwatch.Stop(); + if (stopwatch.ElapsedMilliseconds > 100) + _logger.Warning("Activation requirements evaluation took too long: {moduleCount} module(s) in {elapsed}", _modules.Count, stopwatch.Elapsed); + } + private void SurfaceOnUpdated(UpdatedEventArgs args) { if (_rgbService.IsRenderPaused) diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index 7ebbae54a..b4cb7235b 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -11,11 +11,6 @@ namespace Artemis.Core.Services.Storage.Interfaces /// public interface IProfileService : IArtemisService { - /// - /// Activates the last profile for each module - /// - void ActivateLastActiveProfiles(); - /// /// Creates a new profile for the given module and returns a descriptor pointing to it /// @@ -50,6 +45,12 @@ namespace Artemis.Core.Services.Storage.Interfaces /// The descriptor pointing to the profile to delete void DeleteProfile(ProfileDescriptor profileDescriptor); + /// + /// Activates the last profile of the given profile module + /// + /// + void ActivateLastProfile(ProfileModule profileModule); + /// /// Activates the profile described in the given with the currently active surface /// diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 0eee8c30d..e79e8f450 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using Artemis.Core.Events; @@ -11,7 +10,6 @@ using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage.Interfaces; -using Artemis.Core.Utilities; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Repositories.Interfaces; using Newtonsoft.Json; @@ -44,15 +42,6 @@ namespace Artemis.Core.Services.Storage public JsonSerializerSettings MementoSettings { get; set; } = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}; public JsonSerializerSettings ExportSettings { get; set; } = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented}; - public void ActivateLastActiveProfiles() - { - foreach (var profileModule in _pluginService.GetPluginsOfType()) - { - var activeProfile = GetLastActiveProfile(profileModule); - ActivateProfile(activeProfile); - } - } - public List GetProfileDescriptors(ProfileModule module) { var profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); @@ -67,6 +56,12 @@ namespace Artemis.Core.Services.Storage return new ProfileDescriptor(module, profileEntity); } + public void ActivateLastProfile(ProfileModule profileModule) + { + var activeProfile = GetLastActiveProfile(profileModule); + ActivateProfile(activeProfile); + } + public Profile ActivateProfile(ProfileDescriptor profileDescriptor) { if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id) @@ -109,16 +104,6 @@ namespace Artemis.Core.Services.Storage SaveActiveProfile(module); } - private void SaveActiveProfile(ProfileModule module) - { - var profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); - foreach (var profileEntity in profileEntities) - { - profileEntity.IsActive = module.ActiveProfile.EntityId == profileEntity.Id; - _profileRepository.Save(profileEntity); - } - } - public async Task ClearActiveProfileAnimated(ProfileModule module) { await module.ChangeActiveProfileAnimated(null, _surfaceService.ActiveSurface); @@ -210,13 +195,6 @@ namespace Artemis.Core.Services.Storage } } - public ProfileDescriptor GetLastActiveProfile(ProfileModule module) - { - var moduleProfiles = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); - var profileEntity = moduleProfiles.FirstOrDefault(p => p.IsActive) ?? moduleProfiles.FirstOrDefault(); - return profileEntity == null ? null : new ProfileDescriptor(module, profileEntity); - } - public void InstantiateProfile(Profile profile) { profile.PopulateLeds(_surfaceService.ActiveSurface); @@ -245,6 +223,23 @@ namespace Artemis.Core.Services.Storage return new ProfileDescriptor(profileModule, profileEntity); } + public ProfileDescriptor GetLastActiveProfile(ProfileModule module) + { + var moduleProfiles = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); + var profileEntity = moduleProfiles.FirstOrDefault(p => p.IsActive) ?? moduleProfiles.FirstOrDefault(); + return profileEntity == null ? null : new ProfileDescriptor(module, profileEntity); + } + + private void SaveActiveProfile(ProfileModule module) + { + var profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); + foreach (var profileEntity in profileEntities) + { + profileEntity.IsActive = module.ActiveProfile.EntityId == profileEntity.Id; + _profileRepository.Save(profileEntity); + } + } + /// /// Initializes the properties on the layers of the given profile /// @@ -347,11 +342,6 @@ namespace Artemis.Core.Services.Storage ActiveProfilesInstantiatePlugins(); if (e.PluginInfo.Instance is LayerEffectProvider) ActiveProfilesInstantiatePlugins(); - else if (e.PluginInfo.Instance is ProfileModule profileModule) - { - var activeProfile = GetLastActiveProfile(profileModule); - ActivateProfile(activeProfile); - } } #endregion diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index d44860799..879e6d9a4 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -2,18 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using Artemis.Core.Extensions; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.ViewModels; -using Artemis.Core.Plugins.LayerBrush; -using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage.Interfaces; using Artemis.Storage.Entities.Profile; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Serilog; using SkiaSharp; @@ -33,6 +28,17 @@ namespace Artemis.Core.Utilities CreateIntroProfile(); } + public Profile AnimationProfile { get; set; } + + public void Render(double deltaTime, SKCanvas canvas, SKImageInfo bitmapInfo) + { + if (AnimationProfile == null) + return; + + AnimationProfile.Update(deltaTime); + AnimationProfile.Render(deltaTime, canvas, bitmapInfo); + } + private void CreateIntroProfile() { try @@ -43,10 +49,10 @@ namespace Artemis.Core.Utilities // Inject every LED on the surface into each layer foreach (var profileEntityLayer in profileEntity.Layers) { - profileEntityLayer.Leds.AddRange(_surfaceService.ActiveSurface.Devices.SelectMany(d => d.Leds).Select(l => new LedEntity() + profileEntityLayer.Leds.AddRange(_surfaceService.ActiveSurface.Devices.SelectMany(d => d.Leds).Select(l => new LedEntity { DeviceIdentifier = l.Device.RgbDevice.GetDeviceIdentifier(), - LedName = l.RgbLed.Id.ToString(), + LedName = l.RgbLed.Id.ToString() })); } @@ -61,32 +67,33 @@ namespace Artemis.Core.Utilities _logger.Warning(e, "Failed to load intro profile"); } } - - public Profile AnimationProfile { get; set; } - - public void Render(double deltaTime, SKCanvas canvas, SKImageInfo bitmapInfo) - { - if (AnimationProfile == null) - return; - - AnimationProfile.Update(deltaTime); - AnimationProfile.Render(deltaTime, canvas, bitmapInfo); - } } internal class DummyModule : ProfileModule { public override void EnablePlugin() { + throw new NotImplementedException(); } public override void DisablePlugin() { + throw new NotImplementedException(); + } + + public override void ModuleActivated() + { + throw new NotImplementedException(); + } + + public override void ModuleDeactivated() + { + throw new NotImplementedException(); } public override IEnumerable GetViewModels() { - return new List(); + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 7d5a3bd20..67ac10f68 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -121,6 +121,7 @@ + @@ -296,6 +297,7 @@ + diff --git a/src/Artemis.UI/Resources/Images/Sidebar/sidebar-header.png b/src/Artemis.UI/Resources/Images/Sidebar/sidebar-header.png new file mode 100644 index 000000000..d377df9d7 Binary files /dev/null and b/src/Artemis.UI/Resources/Images/Sidebar/sidebar-header.png differ diff --git a/src/Artemis.UI/Screens/RootView.xaml b/src/Artemis.UI/Screens/RootView.xaml index 263ec1c2c..2d0bb3fcf 100644 --- a/src/Artemis.UI/Screens/RootView.xaml +++ b/src/Artemis.UI/Screens/RootView.xaml @@ -51,13 +51,13 @@ - + diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index cdc5c2fe0..cb4a09650 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -66,13 +66,7 @@ namespace Artemis.UI.Screens get => _mainMessageQueue; set => SetAndNotify(ref _mainMessageQueue, value); } - - public bool IsSidebarVisible - { - get => _isSidebarVisible; - set => SetAndNotify(ref _isSidebarVisible, value); - } - + public bool ActiveItemReady { get => _activeItemReady; @@ -140,7 +134,7 @@ namespace Artemis.UI.Screens { if (e.PropertyName == nameof(SidebarViewModel.SelectedItem)) { - IsSidebarVisible = false; + SidebarViewModel.IsSidebarOpen = false; ActiveItemReady = false; // Allow the menu to close, it's slower but feels more responsive, funny how that works right @@ -254,7 +248,7 @@ namespace Artemis.UI.Screens protected override void OnClose() { SidebarViewModel.Dispose(); - + // Lets force the GC to run after closing the window so it is obvious to users watching task manager // that closing the UI will decrease the memory footprint of the application. diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml index c0e13a66d..ae8d4acfb 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml @@ -10,22 +10,19 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance sidebar:SidebarViewModel}"> - - - - - - Active module - - - Profile 1 - Profile 2 - Profile 3 - Profile 4 - - + + + + + + + + - - + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index c45634af6..2b708ee6f 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Timers; using Artemis.Core.Events; using Artemis.Core.Services.Interfaces; using Artemis.UI.Events; @@ -27,6 +28,9 @@ namespace Artemis.UI.Screens.Sidebar private BindableCollection _sidebarItems; private Dictionary _sidebarModules; private IScreen _selectedItem; + private bool _isSidebarOpen; + private readonly Timer _activeModulesUpdateTimer; + private string _activeModules; public SidebarViewModel(IKernel kernel, IEventAggregator eventAggregator, IModuleVmFactory moduleVmFactory, IPluginService pluginService) { @@ -37,6 +41,10 @@ namespace Artemis.UI.Screens.Sidebar SidebarModules = new Dictionary(); SidebarItems = new BindableCollection(); + _activeModulesUpdateTimer = new Timer(1000); + _activeModulesUpdateTimer.Start(); + _activeModulesUpdateTimer.Elapsed += ActiveModulesUpdateTimerOnElapsed; + _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; _pluginService.PluginDisabled += PluginServiceOnPluginDisabled; @@ -44,6 +52,15 @@ namespace Artemis.UI.Screens.Sidebar eventAggregator.Subscribe(this); } + private void ActiveModulesUpdateTimerOnElapsed(object sender, EventArgs e) + { + if (!IsSidebarOpen) + return; + + var activeModules = SidebarModules.Count(m => m.Value.IsActivated); + ActiveModules = activeModules == 1 ? "1 active module" : $"{activeModules} active modules"; + } + public BindableCollection SidebarItems { get => _sidebarItems; @@ -56,19 +73,35 @@ namespace Artemis.UI.Screens.Sidebar set => SetAndNotify(ref _sidebarModules, value); } + public string ActiveModules + { + get => _activeModules; + set => SetAndNotify(ref _activeModules, value); + } + public IScreen SelectedItem { get => _selectedItem; set => SetAndNotify(ref _selectedItem, value); } + public bool IsSidebarOpen + { + get => _isSidebarOpen; + set + { + SetAndNotify(ref _isSidebarOpen, value); + if (value) + ActiveModulesUpdateTimerOnElapsed(this, EventArgs.Empty); + } + } + public void SetupSidebar() { - SidebarItems.Clear(); + SidebarItems.Clear(); SidebarModules.Clear(); // Add all default sidebar items - SidebarItems.Add(new DividerNavigationItem()); SidebarItems.Add(new FirstLevelNavigationItem {Icon = PackIconKind.Home, Label = "Home"}); SidebarItems.Add(new FirstLevelNavigationItem {Icon = PackIconKind.Newspaper, Label = "News"}); SidebarItems.Add(new FirstLevelNavigationItem {Icon = PackIconKind.TestTube, Label = "Workshop"}); @@ -83,7 +116,7 @@ namespace Artemis.UI.Screens.Sidebar AddModule(module); // Select the top item, which will be one of the defaults - Task.Run(() => SelectSidebarItem(SidebarItems[1])); + Task.Run(() => SelectSidebarItem(SidebarItems[0])); } // ReSharper disable once UnusedMember.Global - Called by view @@ -202,6 +235,9 @@ namespace Artemis.UI.Screens.Sidebar _pluginService.PluginEnabled -= PluginServiceOnPluginEnabled; _pluginService.PluginDisabled -= PluginServiceOnPluginDisabled; + + _activeModulesUpdateTimer.Stop(); + _activeModulesUpdateTimer.Elapsed -= ActiveModulesUpdateTimerOnElapsed; } } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs index 17e07abd4..e5e7e1da2 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Artemis.Core.Extensions; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; using Artemis.Plugins.Modules.General.Utilities; @@ -16,7 +17,7 @@ namespace Artemis.Plugins.Modules.General.DataModel.Windows ProcessName = process.ProcessName; // Accessing MainModule requires admin privileges, this way does not - ProgramLocation = WindowUtilities.GetProcessFilename(process); + ProgramLocation = process.GetProcessFilename(); } public string WindowTitle { get; set; } diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs index cb5af53a0..9eda911f1 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -12,9 +12,28 @@ namespace Artemis.Plugins.Modules.General { public class GeneralModule : ProfileModule { - public override IEnumerable GetViewModels() + public override void EnablePlugin() + { + DisplayName = "General"; + DisplayIcon = "AllInclusive"; + ExpandsDataModel = true; + + DataModel.TestTimeList.Add(new TimeDataModel { CurrentTime = DateTime.Now.AddDays(1), CurrentTimeUTC = DateTime.UtcNow.AddDays(1) }); + DataModel.TestTimeList.Add(new TimeDataModel { CurrentTime = DateTime.Now.AddDays(2), CurrentTimeUTC = DateTime.UtcNow.AddDays(2) }); + DataModel.TestTimeList.Add(new TimeDataModel { CurrentTime = DateTime.Now.AddDays(3), CurrentTimeUTC = DateTime.UtcNow.AddDays(3) }); + DataModel.TestTimeList.Add(new TimeDataModel { CurrentTime = DateTime.Now.AddDays(4), CurrentTimeUTC = DateTime.UtcNow.AddDays(4) }); + } + + public override void DisablePlugin() + { + } + + public override void ModuleActivated() + { + } + + public override void ModuleDeactivated() { - return new List {new GeneralViewModel(this)}; } public override void Update(double deltaTime) @@ -26,20 +45,9 @@ namespace Artemis.Plugins.Modules.General base.Update(deltaTime); } - public override void EnablePlugin() - { - DisplayName = "General"; - DisplayIcon = "AllInclusive"; - ExpandsDataModel = true; - - DataModel.TestTimeList.Add(new TimeDataModel {CurrentTime = DateTime.Now.AddDays(1), CurrentTimeUTC = DateTime.UtcNow.AddDays(1)}); - DataModel.TestTimeList.Add(new TimeDataModel {CurrentTime = DateTime.Now.AddDays(2), CurrentTimeUTC = DateTime.UtcNow.AddDays(2)}); - DataModel.TestTimeList.Add(new TimeDataModel {CurrentTime = DateTime.Now.AddDays(3), CurrentTimeUTC = DateTime.UtcNow.AddDays(3)}); - DataModel.TestTimeList.Add(new TimeDataModel {CurrentTime = DateTime.Now.AddDays(4), CurrentTimeUTC = DateTime.UtcNow.AddDays(4)}); - } - - public override void DisablePlugin() + public override IEnumerable GetViewModels() { + return new List { new GeneralViewModel(this) }; } #region Open windows diff --git a/src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowUtilities.cs b/src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowUtilities.cs index 37267cc4f..e8b32b08b 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowUtilities.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowUtilities.cs @@ -1,7 +1,5 @@ using System; -using System.Diagnostics; using System.Runtime.InteropServices; -using System.Text; namespace Artemis.Plugins.Modules.General.Utilities { @@ -14,33 +12,11 @@ namespace Artemis.Plugins.Modules.General.Utilities return (int) processId; } - public static string GetProcessFilename(Process p) - { - var capacity = 2000; - var builder = new StringBuilder(capacity); - var ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id); - if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) return string.Empty; - - return builder.ToString(); - } [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [DllImport("kernel32.dll")] - private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize); - - [DllImport("kernel32.dll")] - private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId); - - - [Flags] - private enum ProcessAccessFlags : uint - { - QueryLimitedInformation = 0x00001000 - } } } \ No newline at end of file