From 7d7d4298d3ab50784d1184b7409095e5d375a782 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 23 Apr 2017 13:00:23 +0200 Subject: [PATCH] Added Timer-Based ActiveWindow-detection --- Artemis/Artemis/Artemis.csproj | 6 +- Artemis/Artemis/ArtemisBootstrapper.cs | 3 +- Artemis/Artemis/Managers/PreviewManager.cs | 1 + .../GeneralProfile/GeneralProfileModel.cs | 1 + Artemis/Artemis/Settings/GeneralSettings.cs | 8 ++ .../ActiveWindowDetectionType.cs | 9 ++ .../ActiveWindowHelper.cs | 48 ++++++++++ .../EventActiveWindowDetector.cs} | 57 ++++++------ .../IActiveWindowDetector.cs | 12 +++ .../TimerActiveWindowDetector.cs | 91 +++++++++++++++++++ .../Flyouts/FlyoutSettingsViewModel.cs | 15 +++ .../ViewModels/ProfileEditorViewModel.cs | 1 + .../Views/Flyouts/FlyoutSettingsView.xaml | 27 ++++-- 13 files changed, 238 insertions(+), 41 deletions(-) create mode 100644 Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowDetectionType.cs create mode 100644 Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowHelper.cs rename Artemis/Artemis/Utilities/{ActiveWindowHelper.cs => ActiveWindowDetection/EventActiveWindowDetector.cs} (69%) create mode 100644 Artemis/Artemis/Utilities/ActiveWindowDetection/IActiveWindowDetector.cs create mode 100644 Artemis/Artemis/Utilities/ActiveWindowDetection/TimerActiveWindowDetector.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 04593aefe..4993110db 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -670,7 +670,11 @@ - + + + + + diff --git a/Artemis/Artemis/ArtemisBootstrapper.cs b/Artemis/Artemis/ArtemisBootstrapper.cs index 1e3f496d8..ddb65e86d 100644 --- a/Artemis/Artemis/ArtemisBootstrapper.cs +++ b/Artemis/Artemis/ArtemisBootstrapper.cs @@ -10,6 +10,7 @@ using Artemis.DAL; using Artemis.InjectionModules; using Artemis.Settings; using Artemis.Utilities; +using Artemis.Utilities.ActiveWindowDetection; using Artemis.Utilities.Converters; using Artemis.Utilities.DataReaders; using Artemis.ViewModels; @@ -113,7 +114,7 @@ namespace Artemis //TODO DarthAffe 17.12.2016: Is this the right location for this? //TODO Move to Mainmanager and make disposable - ActiveWindowHelper.Initialize(); + ActiveWindowHelper.SetActiveWindowDetectionType(SettingsProvider.Load().ActiveWindowDetection); } protected override void OnExit(object sender, EventArgs e) diff --git a/Artemis/Artemis/Managers/PreviewManager.cs b/Artemis/Artemis/Managers/PreviewManager.cs index 612f2e120..68a7f8aed 100644 --- a/Artemis/Artemis/Managers/PreviewManager.cs +++ b/Artemis/Artemis/Managers/PreviewManager.cs @@ -5,6 +5,7 @@ using Artemis.DAL; using Artemis.Modules.Abstract; using Artemis.Settings; using Artemis.Utilities; +using Artemis.Utilities.ActiveWindowDetection; using Ninject.Extensions.Logging; namespace Artemis.Managers diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index 56ac83987..dbe22a298 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -11,6 +11,7 @@ using Artemis.Events; using Artemis.Managers; using Artemis.Modules.Abstract; using Artemis.Utilities; +using Artemis.Utilities.ActiveWindowDetection; using CSCore.CoreAudioAPI; using Newtonsoft.Json; using SpotifyAPI.Local; diff --git a/Artemis/Artemis/Settings/GeneralSettings.cs b/Artemis/Artemis/Settings/GeneralSettings.cs index b304c114d..13240abee 100644 --- a/Artemis/Artemis/Settings/GeneralSettings.cs +++ b/Artemis/Artemis/Settings/GeneralSettings.cs @@ -5,9 +5,11 @@ using System.Windows; using Artemis.DAL; using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing; using Artemis.Utilities; +using Artemis.Utilities.ActiveWindowDetection; using Caliburn.Micro; using MahApps.Metro; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Squirrel; namespace Artemis.Settings @@ -68,6 +70,11 @@ namespace Artemis.Settings [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public string LogLevel { get; set; } + [DefaultValue(ActiveWindowDetectionType.Events)] + [JsonConverter(typeof(StringEnumConverter))] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public ActiveWindowDetectionType ActiveWindowDetection { get; set; } + public Version LastRanVersion { get; set; } public void Save() @@ -75,6 +82,7 @@ namespace Artemis.Settings SettingsProvider.Save(this); Logging.SetupLogging(LogLevel); + ActiveWindowHelper.SetActiveWindowDetectionType(ActiveWindowDetection); ApplyAutorun(); ApplyTheme(); ApplyGamestatePort(); diff --git a/Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowDetectionType.cs b/Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowDetectionType.cs new file mode 100644 index 000000000..d567caade --- /dev/null +++ b/Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowDetectionType.cs @@ -0,0 +1,9 @@ +namespace Artemis.Utilities.ActiveWindowDetection +{ + public enum ActiveWindowDetectionType + { + Disabled = -1, + Events = 0, + Timer = 1, + } +} diff --git a/Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowHelper.cs b/Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowHelper.cs new file mode 100644 index 000000000..1edaa2f84 --- /dev/null +++ b/Artemis/Artemis/Utilities/ActiveWindowDetection/ActiveWindowHelper.cs @@ -0,0 +1,48 @@ +namespace Artemis.Utilities.ActiveWindowDetection +{ + public static class ActiveWindowHelper + { + #region Properties & Fields + + private static ActiveWindowDetectionType _currentDetectionType = ActiveWindowDetectionType.Disabled; + private static IActiveWindowDetector _activeWindowDetector; + + public static string ActiveWindowProcessName => _activeWindowDetector?.ActiveWindowProcessName ?? string.Empty; + public static string ActiveWindowWindowTitle => _activeWindowDetector?.ActiveWindowWindowTitle ?? string.Empty; + public static bool MainWindowActive => ActiveWindowProcessName.Contains("Artemis"); + + #endregion + + #region Methods + + public static void Dispose() + { + _activeWindowDetector?.Dispose(); + } + + public static void SetActiveWindowDetectionType(ActiveWindowDetectionType detectionType) + { + if (detectionType == _currentDetectionType) return; + + _activeWindowDetector?.Dispose(); + + switch (detectionType) + { + case ActiveWindowDetectionType.Events: + _activeWindowDetector = new EventActiveWindowDetector(); + break; + case ActiveWindowDetectionType.Timer: + _activeWindowDetector = new TimerActiveWindowDetector(); + break; + case ActiveWindowDetectionType.Disabled: + _activeWindowDetector = null; + break; + } + + _activeWindowDetector?.Initialize(); + _currentDetectionType = detectionType; + } + + #endregion + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Utilities/ActiveWindowHelper.cs b/Artemis/Artemis/Utilities/ActiveWindowDetection/EventActiveWindowDetector.cs similarity index 69% rename from Artemis/Artemis/Utilities/ActiveWindowHelper.cs rename to Artemis/Artemis/Utilities/ActiveWindowDetection/EventActiveWindowDetector.cs index 2c9430ee9..3c25a678b 100644 --- a/Artemis/Artemis/Utilities/ActiveWindowHelper.cs +++ b/Artemis/Artemis/Utilities/ActiveWindowDetection/EventActiveWindowDetector.cs @@ -2,9 +2,9 @@ using System.Runtime.InteropServices; using System.Text; -namespace Artemis.Utilities +namespace Artemis.Utilities.ActiveWindowDetection { - public static class ActiveWindowHelper + public class EventActiveWindowDetector : IActiveWindowDetector { #region DLL-Imports @@ -14,7 +14,7 @@ namespace Artemis.Utilities [DllImport("user32.dll")] private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, - WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); + WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); [DllImport("user32.dll")] private static extern bool UnhookWinEvent(IntPtr hWinEventHook); @@ -41,47 +41,46 @@ namespace Artemis.Utilities #region Properties & Fields // DarthAffe 17.12.2016: We need to keep a reference to this or it might get collected by the garbage collector and cause some random crashes afterwards. - private static WinEventDelegate _activeWindowChangedDelegate; - private static IntPtr _activeWindowEventHook; + private WinEventDelegate _activeWindowChangedDelegate; + private IntPtr _activeWindowEventHook; - private static WinEventDelegate _windowTitleChangedDelegate; - private static IntPtr _windowTitleEventHook; + private WinEventDelegate _windowTitleChangedDelegate; + private IntPtr _windowTitleEventHook; - private static WinEventDelegate _windowMinimizedChangedDelegate; - private static IntPtr _windowMinimizedEventHook; + private WinEventDelegate _windowMinimizedChangedDelegate; + private IntPtr _windowMinimizedEventHook; - private static IntPtr _activeWindow; + private IntPtr _activeWindow; - public static string ActiveWindowProcessName { get; private set; } = string.Empty; - public static string ActiveWindowWindowTitle { get; private set; } = string.Empty; - public static bool MainWindowActive => ActiveWindowProcessName.Contains("Artemis"); + public string ActiveWindowProcessName { get; private set; } = string.Empty; + public string ActiveWindowWindowTitle { get; private set; } = string.Empty; #endregion #region Methods - private static void ActiveWindowChanged(IntPtr hWinEventHook, uint eventType, - IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + private void ActiveWindowChanged(IntPtr hWinEventHook, uint eventType, + IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { UpdateForWindow(hwnd); } - private static void WindowTitleChanged(IntPtr hWinEventHook, uint eventType, - IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + private void WindowTitleChanged(IntPtr hWinEventHook, uint eventType, + IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { if (_activeWindow == hwnd) UpdateForWindow(hwnd); } - private static void WindowMinimizedChanged(IntPtr hWinEventHook, uint eventType, - IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + private void WindowMinimizedChanged(IntPtr hWinEventHook, uint eventType, + IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { // DarthAffe 19.12.2016: We expect currently un-minimized windows to be active. // DarthAffe 19.12.2016: The result of the API-function GetActiveWindow at this moment is 'idle' so we can't use this to validate this estimation. UpdateForWindow(hwnd); } - private static void UpdateForWindow(IntPtr hwnd) + private void UpdateForWindow(IntPtr hwnd) { _activeWindow = hwnd; @@ -89,13 +88,13 @@ namespace Artemis.Utilities ActiveWindowWindowTitle = GetActiveWindowTitle(hwnd) ?? string.Empty; } - private static string GetActiveWindowProcessName(IntPtr hwnd) + private string GetActiveWindowProcessName(IntPtr hwnd) { try { uint pid; GetWindowThreadProcessId(hwnd, out pid); - return System.Diagnostics.Process.GetProcessById((int) pid).ProcessName; + return System.Diagnostics.Process.GetProcessById((int)pid).ProcessName; } catch { @@ -103,7 +102,7 @@ namespace Artemis.Utilities } } - private static string GetActiveWindowTitle(IntPtr hwnd) + private string GetActiveWindowTitle(IntPtr hwnd) { try { @@ -116,7 +115,7 @@ namespace Artemis.Utilities } } - public static void Initialize() + public void Initialize() { try { @@ -124,21 +123,21 @@ namespace Artemis.Utilities { _activeWindowChangedDelegate = ActiveWindowChanged; _activeWindowEventHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, - IntPtr.Zero, _activeWindowChangedDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); + IntPtr.Zero, _activeWindowChangedDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); } if (_windowTitleEventHook == IntPtr.Zero) { _windowTitleChangedDelegate = WindowTitleChanged; _windowTitleEventHook = SetWinEventHook(EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, - IntPtr.Zero, _windowTitleChangedDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); + IntPtr.Zero, _windowTitleChangedDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); } if (_windowMinimizedEventHook == IntPtr.Zero) { _windowMinimizedChangedDelegate = WindowMinimizedChanged; _windowMinimizedEventHook = SetWinEventHook(EVENT_SYSTEM_MINIMIZEEND, EVENT_SYSTEM_MINIMIZEEND, - IntPtr.Zero, _windowMinimizedChangedDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); + IntPtr.Zero, _windowMinimizedChangedDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); } } catch @@ -147,7 +146,7 @@ namespace Artemis.Utilities } } - public static void Dispose() + public void Dispose() { try { @@ -180,4 +179,4 @@ namespace Artemis.Utilities #endregion } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Utilities/ActiveWindowDetection/IActiveWindowDetector.cs b/Artemis/Artemis/Utilities/ActiveWindowDetection/IActiveWindowDetector.cs new file mode 100644 index 000000000..400ecf4ed --- /dev/null +++ b/Artemis/Artemis/Utilities/ActiveWindowDetection/IActiveWindowDetector.cs @@ -0,0 +1,12 @@ +using System; + +namespace Artemis.Utilities.ActiveWindowDetection +{ + public interface IActiveWindowDetector : IDisposable + { + string ActiveWindowProcessName { get; } + string ActiveWindowWindowTitle { get; } + + void Initialize(); + } +} diff --git a/Artemis/Artemis/Utilities/ActiveWindowDetection/TimerActiveWindowDetector.cs b/Artemis/Artemis/Utilities/ActiveWindowDetection/TimerActiveWindowDetector.cs new file mode 100644 index 000000000..b865a859c --- /dev/null +++ b/Artemis/Artemis/Utilities/ActiveWindowDetection/TimerActiveWindowDetector.cs @@ -0,0 +1,91 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Timers; + +namespace Artemis.Utilities.ActiveWindowDetection +{ + public class TimerActiveWindowDetector : IActiveWindowDetector + { + #region DLL-Imports + + [DllImport("user32.dll")] + static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count); + + [DllImport("user32.dll")] + private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + #endregion + + #region Constants + + private const int TIMER_INTERVAL = 1000; + private const int MAX_TITLE_LENGTH = 256; + + #endregion + + #region Properties & Fields + + private Timer _timer; + + public string ActiveWindowProcessName { get; private set; } + public string ActiveWindowWindowTitle { get; private set; } + + #endregion + + #region Methods + + private void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs) + { + IntPtr activeWindow = GetForegroundWindow(); + ActiveWindowProcessName = GetActiveWindowProcessName(activeWindow); + ActiveWindowWindowTitle = GetActiveWindowTitle(activeWindow); + } + + private string GetActiveWindowProcessName(IntPtr hwnd) + { + try + { + uint pid; + GetWindowThreadProcessId(hwnd, out pid); + return System.Diagnostics.Process.GetProcessById((int)pid).ProcessName; + } + catch + { + return null; + } + } + + private string GetActiveWindowTitle(IntPtr hwnd) + { + try + { + StringBuilder buffer = new StringBuilder(MAX_TITLE_LENGTH); + return GetWindowText(hwnd, buffer, MAX_TITLE_LENGTH) > 0 ? buffer.ToString() : null; + } + catch + { + return null; + } + } + + public void Initialize() + { + _timer = new Timer(TIMER_INTERVAL) { AutoReset = true }; + _timer.Elapsed += TimerOnElapsed; + _timer.Start(); + } + + public void Dispose() + { + _timer?.Stop(); + _timer?.Dispose(); + _timer = null; + } + + #endregion + } +} diff --git a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs index a6169b985..88394d44b 100644 --- a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs +++ b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs @@ -8,6 +8,7 @@ using Artemis.Events; using Artemis.Managers; using Artemis.Services; using Artemis.Settings; +using Artemis.Utilities.ActiveWindowDetection; using Caliburn.Micro; using MahApps.Metro.Controls; using NLog; @@ -37,6 +38,8 @@ namespace Artemis.ViewModels.Flyouts LogLevels = new BindableCollection(); LogLevels.AddRange(LogLevel.AllLoggingLevels.Select(l => l.Name)); + ActiveWindowDetections = new BindableCollection(Enum.GetValues(typeof(ActiveWindowDetectionType)).Cast()); + PropertyChanged += KeyboardUpdater; mainManager.EnabledChanged += MainManagerEnabledChanged; mainManager.ModuleManager.EffectChanged += EffectManagerEffectChanged; @@ -96,6 +99,7 @@ namespace Artemis.ViewModels.Flyouts public string VersionText => "Artemis " + Assembly.GetExecutingAssembly().GetName().Version; public BindableCollection LogLevels { get; set; } + public BindableCollection ActiveWindowDetections { get; set; } public string SelectedTheme { @@ -130,6 +134,17 @@ namespace Artemis.ViewModels.Flyouts } } + public ActiveWindowDetectionType SelectedActiveWindowDetection + { + get { return GeneralSettings.ActiveWindowDetection; } + set + { + if (value == GeneralSettings.ActiveWindowDetection) return; + GeneralSettings.ActiveWindowDetection = value; + NotifyOfPropertyChange(() => SelectedActiveWindowDetection); + } + } + public string SelectedKeyboardProvider { get { return _selectedKeyboardProvider; } diff --git a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 570fd5fb7..9916680fa 100644 --- a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -23,6 +23,7 @@ using Artemis.Properties; using Artemis.Services; using Artemis.Styles.DropTargetAdorners; using Artemis.Utilities; +using Artemis.Utilities.ActiveWindowDetection; using Caliburn.Micro; using Castle.Components.DictionaryAdapter; using GongSolutions.Wpf.DragDrop; diff --git a/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml b/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml index 467284257..a7b50d2e0 100644 --- a/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml +++ b/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml @@ -6,7 +6,7 @@ xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls" xmlns:cal="http://www.caliburnproject.org" mc:Ignorable="d" - d:DesignHeight="600" d:DesignWidth="300"> + d:DesignHeight="600" d:DesignWidth="310"> @@ -25,6 +25,7 @@ + @@ -94,30 +95,36 @@ - - +