diff --git a/Artemis/Artemis.sln b/Artemis/Artemis.sln index 9e2a09e30..180e87f3f 100644 --- a/Artemis/Artemis.sln +++ b/Artemis/Artemis.sln @@ -7,10 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis", "Artemis\Artemis. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Razer2Artemis", "Razer2Artemis\Razer2Artemis.vcxproj", "{39711909-C1D5-46CE-A9EA-2D561692EA47}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnrealTournament2Artemis", "UnrealTournament2Artemis\UnrealTournament2Artemis.vcxproj", "{3541864F-1662-4BD6-8328-2C87AE61D152}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorBox", "ColorBox\ColorBox.csproj", "{40085232-ACED-4CBE-945B-90BA8153C151}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LogiLed2Artemis", "LogiLed2Artemis\LogiLed2Artemis.vcxproj", "{D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CD_ROM|Any CPU = CD_ROM|Any CPU @@ -88,34 +88,6 @@ Global {39711909-C1D5-46CE-A9EA-2D561692EA47}.SingleImage|x64.Build.0 = Release|x64 {39711909-C1D5-46CE-A9EA-2D561692EA47}.SingleImage|x86.ActiveCfg = Release|Win32 {39711909-C1D5-46CE-A9EA-2D561692EA47}.SingleImage|x86.Build.0 = Release|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.CD_ROM|Any CPU.ActiveCfg = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.CD_ROM|Any CPU.Build.0 = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.CD_ROM|x64.ActiveCfg = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.CD_ROM|x64.Build.0 = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.CD_ROM|x86.ActiveCfg = Release|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.CD_ROM|x86.Build.0 = Release|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Debug|x64.ActiveCfg = Debug|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Debug|x64.Build.0 = Debug|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Debug|x86.ActiveCfg = Debug|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Debug|x86.Build.0 = Debug|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.DVD-5|Any CPU.ActiveCfg = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.DVD-5|Any CPU.Build.0 = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.DVD-5|x64.ActiveCfg = Debug|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.DVD-5|x64.Build.0 = Debug|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.DVD-5|x86.ActiveCfg = Debug|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.DVD-5|x86.Build.0 = Debug|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Release|Any CPU.ActiveCfg = Release|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Release|x64.ActiveCfg = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Release|x64.Build.0 = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Release|x86.ActiveCfg = Release|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.Release|x86.Build.0 = Release|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.SingleImage|Any CPU.ActiveCfg = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.SingleImage|Any CPU.Build.0 = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.SingleImage|x64.ActiveCfg = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.SingleImage|x64.Build.0 = Release|x64 - {3541864F-1662-4BD6-8328-2C87AE61D152}.SingleImage|x86.ActiveCfg = Release|Win32 - {3541864F-1662-4BD6-8328-2C87AE61D152}.SingleImage|x86.Build.0 = Release|Win32 {40085232-ACED-4CBE-945B-90BA8153C151}.CD_ROM|Any CPU.ActiveCfg = Release|Any CPU {40085232-ACED-4CBE-945B-90BA8153C151}.CD_ROM|Any CPU.Build.0 = Release|Any CPU {40085232-ACED-4CBE-945B-90BA8153C151}.CD_ROM|x64.ActiveCfg = Release|Any CPU @@ -146,6 +118,34 @@ Global {40085232-ACED-4CBE-945B-90BA8153C151}.SingleImage|x64.Build.0 = Release|Any CPU {40085232-ACED-4CBE-945B-90BA8153C151}.SingleImage|x86.ActiveCfg = Release|Any CPU {40085232-ACED-4CBE-945B-90BA8153C151}.SingleImage|x86.Build.0 = Release|Any CPU + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.CD_ROM|Any CPU.ActiveCfg = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.CD_ROM|Any CPU.Build.0 = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.CD_ROM|x64.ActiveCfg = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.CD_ROM|x64.Build.0 = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.CD_ROM|x86.ActiveCfg = Release|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.CD_ROM|x86.Build.0 = Release|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Debug|x64.ActiveCfg = Debug|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Debug|x64.Build.0 = Debug|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Debug|x86.ActiveCfg = Debug|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Debug|x86.Build.0 = Debug|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.DVD-5|Any CPU.ActiveCfg = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.DVD-5|Any CPU.Build.0 = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.DVD-5|x64.ActiveCfg = Debug|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.DVD-5|x64.Build.0 = Debug|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.DVD-5|x86.ActiveCfg = Debug|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.DVD-5|x86.Build.0 = Debug|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Release|Any CPU.ActiveCfg = Release|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Release|x64.ActiveCfg = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Release|x64.Build.0 = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Release|x86.ActiveCfg = Release|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.Release|x86.Build.0 = Release|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.SingleImage|Any CPU.ActiveCfg = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.SingleImage|Any CPU.Build.0 = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.SingleImage|x64.ActiveCfg = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.SingleImage|x64.Build.0 = Release|x64 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.SingleImage|x86.ActiveCfg = Release|Win32 + {D2EDB8F3-F0CB-4670-B472-0B46D5800D2C}.SingleImage|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Artemis/Artemis/App.xaml b/Artemis/Artemis/App.xaml index e13bd017f..1b093a2b3 100644 --- a/Artemis/Artemis/App.xaml +++ b/Artemis/Artemis/App.xaml @@ -21,6 +21,7 @@ Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseDark.xaml" /> + diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 9604e7fd8..198390572 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -167,8 +167,8 @@ ..\packages\DynamicExpresso.Core.1.3.3.4\lib\net40\DynamicExpresso.Core.dll True - - ..\packages\gong-wpf-dragdrop.1.0.0\lib\net46\GongSolutions.Wpf.DragDrop.dll + + ..\packages\gong-wpf-dragdrop.0.1.4.3\lib\net40\GongSolutions.Wpf.DragDrop.dll True @@ -206,6 +206,10 @@ ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll True + + ..\packages\MoonSharp.2.0.0.0\lib\net40-client\MoonSharp.Interpreter.dll + True + ..\packages\NAudio.1.7.3\lib\net35\NAudio.dll True @@ -222,10 +226,6 @@ ..\packages\Ninject.Extensions.Conventions.3.2.0.0\lib\net45-full\Ninject.Extensions.Conventions.dll True - - ..\packages\Ninject.Extensions.Factory.3.2.1.0\lib\net45-full\Ninject.Extensions.Factory.dll - True - ..\packages\Ninject.Extensions.Logging.3.2.3.0\lib\net45-full\Ninject.Extensions.Logging.dll True @@ -235,7 +235,7 @@ True - ..\packages\NLog.4.3.9\lib\net45\NLog.dll + ..\packages\NLog.4.3.10\lib\net45\NLog.dll True @@ -246,6 +246,14 @@ ..\packages\Process.NET.1.0.5\lib\Process.NET.dll True + + ..\packages\SharpDX.3.1.1\lib\net45\SharpDX.dll + True + + + ..\packages\SharpDX.Direct3D9.3.1.1\lib\net45\SharpDX.Direct3D9.dll + True + ..\packages\Splat.1.6.2\lib\Net45\Splat.dll True @@ -303,8 +311,14 @@ Code + + LoggingControl.xaml + + + + @@ -313,13 +327,14 @@ + + MarkdownDialog.xaml + - - @@ -354,6 +369,13 @@ EurotruckSimulator2View.xaml + + + + + GtaVView.xaml + + @@ -381,6 +403,28 @@ + + + + + + + AmbientLightPropertiesView.xaml + + + + + + + + + + + + + + + AudioPropertiesView.xaml @@ -403,6 +447,22 @@ + + + + + + + + + + + + + + + + @@ -489,6 +549,7 @@ + @@ -511,6 +572,8 @@ + + @@ -548,7 +611,6 @@ - DebugView.xaml @@ -624,6 +686,9 @@ Code + + + Designer @@ -649,9 +714,13 @@ Designer + + + PreserveNewest + Always @@ -686,6 +755,14 @@ + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -698,6 +775,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -714,6 +795,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -746,6 +831,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -844,6 +933,7 @@ + @@ -864,6 +954,9 @@ ColorBox + + + diff --git a/Artemis/Artemis/ArtemisBootstrapper.cs b/Artemis/Artemis/ArtemisBootstrapper.cs index 07e5ad234..23efdc1bf 100644 --- a/Artemis/Artemis/ArtemisBootstrapper.cs +++ b/Artemis/Artemis/ArtemisBootstrapper.cs @@ -10,6 +10,7 @@ using Artemis.Utilities; using Artemis.Utilities.Converters; using Artemis.ViewModels; using Caliburn.Micro; +using MoonSharp.Interpreter; using Newtonsoft.Json; using Ninject; @@ -80,6 +81,7 @@ namespace Artemis _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); + // Configure JSON.NET var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, @@ -114,7 +116,7 @@ namespace Artemis protected override void OnStartup(object sender, StartupEventArgs e) { - DisplayRootViewFor(); + DisplayRootViewFor(); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Controls/Log/LoggingControl.xaml b/Artemis/Artemis/Controls/Log/LoggingControl.xaml new file mode 100644 index 000000000..85c8b6ff9 --- /dev/null +++ b/Artemis/Artemis/Controls/Log/LoggingControl.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Artemis/Artemis/Controls/Log/LoggingControl.xaml.cs b/Artemis/Artemis/Controls/Log/LoggingControl.xaml.cs new file mode 100644 index 000000000..6809192cf --- /dev/null +++ b/Artemis/Artemis/Controls/Log/LoggingControl.xaml.cs @@ -0,0 +1,37 @@ +using System.Collections.ObjectModel; +using System.Windows.Controls; +using Artemis.Utilities; +using NLog; + +namespace Artemis.Controls.Log +{ + /// + /// Interaction logic for LoggingControl.xaml + /// + public partial class LoggingControl : UserControl + { + public LoggingControl() + { + LogCollection = new ObservableCollection(); + + InitializeComponent(); + + // init memory queue + Logging.ClearLoggingEvent(); + Logging.MemoryEvent += EventReceived; + } + + public static ObservableCollection LogCollection { get; set; } + + private async void EventReceived(LogEventInfo message) + { + await Dispatcher.InvokeAsync(() => + { + if (LogCollection.Count >= 50) + LogCollection.RemoveAt(0); + + LogCollection.Add(message); + }); + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Controls/Log/MemoryEventTarget.cs b/Artemis/Artemis/Controls/Log/MemoryEventTarget.cs new file mode 100644 index 000000000..acbe7690e --- /dev/null +++ b/Artemis/Artemis/Controls/Log/MemoryEventTarget.cs @@ -0,0 +1,20 @@ +using System; +using NLog; +using NLog.Targets; + +namespace Artemis.Controls.Log +{ + public class MemoryEventTarget : Target + { + public event Action EventReceived; + + /// + /// Notifies listeners about new event + /// + /// The logging event. + protected override void Write(LogEventInfo logEvent) + { + EventReceived?.Invoke(logEvent); + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/DAL/ProfileProvider.cs b/Artemis/Artemis/DAL/ProfileProvider.cs index 011868d59..728a683d7 100644 --- a/Artemis/Artemis/DAL/ProfileProvider.cs +++ b/Artemis/Artemis/DAL/ProfileProvider.cs @@ -11,6 +11,7 @@ using Artemis.Profiles; using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Properties; using Artemis.Utilities; +using MoonSharp.Interpreter; using Newtonsoft.Json; using NLog; @@ -23,40 +24,34 @@ namespace Artemis.DAL private static readonly string ProfileFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Artemis\profiles"; - private static readonly List Profiles = new List(); private static bool _installedDefaults; - /// - /// Get all profiles - /// - /// All profiles - public static List GetAll() + static ProfileProvider() { - lock (Profiles) - { - if (!Profiles.Any()) - ReadProfiles(); - - // Return a new list, this'll make sure removing/updating the retrieved list doesn't - // affect the datastore - return Profiles.ToList(); - } + // Configure MoonSharp + UserData.RegisterAssembly(); + CheckProfiles(); + InstallDefaults(); } - /// - /// Get all profiles matching the provided game - /// - /// The game to match - /// The keyboard to match - /// All profiles matching the provided game - public static List GetAll(EffectModel game, KeyboardProvider keyboard) + public static List GetProfileNames(KeyboardProvider keyboard, EffectModel effect) { - if (game == null) - throw new ArgumentNullException(nameof(game)); - if (keyboard == null) - throw new ArgumentNullException(nameof(keyboard)); + if (keyboard == null || effect == null) + return null; + return ReadProfiles(keyboard.Slug + "/" + effect.Name).Select(p => p.Name).ToList(); + } - return GetAll().Where(g => g.GameName.Equals(game.Name) && g.KeyboardSlug.Equals(keyboard.Slug)).ToList(); + public static ProfileModel GetProfile(KeyboardProvider keyboard, EffectModel effect, string name) + { + if (keyboard == null || effect == null) + return null; + return ReadProfiles(keyboard.Slug + "/" + effect.Name).FirstOrDefault(p => p.Name == name); + } + + public static bool IsProfileUnique(ProfileModel profileModel) + { + var existing = ReadProfiles(profileModel.KeyboardSlug + "/" + profileModel.GameName); + return !existing.Contains(profileModel); } /// @@ -69,12 +64,6 @@ namespace Artemis.DAL if (prof == null) throw new ArgumentNullException(nameof(prof)); - lock (Profiles) - { - if (!Profiles.Contains(prof)) - Profiles.Add(prof); - } - lock (prof) { // Store the file @@ -100,103 +89,34 @@ namespace Artemis.DAL } File.WriteAllText(path + $@"\{prof.Name}.json", json); - } - } - - private static void ReadProfiles() - { - CheckProfiles(); - InstallDefaults(); - - lock (Profiles) - { - Profiles.Clear(); - - // Create the directory structure - var profilePaths = Directory.GetFiles(ProfileFolder, "*.json", SearchOption.AllDirectories); - - // Parse the JSON files into objects and add them if they are valid - foreach (var path in profilePaths) - { - try - { - var prof = LoadProfileIfValid(path); - if (prof == null) - continue; - - // Only add unique profiles - if (Profiles.Any(p => p.GameName == prof.GameName && p.Name == prof.Name && - p.KeyboardSlug == prof.KeyboardSlug)) - { - Logger.Error("Didn't load duplicate profile: {0}", path); - } - else - { - Profiles.Add(prof); - } - } - catch (Exception e) - { - Logger.Error("Failed to load profile: {0} - {1}", path, e.InnerException.Message); - } - } + Logger.Debug("Saved profile {0}/{1}/{2}", prof.KeyboardSlug, prof.GameName, prof.Name); } } /// - /// Unpacks the default profiles into the profile directory + /// Renames the profile on the model and filesystem /// - private static void InstallDefaults() + /// The profile to rename + /// The new name + public static void RenameProfile(ProfileModel profile, string name) { - // Only install the defaults once per session - if (_installedDefaults) + if (string.IsNullOrEmpty(name)) return; - _installedDefaults = true; - // Load the ZIP from resources - var stream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream("Artemis.Resources.Keyboards.default-profiles.zip"); + // Remove the old profile + DeleteProfile(profile); - // Extract it over the old defaults in case one was updated - if (stream == null) - return; - var archive = new ZipArchive(stream); - archive.ExtractToDirectory(ProfileFolder, true); - - var demoProfiles = Profiles.Where(d => d.Name == "Demo (duplicate to keep changes)"); - InsertGif(demoProfiles, "GIF", Resources.demo_gif, "demo-gif"); + // Update the profile, creating a new file + profile.Name = name; + AddOrUpdate(profile); } - public static void InsertGif(IEnumerable profileModels, string layerName, Bitmap gifFile, - string fileName) + public static void DeleteProfile(ProfileModel prof) { - // Extract the GIF file - var gifDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Artemis\gifs"; - Directory.CreateDirectory(gifDir); - var gifPath = gifDir + $"\\{fileName}.gif"; - gifFile.Save(gifPath); - - foreach (var profile in profileModels) - { - var gifLayer = profile.GetLayers().FirstOrDefault(l => l.Name == layerName); - if (gifLayer == null) - continue; - - ((KeyboardPropertiesModel) gifLayer.Properties).GifFile = gifPath; - AddOrUpdate(profile); - } - } - - /// - /// Makes sure the profile directory structure is in order and places default profiles - /// - private static void CheckProfiles() - { - // Create the directory structure - if (Directory.Exists(ProfileFolder)) - return; - - Directory.CreateDirectory(ProfileFolder); + // Remove the file + var path = ProfileFolder + $@"\{prof.KeyboardSlug}\{prof.GameName}\{prof.Name}.json"; + if (File.Exists(path)) + File.Delete(path); } /// @@ -222,7 +142,7 @@ namespace Artemis.DAL } /// - /// Exports the given profile to the provided path in XML + /// Exports the given profile to the provided path in JSON /// /// The profile to export /// The path to save the profile to @@ -232,41 +152,102 @@ namespace Artemis.DAL File.WriteAllText(path, json); } - /// - /// Renames the profile on the model and filesystem - /// - /// The profile to rename - /// The new name - public static void RenameProfile(ProfileModel profile, string name) + public static void InsertGif(string effectName, string profileName, string layerName, Bitmap gifFile, string fileName) { - if (string.IsNullOrEmpty(name)) - return; + var directories = new DirectoryInfo(ProfileFolder).GetDirectories(); + var profiles = new List(); + foreach (var directoryInfo in directories) + profiles.AddRange(ReadProfiles(directoryInfo.Name + "/effectName").Where(d => d.Name == profileName)); + + // Extract the GIF file + var gifDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Artemis\gifs"; + Directory.CreateDirectory(gifDir); + var gifPath = gifDir + $"\\{fileName}.gif"; + gifFile.Save(gifPath); - // Remove the old profile - DeleteProfile(profile); + foreach (var profile in profiles) + { + var gifLayer = profile.GetLayers().FirstOrDefault(l => l.Name == layerName); + if (gifLayer == null) + continue; - // Update the profile, creating a new file - profile.Name = name; - AddOrUpdate(profile); + ((KeyboardPropertiesModel) gifLayer.Properties).GifFile = gifPath; + AddOrUpdate(profile); + } } - public static void DeleteProfile(ProfileModel prof) + private static List ReadProfiles(string subDirectory) { - // Remove from datastore - lock (Profiles) - { - // Get the profile from the datastore instead of just the provided value, to be certain it is removed - var dsProfile = Profiles.FirstOrDefault(p => p.GameName == prof.GameName && - p.Name == prof.Name && - p.KeyboardSlug == prof.KeyboardSlug); - if (dsProfile != null) - Profiles.Remove(dsProfile); - } + var profiles = new List(); + var directory = ProfileFolder + "/" + subDirectory; + if (!Directory.Exists(directory)) + return profiles; - // Remove the file - var path = ProfileFolder + $@"\{prof.KeyboardSlug}\{prof.GameName}\{prof.Name}.json"; - if (File.Exists(path)) - File.Delete(path); + // Create the directory structure + var profilePaths = Directory.GetFiles(directory, "*.json", SearchOption.AllDirectories); + + // Parse the JSON files into objects and add them if they are valid + foreach (var path in profilePaths) + { + try + { + var prof = LoadProfileIfValid(path); + if (prof == null) + continue; + + // Only add unique profiles + if (profiles.Any(p => p.GameName == prof.GameName && p.Name == prof.Name && + p.KeyboardSlug == prof.KeyboardSlug)) + { + Logger.Info("Didn't load duplicate profile: {0}", path); + } + else + { + profiles.Add(prof); + } + } + catch (Exception e) + { + Logger.Error("Failed to load profile: {0} - {1}", path, e); + } + } + return profiles; + } + + /// + /// Unpacks the default profiles into the profile directory + /// + private static void InstallDefaults() + { + // Only install the defaults once per session + if (_installedDefaults) + return; + _installedDefaults = true; + + // Load the ZIP from resources + var stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream("Artemis.Resources.Keyboards.default-profiles.zip"); + + // Extract it over the old defaults in case one was updated + if (stream == null) + return; + var archive = new ZipArchive(stream); + archive.ExtractToDirectory(ProfileFolder, true); + + + InsertGif("WindowsProfile", "Demo (duplicate to keep changes)", "GIF", Resources.demo_gif, "demo-gif"); + } + + /// + /// Makes sure the profile directory structure is in order and places default profiles + /// + private static void CheckProfiles() + { + // Create the directory structure + if (Directory.Exists(ProfileFolder)) + return; + + Directory.CreateDirectory(ProfileFolder); } } } \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/CoolerMaster/MasterkeysProL.cs b/Artemis/Artemis/DeviceProviders/CoolerMaster/MasterkeysProL.cs new file mode 100644 index 000000000..e2e95528f --- /dev/null +++ b/Artemis/Artemis/DeviceProviders/CoolerMaster/MasterkeysProL.cs @@ -0,0 +1,91 @@ +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Windows.Forms; +using Artemis.DAL; +using Artemis.DeviceProviders.CoolerMaster.Utilities; +using Artemis.DeviceProviders.Logitech.Utilities; +using Artemis.Properties; +using Artemis.Settings; +using Artemis.Utilities; + +namespace Artemis.DeviceProviders.CoolerMaster +{ + public class MasterkeysProL : KeyboardProvider + { + private GeneralSettings _generalSettings; + + public MasterkeysProL() + { + Name = "CM Masterkeys Pro L"; + Slug = "cm-masterkeys-pro-l"; + + CantEnableText = "Couldn't connect to your CM Masterkeys Pro L.\n" + + "Please check your cables and try updating your CM software.\n\n" + + "If needed, you can select a different keyboard in Artemis under settings."; + + Height = 6; + Width = 22; + + PreviewSettings = new PreviewSettings(670, 189, new Thickness(-2, -5, 0, 0), Resources.masterkeys_pro_l); + _generalSettings = SettingsProvider.Load(); + } + + public override void Disable() + { + CmSdk.EnableLedControl(false); + Thread.Sleep(500); + } + + public override bool CanEnable() + { + CmSdk.SetControlDevice(DEVICE_INDEX.DEV_MKeys_L); + + // Doesn't seem reliable but better than nothing I suppose + return CmSdk.IsDevicePlug(); + } + + public override void Enable() + { + CmSdk.SetControlDevice(DEVICE_INDEX.DEV_MKeys_L); + CmSdk.EnableLedControl(true); + } + + public override void DrawBitmap(Bitmap bitmap) + { + // Resize the bitmap + using (var b = ImageUtilities.ResizeImage(bitmap, Width, Height)) + { + // Create an empty matrix + var matrix = new COLOR_MATRIX {KeyColor = new KEY_COLOR[Height, Width]}; + + // Map the bytes to the matix + for (var x = 0; x < Width; x++) + { + for (var y = 0; y < Height; y++) + { + var c = b.GetPixel(x, y); + matrix.KeyColor[y, x] = new KEY_COLOR(c.R, c.G, c.B); + } + } + + // Send the matrix to the keyboard + CmSdk.SetAllLedColor(matrix); + } + } + + public override KeyMatch? GetKeyPosition(Keys keyCode) + { + switch (_generalSettings.Layout) + { + case "Qwerty": + return KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + case "Qwertz": + return KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode); + default: + return KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + } + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/CoolerMaster/Utilities/CMSDK.cs b/Artemis/Artemis/DeviceProviders/CoolerMaster/Utilities/CMSDK.cs new file mode 100644 index 000000000..4283e412f --- /dev/null +++ b/Artemis/Artemis/DeviceProviders/CoolerMaster/Utilities/CMSDK.cs @@ -0,0 +1,93 @@ +using System.Runtime.InteropServices; + +namespace Artemis.DeviceProviders.CoolerMaster.Utilities +{ + public struct KEY_COLOR + { + public byte r; + public byte g; + public byte b; + + public KEY_COLOR(byte colR, byte colG, byte colB) + { + r = colR; + g = colG; + b = colB; + } + } + + public struct COLOR_MATRIX + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 132)] public KEY_COLOR[,] KeyColor; + } + + //Enumeration of device list + public enum DEVICE_INDEX + { + DEV_MKeys_L = 0, + DEV_MKeys_S = 1, + DEV_MKeys_L_White = 2, + DEV_MKeys_M_White = 3, + DEV_MMouse_L = 4 + } + + //Enumeration of device layout + public enum LAYOUT_KEYBOARD + { + LAYOUT_UNINIT = 0, + LAYOUT_US = 1, + LAYOUT_EU = 2 + } + + public static class CmSdk + { + /// + /// Sets the control device which all following actions are targetted to + /// + /// + [DllImport("lib\\SDKDLL ", CallingConvention = CallingConvention.Cdecl)] + public static extern void SetControlDevice(DEVICE_INDEX devIndex); + + /// + /// Obtain current device layout + /// + /// + [DllImport("lib\\SDKDLL ", CallingConvention = CallingConvention.Cdecl)] + public static extern LAYOUT_KEYBOARD GetDeviceLayout(); + + /// + /// Verify if the currently conrolled device is plugged in + /// + /// + [DllImport("lib\\SDKDLL ", CallingConvention = CallingConvention.Cdecl)] + public static extern bool IsDevicePlug(); + + /// + /// Enables led control on the currently controlled device + /// + /// + /// + [DllImport("lib\\SDKDLL ", CallingConvention = CallingConvention.Cdecl)] + public static extern bool EnableLedControl(bool bEnable); + + /// + /// Sets the LED of the currently controlled device + /// + /// + /// + /// + /// + /// + /// + [DllImport("lib\\SDKDLL ", CallingConvention = CallingConvention.Cdecl)] + public static extern bool SetLedColor(int iRow, int iColumn, byte r, byte g, byte b); + + /// + /// Sets all LEDS using the given color matrix + /// + /// + /// + [DllImport("lib\\SDKDLL ", CallingConvention = CallingConvention.Cdecl)] + public static extern bool SetAllLedColor(COLOR_MATRIX colorMatrix); + } +} \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboards.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboards.cs index 7bd557dd5..a1840dcbe 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboards.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboards.cs @@ -11,6 +11,7 @@ using CUE.NET.Brushes; using CUE.NET.Devices.Generic; using CUE.NET.Devices.Generic.Enums; using CUE.NET.Devices.Keyboard; +using CUE.NET.Helper; using Ninject.Extensions.Logging; using Point = System.Drawing.Point; @@ -82,6 +83,7 @@ namespace Artemis.DeviceProviders.Corsair } Logger.Debug("Corsair SDK reported device as: {0}", _keyboard.DeviceInfo.Model); + _keyboard.Brush = _keyboardBrush ?? (_keyboardBrush = new ImageBrush()); } @@ -103,19 +105,21 @@ namespace Artemis.DeviceProviders.Corsair // For STRAFE, stretch the image on row 2. if (_keyboard.DeviceInfo.Model == "STRAFE RGB") { - var strafeBitmap = new Bitmap(22, 8); - using (var g = Graphics.FromImage(strafeBitmap)) + using (var strafeBitmap = new Bitmap(22, 8)) { - g.DrawImage(image, new Point(0, 0)); - g.DrawImage(image, new Rectangle(0, 3, 22, 7), new Rectangle(0, 2, 22, 7), GraphicsUnit.Pixel); - } + using (var g = Graphics.FromImage(strafeBitmap)) + { + g.DrawImage(image, new Point(0, 0)); + g.DrawImage(image, new Rectangle(0, 3, 22, 7), new Rectangle(0, 2, 22, 7), GraphicsUnit.Pixel); + } - image.Dispose(); - image = strafeBitmap; + image.Dispose(); + image = strafeBitmap; + } } _keyboardBrush.Image = image; - _keyboard.Update(true); + _keyboard.Update(); image.Dispose(); } @@ -136,11 +140,11 @@ namespace Artemis.DeviceProviders.Corsair // ignored } - if (cueLed != null) - return new KeyMatch(keyCode, (int) (cueLed.LedRectangle.X*widthMultiplier), - (int) (cueLed.LedRectangle.Y*heightMultiplier)); + if (cueLed == null) + return null; - return null; + var center = cueLed.LedRectangle.GetCenter(); + return new KeyMatch(keyCode, (int) (center.X*widthMultiplier),(int) (center.Y*heightMultiplier)); } } } \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs index b4cf7f1e4..afa83b09c 100644 --- a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs +++ b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs @@ -39,7 +39,7 @@ namespace Artemis.DeviceProviders /// public Bitmap KeyboardBitmap(int scale) => new Bitmap(Width*scale, Height*scale); - public Rect KeyboardRectangle(int scale) => new Rect(new Size(Width*scale, Height*scale)); + public Rect KeyboardRectangle(int scale = 4) => new Rect(new Size(Width*scale, Height*scale)); /// /// Runs CanEnable asynchronously multiple times until successful, cancelled or max tries reached diff --git a/Artemis/Artemis/DeviceProviders/Logitech/G810.cs b/Artemis/Artemis/DeviceProviders/Logitech/G810.cs index 2b3c1a509..e51e0b9a5 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/G810.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/G810.cs @@ -28,9 +28,15 @@ namespace Artemis.DeviceProviders.Logitech public override KeyMatch? GetKeyPosition(Keys keyCode) { - return _generalSettings.Layout == "Qwerty" - ? KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode) - : KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + switch (_generalSettings.Layout) + { + case "Qwerty": + return KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + case "Qwertz": + return KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode); + default: + return KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + } } } } \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/Logitech/G910.cs b/Artemis/Artemis/DeviceProviders/Logitech/G910.cs index 126eefcbf..d79aca306 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/G910.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/G910.cs @@ -30,9 +30,19 @@ namespace Artemis.DeviceProviders.Logitech public override KeyMatch? GetKeyPosition(Keys keyCode) { - var value = _generalSettings.Layout == "Qwerty" - ? KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode) - : KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + KeyMatch value; + switch (_generalSettings.Layout) + { + case "Qwerty": + value = KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + break; + case "Qwertz": + value = KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode); + break; + default: + value = KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + break; + } // Adjust the distance by 1 on both x and y for the G910 return new KeyMatch(value.KeyCode, value.X + 1, value.Y + 1); diff --git a/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs b/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs index ac6a96f0d..80491a1b1 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs @@ -13,12 +13,12 @@ namespace Artemis.DeviceProviders.Logitech { // Check to see if VC++ 2012 x64 is installed. if (Registry.LocalMachine.OpenSubKey( - @"SOFTWARE\Classes\Installer\Dependencies\{ca67548a-5ebe-413a-b50c-4b9ceb6d66c6}") == null) + @"SOFTWARE\Classes\Installer\Dependencies\{ca67548a-5ebe-413a-b50c-4b9ceb6d66c6}") == null) { CantEnableText = "Couldn't connect to your Logitech keyboard.\n" + - "The Visual C++ 2012 Redistributable could not be found, which is required.\n" + - "Please download it by going to the following URL:\n\n" + - "https://www.microsoft.com/download/confirmation.aspx?id=30679"; + "The Visual C++ 2012 Redistributable v11.0.61030.0 could not be found, which is required.\n" + + "Please download it by going to the following URL (link also in wiki):\n\n" + + "https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe"; return false; } diff --git a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs index 1c4264e0e..140b16380 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs @@ -8,6 +8,9 @@ namespace Artemis.DeviceProviders.Logitech.Utilities static KeyMap() { // There are several keyboard layouts + + #region Qwerty + QwertyLayout = new List { // Row 1 @@ -129,6 +132,135 @@ namespace Artemis.DeviceProviders.Logitech.Utilities new KeyMatch(Keys.Decimal, 19, 5) }; + #endregion + + #region Qwertz + + QwertzLayout = new List + { + // Row 1 + new KeyMatch(Keys.Escape, 0, 0), + new KeyMatch(Keys.F1, 2, 0), + new KeyMatch(Keys.F2, 3, 0), + new KeyMatch(Keys.F3, 4, 0), + new KeyMatch(Keys.F4, 5, 0), + new KeyMatch(Keys.F5, 6, 0), + new KeyMatch(Keys.F6, 7, 0), + new KeyMatch(Keys.F7, 8, 0), + new KeyMatch(Keys.F8, 9, 0), + new KeyMatch(Keys.F9, 11, 0), + new KeyMatch(Keys.F10, 12, 0), // returns 'None' + new KeyMatch(Keys.F11, 13, 0), + new KeyMatch(Keys.F12, 14, 0), + new KeyMatch(Keys.PrintScreen, 15, 0), + new KeyMatch(Keys.Scroll, 16, 0), + new KeyMatch(Keys.Pause, 17, 0), + + // Row 2 + new KeyMatch(Keys.Oem5, 0, 1), + new KeyMatch(Keys.D1, 1, 1), + new KeyMatch(Keys.D2, 2, 1), + new KeyMatch(Keys.D3, 3, 1), + new KeyMatch(Keys.D4, 4, 1), + new KeyMatch(Keys.D5, 5, 1), + new KeyMatch(Keys.D6, 6, 1), + new KeyMatch(Keys.D7, 7, 1), + new KeyMatch(Keys.D8, 8, 1), + new KeyMatch(Keys.D9, 9, 1), + new KeyMatch(Keys.D0, 10, 1), + new KeyMatch(Keys.OemOpenBrackets, 11, 1), + new KeyMatch(Keys.Oem6, 12, 1), + new KeyMatch(Keys.Back, 13, 1), + new KeyMatch(Keys.Insert, 14, 1), + new KeyMatch(Keys.Home, 15, 1), + new KeyMatch(Keys.PageUp, 16, 1), + new KeyMatch(Keys.NumLock, 17, 1), + new KeyMatch(Keys.Divide, 18, 1), + new KeyMatch(Keys.Multiply, 19, 1), + new KeyMatch(Keys.Subtract, 20, 1), + + // Row 3 + new KeyMatch(Keys.Tab, 0, 2), + new KeyMatch(Keys.Q, 1, 2), + new KeyMatch(Keys.W, 2, 2), + new KeyMatch(Keys.E, 3, 2), + new KeyMatch(Keys.R, 5, 2), + new KeyMatch(Keys.T, 6, 2), + new KeyMatch(Keys.Z, 7, 2), + new KeyMatch(Keys.U, 8, 2), + new KeyMatch(Keys.I, 9, 2), + new KeyMatch(Keys.O, 10, 2), + new KeyMatch(Keys.P, 11, 2), + new KeyMatch(Keys.Oem1, 12, 2), + new KeyMatch(Keys.Oemplus, 13, 2), + new KeyMatch(Keys.Delete, 14, 2), + new KeyMatch(Keys.End, 15, 2), + new KeyMatch(Keys.Next, 16, 2), + new KeyMatch(Keys.NumPad7, 17, 2), + new KeyMatch(Keys.NumPad8, 18, 2), + new KeyMatch(Keys.NumPad9, 19, 2), + new KeyMatch(Keys.Add, 20, 2), + + // Row 4 + new KeyMatch(Keys.Capital, 0, 3), + new KeyMatch(Keys.A, 1, 3), + new KeyMatch(Keys.S, 3, 3), + new KeyMatch(Keys.D, 4, 3), + new KeyMatch(Keys.F, 5, 3), + new KeyMatch(Keys.G, 6, 3), + new KeyMatch(Keys.H, 7, 3), + new KeyMatch(Keys.J, 8, 3), + new KeyMatch(Keys.K, 9, 3), + new KeyMatch(Keys.L, 10, 3), + new KeyMatch(Keys.Oemtilde, 11, 3), + new KeyMatch(Keys.Oem7, 12, 3), + new KeyMatch(Keys.OemQuestion, 13, 3), + new KeyMatch(Keys.Return, 14, 3), + new KeyMatch(Keys.NumPad4, 17, 3), + new KeyMatch(Keys.NumPad5, 18, 3), + new KeyMatch(Keys.NumPad6, 19, 3), + + // Row 5 + new KeyMatch(Keys.LShiftKey, 1, 4), + new KeyMatch(Keys.OemBackslash, 2, 4), + new KeyMatch(Keys.Y, 2, 4), + new KeyMatch(Keys.X, 3, 4), + new KeyMatch(Keys.C, 4, 4), + new KeyMatch(Keys.V, 5, 4), + new KeyMatch(Keys.B, 6, 4), + new KeyMatch(Keys.N, 7, 4), + new KeyMatch(Keys.M, 8, 4), + new KeyMatch(Keys.Oemcomma, 9, 4), + new KeyMatch(Keys.OemPeriod, 10, 4), + new KeyMatch(Keys.OemMinus, 11, 4), + new KeyMatch(Keys.RShiftKey, 13, 4), + new KeyMatch(Keys.Up, 15, 4), + new KeyMatch(Keys.NumPad1, 17, 4), + new KeyMatch(Keys.NumPad2, 18, 4), + new KeyMatch(Keys.NumPad3, 19, 4), + // Both returns return "Return" (Yes...) + // new OrionKey(System.Windows.Forms.Keys.Return, 20, 4), + + // Row 6 + new KeyMatch(Keys.LControlKey, 0, 5), + new KeyMatch(Keys.LWin, 1, 5), + new KeyMatch(Keys.Menu, 3, 5), // returns 'None' + new KeyMatch(Keys.Space, 6, 5), + new KeyMatch(Keys.RMenu, 11, 5), + new KeyMatch(Keys.RWin, 12, 5), + new KeyMatch(Keys.Apps, 13, 5), + new KeyMatch(Keys.RControlKey, 14, 5), + new KeyMatch(Keys.Left, 15, 5), + new KeyMatch(Keys.Down, 16, 5), + new KeyMatch(Keys.Right, 17, 5), + new KeyMatch(Keys.NumPad0, 18, 5), + new KeyMatch(Keys.Decimal, 19, 5) + }; + + #endregion + + #region Azerty + AzertyLayout = new List { // Row 1 @@ -249,9 +381,12 @@ namespace Artemis.DeviceProviders.Logitech.Utilities new KeyMatch(Keys.NumPad0, 18, 5), new KeyMatch(Keys.Decimal, 19, 5) }; + + #endregion } public static List QwertyLayout { get; set; } + public static List QwertzLayout { get; set; } public static List AzertyLayout { get; set; } } } \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs index 370460b98..736794cca 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs @@ -202,10 +202,16 @@ namespace Artemis.DeviceProviders.Logitech.Utilities { graphics.CompositingMode = CompositingMode.SourceCopy; graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.SmoothingMode = SmoothingMode.HighQuality; + + graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + // TODO: Make configurable + // Prevents light bleed + graphics.InterpolationMode = InterpolationMode.NearestNeighbor; + // Soft/semi-transparent keys + //graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + using (var wrapMode = new ImageAttributes()) { wrapMode.SetWrapMode(WrapMode.TileFlipXY); diff --git a/Artemis/Artemis/DeviceProviders/Razer/BlackWidow.cs b/Artemis/Artemis/DeviceProviders/Razer/BlackWidow.cs index bb934550e..4814c32bb 100644 --- a/Artemis/Artemis/DeviceProviders/Razer/BlackWidow.cs +++ b/Artemis/Artemis/DeviceProviders/Razer/BlackWidow.cs @@ -2,9 +2,11 @@ using System.Linq; using System.Windows; using System.Windows.Forms; +using Artemis.DAL; using Artemis.DeviceProviders.Logitech.Utilities; using Artemis.DeviceProviders.Razer.Utilities; using Artemis.Properties; +using Artemis.Settings; using Corale.Colore.Core; using Corale.Colore.Razer; using Constants = Corale.Colore.Razer.Keyboard.Constants; @@ -13,6 +15,8 @@ namespace Artemis.DeviceProviders.Razer { public class BlackWidow : KeyboardProvider { + private GeneralSettings _generalSettings; + public BlackWidow() { Name = "Razer BlackWidow Chroma"; @@ -24,6 +28,7 @@ namespace Artemis.DeviceProviders.Razer Height = Constants.MaxRows; Width = Constants.MaxColumns; PreviewSettings = new PreviewSettings(665, 175, new Thickness(0, -15, 0, 0), Resources.blackwidow); + _generalSettings = SettingsProvider.Load(); } public override bool CanEnable() @@ -56,7 +61,15 @@ namespace Artemis.DeviceProviders.Razer public override KeyMatch? GetKeyPosition(Keys keyCode) { // TODO: Needs it's own keymap or a way to get it from the Chroma SDK - return KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + switch (_generalSettings.Layout) + { + case "Qwerty": + return KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + case "Qwertz": + return KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode); + default: + return KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + } } } } \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/Razer/Utilities/RazerUtilities.cs b/Artemis/Artemis/DeviceProviders/Razer/Utilities/RazerUtilities.cs index 2ec0ebd43..44c7ae96a 100644 --- a/Artemis/Artemis/DeviceProviders/Razer/Utilities/RazerUtilities.cs +++ b/Artemis/Artemis/DeviceProviders/Razer/Utilities/RazerUtilities.cs @@ -10,19 +10,21 @@ namespace Artemis.DeviceProviders.Razer.Utilities public static Custom BitmapColorArray(Bitmap b, int height, int width) { var keyboardGrid = Custom.Create(); - if (b.Width > width || b.Height > height) - b = ImageUtilities.ResizeImage(b, width, height); - - for (var y = 0; y < b.Height; y++) + // Resize the bitmap + using (b = ImageUtilities.ResizeImage(b, width, height)) { + // Map the bytes to the grid for (var x = 0; x < b.Width; x++) { - var pixel = b.GetPixel(x, y); - keyboardGrid[y, x] = new Color(pixel.R, pixel.G, pixel.B); + for (var y = 0; y < b.Height; y++) + { + var c = b.GetPixel(x, y); + keyboardGrid[y, x] = new Color(c.R, c.G, c.B); + } } - } - return keyboardGrid; + return keyboardGrid; + } } } } \ No newline at end of file diff --git a/Artemis/Artemis/Dialogs/MarkdownDialog.xaml b/Artemis/Artemis/Dialogs/MarkdownDialog.xaml new file mode 100644 index 000000000..8a9587fe5 --- /dev/null +++ b/Artemis/Artemis/Dialogs/MarkdownDialog.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + private IEnumerable DoCodeSpans(string text, Func> defaultHandler) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + + // * You can use multiple backticks as the delimiters if you want to + // include literal backticks in the code span. So, this input: + // + // Just type ``foo `bar` baz`` at the prompt. + // + // Will translate to: + // + //

Just type foo `bar` baz at the prompt.

+ // + // There's no arbitrary limit to the number of backticks you + // can use as delimters. If you need three consecutive backticks + // in your code, use four for delimiters, etc. + // + // * You can use spaces to get literal backticks at the edges: + // + // ... type `` `bar` `` ... + // + // Turns to: + // + // ... type `bar` ... + // + + return Evaluate(text, _codeSpan, CodeSpanEvaluator, defaultHandler); + } + + private Inline CodeSpanEvaluator(Match match) + { + if (match == null) + { + throw new ArgumentNullException("match"); + } + + string span = match.Groups[2].Value; + span = Regex.Replace(span, @"^[ ]*", ""); // leading whitespace + span = Regex.Replace(span, @"[ ]*$", ""); // trailing whitespace + + var result = new Run(span); + if (CodeStyle != null) + { + result.Style = CodeStyle; + } + + return result; + } + + private static Regex _bold = new Regex(@"(\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1", + RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled); + private static Regex _strictBold = new Regex(@"([\W_]|^) (\*\*|__) (?=\S) ([^\r]*?\S[\*_]*) \2 ([\W_]|$)", + RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled); + + private static Regex _italic = new Regex(@"(\*|_) (?=\S) (.+?) (?<=\S) \1", + RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled); + private static Regex _strictItalic = new Regex(@"([\W_]|^) (\*|_) (?=\S) ([^\r\*_]*?\S) \2 ([\W_]|$)", + RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled); + + /// + /// Turn Markdown *italics* and **bold** into HTML strong and em tags + /// + private IEnumerable DoItalicsAndBold(string text, Func> defaultHandler) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + + // must go first, then + if (StrictBoldItalic) + { + return Evaluate(text, _strictBold, m => BoldEvaluator(m, 3), + s1 => Evaluate(s1, _strictItalic, m => ItalicEvaluator(m, 3), + s2 => defaultHandler(s2))); + } + else + { + return Evaluate(text, _bold, m => BoldEvaluator(m, 2), + s1 => Evaluate(s1, _italic, m => ItalicEvaluator(m, 2), + s2 => defaultHandler(s2))); + } + } + + private Inline ItalicEvaluator(Match match, int contentGroup) + { + if (match == null) + { + throw new ArgumentNullException("match"); + } + + var content = match.Groups[contentGroup].Value; + return Create(RunSpanGamut(content)); + } + + private Inline BoldEvaluator(Match match, int contentGroup) + { + if (match == null) + { + throw new ArgumentNullException("match"); + } + + var content = match.Groups[contentGroup].Value; + return Create(RunSpanGamut(content)); + } + + private static Regex _outDent = new Regex(@"^[ ]{1," + _tabWidth + @"}", RegexOptions.Multiline | RegexOptions.Compiled); + + /// + /// Remove one level of line-leading spaces + /// + private string Outdent(string block) + { + return _outDent.Replace(block, ""); + } + + /// + /// convert all tabs to _tabWidth spaces; + /// standardizes line endings from DOS (CR LF) or Mac (CR) to UNIX (LF); + /// makes sure text ends with a couple of newlines; + /// removes any blank lines (only spaces) in the text + /// + private string Normalize(string text) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + + var output = new StringBuilder(text.Length); + var line = new StringBuilder(); + bool valid = false; + + for (int i = 0; i < text.Length; i++) + { + switch (text[i]) + { + case '\n': + if (valid) + output.Append(line); + output.Append('\n'); + line.Length = 0; + valid = false; + break; + case '\r': + if ((i < text.Length - 1) && (text[i + 1] != '\n')) + { + if (valid) + output.Append(line); + output.Append('\n'); + line.Length = 0; + valid = false; + } + break; + case '\t': + int width = (_tabWidth - line.Length % _tabWidth); + for (int k = 0; k < width; k++) + line.Append(' '); + break; + case '\x1A': + break; + default: + if (!valid && text[i] != ' ') + valid = true; + line.Append(text[i]); + break; + } + } + + if (valid) + output.Append(line); + output.Append('\n'); + + // add two newlines to the end before return + return output.Append("\n\n").ToString(); + } + + /// + /// this is to emulate what's evailable in PHP + /// + private static string RepeatString(string text, int count) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + + var sb = new StringBuilder(text.Length * count); + for (int i = 0; i < count; i++) + sb.Append(text); + return sb.ToString(); + } + + private TResult Create(IEnumerable content) + where TResult : IAddChild, new() + { + var result = new TResult(); + foreach (var c in content) + { + result.AddChild(c); + } + + return result; + } + + private IEnumerable Evaluate(string text, Regex expression, Func build, Func> rest) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + + var matches = expression.Matches(text); + var index = 0; + foreach (Match m in matches) + { + if (m.Index > index) + { + var prefix = text.Substring(index, m.Index - index); + foreach (var t in rest(prefix)) + { + yield return t; + } + } + + yield return build(m); + + index = m.Index + m.Length; + } + + if (index < text.Length) + { + var suffix = text.Substring(index, text.Length - index); + foreach (var t in rest(suffix)) + { + yield return t; + } + } + } + + private static Regex _eoln = new Regex("\\s+"); + + public IEnumerable DoText(string text) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + + var t = _eoln.Replace(text, " "); + yield return new Run(t); + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Utilities/Markdown/TextToFlowDocumentConverter.cs b/Artemis/Artemis/Utilities/Markdown/TextToFlowDocumentConverter.cs new file mode 100644 index 000000000..cfafc9bf0 --- /dev/null +++ b/Artemis/Artemis/Utilities/Markdown/TextToFlowDocumentConverter.cs @@ -0,0 +1,62 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Artemis.Utilities.Markdown +{ + public class TextToFlowDocumentConverter : DependencyObject, IValueConverter + { + public Markdown Markdown + { + get { return (Markdown)GetValue(MarkdownProperty); } + set { SetValue(MarkdownProperty, value); } + } + + // Using a DependencyProperty as the backing store for Markdown. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MarkdownProperty = + DependencyProperty.Register("Markdown", typeof(Markdown), typeof(TextToFlowDocumentConverter), new PropertyMetadata(null)); + + /// + /// Converts a value. + /// + /// + /// A converted value. If the method returns null, the valid null value is used. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return null; + } + + var text = (string)value; + + var engine = Markdown ?? mMarkdown.Value; + + return engine.Transform(text); + } + + /// + /// Converts a value. + /// + /// + /// A converted value. If the method returns null, the valid null value is used. + /// + /// The value that is produced by the binding target. + /// The type to convert to. + /// The converter parameter to use. + /// The culture to use in the converter. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + private Lazy mMarkdown + = new Lazy(() => new Markdown()); + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Utilities/Updater.cs b/Artemis/Artemis/Utilities/Updater.cs index fe9b5f51d..0c856cfd9 100644 --- a/Artemis/Artemis/Utilities/Updater.cs +++ b/Artemis/Artemis/Utilities/Updater.cs @@ -56,7 +56,7 @@ namespace Artemis.Utilities ///
/// The dialog service to use for progress and result dialogs /// - public static async Task CheckChangelog(MetroDialogService dialogService) + public static async void CheckChangelog(MetroDialogService dialogService) { var settings = SettingsProvider.Load(); var currentVersion = Assembly.GetExecutingAssembly().GetName().Version; @@ -114,7 +114,7 @@ namespace Artemis.Utilities } if (release != null) - dialogService.ShowMessageBox(release["name"].Value(), release["body"].Value()); + dialogService.ShowMarkdownDialog(release["name"].Value(), release["body"].Value()); else dialogService.ShowMessageBox("Couldn't fetch release", "Sorry, Artemis was unable to fetch the release data off of GitHub.\n" + diff --git a/Artemis/Artemis/ViewModels/Abstract/EffectViewModel.cs b/Artemis/Artemis/ViewModels/Abstract/EffectViewModel.cs index e871bbbe4..89e9ec09e 100644 --- a/Artemis/Artemis/ViewModels/Abstract/EffectViewModel.cs +++ b/Artemis/Artemis/ViewModels/Abstract/EffectViewModel.cs @@ -79,7 +79,7 @@ namespace Artemis.ViewModels.Abstract MainManager.EffectManager.ChangeEffect(EffectModel, MainManager.LoopManager); } - public void SaveSettings() + public virtual void SaveSettings() { EffectSettings?.Save(); if (!EffectEnabled) diff --git a/Artemis/Artemis/ViewModels/Abstract/GameViewModel.cs b/Artemis/Artemis/ViewModels/Abstract/GameViewModel.cs index 33b737cb5..295e45b6a 100644 --- a/Artemis/Artemis/ViewModels/Abstract/GameViewModel.cs +++ b/Artemis/Artemis/ViewModels/Abstract/GameViewModel.cs @@ -1,5 +1,5 @@ using System.ComponentModel; -using Artemis.InjectionFactories; +using System.Timers; using Artemis.Managers; using Artemis.Models; using Artemis.Modules.Effects.ProfilePreview; @@ -9,6 +9,7 @@ using Artemis.ViewModels.Profiles; using Caliburn.Micro; using Ninject; using Ninject.Extensions.Logging; +using Ninject.Parameters; namespace Artemis.ViewModels.Abstract { @@ -16,16 +17,22 @@ namespace Artemis.ViewModels.Abstract { private GameSettings _gameSettings; - protected GameViewModel(MainManager mainManager, GameModel gameModel, IProfileEditorVmFactory pFactory) + protected GameViewModel(MainManager mainManager, GameModel gameModel, IKernel kernel) { MainManager = mainManager; GameModel = gameModel; - PFactory = pFactory; GameSettings = gameModel.Settings; - ProfileEditor = PFactory.CreateProfileEditorVm(mainManager, gameModel, GameSettings.LastProfile); - GameModel.Profile = ProfileEditor.SelectedProfile; + IParameter[] args = + { + new ConstructorArgument("mainManager", mainManager), + new ConstructorArgument("gameModel", gameModel), + new ConstructorArgument("lastProfile", GameSettings.LastProfile) + }; + ProfileEditor = kernel.Get(args); ProfileEditor.PropertyChanged += ProfileUpdater; + + GameModel.Profile = ProfileEditor.SelectedProfile; } [Inject] @@ -37,8 +44,6 @@ namespace Artemis.ViewModels.Abstract [Inject] public MetroDialogService DialogService { get; set; } - public IProfileEditorVmFactory PFactory { get; set; } - public ProfileEditorViewModel ProfileEditor { get; set; } public GameModel GameModel { get; set; } @@ -65,9 +70,11 @@ namespace Artemis.ViewModels.Abstract public void SaveSettings() { GameSettings?.Save(); + ProfileEditor.SaveSelectedProfile(); + if (!GameEnabled) return; - + // Restart the game if it's currently running to apply settings. MainManager.EffectManager.ChangeEffect(GameModel, MainManager.LoopManager); } @@ -89,14 +96,17 @@ namespace Artemis.ViewModels.Abstract private void ProfileUpdater(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName != "SelectedProfile" && IsActive) + if ((e.PropertyName != "SelectedProfile") && IsActive) return; + GameModel.Profile = ProfileEditor.SelectedProfile; ProfilePreviewModel.Profile = ProfileEditor.SelectedProfile; - if (e.PropertyName != "SelectedProfile" || !ProfileEditor.ProfileViewModel.Activated || - ProfileEditor.ProfileViewModel.SelectedProfile == null) + // Only update the last selected profile if it the editor was active and the new profile isn't null + if ((e.PropertyName != "SelectedProfile") || !ProfileEditor.ProfileViewModel.Activated || + (ProfileEditor.ProfileViewModel.SelectedProfile == null)) return; + GameSettings.LastProfile = ProfileEditor.ProfileViewModel.SelectedProfile.Name; GameSettings.Save(); } diff --git a/Artemis/Artemis/ViewModels/DebugViewModel.cs b/Artemis/Artemis/ViewModels/DebugViewModel.cs index e8b891f95..2dab9f89b 100644 --- a/Artemis/Artemis/ViewModels/DebugViewModel.cs +++ b/Artemis/Artemis/ViewModels/DebugViewModel.cs @@ -1,4 +1,7 @@ -using System.Windows; +using System; +using System.IO; +using System.Linq; +using System.Windows; using System.Windows.Media; using Caliburn.Micro; @@ -6,7 +9,6 @@ namespace Artemis.ViewModels { public class DebugViewModel : Screen { - private DrawingImage _razerDisplay; public DrawingImage RazerDisplay @@ -20,6 +22,18 @@ namespace Artemis.ViewModels } } + public void OpenLog() + { + // Get the logging directory + var logDir = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + + @"\Artemis\logs"); + // Get the newest log file + var currentLog = logDir.GetFiles().OrderByDescending(f => f.LastWriteTime).FirstOrDefault(); + // Open the file with the user's default program + if (currentLog != null) + System.Diagnostics.Process.Start(currentLog.FullName); + } + public void UpdateRazerDisplay(Color[,] colors) { // No point updating the display if the view isn't visible @@ -31,10 +45,8 @@ namespace Artemis.ViewModels { dc.PushClip(new RectangleGeometry(new Rect(0, 0, 22, 6))); for (var y = 0; y < 6; y++) - { for (var x = 0; x < 22; x++) dc.DrawRectangle(new SolidColorBrush(colors[y, x]), null, new Rect(x, y, 1, 1)); - } } var drawnDisplay = new DrawingImage(visual.Drawing); drawnDisplay.Freeze(); diff --git a/Artemis/Artemis/ViewModels/EffectsViewModel.cs b/Artemis/Artemis/ViewModels/EffectsViewModel.cs index fe73c921f..5f41ea4a3 100644 --- a/Artemis/Artemis/ViewModels/EffectsViewModel.cs +++ b/Artemis/Artemis/ViewModels/EffectsViewModel.cs @@ -5,20 +5,20 @@ namespace Artemis.ViewModels { public sealed class EffectsViewModel : BaseViewModel { - private readonly EffectViewModel[] _effectViewModels; + private IOrderedEnumerable _vms; public EffectsViewModel(EffectViewModel[] effectViewModels) { DisplayName = "Effects"; - _effectViewModels = effectViewModels; + + _vms = effectViewModels.OrderBy(o => o.DisplayName); } protected override void OnActivate() { base.OnActivate(); - - foreach (var effectViewModel in _effectViewModels.OrderBy(e => e.DisplayName)) - ActivateItem(effectViewModel); + Items.Clear(); + Items.AddRange(_vms); } } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs index 61d9ba4d7..215b23bde 100644 --- a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs +++ b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs @@ -89,6 +89,7 @@ namespace Artemis.ViewModels.Flyouts public BindableCollection Layouts => new BindableCollection { "Qwerty", + "Qwertz", "Azerty" }; diff --git a/Artemis/Artemis/ViewModels/GamesViewModel.cs b/Artemis/Artemis/ViewModels/GamesViewModel.cs index 69bfe4c75..6aac8e4e5 100644 --- a/Artemis/Artemis/ViewModels/GamesViewModel.cs +++ b/Artemis/Artemis/ViewModels/GamesViewModel.cs @@ -2,7 +2,6 @@ using Artemis.DAL; using Artemis.Managers; using Artemis.Modules.Effects.ProfilePreview; -using Artemis.Modules.Games.WoW; using Artemis.Settings; using Artemis.ViewModels.Abstract; @@ -10,30 +9,28 @@ namespace Artemis.ViewModels { public sealed class GamesViewModel : BaseViewModel { - private readonly GameViewModel[] _gameViewModels; + private readonly IOrderedEnumerable _vms; public GamesViewModel(GameViewModel[] gameViewModels, ProfileManager profileManager, ProfilePreviewModel profilePreviewModel) { DisplayName = "Games"; - _gameViewModels = gameViewModels; + + // Currently WoW is locked behind a hidden trigger (obviously not that hidden since you're reading this) + // It is using memory reading and lets first try to contact Blizzard + _vms = SettingsProvider.Load().GamestatePort == 62575 + ? gameViewModels.OrderBy(g => g.DisplayName) + : gameViewModels.Where(g => g.DisplayName != "WoW").OrderBy(g => g.DisplayName); profileManager.ProfilePreviewModel = profilePreviewModel; - profileManager.GameViewModels.AddRange(_gameViewModels); + profileManager.GameViewModels.AddRange(_vms); } protected override void OnActivate() { base.OnActivate(); - - var settings = SettingsProvider.Load(); - foreach (var gameViewModel in _gameViewModels.OrderBy(g => g.DisplayName)) - { - if (settings.GamestatePort != 62575 && gameViewModel is WoWViewModel) - continue; - - ActivateItem(gameViewModel); - } + Items.Clear(); + Items.AddRange(_vms); } } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/OverlaysViewModel.cs b/Artemis/Artemis/ViewModels/OverlaysViewModel.cs index 578785000..cb7d93271 100644 --- a/Artemis/Artemis/ViewModels/OverlaysViewModel.cs +++ b/Artemis/Artemis/ViewModels/OverlaysViewModel.cs @@ -1,23 +1,23 @@ -using Artemis.ViewModels.Abstract; +using System.Linq; +using Artemis.ViewModels.Abstract; namespace Artemis.ViewModels { public sealed class OverlaysViewModel : BaseViewModel { - private readonly OverlayViewModel[] _overlayViewModels; + private IOrderedEnumerable _vms; public OverlaysViewModel(OverlayViewModel[] overlayViewModels) { DisplayName = "Overlays"; - _overlayViewModels = overlayViewModels; + _vms = overlayViewModels.OrderBy(o => o.DisplayName); } protected override void OnActivate() { base.OnActivate(); - - foreach (var overlayViewModel in _overlayViewModels) - ActivateItem(overlayViewModel); + Items.Clear(); + Items.AddRange(_vms); } } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs index aac0629c6..f6a22b203 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using Artemis.Models.Interfaces; using Artemis.Profiles.Layers.Abstract; -using Artemis.Profiles.Layers.Conditions; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Keyboard; @@ -205,7 +204,7 @@ namespace Artemis.ViewModels.Profiles ProposedLayer.Properties.Conditions.Add(conditionViewModel.LayerConditionModel); // If not a keyboard, ignore size and position - if (ProposedLayer.LayerType.DrawType != DrawType.Keyboard || !ProposedLayer.LayerType.ShowInEdtor) + if ((ProposedLayer.LayerType.DrawType != DrawType.Keyboard) || !ProposedLayer.LayerType.ShowInEdtor) { ProposedLayer.Properties.Width = Layer.Properties.Width; ProposedLayer.Properties.Height = Layer.Properties.Height; @@ -213,7 +212,7 @@ namespace Artemis.ViewModels.Profiles ProposedLayer.Properties.Y = Layer.Properties.Y; ProposedLayer.Properties.Contain = Layer.Properties.Contain; } - + // Ignore the children, can't just temporarily add them to the proposed layer because // that would upset the child layers' relations (sounds like Dr. Phil amirite?) var currentObj = Clone(Layer); diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs index 0fd5e12ff..02576c557 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.ObjectModel; using System.ComponentModel; -using System.Dynamic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,12 +11,12 @@ using System.Windows.Media; using Artemis.DAL; using Artemis.DeviceProviders; using Artemis.Events; -using Artemis.InjectionFactories; using Artemis.Managers; using Artemis.Models; using Artemis.Profiles; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Folder; +using Artemis.Profiles.Lua; using Artemis.Services; using Artemis.Styles.DropTargetAdorners; using Artemis.Utilities; @@ -24,6 +24,8 @@ using Caliburn.Micro; using GongSolutions.Wpf.DragDrop; using MahApps.Metro.Controls.Dialogs; using Ninject; +using Ninject.Parameters; +using NuGet; using DragDropEffects = System.Windows.DragDropEffects; using IDropTarget = GongSolutions.Wpf.DragDrop.IDropTarget; using MouseEventArgs = System.Windows.Input.MouseEventArgs; @@ -34,32 +36,32 @@ namespace Artemis.ViewModels.Profiles { public sealed class ProfileEditorViewModel : Screen, IDropTarget { + private readonly DeviceManager _deviceManager; private readonly EffectModel _gameModel; - private readonly ILayerEditorVmFactory _layerEditorVmFactory; - private readonly MainManager _mainManager; private readonly Timer _saveTimer; private ImageSource _keyboardPreview; - private BindableCollection _layers; - private BindableCollection _profiles; + private ObservableCollection _layers; + private ObservableCollection _profileNames; private bool _saving; private ProfileModel _selectedProfile; - public ProfileEditorViewModel(MainManager mainManager, EffectModel gameModel, ProfileViewModel profileViewModel, - MetroDialogService dialogService, string lastProfile, ILayerEditorVmFactory layerEditorVmFactory) + public ProfileEditorViewModel(DeviceManager deviceManager, EffectModel gameModel, + ProfileViewModel profileViewModel, MetroDialogService dialogService, WindowService windowService, + string lastProfile) { - _mainManager = mainManager; + _deviceManager = deviceManager; _gameModel = gameModel; - _layerEditorVmFactory = layerEditorVmFactory; - Profiles = new BindableCollection(); - Layers = new BindableCollection(); + ProfileNames = new ObservableCollection(); + Layers = new ObservableCollection(); ProfileViewModel = profileViewModel; DialogService = dialogService; + WindowService = windowService; LastProfile = lastProfile; PropertyChanged += EditorStateHandler; ProfileViewModel.PropertyChanged += LayerSelectedHandler; - mainManager.DeviceManager.OnKeyboardChangedEvent += DeviceManagerOnOnKeyboardChangedEvent; + _deviceManager.OnKeyboardChangedEvent += DeviceManagerOnOnKeyboardChangedEvent; _saveTimer = new Timer(5000); _saveTimer.Elapsed += ProfileSaveHandler; @@ -70,27 +72,27 @@ namespace Artemis.ViewModels.Profiles [Inject] public MetroDialogService DialogService { get; set; } + public WindowService WindowService { get; set; } + public string LastProfile { get; set; } public ProfileViewModel ProfileViewModel { get; set; } public bool EditorEnabled - => - SelectedProfile != null && !SelectedProfile.IsDefault && - _mainManager.DeviceManager.ActiveKeyboard != null; + => (SelectedProfile != null) && !SelectedProfile.IsDefault && (_deviceManager.ActiveKeyboard != null); - public BindableCollection Profiles + public ObservableCollection ProfileNames { - get { return _profiles; } + get { return _profileNames; } set { - if (Equals(value, _profiles)) return; - _profiles = value; - NotifyOfPropertyChange(() => Profiles); + if (Equals(value, _profileNames)) return; + _profileNames = value; + NotifyOfPropertyChange(() => ProfileNames); } } - public BindableCollection Layers + public ObservableCollection Layers { get { return _layers; } set @@ -101,14 +103,35 @@ namespace Artemis.ViewModels.Profiles } } + public string SelectedProfileName + { + get { return SelectedProfile?.Name; } + set + { + if (value == SelectedProfile?.Name) + return; + + SelectedProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, _gameModel, value); + NotifyOfPropertyChange(() => SelectedProfileName); + } + } + public ProfileModel SelectedProfile { get { return _selectedProfile; } set { - if (Equals(value, _selectedProfile)) return; + if (Equals(value, _selectedProfile)) + return; + + // Deactivate old profile + _selectedProfile?.Deactivate(); + // Update the value _selectedProfile = value; + // Activate new profile + _selectedProfile?.Activate(_deviceManager.ActiveKeyboard); NotifyOfPropertyChange(() => SelectedProfile); + NotifyOfPropertyChange(() => SelectedProfileName); } } @@ -123,19 +146,19 @@ namespace Artemis.ViewModels.Profiles } } - public PreviewSettings? PreviewSettings => _mainManager.DeviceManager.ActiveKeyboard?.PreviewSettings; + public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; public bool ProfileSelected => SelectedProfile != null; - public bool LayerSelected => SelectedProfile != null && ProfileViewModel.SelectedLayer != null; + public bool LayerSelected => (SelectedProfile != null) && (ProfileViewModel.SelectedLayer != null); public void DragOver(IDropInfo dropInfo) { var source = dropInfo.Data as LayerModel; var target = dropInfo.TargetItem as LayerModel; - if (source == null || target == null || source == target) + if ((source == null) || (target == null) || (source == target)) return; - if (dropInfo.InsertPosition == RelativeInsertPosition.TargetItemCenter && + if ((dropInfo.InsertPosition == RelativeInsertPosition.TargetItemCenter) && target.LayerType is FolderType) { dropInfo.DropTargetAdorner = typeof(DropTargetMetroHighlightAdorner); @@ -152,7 +175,7 @@ namespace Artemis.ViewModels.Profiles { var source = dropInfo.Data as LayerModel; var target = dropInfo.TargetItem as LayerModel; - if (source == null || target == null || source == target) + if ((source == null) || (target == null) || (source == target)) return; // Don't allow a folder to become it's own child, that's just weird @@ -173,7 +196,7 @@ namespace Artemis.ViewModels.Profiles parent.FixOrder(); } - if (dropInfo.InsertPosition == RelativeInsertPosition.TargetItemCenter && + if ((dropInfo.InsertPosition == RelativeInsertPosition.TargetItemCenter) && target.LayerType is FolderType) { // Insert into folder @@ -185,9 +208,9 @@ namespace Artemis.ViewModels.Profiles else { // Insert the source into it's new profile/parent and update the order - if (dropInfo.InsertPosition == RelativeInsertPosition.AfterTargetItem || - dropInfo.InsertPosition == - (RelativeInsertPosition.TargetItemCenter | RelativeInsertPosition.AfterTargetItem)) + if ((dropInfo.InsertPosition == RelativeInsertPosition.AfterTargetItem) || + (dropInfo.InsertPosition == + (RelativeInsertPosition.TargetItemCenter | RelativeInsertPosition.AfterTargetItem))) target.InsertAfter(source); else target.InsertBefore(source); @@ -214,6 +237,7 @@ namespace Artemis.ViewModels.Profiles public void Deactivate() { ProfileViewModel.Deactivate(); + SelectedProfile?.Deactivate(); _saveTimer.Stop(); } @@ -222,20 +246,29 @@ namespace Artemis.ViewModels.Profiles ///
private void LoadProfiles() { - Profiles.Clear(); - if (_gameModel == null || _mainManager.DeviceManager.ActiveKeyboard == null) + ProfileNames.Clear(); + if ((_gameModel == null) || (_deviceManager.ActiveKeyboard == null)) return; - Profiles.AddRange(ProfileProvider.GetAll(_gameModel, _mainManager.DeviceManager.ActiveKeyboard)); + ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _gameModel)); // If a profile name was provided, try to load it ProfileModel lastProfileModel = null; if (!string.IsNullOrEmpty(LastProfile)) - lastProfileModel = Profiles.FirstOrDefault(p => p.Name == LastProfile); + { + lastProfileModel = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, _gameModel, LastProfile); + } - SelectedProfile = lastProfileModel ?? Profiles.FirstOrDefault(); + if (lastProfileModel != null) + SelectedProfile = lastProfileModel; + else + { + SelectedProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, _gameModel, + ProfileNames.FirstOrDefault()); + } } + public void EditLayerFromDoubleClick() { if (ProfileViewModel.SelectedLayer?.LayerType is FolderType) @@ -262,24 +295,15 @@ namespace Artemis.ViewModels.Profiles if (layer == null) return; - IWindowManager manager = new WindowManager(); - var editorVm = _layerEditorVmFactory.CreateLayerEditorVm(_gameModel.DataModel, layer); - - dynamic settings = new ExpandoObject(); - var icon = ImageUtilities.GenerateWindowIcon(); - - settings.Title = "Artemis | Edit " + layer.Name; - settings.Icon = icon; - - manager.ShowDialog(editorVm, null, settings); - - //// The layer editor VM may have created a new instance of the layer, reapply it to the list - //layer.Replace(editorVm.Layer); - //layer = editorVm.Layer; + IParameter[] args = + { + new ConstructorArgument("dataModel", _gameModel.DataModel), + new ConstructorArgument("layer", layer) + }; + WindowService.ShowDialog(args); // If the layer was a folder, but isn't anymore, assign it's children to it's parent. if (!(layer.LayerType is FolderType) && layer.Children.Any()) - { while (layer.Children.Any()) { var child = layer.Children[0]; @@ -295,7 +319,6 @@ namespace Artemis.ViewModels.Profiles layer.Profile.FixOrder(); } } - } UpdateLayerList(layer); } @@ -308,18 +331,9 @@ namespace Artemis.ViewModels.Profiles if (SelectedProfile == null) return null; - // Create a new layer - var layer = LayerModel.CreateLayer(); - - if (ProfileViewModel.SelectedLayer != null) - ProfileViewModel.SelectedLayer.InsertAfter(layer); - else - { - SelectedProfile.Layers.Add(layer); - SelectedProfile.FixOrder(); - } - + var layer = SelectedProfile.AddLayer(ProfileViewModel.SelectedLayer); UpdateLayerList(layer); + return layer; } @@ -466,7 +480,7 @@ namespace Artemis.ViewModels.Profiles /// public async void AddProfile() { - if (_mainManager.DeviceManager.ActiveKeyboard == null) + if (_deviceManager.ActiveKeyboard == null) { DialogService.ShowMessageBox("Cannot add profile.", "To add a profile, please select a keyboard in the options menu first."); @@ -489,13 +503,13 @@ namespace Artemis.ViewModels.Profiles var profile = new ProfileModel { Name = name, - KeyboardSlug = _mainManager.DeviceManager.ActiveKeyboard.Slug, - Width = _mainManager.DeviceManager.ActiveKeyboard.Width, - Height = _mainManager.DeviceManager.ActiveKeyboard.Height, + KeyboardSlug = _deviceManager.ActiveKeyboard.Slug, + Width = _deviceManager.ActiveKeyboard.Width, + Height = _deviceManager.ActiveKeyboard.Height, GameName = _gameModel.Name }; - if (ProfileProvider.GetAll().Contains(profile)) + if (!ProfileProvider.IsProfileUnique(profile)) { var overwrite = await DialogService.ShowQuestionMessageBox("Overwrite existing profile", "A profile with this name already exists for this game. Would you like to overwrite it?"); @@ -505,8 +519,8 @@ namespace Artemis.ViewModels.Profiles ProfileProvider.AddOrUpdate(profile); + LastProfile = profile.Name; LoadProfiles(); - SelectedProfile = profile; } public async void RenameProfile() @@ -514,28 +528,34 @@ namespace Artemis.ViewModels.Profiles if (SelectedProfile == null) return; + var oldName = SelectedProfile.Name; var name = await DialogService.ShowInputDialog("Rename profile", "Please enter a unique new profile name"); // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) + if (string.IsNullOrEmpty(name) || (name.Length < 2)) return; + SelectedProfile.Name = name; + // Verify the name - while (ProfileProvider.GetAll().Any(p => p.Name == name && p.GameName == SelectedProfile.GameName && - p.KeyboardSlug == SelectedProfile.KeyboardSlug)) + while (!ProfileProvider.IsProfileUnique(SelectedProfile)) { - name = - await DialogService.ShowInputDialog("Name already in use", "Please enter a unique new profile name"); + name = await DialogService + .ShowInputDialog("Name already in use", "Please enter a unique new profile name"); // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) + if (string.IsNullOrEmpty(name) || (name.Length < 2)) + { + SelectedProfile.Name = oldName; return; + } + SelectedProfile.Name = name; } var profile = SelectedProfile; - SelectedProfile = null; ProfileProvider.RenameProfile(profile, name); + SelectedProfile = null; LastProfile = name; LoadProfiles(); } @@ -554,10 +574,10 @@ namespace Artemis.ViewModels.Profiles return; // Verify the name - while (ProfileProvider.GetAll().Contains(newProfile)) + while (!ProfileProvider.IsProfileUnique(newProfile)) { - newProfile.Name = - await DialogService.ShowInputDialog("Name already in use", "Please enter a unique profile name"); + newProfile.Name = await DialogService + .ShowInputDialog("Name already in use", "Please enter a unique profile name"); // Null when the user cancelled if (string.IsNullOrEmpty(newProfile.Name)) @@ -566,8 +586,8 @@ namespace Artemis.ViewModels.Profiles newProfile.IsDefault = false; ProfileProvider.AddOrUpdate(newProfile); + LastProfile = newProfile.Name; LoadProfiles(); - SelectedProfile = Profiles.FirstOrDefault(p => p.Name == newProfile.Name); } public async void DeleteProfile() @@ -588,7 +608,7 @@ namespace Artemis.ViewModels.Profiles public async void ImportProfile() { - if (_mainManager.DeviceManager.ActiveKeyboard == null) + if (_deviceManager.ActiveKeyboard == null) { DialogService.ShowMessageBox("Cannot import profile.", "To import a profile, please select a keyboard in the options menu first."); @@ -616,7 +636,7 @@ namespace Artemis.ViewModels.Profiles } // Verify the keyboard - var deviceManager = _mainManager.DeviceManager; + var deviceManager = _deviceManager; if (profile.KeyboardSlug != deviceManager.ActiveKeyboard.Slug) { var adjustKeyboard = await DialogService.ShowQuestionMessageBox("Profile not inteded for this keyboard", @@ -640,7 +660,7 @@ namespace Artemis.ViewModels.Profiles profile.IsDefault = false; // Verify the name - while (ProfileProvider.GetAll().Contains(profile)) + while (!ProfileProvider.IsProfileUnique(profile)) { profile.Name = await DialogService.ShowInputDialog("Rename imported profile", "A profile with this name already exists for this game. Please enter a new name"); @@ -651,9 +671,8 @@ namespace Artemis.ViewModels.Profiles } ProfileProvider.AddOrUpdate(profile); + LastProfile = profile.Name; LoadProfiles(); - - SelectedProfile = Profiles.FirstOrDefault(p => p.Name == profile.Name); } public void ExportProfile() @@ -669,6 +688,23 @@ namespace Artemis.ViewModels.Profiles ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); } + public void EditLua() + { + if (SelectedProfile == null) + return; + try + { + SelectedProfile?.Activate(_deviceManager.ActiveKeyboard); + LuaWrapper.OpenEditor(); + } + catch (Exception e) + { + DialogService.ShowMessageBox("Couldn't open LUA file", + "Please make sure you have a program such as Notepad associated with the .lua extension.\n\n" + + "Windows error message: \n" + e.Message); + } + } + private void EditorStateHandler(object sender, PropertyChangedEventArgs e) { if (e.PropertyName != "SelectedProfile") @@ -697,7 +733,12 @@ namespace Artemis.ViewModels.Profiles private void ProfileSaveHandler(object sender, ElapsedEventArgs e) { - if (_saving || SelectedProfile == null) + SaveSelectedProfile(); + } + + public void SaveSelectedProfile() + { + if (_saving || (SelectedProfile == null) || _deviceManager.ChangingKeyboard) return; _saving = true; diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs index 4fbf21493..fe7fdc0c5 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs @@ -12,6 +12,7 @@ using Artemis.Modules.Effects.ProfilePreview; using Artemis.Profiles; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Folder; +using Artemis.Profiles.Lua; using Artemis.Properties; using Artemis.Utilities; using Caliburn.Micro; @@ -34,19 +35,99 @@ namespace Artemis.ViewModels.Profiles private LayerModel _selectedLayer; private bool _showAll; - public ProfileViewModel(DeviceManager deviceManager) + public ProfileViewModel(DeviceManager deviceManager, LoopManager loopManager) { _deviceManager = deviceManager; - PreviewTimer = new Timer(40); ShowAll = false; - PreviewTimer.Elapsed += InvokeUpdateKeyboardPreview; + loopManager.RenderCompleted += LoopManagerOnRenderCompleted; deviceManager.OnKeyboardChangedEvent += DeviceManagerOnOnKeyboardChangedEvent; } + private void LoopManagerOnRenderCompleted(object sender, EventArgs eventArgs) + { + if (!Activated) + return; + + if (_blurProgress > 2) + _blurProgress = 0; + _blurProgress = _blurProgress + 0.025; + BlurRadius = (Math.Sin(_blurProgress*Math.PI) + 1)*10 + 10; + + if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null) + { + var preview = new DrawingImage(); + preview.Freeze(); + KeyboardPreview = preview; + return; + } + + var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(4); + var visual = new DrawingVisual(); + using (var drawingContext = visual.RenderOpen()) + { + // Setup the DrawingVisual's size + drawingContext.PushClip(new RectangleGeometry(keyboardRect)); + drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); + + // Get the layers that must be drawn + var drawLayers = GetRenderLayers(); + + // Draw the layers + foreach (var layer in drawLayers) + { + layer.Update(null, true, false); + if (layer.LayerType.ShowInEdtor) + layer.Draw(null, drawingContext, true, false); + } + + // Get the selection color + var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; + if (accentColor == null) + { + var preview = new DrawingImage(); + preview.Freeze(); + KeyboardPreview = preview; + return; + } + + var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); + + // Draw the selection outline and resize indicator + if (SelectedLayer != null && SelectedLayer.MustDraw()) + { + var layerRect = SelectedLayer.Properties.GetRect(); + // Deflate the rect so that the border is drawn on the inside + layerRect.Inflate(-0.2, -0.2); + + // Draw an outline around the selected layer + drawingContext.DrawRectangle(null, pen, layerRect); + // Draw a resize indicator in the bottom-right + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); + } + + LuaWrapper.LuaEventsWrapper?.InvokeDeviceDraw(SelectedProfile, "preview", new ProfilePreviewDataModel(), + true, drawingContext); + + // Remove the clip + drawingContext.Pop(); + } + var drawnPreview = new DrawingImage(visual.Drawing); + drawnPreview.Freeze(); + + KeyboardPreview = drawnPreview; + } + public ProfileModel SelectedProfile { get; set; } - public Timer PreviewTimer { get; set; } public LayerModel SelectedLayer { @@ -102,92 +183,15 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => KeyboardImage); } - private void InvokeUpdateKeyboardPreview(object sender, ElapsedEventArgs e) - { - if (_blurProgress > 2) - _blurProgress = 0; - _blurProgress = _blurProgress + 0.025; - BlurRadius = (Math.Sin(_blurProgress*Math.PI) + 1)*10 + 10; - - if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null || (!ShowAll && SelectedLayer == null)) - { - var preview = new DrawingImage(); - preview.Freeze(); - KeyboardPreview = preview; - return; - } - - - var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(4); - var visual = new DrawingVisual(); - using (var drawingContext = visual.RenderOpen()) - { - // Setup the DrawingVisual's size - drawingContext.PushClip(new RectangleGeometry(keyboardRect)); - drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); - - // Get the layers that must be drawn - var drawLayers = GetRenderLayers(); - - // Draw the layers - foreach (var layer in drawLayers) - { - layer.Update(null, true, false); - if (layer.LayerType.ShowInEdtor) - layer.Draw(null, drawingContext, true, false); - } - - // Get the selection color - var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; - if (accentColor == null) - { - var preview = new DrawingImage(); - preview.Freeze(); - KeyboardPreview = preview; - return; - } - - var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); - - // Draw the selection outline and resize indicator - if (SelectedLayer != null && SelectedLayer.MustDraw()) - { - var layerRect = SelectedLayer.Properties.GetRect(); - // Deflate the rect so that the border is drawn on the inside - layerRect.Inflate(-0.2, -0.2); - - // Draw an outline around the selected layer - drawingContext.DrawRectangle(null, pen, layerRect); - // Draw a resize indicator in the bottom-right - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); - } - - // Remove the clip - drawingContext.Pop(); - } - var drawnPreview = new DrawingImage(visual.Drawing); - drawnPreview.Freeze(); - KeyboardPreview = drawnPreview; - } public void Activate() { Activated = true; - PreviewTimer.Start(); } public void Deactivate() { Activated = false; - PreviewTimer.Stop(); } #region Processing @@ -203,8 +207,7 @@ namespace Artemis.ViewModels.Profiles } /// - /// Second handler for clicking, selects a the layer the user clicked on - /// if the used clicked on an empty spot, deselects the current layer + /// Second handler for clicking, selects a the layer the user clicked on. /// /// public void MouseUpKeyboardPreview(MouseButtonEventArgs e) @@ -226,7 +229,8 @@ namespace Artemis.ViewModels.Profiles var hoverLayer = GetLayers().Where(l => l.MustDraw()) .FirstOrDefault(l => l.Properties.GetRect(1).Contains(x, y)); - SelectedLayer = hoverLayer; + if (hoverLayer != null) + SelectedLayer = hoverLayer; } /// @@ -317,17 +321,31 @@ namespace Artemis.ViewModels.Profiles // If no setup or reset was done, handle the actual dragging action if (_resizing) { - draggingProps.Width = (int) Math.Round(x - draggingProps.X); - draggingProps.Height = (int) Math.Round(y - draggingProps.Y); - if (draggingProps.Width < 1) - draggingProps.Width = 1; - if (draggingProps.Height < 1) - draggingProps.Height = 1; + var newWidth = Math.Round(x - draggingProps.X); + var newHeight = Math.Round(y - draggingProps.Y); + + // Ensure the layer doesn't leave the canvas + if (newWidth < 1 || draggingProps.X + newWidth <= 0) + newWidth = draggingProps.Width; + if (newHeight < 1 || draggingProps.Y + newHeight <= 0) + newHeight = draggingProps.Height; + + draggingProps.Width = newWidth; + draggingProps.Height = newHeight; } else { - draggingProps.X = (int) Math.Round(x - _draggingLayerOffset.Value.X); - draggingProps.Y = (int) Math.Round(y - _draggingLayerOffset.Value.Y); + var newX = Math.Round(x - _draggingLayerOffset.Value.X); + var newY = Math.Round(y - _draggingLayerOffset.Value.Y); + + // Ensure the layer doesn't leave the canvas + if (newX >= SelectedProfile.Width || newX + draggingProps.Width <= 0) + newX = draggingProps.X; + if (newY >= SelectedProfile.Height || newY + draggingProps.Height <= 0) + newY = draggingProps.Y; + + draggingProps.X = newX; + draggingProps.Y = newY; } } @@ -338,7 +356,7 @@ namespace Artemis.ViewModels.Profiles if (ShowAll) return SelectedProfile.GetRenderLayers(new ProfilePreviewDataModel(), false, true); - if (SelectedLayer == null) + if (SelectedLayer == null || !SelectedLayer.Enabled) return new EditableList(); if (SelectedLayer.LayerType is FolderType) @@ -352,21 +370,17 @@ namespace Artemis.ViewModels.Profiles private List GetLayers() { + if (ShowAll) + return SelectedProfile.GetLayers(); if (SelectedLayer == null) return new List(); lock (SelectedLayer) { // Get the layers that must be drawn - List drawLayers; - if (ShowAll) - drawLayers = SelectedProfile.GetLayers(); - else if (SelectedLayer.LayerType is FolderType) - drawLayers = SelectedLayer.GetLayers().ToList(); - else - drawLayers = new List {SelectedLayer}; - - return drawLayers; + if (SelectedLayer.LayerType is FolderType) + return SelectedLayer.GetLayers().ToList(); + return new List {SelectedLayer}; } } diff --git a/Artemis/Artemis/ViewModels/ShellViewModel.cs b/Artemis/Artemis/ViewModels/ShellViewModel.cs index d8d2568d2..163fdb432 100644 --- a/Artemis/Artemis/ViewModels/ShellViewModel.cs +++ b/Artemis/Artemis/ViewModels/ShellViewModel.cs @@ -1,38 +1,201 @@ -using System.Linq; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using Artemis.DAL; +using Artemis.Events; using Artemis.Managers; using Artemis.Services; +using Artemis.Settings; +using Artemis.Utilities; using Artemis.ViewModels.Abstract; using Artemis.ViewModels.Flyouts; using Caliburn.Micro; +using Hardcodet.Wpf.TaskbarNotification; +using MahApps.Metro.Controls; using Ninject; namespace Artemis.ViewModels { public sealed class ShellViewModel : Conductor.Collection.OneActive { - private readonly BaseViewModel[] _viewModels; + private readonly IKernel _kernel; + private string _activeIcon; + private bool _checked; + private bool _enabled; + private string _toggleText; + private bool _exiting; - public ShellViewModel(IKernel kernel, BaseViewModel[] viewModels) + public ShellViewModel(IKernel kernel, MainManager mainManager, MetroDialogService metroDialogService, + FlyoutSettingsViewModel flyoutSettings) { - _viewModels = viewModels; + _kernel = kernel; + + MainManager = mainManager; + MetroDialogService = metroDialogService; - // Setup UI DisplayName = "Artemis"; + GeneralSettings = SettingsProvider.Load(); Flyouts = new BindableCollection { - kernel.Get() + flyoutSettings }; + + MainManager.OnEnabledChangedEvent += (sender, args) => Enabled = args.Enabled; + + // This gets updated automatically but during startup lets quickly preset it + Enabled = GeneralSettings.Suspended; } - public IObservableCollection Flyouts { get; set; } - - protected override void OnActivate() + protected override void OnViewReady(object view) { - base.OnActivate(); - foreach (var screen in _viewModels) - ActivateItem(screen); + base.OnViewReady(view); - ActiveItem = _viewModels.FirstOrDefault(); + Task.Run(() => StartupHide()); + } + + private void StartupHide() + { + // TODO: This is probably an awful idea. I can't reliably hook into the view being ready to be hidden + Thread.Sleep(500); + + if (GeneralSettings.ShowOnStartup) + ShowWindow(); + else + HideWindow(); + + if (!GeneralSettings.Suspended) + MainManager.EnableProgram(); + } + + public Mutex Mutex { get; set; } + public MainManager MainManager { get; set; } + public MetroDialogService MetroDialogService { get; set; } + public IObservableCollection Flyouts { get; set; } + public GeneralSettings GeneralSettings { get; set; } + private MetroWindow Window => (MetroWindow) GetView(); + + public bool CanShowWindow => Window != null && (Window != null || !Window.IsVisible); + public bool CanHideWindow => Window != null && Window.IsVisible; + + public bool Enabled + { + get { return _enabled; } + set + { + if (value == _enabled) return; + _enabled = value; + + ToggleText = _enabled ? "Disable Artemis" : "Enable Artemis"; + ActiveIcon = _enabled ? "../Resources/logo.ico" : "../Resources/logo-disabled.ico"; + NotifyOfPropertyChange(() => Enabled); + } + } + + public string ActiveIcon + { + get { return _activeIcon; } + set + { + _activeIcon = value; + NotifyOfPropertyChange(); + } + } + + public string ToggleText + { + get { return _toggleText; } + set + { + if (value == _toggleText) return; + _toggleText = value; + NotifyOfPropertyChange(() => ToggleText); + } + } + + public override void CanClose(Action callback) + { + if (CanHideWindow) + HideWindow(); + else if (!_exiting) + ShowWindow(); + + // Only close if ExitApplication was called + callback(_exiting); + } + + public void ShowWindow() + { + if (CanShowWindow) + Window?.Dispatcher.Invoke(() => + { + Window.Show(); + Window.Activate(); + }); + + GeneralSettings.ApplyTheme(); + + // Show certain dialogs if needed + CheckKeyboardState(); + CheckDuplicateInstances(); + Updater.CheckChangelog(MetroDialogService); + + // Run this on the UI thread to avoid having to use dispatchers in VMs + Execute.OnUIThread(ActivateViews); + } + + private void ActivateViews() + { + var vms = _kernel.GetAll().ToList(); + Items.Clear(); + Items.AddRange(vms); + ActivateItem(vms.FirstOrDefault()); + + NotifyOfPropertyChange(() => CanShowWindow); + NotifyOfPropertyChange(() => CanHideWindow); + } + + public void HideWindow() + { + if (CanHideWindow) + Window?.Dispatcher.Invoke(() => { Window.Hide(); }); + + Items.Clear(); + NotifyOfPropertyChange(() => CanShowWindow); + NotifyOfPropertyChange(() => CanHideWindow); + + // Force a GC since the UI releases a lot of resources + GC.Collect(); + } + + public void ToggleEnabled() + { + if (Enabled) + MainManager.DisableProgram(); + else + MainManager.EnableProgram(); + } + + public void ExitApplication() + { + MainManager.Dispose(); + + try + { + var icon = (TaskbarIcon) Window.FindResource("SystemTrayIcon"); + icon.Dispose(); + } + catch (Exception) + { + //ignored + } + + _exiting = true; + + // TODO: CoolerMaster SDK is freezing Artemis on shutdown, dunno what to do about it yet + System.Diagnostics.Process.GetCurrentProcess().Kill(); + // Application.Current.Shutdown(); } public void Settings() @@ -44,5 +207,42 @@ namespace Artemis.ViewModels { Flyouts.First().IsOpen = false; } + + private async void CheckKeyboardState() + { + var dialog = await MetroDialogService.ShowProgressDialog("Enabling keyboard", + "Artemis is still busy trying to enable your last used keyboard. " + + "Please wait while the process completes"); + dialog.SetIndeterminate(); + + while (MainManager.DeviceManager.ChangingKeyboard) + await Task.Delay(10); + + try + { + await dialog.CloseAsync(); + } + catch (InvalidOperationException) + { + // Occurs when window is closed again, can't find a proper check for this + } + } + + private void CheckDuplicateInstances() + { + if (_checked) + return; + _checked = true; + + bool aIsNewInstance; + Mutex = new Mutex(true, "ArtemisMutex", out aIsNewInstance); + if (aIsNewInstance) + return; + + MetroDialogService.ShowMessageBox("Multiple instances found", + "It looks like there are multiple running instances of Artemis. " + + "This can cause issues, especially with CS:GO and Dota2. " + + "If so, please make sure Artemis isn't already running"); + } } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/SystemTrayViewModel.cs b/Artemis/Artemis/ViewModels/SystemTrayViewModel.cs deleted file mode 100644 index 368ccaf46..000000000 --- a/Artemis/Artemis/ViewModels/SystemTrayViewModel.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using Artemis.DAL; -using Artemis.Events; -using Artemis.Managers; -using Artemis.Services; -using Artemis.Settings; -using Artemis.Utilities; -using Caliburn.Micro; - -namespace Artemis.ViewModels -{ - public class SystemTrayViewModel : Screen - { - private readonly ShellViewModel _shellViewModel; - private readonly IWindowManager _windowManager; - private string _activeIcon; - private bool _checked; - private bool _enabled; - private string _toggleText; - - public SystemTrayViewModel(IWindowManager windowManager, MetroDialogService dialogService, - ShellViewModel shellViewModel, MainManager mainManager) - { - _windowManager = windowManager; - _shellViewModel = shellViewModel; - - DialogService = dialogService; - MainManager = mainManager; - - MainManager.EnableProgram(); - MainManager.OnEnabledChangedEvent += MainManagerOnOnEnabledChangedEvent; - - var generalSettings = SettingsProvider.Load(); - Enabled = !generalSettings.Suspended; - if (generalSettings.ShowOnStartup) - ShowWindow(); - } - - public MetroDialogService DialogService { get; set; } - - public MainManager MainManager { get; set; } - - public bool CanShowWindow => !_shellViewModel.IsActive; - public bool CanHideWindow => _shellViewModel.IsActive && !MainManager.DeviceManager.ChangingKeyboard; - public bool CanToggleEnabled => !MainManager.DeviceManager.ChangingKeyboard; - - public bool Enabled - { - get { return _enabled; } - set - { - if (value == _enabled) return; - _enabled = value; - - ToggleText = _enabled ? "Disable Artemis" : "Enable Artemis"; - ActiveIcon = _enabled ? "../Resources/logo.ico" : "../Resources/logo-disabled.ico"; - NotifyOfPropertyChange(() => Enabled); - } - } - - public string ActiveIcon - { - get { return _activeIcon; } - set - { - _activeIcon = value; - NotifyOfPropertyChange(); - } - } - - public string ToggleText - { - get { return _toggleText; } - set - { - if (value == _toggleText) return; - _toggleText = value; - NotifyOfPropertyChange(() => ToggleText); - } - } - - public Mutex Mutex { get; set; } - - private void MainManagerOnOnEnabledChangedEvent(object sender, EnabledChangedEventArgs e) - { - Enabled = e.Enabled; - } - - public void ToggleEnabled() - { - if (Enabled) - MainManager.DisableProgram(); - else - MainManager.EnableProgram(); - } - - protected override void OnActivate() - { - base.OnActivate(); - - NotifyOfPropertyChange(() => CanShowWindow); - NotifyOfPropertyChange(() => CanHideWindow); - } - - public void ShowWindow() - { - if (!CanShowWindow) - return; - - // manually show the next window view-model - _windowManager.ShowWindow(_shellViewModel); - - NotifyOfPropertyChange(() => CanShowWindow); - NotifyOfPropertyChange(() => CanHideWindow); - - SettingsProvider.Load().ApplyTheme(); - - // Show certain dialogs if needed - CheckKeyboardState(); - CheckDuplicateInstances(); - Updater.CheckChangelog(DialogService); - } - - private void CheckDuplicateInstances() - { - if (_checked) - return; - _checked = true; - - bool aIsNewInstance; - Mutex = new Mutex(true, "ArtemisMutex", out aIsNewInstance); - if (aIsNewInstance) - return; - - DialogService.ShowMessageBox("Multiple instances found", - "It looks like there are multiple running instances of Artemis. " + - "This can cause issues, especially with CS:GO and Dota2. " + - "If so, please make sure Artemis isn't already running"); - } - - private async void CheckKeyboardState() - { - while (!_shellViewModel.IsActive) - await Task.Delay(200); - - NotifyOfPropertyChange(() => CanHideWindow); - NotifyOfPropertyChange(() => CanToggleEnabled); - - var dialog = await DialogService.ShowProgressDialog("Enabling keyboard", - "Artemis is still busy trying to enable your last used keyboard. " + - "Please wait while the process completes"); - dialog.SetIndeterminate(); - - while (MainManager.DeviceManager.ChangingKeyboard) - await Task.Delay(10); - - NotifyOfPropertyChange(() => CanHideWindow); - NotifyOfPropertyChange(() => CanToggleEnabled); - - try - { - await dialog.CloseAsync(); - } - catch (InvalidOperationException) - { - // Occurs when window is closed again, can't find a proper check for this - } - } - - public void HideWindow() - { - if (!CanHideWindow) - return; - - _shellViewModel.TryClose(); - - NotifyOfPropertyChange(() => CanShowWindow); - NotifyOfPropertyChange(() => CanHideWindow); - } - - public void ExitApplication() - { - MainManager.Dispose(); - Application.Current.Shutdown(); - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Views/DebugView.xaml b/Artemis/Artemis/Views/DebugView.xaml index 5c9dd7b8d..32f3d9e9d 100644 --- a/Artemis/Artemis/Views/DebugView.xaml +++ b/Artemis/Artemis/Views/DebugView.xaml @@ -4,8 +4,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:log="clr-namespace:Artemis.Controls.Log" mc:Ignorable="d" - Title="DebugView" Height="329.904" Width="446.624" + Title="DebugView" Height="600" Width="900" GlowBrush="{DynamicResource AccentColorBrush}"> @@ -13,17 +14,27 @@ + + - \ No newline at end of file diff --git a/Artemis/Artemis/Views/WelcomeView.xaml b/Artemis/Artemis/Views/WelcomeView.xaml index 5383ceab9..f0e2d9d70 100644 --- a/Artemis/Artemis/Views/WelcomeView.xaml +++ b/Artemis/Artemis/Views/WelcomeView.xaml @@ -18,7 +18,7 @@ + HorizontalAlignment="Left" MaxWidth="520" TextAlignment="Justify" FontFamily="Te"> Hello, Thanks a bunch for downloading this application. You're going to enjoy this! :) diff --git a/Artemis/Artemis/lib/SDKDLL.dll b/Artemis/Artemis/lib/SDKDLL.dll new file mode 100644 index 000000000..279981433 Binary files /dev/null and b/Artemis/Artemis/lib/SDKDLL.dll differ diff --git a/Artemis/Artemis/packages.config b/Artemis/Artemis/packages.config index bc7832355..0e4350b48 100644 --- a/Artemis/Artemis/packages.config +++ b/Artemis/Artemis/packages.config @@ -7,22 +7,24 @@ - + + - - - + + + + diff --git a/Artemis/ColorBox/GradientStopAdder.cs b/Artemis/ColorBox/GradientStopAdder.cs index 724eb45e7..75a31e91c 100644 --- a/Artemis/ColorBox/GradientStopAdder.cs +++ b/Artemis/ColorBox/GradientStopAdder.cs @@ -66,12 +66,16 @@ namespace ColorBox arr = stream.ToArray(); } - BitmapSource bitmap = BitmapFrame.Create(new MemoryStream(arr)); + using (var ms = new MemoryStream(arr)) + { + BitmapSource bitmap = BitmapFrame.Create(ms); - var pixels = new byte[4]; - var cb = new CroppedBitmap(bitmap, new Int32Rect((int) p.X, (int) p.Y, 1, 1)); - cb.CopyPixels(pixels, 4, 0); - return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]); + var pixels = new byte[4]; + var cb = new CroppedBitmap(bitmap, new Int32Rect((int) p.X, (int) p.Y, 1, 1)); + + cb.CopyPixels(pixels, 4, 0); + return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]); + } } catch (Exception) { diff --git a/Artemis/LogiLed2Artemis/LogiLed2Artemis.def b/Artemis/LogiLed2Artemis/LogiLed2Artemis.def index ef38be3f1..890fbfc44 100644 --- a/Artemis/LogiLed2Artemis/LogiLed2Artemis.def +++ b/Artemis/LogiLed2Artemis/LogiLed2Artemis.def @@ -1,27 +1,27 @@ -// Original work by VRocker https://github.com/VRocker/LogiLed2Corsair -// I'm mainly a C# developer, and these modification aren't a piece of art, but it suits our needs. - -// The MIT License (MIT) -// -// Copyright (c) 2015 VRocker -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +; Original work by VRocker https://github.com/VRocker/LogiLed2Corsair +; I'm mainly a C# developer, and these modification aren't a piece of art, but it suits our needs. +; +; The MIT License (MIT) +; +; Copyright (c) 2015 VRocker +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in all +; copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +; SOFTWARE. LIBRARY LogiLed2Artemis.dll diff --git a/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj b/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj index 670478206..e41b51302 100644 --- a/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj +++ b/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj @@ -104,6 +104,7 @@ Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;LOGILEDCORSAIR_EXPORTS;%(PreprocessorDefinitions) %(AdditionalIncludeDirectories) + true Windows @@ -111,6 +112,9 @@ LogiLed2Artemis.def %(AdditionalDependencies) + + true + @@ -151,12 +155,10 @@ - - diff --git a/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj.filters b/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj.filters index 34a553926..00ff5943c 100644 --- a/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj.filters +++ b/Artemis/LogiLed2Artemis/LogiLed2Artemis.vcxproj.filters @@ -21,17 +21,11 @@ Header Files - - Header Files - Source Files - - Source Files - diff --git a/Artemis/LogiLed2Artemis/main.cpp b/Artemis/LogiLed2Artemis/main.cpp index c224e7daf..8181302bf 100644 --- a/Artemis/LogiLed2Artemis/main.cpp +++ b/Artemis/LogiLed2Artemis/main.cpp @@ -30,69 +30,61 @@ #include "LogiLedDefs.h" #define WIN32_LEAN_AND_MEAN #include -#include "Logger.h" - #include #include - +#include +using namespace std; static bool g_hasInitialised = false; +static bool mustLog = false; +static int throttle = 0; const char* game = ""; -void cleanup() + +const char* GetGame() { - CLogger::EndLogging(); -} + CHAR divisionFind[] = ("Division"); + CHAR gtaFind[] = ("GTA"); + CHAR szPath[MAX_PATH]; + + GetModuleFileNameA(NULL, szPath, MAX_PATH); + char *output; + + output = strstr(szPath, divisionFind); + if (output) + return "division"; + output = strstr(szPath, gtaFind); + if (output) + return "gta"; + + return "bf1"; +}; BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID) { - switch (fdwReason) - { - case DLL_PROCESS_ATTACH: + if (fdwReason == DLL_PROCESS_ATTACH) + { + game = GetGame(); + + if (mustLog) { - atexit(cleanup); - - CLogger::InitLogging("Log.txt"); - CLogger::SetLogLevel(LogLevel::None); - - // Get the process that loaded the DLL - TCHAR divisionFind[] = _T("Division"); - TCHAR gtaFind[] = _T("GTA"); - TCHAR szPath[MAX_PATH]; - GetModuleFileName(NULL, szPath, MAX_PATH); - - if (_tcscmp(szPath, divisionFind) != 0) - game = "division"; - else if (_tcscmp(szPath, gtaFind) != 0) - game = "gta"; - - CLogger::OutputLog("Attached to process.", LogLevel::Debug); + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "Main called, DLL loaded into " << game << "\n"; + myfile.close(); } - break; - - case DLL_PROCESS_DETACH: - { - cleanup(); - - CLogger::OutputLog_s("Detached from process.", LogLevel::Debug); - } - break; } - return true; } bool LogiLedInit() { - CLogger::OutputLog_s("Logitech LED init called.", LogLevel::Debug); g_hasInitialised = true; return true; } bool LogiLedGetSdkVersion(int* majorNum, int* minorNum, int* buildNum) { - CLogger::OutputLog("LogiLedGetSdkVersion called.", LogLevel::Debug); - // Mimic the SDK version *majorNum = 8; *minorNum = 81; @@ -103,15 +95,12 @@ bool LogiLedGetSdkVersion(int* majorNum, int* minorNum, int* buildNum) bool LogiLedSetTargetDevice(int targetDevice) { - CLogger::OutputLog("LogiLedSetTargetDevice called (%i)", LogLevel::Debug, targetDevice); - // Logitech SDK says this function returns false if LogiLedInit hasn't been called return g_hasInitialised; } bool LogiLedSaveCurrentLighting() { - CLogger::OutputLog("LogiLedSaveCurrentLighting called (%i)", LogLevel::Debug); return true; } @@ -125,21 +114,49 @@ void Transmit(const char* msg) hPipe1 = CreateFile(lpszPipename1, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (hPipe1 == NULL || hPipe1 == INVALID_HANDLE_VALUE) { - CLogger::OutputLog("Could not open the pipe - (error %i)", LogLevel::Debug, GetLastError()); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "Could not open the pipe - " << GetLastError() << "\n"; + myfile.close(); + } return; } DWORD cbWritten; WriteFile(hPipe1, msg, strlen(msg), &cbWritten, NULL); - CLogger::OutputLog_s("Transmitted to pipe", LogLevel::Debug); } -bool LogiLedSetLighting(int redPercentage, int greenPercentage, int bluePercentage) +// LogiLedSetLighting appears to have an undocumented extra argument +bool LogiLedSetLighting(int redPercentage, int greenPercentage, int bluePercentage, int custom = 0) { - CLogger::OutputLog("LogiLedSetLighting called (%i %i %i)", LogLevel::Debug, redPercentage, greenPercentage, bluePercentage); + // GTA goes mental on the SetLighting calls, lets only send one in every 20 + if (game == "gta") + { + throttle++; + if (throttle > 20) + throttle = 0; + else + return true; + } - std::ostringstream os; - os << "0 0 " << redPercentage << " " << greenPercentage << " " << bluePercentage; + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + if (custom == 0) + { + myfile << "LogiLedSetLighting called\n"; + } + else + { + myfile << "LogiLedSetLighting called with custom " << custom << "\n"; + } + myfile.close(); + } + ostringstream os; + os << "0 " << hex << custom << " " << dec << redPercentage << " " << greenPercentage << " " << bluePercentage; Transmit(os.str().c_str()); return true; @@ -147,108 +164,209 @@ bool LogiLedSetLighting(int redPercentage, int greenPercentage, int bluePercenta bool LogiLedRestoreLighting() { - CLogger::OutputLog("LogiLedRestoreLighting called", LogLevel::Debug); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedRestoreLighting called\n"; + myfile.close(); + } + return true; } bool LogiLedFlashLighting(int redPercentage, int greenPercentage, int bluePercentage, int milliSecondsDuration, int milliSecondsInterval) { - CLogger::OutputLog("LogiLedFlashLighting called (%i %i %i %i %i)", LogLevel::Debug, redPercentage, greenPercentage, bluePercentage, milliSecondsDuration, milliSecondsInterval); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedFlashLighting called\n"; + myfile.close(); + } + return true; } bool LogiLedPulseLighting(int redPercentage, int greenPercentage, int bluePercentage, int milliSecondsDuration, int milliSecondsInterval) { - CLogger::OutputLog("LogiLedPulseLighting called (%i %i %i %i %i)", LogLevel::Debug, redPercentage, greenPercentage, bluePercentage, milliSecondsDuration, milliSecondsInterval); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedPulseLighting called\n"; + myfile.close(); + } + return true; } bool LogiLedStopEffects() { - CLogger::OutputLog_s("LogiLedStopEffects called", LogLevel::Debug); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedStopEffects called\n"; + myfile.close(); + } + return true; } bool LogiLedSetLightingFromBitmap(unsigned char bitmap[]) { - CLogger::OutputLog_s("LogiLedSetLightingFromBitmap called", LogLevel::Debug); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedSetLightingFromBitmap called\n"; + myfile.close(); + } + return true; } bool LogiLedSetLightingForKeyWithScanCode(int keyCode, int redPercentage, int greenPercentage, int bluePercentage) { - CLogger::OutputLog("LogiLedSetLightingForKeyWithScanCode called [Key: %i] (%i %i %i)", LogLevel::Debug, keyCode, redPercentage, greenPercentage, bluePercentage); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedSetLightingForKeyWithScanCode called\n"; + myfile.close(); + } + return true; } bool LogiLedSetLightingForKeyWithHidCode(int keyCode, int redPercentage, int greenPercentage, int bluePercentage) { - CLogger::OutputLog("LogiLedSetLightingForKeyWithHidCode called [Key: %i] (%i %i %i)", LogLevel::Debug, keyCode, redPercentage, greenPercentage, bluePercentage); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedSetLightingForKeyWithHidCode called\n"; + myfile.close(); + } + return true; } bool LogiLedSetLightingForKeyWithQuartzCode(int keyCode, int redPercentage, int greenPercentage, int bluePercentage) { - CLogger::OutputLog("LogiLedSetLightingForKeyWithQuartzCode called [Key: %i] (%i %i %i)", LogLevel::Debug, keyCode, redPercentage, greenPercentage, bluePercentage); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedSetLightingForKeyWithQuartzCode called\n"; + myfile.close(); + } + return true; } bool LogiLedSetLightingForKeyWithKeyName(LogiLed::KeyName keyName, int redPercentage, int greenPercentage, int bluePercentage) { - CLogger::OutputLog("LogiLedSetLightingForKeyWithKeyName called [Key: %i] (%i %i %i)", LogLevel::Debug, keyName, redPercentage, greenPercentage, bluePercentage); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedSetLightingForKeyWithKeyName called\n"; + myfile.close(); + } // Only transmit interesting keys. This can most likely be done prettier, but I'm no C++ dev. - if (game == "division" && - (keyName == LogiLed::F1 || - keyName == LogiLed::F2 || - keyName == LogiLed::F3 || - keyName == LogiLed::F4 || - keyName == LogiLed::R || - keyName == LogiLed::G || - keyName == LogiLed::V) - ) + if (game == "division" && (keyName == LogiLed::F1 || keyName == LogiLed::F2 || keyName == LogiLed::F3 || keyName == LogiLed::F4 || keyName == LogiLed::R || keyName == LogiLed::G || keyName == LogiLed::V)) { - std::ostringstream os; + ostringstream os; os << "1 " << keyName << " " << redPercentage << " " << greenPercentage << " " << bluePercentage; - std::string s = os.str(); + string s = os.str(); Transmit(os.str().c_str()); + return true; } + + ostringstream os; + os << "1 " << keyName << " " << redPercentage << " " << greenPercentage << " " << bluePercentage; + string s = os.str(); + Transmit(os.str().c_str()); return true; } bool LogiLedSaveLightingForKey(LogiLed::KeyName keyName) { - CLogger::OutputLog("LogiLedSaveLightingForKey called [Key: %i]", LogLevel::Debug, keyName); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedSaveLightingForKey called\n"; + myfile.close(); + } + return true; } bool LogiLedRestoreLightingForKey(LogiLed::KeyName keyName) { - CLogger::OutputLog("LogiLedRestoreLightingForKey called [Key: %i]", LogLevel::Debug, keyName); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedRestoreLightingForKey called\n"; + myfile.close(); + } + return true; } bool LogiLedFlashSingleKey(LogiLed::KeyName keyName, int redPercentage, int greenPercentage, int bluePercentage, int msDuration, int msInterval) { - CLogger::OutputLog("LogiLedFlashSingleKey called [Key: %i] (%i %i %i %i %i)", LogLevel::Debug, keyName, redPercentage, greenPercentage, bluePercentage, msDuration, msInterval); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedFlashSingleKey called\n"; + myfile.close(); + } + return true; } bool LogiLedPulseSingleKey(LogiLed::KeyName keyName, int startRedPercentage, int startGreenPercentage, int startBluePercentage, int finishRedPercentage, int finishGreenPercentage, int finishBluePercentage, int msDuration, bool isInfinite) { - CLogger::OutputLog("LogiLedPulseSingleKey called [Key: %i] (%i %i %i %i %i %i %i %i)", LogLevel::Debug, keyName, startRedPercentage, startGreenPercentage, startBluePercentage, finishRedPercentage, finishGreenPercentage, finishBluePercentage, msDuration, isInfinite); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedPulseSingleKey called\n"; + myfile.close(); + } + return true; } bool LogiLedStopEffectsOnKey(LogiLed::KeyName keyName) { - CLogger::OutputLog("LogiLedStopEffectsOnKey called [Key: %i]", LogLevel::Debug, keyName); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedStopEffectsOnKey called\n"; + myfile.close(); + } + return true; } void LogiLedShutdown() { - CLogger::OutputLog_s("LogiLedShutdown called.", LogLevel::Debug); + if (mustLog) + { + ofstream myfile; + myfile.open("log.txt", ios::out | ios::app); + myfile << "LogiLedShutdown called\n"; + myfile.close(); + } + g_hasInitialised = false; } - diff --git a/Artemis/UnrealTournament2Artemis/Artemis.uplugin b/Artemis/UnrealTournament2Artemis/Artemis.uplugin index 8ea2a6467..1e48f9cd3 100644 --- a/Artemis/UnrealTournament2Artemis/Artemis.uplugin +++ b/Artemis/UnrealTournament2Artemis/Artemis.uplugin @@ -2,10 +2,10 @@ "Version" : 1, "FileVersion": 3, "FriendlyName": "Artemis Plugin", - "VersionName": "1.0", + "VersionName": "1", "CreatedBy": "Robert Beekman", "CreatedByURL": "https://github.com/SpoinkyNL/Artemis", - "EngineVersion": "4.4.0", + "EngineVersion": "4.12.0", "Description": "Communicates with Artemis to let the main program know what's happening ingame", "Category": "UnrealTournament.Mod", "EnabledByDefault": true, diff --git a/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Artemis-Win64-Shipping.dll b/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Artemis-Win64-Shipping.dll index 87c719160..fad8730cb 100644 Binary files a/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Artemis-Win64-Shipping.dll and b/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Artemis-Win64-Shipping.dll differ diff --git a/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Win64-Shipping.modules b/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Win64-Shipping.modules index a577932a1..a1785bdf8 100644 --- a/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Win64-Shipping.modules +++ b/Artemis/UnrealTournament2Artemis/Binaries/Win64/UE4-Win64-Shipping.modules @@ -1,6 +1,6 @@ { - "Changelist" : 2897679, - "BuildId" : "5a8ef5b7-c9bb-42bd-ab7c-fb8e33f704ec", + "Changelist" : 3104517, + "BuildId" : "ea171f74-1095-4143-adb4-98b95d950129", "Modules" : { "Artemis" : "UE4-Artemis-Win64-Shipping.dll" diff --git a/Artemis/UnrealTournament2Artemis/Source/Private/Artemis.cpp b/Artemis/UnrealTournament2Artemis/Source/Private/Artemis.cpp index 3680cf068..657ee9e3e 100644 --- a/Artemis/UnrealTournament2Artemis/Source/Private/Artemis.cpp +++ b/Artemis/UnrealTournament2Artemis/Source/Private/Artemis.cpp @@ -48,7 +48,7 @@ void FArtemis::Tick(float DeltaTime) LastFrameCounter = GFrameCounter; - // We may be going 120hz, don't spam the device + // We may be going 120hz, don't spam the pipe DeltaTimeAccumulator += DeltaTime; if (DeltaTimeAccumulator < FrameTimeMinimum) { @@ -112,7 +112,7 @@ void FArtemis::Tick(float DeltaTime) { EnvironmentJson->RemoveField("Players"); } - + // Update player data // If character not found player must be spectating(?) if (!UTPC->GetUTCharacter()) @@ -132,7 +132,8 @@ void FArtemis::Tick(float DeltaTime) // Update HP and armor RootJson->SetStringField("State", "Alive"); PlayerJson->SetNumberField("Health", UTPC->GetUTCharacter()->Health); - PlayerJson->SetNumberField("Armor", UTPC->GetUTCharacter()->ArmorAmount); + // TODO: Crashes the game + // PlayerJson->SetNumberField("Armor", UTPC->GetUTCharacter()->GetArmorAmount()); // Update player powerups data TSharedRef InventoryJson(new FJsonObject()); @@ -146,6 +147,7 @@ void FArtemis::Tick(float DeltaTime) InventoryJson->SetBoolField("HasChestArmor", false); InventoryJson->SetBoolField("HasHelmet", false); + for (TInventoryIterator<> It(UTPC->GetUTCharacter()); It; ++It) { AUTInventory* InventoryItem = (*It); @@ -200,13 +202,14 @@ void FArtemis::Tick(float DeltaTime) WeaponJson->SetStringField("Name", "None"); } } + // Insert PlayerState JsonReport TSharedRef PlayerStateJson(new FJsonObject()); PlayerJson->SetObjectField("State", PlayerStateJson); if (UTPC->UTPlayerState) { UTPC->UTPlayerState->MakeJsonReport(PlayerStateJson); - } + } FJsonSerializer::Serialize(RootJson, Writer); WritePipe(Buffer); diff --git a/Artemis/UnrealTournament2Artemis/UnrealTournament2Artemis.vcxproj b/Artemis/UnrealTournament2Artemis/UnrealTournament2Artemis.vcxproj deleted file mode 100644 index b4cd9703f..000000000 --- a/Artemis/UnrealTournament2Artemis/UnrealTournament2Artemis.vcxproj +++ /dev/null @@ -1,165 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {3541864F-1662-4BD6-8328-2C87AE61D152} - Win32Proj - UnrealTournament2Artemis - 8.1 - - - - DynamicLibrary - true - v140 - Unicode - - - DynamicLibrary - false - v140 - true - Unicode - - - DynamicLibrary - true - v140 - Unicode - - - DynamicLibrary - false - v140 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - true - - - false - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;UNREALTOURNAMENT2ARTEMIS_EXPORTS;%(PreprocessorDefinitions) - true - - - Windows - true - - - - - - - Level3 - Disabled - _DEBUG;_WINDOWS;_USRDLL;UNREALTOURNAMENT2ARTEMIS_EXPORTS;%(PreprocessorDefinitions) - true - - - Windows - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;_USRDLL;UNREALTOURNAMENT2ARTEMIS_EXPORTS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_WINDOWS;_USRDLL;UNREALTOURNAMENT2ARTEMIS_EXPORTS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Artemis/UnrealTournament2Artemis/UnrealTournament2Artemis.vcxproj.filters b/Artemis/UnrealTournament2Artemis/UnrealTournament2Artemis.vcxproj.filters deleted file mode 100644 index 256e298e2..000000000 --- a/Artemis/UnrealTournament2Artemis/UnrealTournament2Artemis.vcxproj.filters +++ /dev/null @@ -1,45 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - - \ No newline at end of file diff --git a/Artemis/UnrealTournament2Artemis/ut-plugin.zip b/Artemis/UnrealTournament2Artemis/ut-plugin.zip deleted file mode 100644 index c013a0f6f..000000000 Binary files a/Artemis/UnrealTournament2Artemis/ut-plugin.zip and /dev/null differ