diff --git a/.gitignore b/.gitignore
index 685cd97..2bdc199 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,34 +1,73 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
+*.userosscache
*.sln.docstates
-# Build results
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+# Build results
[Dd]ebug/
+[Dd]ebugPublic/
[Rr]elease/
+[Rr]eleases/
x64/
-build/
+x86/
+bld/
[Bb]in/
[Oo]bj/
+[Ll]og/
-# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
-!packages/*/build/
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
*_i.c
*_p.c
+*_i.h
*.ilk
*.meta
*.obj
+*.iobj
*.pch
*.pdb
+*.ipdb
*.pgc
*.pgd
*.rsp
@@ -43,21 +82,34 @@ build/
*.vssscc
.builds
*.pidb
-*.log
+*.svclog
*.scc
+# Chutzpah Test files
+_Chutzpah*
+
# Visual C++ cache files
ipch/
*.aps
*.ncb
+*.opendb
*.opensdf
*.sdf
*.cachefile
+*.VC.db
+*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
# Guidance Automation Toolkit
*.gpState
@@ -65,6 +117,10 @@ ipch/
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
# TeamCity is a build add-in
_TeamCity*
@@ -72,9 +128,25 @@ _TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
# NCrunch
-*.ncrunch*
+_NCrunch_*
.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
# Installshield output folder
[Ee]xpress/
@@ -93,67 +165,170 @@ DocProject/Help/html
publish/
# Publish Web Output
-*.Publish.xml
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
-# NuGet Packages Directory
-## TODO: If you have NuGet Package Restore enabled, uncomment the next line
-packages/
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
-# Windows Azure Build Output
-csx
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
*.build.csdef
-# Windows Store app package directory
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
# Others
-sql/
-*.Cache
ClientBin/
-[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
-*.[Pp]ublish.xml
+*.dbproj.schemaview
+*.jfm
*.pfx
*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
# RIA/Silverlight projects
Generated_Code/
-# Backup & report files from converting an old project file to a newer
-# Visual Studio version. Backup files are not needed, because we have git ;-)
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
# SQL Server files
-App_Data/*.mdf
-App_Data/*.ldf
+*.mdf
+*.ldf
+*.ndf
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
-#LightSwitch generated files
-GeneratedArtifacts/
-_Pvt_Extensions/
-ModelManifest.xml
+# Microsoft Fakes
+FakesAssemblies/
-# =========================
-# Windows detritus
-# =========================
+# GhostDoc plugin setting file
+*.GhostDoc.xml
-# Windows image file caches
-Thumbs.db
-ehthumbs.db
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
-# Folder config file
-Desktop.ini
+# Visual Studio 6 build log
+*.plg
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
+# Visual Studio 6 workspace options file
+*.opt
-# Mac desktop service store files
-.DS_Store
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+!DS4WinWPF/libs/x64/
+!DS4WinWPF/libs/x86/
-.vs/
-!DS4Windows/libs/x64/
diff --git a/DS4Windows.sln b/DS4Windows.sln
deleted file mode 100644
index 225754c..0000000
--- a/DS4Windows.sln
+++ /dev/null
@@ -1,31 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.28010.2046
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DS4Windows", "DS4Windows\DS4Windows.csproj", "{7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
- Release|x64 = Release|x64
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Debug|x64.ActiveCfg = Debug|x64
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Debug|x64.Build.0 = Debug|x64
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Debug|x86.ActiveCfg = Debug|x86
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Debug|x86.Build.0 = Debug|x86
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Release|x64.ActiveCfg = Release|x64
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Release|x64.Build.0 = Release|x64
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Release|x86.ActiveCfg = Release|x86
- {7B9354BF-AF82-4CCB-A83D-4BEB1E9D8C96}.Release|x86.Build.0 = Release|x86
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {113BEACD-0A7A-44CD-BC67-02082B5134EF}
- EndGlobalSection
-EndGlobal
diff --git a/DS4Windows/App.xaml b/DS4Windows/App.xaml
new file mode 100644
index 0000000..f84b267
--- /dev/null
+++ b/DS4Windows/App.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/DS4Windows/App.xaml.cs b/DS4Windows/App.xaml.cs
new file mode 100644
index 0000000..1a2f930
--- /dev/null
+++ b/DS4Windows/App.xaml.cs
@@ -0,0 +1,446 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.IO.MemoryMappedFiles;
+using System.Linq;
+using System.Net.Http;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Interop;
+using WPFLocalizeExtension.Engine;
+using NLog;
+
+namespace DS4WinWPF
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ [System.Security.SuppressUnmanagedCodeSecurity]
+ public partial class App : Application
+ {
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
+
+ [DllImport("user32.dll", EntryPoint = "FindWindow")]
+ private static extern IntPtr FindWindow(string sClass, string sWindow);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
+ private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct COPYDATASTRUCT
+ {
+ public IntPtr dwData;
+ public int cbData;
+ public IntPtr lpData;
+ }
+
+ private Thread controlThread;
+ public static DS4Windows.ControlService rootHub;
+ public static HttpClient requestClient;
+ private bool skipSave;
+ private bool runShutdown;
+ private bool exitApp;
+ private Thread testThread;
+ private bool exitComThread = false;
+ private const string SingleAppComEventName = "{a52b5b20-d9ee-4f32-8518-307fa14aa0c6}";
+ private EventWaitHandle threadComEvent = null;
+ private Timer collectTimer;
+ private static LoggerHolder logHolder;
+
+ private MemoryMappedFile ipcClassNameMMF = null; // MemoryMappedFile for inter-process communication used to hold className of DS4Form window
+ private MemoryMappedViewAccessor ipcClassNameMMA = null;
+
+ private void Application_Startup(object sender, StartupEventArgs e)
+ {
+ runShutdown = true;
+ skipSave = true;
+
+ ArgumentParser parser = new ArgumentParser();
+ parser.Parse(e.Args);
+ CheckOptions(parser);
+
+ if (exitApp)
+ {
+ return;
+ }
+
+ try
+ {
+ Process.GetCurrentProcess().PriorityClass =
+ ProcessPriorityClass.High;
+ }
+ catch { } // Ignore problems raising the priority.
+
+ // Force Normal IO Priority
+ IntPtr ioPrio = new IntPtr(2);
+ DS4Windows.Util.NtSetInformationProcess(Process.GetCurrentProcess().Handle,
+ DS4Windows.Util.PROCESS_INFORMATION_CLASS.ProcessIoPriority, ref ioPrio, 4);
+
+ // Force Normal Page Priority
+ IntPtr pagePrio = new IntPtr(5);
+ DS4Windows.Util.NtSetInformationProcess(Process.GetCurrentProcess().Handle,
+ DS4Windows.Util.PROCESS_INFORMATION_CLASS.ProcessPagePriority, ref pagePrio, 4);
+
+ try
+ {
+ // another instance is already running if OpenExisting succeeds.
+ threadComEvent = EventWaitHandle.OpenExisting(SingleAppComEventName,
+ System.Security.AccessControl.EventWaitHandleRights.Synchronize |
+ System.Security.AccessControl.EventWaitHandleRights.Modify);
+ threadComEvent.Set(); // signal the other instance.
+ threadComEvent.Close();
+ Current.Shutdown(); // Quit temp instance
+ return;
+ }
+ catch { /* don't care about errors */ }
+
+ // Create the Event handle
+ threadComEvent = new EventWaitHandle(false, EventResetMode.ManualReset, SingleAppComEventName);
+ CreateTempWorkerThread();
+
+ CreateControlService();
+
+ DS4Windows.Global.FindConfigLocation();
+ bool firstRun = DS4Windows.Global.firstRun;
+ if (firstRun)
+ {
+ DS4Forms.SaveWhere savewh = new DS4Forms.SaveWhere(false);
+ savewh.ShowDialog();
+ }
+
+ DS4Windows.Global.Load();
+ if (!CreateConfDirSkeleton())
+ {
+ MessageBox.Show($"Cannot create config folder structure in {DS4Windows.Global.appdatapath}. Exiting",
+ "DS4Windows", MessageBoxButton.OK, MessageBoxImage.Error);
+ Current.Shutdown(1);
+ }
+
+ logHolder = new LoggerHolder(rootHub);
+ DispatcherUnhandledException += App_DispatcherUnhandledException;
+ Logger logger = logHolder.Logger;
+ string version = DS4Windows.Global.exeversion;
+ logger.Info($"DS4Windows version {version}");
+ //logger.Info("DS4Windows version 2.0");
+ logger.Info("Logger created");
+
+ //DS4Windows.Global.ProfilePath[0] = "mixed";
+ //DS4Windows.Global.LoadProfile(0, false, rootHub, false, false);
+ if (firstRun)
+ {
+ logger.Info("No config found. Creating default config");
+ //Directory.CreateDirectory(DS4Windows.Global.appdatapath);
+ AttemptSave();
+
+ //Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Profiles\");
+ //Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Macros\");
+ DS4Windows.Global.SaveProfile(0, "Default");
+ DS4Windows.Global.ProfilePath[0] = DS4Windows.Global.OlderProfilePath[0] = "Default";
+ /*DS4Windows.Global.ProfilePath[1] = DS4Windows.Global.OlderProfilePath[1] = "Default";
+ DS4Windows.Global.ProfilePath[2] = DS4Windows.Global.OlderProfilePath[2] = "Default";
+ DS4Windows.Global.ProfilePath[3] = DS4Windows.Global.OlderProfilePath[3] = "Default";
+ */
+ logger.Info("Default config created");
+ }
+
+ skipSave = false;
+
+ if (!DS4Windows.Global.LoadActions())
+ {
+ DS4Windows.Global.CreateStdActions();
+ }
+
+ SetUICulture(DS4Windows.Global.UseLang);
+ DS4Forms.MainWindow window = new DS4Forms.MainWindow(parser);
+ MainWindow = window;
+ window.Show();
+ window.CheckMinStatus();
+ HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
+ CreateIPCClassNameMMF(source.Handle);
+ }
+
+ private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
+ {
+ //Console.WriteLine("App Crashed");
+ //Console.WriteLine(e.Exception.StackTrace);
+ Logger logger = logHolder.Logger;
+ logger.Error($"App Crashed with message {e.Exception.Message}");
+ logger.Error(e.Exception.ToString());
+ LogManager.Flush();
+ LogManager.Shutdown();
+ }
+
+ private bool CreateConfDirSkeleton()
+ {
+ bool result = true;
+ try
+ {
+ Directory.CreateDirectory(DS4Windows.Global.appdatapath);
+ Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Profiles\");
+ Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Logs\");
+ //Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Macros\");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ result = false;
+ }
+
+
+ return result;
+ }
+
+ private void AttemptSave()
+ {
+ if (!DS4Windows.Global.Save()) //if can't write to file
+ {
+ if (MessageBox.Show("Cannot write at current location\nCopy Settings to appdata?", "DS4Windows",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
+ {
+ try
+ {
+ Directory.CreateDirectory(DS4Windows.Global.appDataPpath);
+ File.Copy(DS4Windows.Global.exedirpath + "\\Profiles.xml",
+ DS4Windows.Global.appDataPpath + "\\Profiles.xml");
+ File.Copy(DS4Windows.Global.exedirpath + "\\Auto Profiles.xml",
+ DS4Windows.Global.appDataPpath + "\\Auto Profiles.xml");
+ Directory.CreateDirectory(DS4Windows.Global.appDataPpath + "\\Profiles");
+ foreach (string s in Directory.GetFiles(DS4Windows.Global.exedirpath + "\\Profiles"))
+ {
+ File.Copy(s, DS4Windows.Global.appDataPpath + "\\Profiles\\" + Path.GetFileName(s));
+ }
+ }
+ catch { }
+ MessageBox.Show("Copy complete, please relaunch DS4Windows and remove settings from Program Directory",
+ "DS4Windows");
+ }
+ else
+ {
+ MessageBox.Show("DS4Windows cannot edit settings here, This will now close",
+ "DS4Windows");
+ }
+
+ DS4Windows.Global.appdatapath = null;
+ skipSave = true;
+ Current.Shutdown();
+ return;
+ }
+ }
+
+ private void CheckOptions(ArgumentParser parser)
+ {
+ if (parser.HasErrors)
+ {
+ runShutdown = false;
+ exitApp = true;
+ Current.Shutdown(1);
+ }
+ else if (parser.Driverinstall)
+ {
+ DS4Forms.WelcomeDialog dialog = new DS4Forms.WelcomeDialog(true);
+ dialog.ShowDialog();
+ runShutdown = false;
+ exitApp = true;
+ Current.Shutdown();
+ }
+ else if (parser.ReenableDevice)
+ {
+ DS4Windows.DS4Devices.reEnableDevice(parser.DeviceInstanceId);
+ runShutdown = false;
+ exitApp = true;
+ Current.Shutdown();
+ }
+ else if (parser.Runtask)
+ {
+ StartupMethods.LaunchOldTask();
+ runShutdown = false;
+ exitApp = true;
+ Current.Shutdown();
+ }
+ else if (parser.Command)
+ {
+ IntPtr hWndDS4WindowsForm = IntPtr.Zero;
+ hWndDS4WindowsForm = FindWindow(ReadIPCClassNameMMF(), "DS4Windows");
+ if (hWndDS4WindowsForm != IntPtr.Zero)
+ {
+ COPYDATASTRUCT cds;
+ cds.lpData = IntPtr.Zero;
+
+ try
+ {
+ cds.dwData = IntPtr.Zero;
+ cds.cbData = parser.CommandArgs.Length;
+ cds.lpData = Marshal.StringToHGlobalAnsi(parser.CommandArgs);
+ SendMessage(hWndDS4WindowsForm, DS4Forms.MainWindow.WM_COPYDATA, IntPtr.Zero, ref cds);
+ }
+ finally
+ {
+ if (cds.lpData != IntPtr.Zero)
+ Marshal.FreeHGlobal(cds.lpData);
+ }
+ }
+
+ runShutdown = false;
+ exitApp = true;
+ Current.Shutdown();
+ }
+ }
+
+ private void CreateControlService()
+ {
+ controlThread = new Thread(() => {
+ rootHub = new DS4Windows.ControlService();
+ DS4Windows.Program.rootHub = rootHub;
+ requestClient = new HttpClient();
+ collectTimer = new Timer(GarbageTask, null, 30000, 30000);
+
+ });
+ controlThread.Priority = ThreadPriority.Normal;
+ controlThread.IsBackground = true;
+ controlThread.Start();
+ while (controlThread.IsAlive)
+ Thread.SpinWait(500);
+ }
+
+ private void GarbageTask(object state)
+ {
+ GC.Collect(0, GCCollectionMode.Forced, false);
+ }
+
+ private void CreateTempWorkerThread()
+ {
+ testThread = new Thread(SingleAppComThread_DoWork);
+ testThread.Priority = ThreadPriority.Lowest;
+ testThread.IsBackground = true;
+ testThread.Start();
+ }
+
+ private void SingleAppComThread_DoWork()
+ {
+ while (!exitComThread)
+ {
+ // check for a signal.
+ if (threadComEvent.WaitOne())
+ {
+ threadComEvent.Reset();
+ // The user tried to start another instance. We can't allow that,
+ // so bring the other instance back into view and enable that one.
+ // That form is created in another thread, so we need some thread sync magic.
+ if (!exitComThread)
+ {
+ Dispatcher.BeginInvoke((Action)(() =>
+ {
+ MainWindow.Show();
+ MainWindow.WindowState = WindowState.Normal;
+ }));
+ }
+ }
+ }
+ }
+
+ public void CreateIPCClassNameMMF(IntPtr hWnd)
+ {
+ if (ipcClassNameMMA != null) return; // Already holding a handle to MMF file. No need to re-write the data
+
+ try
+ {
+ StringBuilder wndClassNameStr = new StringBuilder(128);
+ if (GetClassName(hWnd, wndClassNameStr, wndClassNameStr.Capacity) != 0 && wndClassNameStr.Length > 0)
+ {
+ byte[] buffer = ASCIIEncoding.ASCII.GetBytes(wndClassNameStr.ToString());
+
+ ipcClassNameMMF = MemoryMappedFile.CreateNew("DS4Windows_IPCClassName.dat", 128);
+ ipcClassNameMMA = ipcClassNameMMF.CreateViewAccessor(0, buffer.Length);
+ ipcClassNameMMA.WriteArray(0, buffer, 0, buffer.Length);
+ // The MMF file is alive as long this process holds the file handle open
+ }
+ }
+ catch (Exception)
+ {
+ /* Eat all exceptions because errors here are not fatal for DS4Win */
+ }
+ }
+
+ private string ReadIPCClassNameMMF()
+ {
+ MemoryMappedFile mmf = null;
+ MemoryMappedViewAccessor mma = null;
+
+ try
+ {
+ byte[] buffer = new byte[128];
+ mmf = MemoryMappedFile.OpenExisting("DS4Windows_IPCClassName.dat");
+ mma = mmf.CreateViewAccessor(0, 128);
+ mma.ReadArray(0, buffer, 0, buffer.Length);
+ return ASCIIEncoding.ASCII.GetString(buffer);
+ }
+ catch (Exception)
+ {
+ // Eat all exceptions
+ }
+ finally
+ {
+ if (mma != null) mma.Dispose();
+ if (mmf != null) mmf.Dispose();
+ }
+
+ return null;
+ }
+
+ private void SetUICulture(string culture)
+ {
+ try
+ {
+ //CultureInfo ci = new CultureInfo("ja");
+ CultureInfo ci = CultureInfo.GetCultureInfo(culture);
+ LocalizeDictionary.Instance.SetCurrentThreadCulture = true;
+ LocalizeDictionary.Instance.Culture = ci;
+ // fixes the culture in threads
+ CultureInfo.DefaultThreadCurrentCulture = ci;
+ CultureInfo.DefaultThreadCurrentUICulture = ci;
+ //DS4WinWPF.Properties.Resources.Culture = ci;
+ Thread.CurrentThread.CurrentCulture = ci;
+ Thread.CurrentThread.CurrentUICulture = ci;
+ }
+ catch (CultureNotFoundException) { /* Skip setting culture that we cannot set */ }
+ }
+
+ private void Application_Exit(object sender, ExitEventArgs e)
+ {
+ if (runShutdown)
+ {
+ if (rootHub != null)
+ {
+ Task.Run(() =>
+ {
+ rootHub.Stop();
+ }).Wait();
+ }
+
+ if (!skipSave)
+ {
+ DS4Windows.Global.Save();
+ }
+
+ exitComThread = true;
+ if (threadComEvent != null)
+ {
+ threadComEvent.Set(); // signal the other instance.
+ while (testThread.IsAlive)
+ Thread.SpinWait(500);
+ threadComEvent.Close();
+ }
+
+ if (ipcClassNameMMA != null) ipcClassNameMMA.Dispose();
+ if (ipcClassNameMMF != null) ipcClassNameMMF.Dispose();
+ }
+ }
+ }
+}
diff --git a/DS4Windows/ArgumentParser.cs b/DS4Windows/ArgumentParser.cs
new file mode 100644
index 0000000..37a7693
--- /dev/null
+++ b/DS4Windows/ArgumentParser.cs
@@ -0,0 +1,97 @@
+
+using System.Collections.Generic;
+
+namespace DS4WinWPF
+{
+ public class ArgumentParser
+ {
+ private bool mini;
+ private bool stop;
+ private bool driverinstall;
+ private bool reenableDevice;
+ private string deviceInstanceId;
+ private bool runtask;
+ private bool command;
+ private string commandArgs;
+
+ private Dictionary errors =
+ new Dictionary();
+
+ public bool Mini { get => mini; }
+ public bool Stop { get => stop; }
+ public bool Driverinstall { get => driverinstall; }
+ public bool ReenableDevice { get => reenableDevice; }
+ public bool Runtask { get => runtask; }
+ public bool Command { get => command; }
+ public string DeviceInstanceId { get => deviceInstanceId; }
+ public string CommandArgs { get => commandArgs; }
+ public Dictionary Errors { get => errors; }
+
+ public bool HasErrors => errors.Count > 0;
+
+ public void Parse(string[] args)
+ {
+ errors.Clear();
+ //foreach (string arg in args)
+ for (int i = 0; i < args.Length; i++)
+ {
+ string arg = args[i];
+ switch(arg)
+ {
+ case "driverinstall":
+ case "-driverinstall":
+ driverinstall = true;
+ break;
+
+ case "re-enabledevice":
+ case "-re-enabledevice":
+ reenableDevice = true;
+ if (i + 1 < args.Length)
+ {
+ deviceInstanceId = args[++i];
+ }
+
+ break;
+
+ case "runtask":
+ case "-runtask":
+ runtask = true;
+ break;
+
+ case "-stop":
+ stop = true;
+ break;
+
+ case "-m":
+ mini = true;
+ break;
+
+ case "command":
+ case "-command":
+ command = true;
+ if (i + 1 < args.Length)
+ {
+ i++;
+ string temp = args[i];
+ if (temp.Length > 0 && temp.Length <= 256)
+ {
+ commandArgs = temp;
+ }
+ else
+ {
+ command = false;
+ errors["Command"] = "Command length is invalid";
+ }
+ }
+ else
+ {
+ errors["Command"] = "Command string not given";
+ }
+ break;
+
+ default: break;
+ }
+ }
+ }
+ }
+}
diff --git a/DS4Windows/AutoProfileChecker.cs b/DS4Windows/AutoProfileChecker.cs
new file mode 100644
index 0000000..b03130b
--- /dev/null
+++ b/DS4Windows/AutoProfileChecker.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+using System.Threading.Tasks;
+using DS4Windows;
+
+namespace DS4WinWPF
+{
+ [SuppressUnmanagedCodeSecurity]
+ public class AutoProfileChecker
+ {
+ private AutoProfileHolder profileHolder;
+ private IntPtr prevForegroundWnd = IntPtr.Zero;
+ private uint prevForegroundProcessID;
+ private string prevForegroundProcessName = string.Empty;
+ private string prevForegroundWndTitleName = string.Empty;
+ private StringBuilder autoProfileCheckTextBuilder = new StringBuilder(1000);
+ private int autoProfileDebugLogLevel = 0;
+ private bool turnOffTemp;
+ private AutoProfileEntity tempAutoProfile;
+ private bool running;
+
+ public int AutoProfileDebugLogLevel { get => autoProfileDebugLogLevel; set => autoProfileDebugLogLevel = value; }
+ public bool Running { get => running; set => running = value; }
+
+ public delegate void ChangeServiceHandler(AutoProfileChecker sender, bool state);
+ public event ChangeServiceHandler RequestServiceChange;
+
+ public AutoProfileChecker(AutoProfileHolder holder)
+ {
+ profileHolder = holder;
+ }
+
+ public void Process()
+ {
+ string topProcessName, topWindowTitle;
+ bool turnOffDS4WinApp = false;
+ AutoProfileEntity matchedProfileEntity = null;
+
+ if (GetTopWindowName(out topProcessName, out topWindowTitle))
+ {
+ // Find a profile match based on autoprofile program path and wnd title list.
+ // The same program may set different profiles for each of the controllers, so we need an array of newProfileName[controllerIdx] values.
+ for (int i = 0, pathsLen = profileHolder.AutoProfileColl.Count; i < pathsLen; i++)
+ {
+ AutoProfileEntity tempEntity = profileHolder.AutoProfileColl[i];
+ if (tempEntity.IsMatch(topProcessName, topWindowTitle))
+ {
+ if (autoProfileDebugLogLevel > 0)
+ DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. Rule#{i + 1} Path={tempEntity.path} Title={tempEntity.title}", false);
+
+ // Matching autoprofile rule found
+ turnOffDS4WinApp = tempEntity.Turnoff;
+ matchedProfileEntity = tempEntity;
+ break;
+ }
+ }
+
+ if (matchedProfileEntity != null)
+ {
+ // Program match found. Check if the new profile is different than current profile of the controller. Load the new profile only if it is not already loaded.
+ for (int j = 0; j < 4; j++)
+ {
+ string tempname = matchedProfileEntity.ProfileNames[j];
+ if (tempname != string.Empty && tempname != "(none)")
+ {
+ if ((Global.useTempProfile[j] && tempname != Global.tempprofilename[j]) ||
+ (!Global.useTempProfile[j] && tempname != Global.ProfilePath[j]))
+ {
+ if (autoProfileDebugLogLevel > 0)
+ DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. LoadProfile Controller {j + 1}={tempname}", false);
+
+ Global.LoadTempProfile(j, tempname, true, Program.rootHub); // j is controller index, i is filename
+ //if (LaunchProgram[j] != string.Empty) Process.Start(LaunchProgram[j]);
+ }
+ else
+ {
+ if (autoProfileDebugLogLevel > 0)
+ DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. LoadProfile Controller {j + 1}={tempname} (already loaded)", false);
+ }
+ }
+ }
+
+ if (turnOffDS4WinApp)
+ {
+ turnOffTemp = true;
+ RequestServiceChange?.Invoke(this, false);
+ }
+
+ tempAutoProfile = matchedProfileEntity;
+ }
+ else if (tempAutoProfile != null)
+ {
+ tempAutoProfile = null;
+ for (int j = 0; j < 4; j++)
+ {
+ if (Global.useTempProfile[j])
+ {
+ if (autoProfileDebugLogLevel > 0)
+ DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. RestoreProfile Controller {j + 1}={Global.ProfilePath[j]} (default)", false);
+
+ Global.LoadProfile(j, false, Program.rootHub);
+ }
+ }
+
+ if (turnOffTemp)
+ {
+ turnOffTemp = false;
+ RequestServiceChange?.Invoke(this, true);
+ }
+ }
+ }
+ }
+
+ private bool GetTopWindowName(out string topProcessName, out string topWndTitleName)
+ {
+ IntPtr hWnd = GetForegroundWindow();
+ if (hWnd == IntPtr.Zero)
+ {
+ // Top window unknown or cannot acquire a handle. Return FALSE and return unknown process and wndTitle values
+ prevForegroundWnd = IntPtr.Zero;
+ prevForegroundProcessID = 0;
+ topProcessName = topWndTitleName = String.Empty;
+ return false;
+ }
+
+ //
+ // If this function was called from "auto-profile watcher timer" then check cached "previous hWnd handle". If the current hWnd is the same
+ // as during the previous check then return cached previous wnd and name values (ie. foreground app and window are assumed to be the same, so no need to re-query names).
+ // This should optimize the auto-profile timer check process and causes less burden to .NET GC collector because StringBuffer is not re-allocated every second.
+ //
+ // Note! hWnd handles may be re-cycled but not during the lifetime of the window. This "cache" optimization still works because when an old window is closed
+ // then foreground window changes to something else and the cached prevForgroundWnd variable is updated to store the new hWnd handle.
+ // It doesn't matter even when the previously cached handle is recycled by WinOS to represent some other window (it is no longer used as a cached value anyway).
+ //
+ if (hWnd == prevForegroundWnd)
+ {
+ // The active window is still the same. Return cached process and wndTitle values and FALSE to indicate caller that no changes since the last call of this method
+ topProcessName = prevForegroundProcessName;
+ topWndTitleName = prevForegroundWndTitleName;
+ return false;
+ }
+
+ prevForegroundWnd = hWnd;
+
+ IntPtr hProcess = IntPtr.Zero;
+ uint lpdwProcessId = 0;
+ GetWindowThreadProcessId(hWnd, out lpdwProcessId);
+
+ if (lpdwProcessId == prevForegroundProcessID)
+ {
+ topProcessName = prevForegroundProcessName;
+ }
+ else
+ {
+ prevForegroundProcessID = lpdwProcessId;
+
+ hProcess = OpenProcess(0x0410, false, lpdwProcessId);
+ if (hProcess != IntPtr.Zero) GetModuleFileNameEx(hProcess, IntPtr.Zero, autoProfileCheckTextBuilder, autoProfileCheckTextBuilder.Capacity);
+ else autoProfileCheckTextBuilder.Clear();
+
+ prevForegroundProcessName = topProcessName = autoProfileCheckTextBuilder.Replace('/', '\\').ToString().ToLower();
+ }
+
+ GetWindowText(hWnd, autoProfileCheckTextBuilder, autoProfileCheckTextBuilder.Capacity);
+ prevForegroundWndTitleName = topWndTitleName = autoProfileCheckTextBuilder.ToString().ToLower();
+
+
+ if (hProcess != IntPtr.Zero) CloseHandle(hProcess);
+
+ if (autoProfileDebugLogLevel > 0)
+ DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. PID={lpdwProcessId} Path={topProcessName} | WND={hWnd} Title={topWndTitleName}", false);
+
+ return true;
+ }
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetForegroundWindow();
+
+ [DllImport("user32.dll")]
+ private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
+
+ [DllImport("kernel32.dll")]
+ private static extern bool CloseHandle(IntPtr handle);
+
+ [DllImport("psapi.dll")]
+ private static extern uint GetModuleFileNameEx(IntPtr hWnd, IntPtr hModule, StringBuilder lpFileName, int nSize);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nSize);
+ }
+}
diff --git a/DS4Windows/AutoProfileHolder.cs b/DS4Windows/AutoProfileHolder.cs
new file mode 100644
index 0000000..9152e2a
--- /dev/null
+++ b/DS4Windows/AutoProfileHolder.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+using System.Xml;
+
+namespace DS4WinWPF
+{
+ public class AutoProfileHolder
+ {
+ private object _colLockobj = new object();
+ private ObservableCollection autoProfileColl;
+ public ObservableCollection AutoProfileColl { get => autoProfileColl; }
+ //public Dictionary AutoProfileDict { get => autoProfileDict; }
+
+ //private Dictionary autoProfileDict;
+
+ public AutoProfileHolder()
+ {
+ autoProfileColl = new ObservableCollection();
+ //autoProfileDict = new Dictionary();
+ Load();
+
+ BindingOperations.EnableCollectionSynchronization(autoProfileColl, _colLockobj);
+ }
+
+ private void Load()
+ {
+ try
+ {
+ XmlDocument doc = new XmlDocument();
+
+ if (!File.Exists(DS4Windows.Global.appdatapath + "\\Auto Profiles.xml"))
+ return;
+
+ doc.Load(DS4Windows.Global.appdatapath + "\\Auto Profiles.xml");
+ XmlNodeList programslist = doc.SelectNodes("Programs/Program");
+ foreach (XmlNode x in programslist)
+ {
+ string path = x.Attributes["path"]?.Value ?? string.Empty;
+ string title = x.Attributes["title"]?.Value ?? string.Empty;
+ AutoProfileEntity autoprof = new AutoProfileEntity(path, title);
+
+ XmlNode item;
+ for (int i = 0; i < 4; i++)
+ {
+ item = x.SelectSingleNode($"Controller{i+1}");
+ if (item != null)
+ {
+ autoprof.ProfileNames[i] = item.InnerText;
+ }
+ }
+
+ item = x.SelectSingleNode($"TurnOff");
+ if (item != null && bool.TryParse(item.InnerText, out bool turnoff))
+ {
+ autoprof.Turnoff = turnoff;
+ }
+
+ autoProfileColl.Add(autoprof);
+ //autoProfileDict.Add(path, autoprof);
+ }
+ }
+ catch (Exception) { }
+ }
+
+ public bool Save(string m_Profile)
+ {
+ XmlDocument doc = new XmlDocument();
+ XmlNode Node;
+ bool saved = true;
+ try
+ {
+ Node = doc.CreateXmlDeclaration("1.0", "utf-8", string.Empty);
+ doc.AppendChild(Node);
+
+ Node = doc.CreateComment(string.Format(" Auto-Profile Configuration Data. {0} ", DateTime.Now));
+ doc.AppendChild(Node);
+
+ Node = doc.CreateWhitespace("\r\n");
+ doc.AppendChild(Node);
+
+ Node = doc.CreateNode(XmlNodeType.Element, "Programs", "");
+ doc.AppendChild(Node);
+ foreach (AutoProfileEntity entity in autoProfileColl)
+ {
+ XmlElement el = doc.CreateElement("Program");
+ el.SetAttribute("path", entity.Path);
+ if (!string.IsNullOrEmpty(entity.Title))
+ {
+ el.SetAttribute("title", entity.Title);
+ }
+
+ el.AppendChild(doc.CreateElement("Controller1")).InnerText = entity.ProfileNames[0];
+ el.AppendChild(doc.CreateElement("Controller2")).InnerText = entity.ProfileNames[1];
+ el.AppendChild(doc.CreateElement("Controller3")).InnerText = entity.ProfileNames[2];
+ el.AppendChild(doc.CreateElement("Controller4")).InnerText = entity.ProfileNames[3];
+ el.AppendChild(doc.CreateElement("TurnOff")).InnerText = entity.Turnoff.ToString();
+
+ Node.AppendChild(el);
+ }
+
+ doc.Save(m_Profile);
+ }
+ catch (Exception) { saved = false; }
+ return saved;
+ }
+
+ public void Remove(AutoProfileEntity item)
+ {
+ //autoProfileDict.Remove(item.Path);
+ autoProfileColl.Remove(item);
+ }
+ }
+
+ public class AutoProfileEntity
+ {
+ public string path = string.Empty;
+ public string title = string.Empty;
+ private string path_lowercase;
+ private string title_lowercase;
+ private bool turnoff;
+ private string[] profileNames = new string[4] { string.Empty, string.Empty,
+ string.Empty, string.Empty };
+ public const string NONE_STRING = "(none)";
+
+ public string Path { get => path; set => SetSearchPath(value); }
+ public string Title { get => title; set => SetSearchTitle(value); }
+ public bool Turnoff { get => turnoff; set => turnoff = value; }
+ public string[] ProfileNames { get => profileNames; set => profileNames = value; }
+
+ public AutoProfileEntity(string pathStr, string titleStr)
+ {
+ // Initialize autoprofile search keywords(xxx_tolower).To improve performance the search keyword is pre - calculated in xxx_tolower variables,
+ // so autoprofile timer thread doesn't have to create substrings/replace/tolower string instances every second over and over again.
+ SetSearchPath(pathStr);
+ SetSearchTitle(titleStr);
+ }
+
+ public bool IsMatch(string searchPath, string searchTitle)
+ {
+ bool bPathMatched = true;
+ bool bTitleMwatched = true;
+
+ if (!string.IsNullOrEmpty(path_lowercase))
+ {
+ bPathMatched = (path_lowercase == searchPath
+ || (path[0] == '^' && searchPath.StartsWith(path_lowercase))
+ || (path[path.Length - 1] == '$' && searchPath.EndsWith(path_lowercase))
+ || (path[0] == '*' && searchPath.Contains(path_lowercase))
+ );
+ }
+
+ if (bPathMatched && !string.IsNullOrEmpty(title_lowercase))
+ {
+ bTitleMwatched = (title_lowercase == searchTitle
+ || (title[0] == '^' && searchTitle.StartsWith(title_lowercase))
+ || (title[title.Length - 1] == '$' && searchTitle.EndsWith(title_lowercase))
+ || (title[0] == '*' && searchTitle.Contains(title_lowercase))
+ );
+ }
+
+ // If both path and title defined in autoprofile entry then do AND condition (ie. both path and title should match)
+ return bPathMatched && bTitleMwatched;
+ }
+
+ private void SetSearchPath(string pathStr)
+ {
+ if (!string.IsNullOrEmpty(pathStr))
+ {
+ path = pathStr;
+ path_lowercase = path.ToLower().Replace('/', '\\');
+
+ if (path.Length >= 2)
+ {
+ if (path[0] == '^') path_lowercase = path_lowercase.Substring(1);
+ else if (path[path.Length - 1] == '$') path_lowercase = path_lowercase.Substring(0, path_lowercase.Length - 1);
+ else if (path[0] == '*') path_lowercase = path_lowercase.Substring(1);
+ }
+ }
+ else path = path_lowercase = string.Empty;
+ }
+
+ private void SetSearchTitle(string titleStr)
+ {
+ if (!string.IsNullOrEmpty(titleStr))
+ {
+ title = titleStr;
+ title_lowercase = title.ToLower();
+
+ if (title.Length >= 2)
+ {
+ if (title[0] == '^') title_lowercase = title_lowercase.Substring(1);
+ else if (title[title.Length - 1] == '$') title_lowercase = title_lowercase.Substring(0, title_lowercase.Length - 1);
+ else if (title[0] == '*') title_lowercase = title_lowercase.Substring(1);
+ }
+ }
+ else title = title_lowercase = string.Empty;
+ }
+ }
+}
diff --git a/DS4Windows/DS4Control/ControlService.cs b/DS4Windows/DS4Control/ControlService.cs
index d298483..ac6eedf 100644
--- a/DS4Windows/DS4Control/ControlService.cs
+++ b/DS4Windows/DS4Control/ControlService.cs
@@ -21,12 +21,13 @@ namespace DS4Windows
public const int DS4_CONTROLLER_COUNT = 4;
public DS4Device[] DS4Controllers = new DS4Device[DS4_CONTROLLER_COUNT];
public Mouse[] touchPad = new Mouse[DS4_CONTROLLER_COUNT];
- private bool running = false;
+ public bool running = false;
private DS4State[] MappedState = new DS4State[DS4_CONTROLLER_COUNT];
private DS4State[] CurrentState = new DS4State[DS4_CONTROLLER_COUNT];
private DS4State[] PreviousState = new DS4State[DS4_CONTROLLER_COUNT];
private DS4State[] TempState = new DS4State[DS4_CONTROLLER_COUNT];
public DS4StateExposed[] ExposedState = new DS4StateExposed[DS4_CONTROLLER_COUNT];
+ public ControllerSlotManager slotManager = new ControllerSlotManager();
public bool recordingMacro = false;
public event EventHandler Debug = null;
bool[] buttonsdown = new bool[4] { false, false, false, false };
@@ -52,6 +53,14 @@ namespace DS4Windows
private UdpServer _udpServer;
private OutputSlotManager outputslotMan;
+ public event EventHandler ServiceStarted;
+ public event EventHandler PreServiceStop;
+ public event EventHandler ServiceStopped;
+ public event EventHandler RunningChanged;
+ //public event EventHandler HotplugFinished;
+ public delegate void HotplugControllerHandler(ControlService sender, DS4Device device, int index);
+ public event HotplugControllerHandler HotplugController;
+
private class X360Data
{
public byte[] Report = new byte[28];
@@ -143,7 +152,7 @@ namespace DS4Windows
{
Crc32Algorithm.InitializeTable(DS4Device.DefaultPolynomial);
- //sp.Stream = Properties.Resources.EE;
+ //sp.Stream = DS4WinWPF.Properties.Resources.EE;
// Cause thread affinity to not be tied to main GUI thread
tempThread = new Thread(() => {
//_udpServer = new UdpServer(GetPadDetailForIdx);
@@ -176,10 +185,10 @@ namespace DS4Windows
if (Global.IsHidGuardianInstalled())
{
ProcessStartInfo startInfo =
- new ProcessStartInfo(Global.exepath + "\\HidGuardHelper.exe");
+ new ProcessStartInfo(Global.exedirpath + "\\HidGuardHelper.exe");
startInfo.Verb = "runas";
startInfo.Arguments = Process.GetCurrentProcess().Id.ToString();
- startInfo.WorkingDirectory = Global.exepath;
+ startInfo.WorkingDirectory = Global.exedirpath;
try
{ Process tempProc = Process.Start(startInfo); tempProc.Dispose(); }
catch { }
@@ -327,8 +336,8 @@ namespace DS4Windows
{
if (DS4Devices.isExclusiveMode && !device.isExclusive())
{
- string message = Properties.Resources.CouldNotOpenDS4.Replace("*Mac address*", device.getMacAddress()) + " " +
- Properties.Resources.QuitOtherPrograms;
+ string message = DS4WinWPF.Properties.Resources.CouldNotOpenDS4.Replace("*Mac address*", device.getMacAddress()) + " " +
+ DS4WinWPF.Properties.Resources.QuitOtherPrograms;
LogDebug(message, true);
AppLogger.LogToTray(message, true);
}
@@ -383,6 +392,8 @@ namespace DS4Windows
};
outputslotMan.DeferredPlugin(tempXbox, index, outputDevices);
+ //tempXbox.Connect();
+ //LogDebug("X360 Controller #" + (index + 1) + " connected");
}
else if (contType == OutContType.DS4)
{
@@ -399,6 +410,8 @@ namespace DS4Windows
};
outputslotMan.DeferredPlugin(tempDS4, index, outputDevices);
+ //tempDS4.Connect();
+ //LogDebug("DS4 Controller #" + (index + 1) + " connected");
}
}
@@ -416,28 +429,29 @@ namespace DS4Windows
outputDevices[index] = null;
activeOutDevType[index] = OutContType.None;
outputslotMan.DeferredRemoval(dev, index, outputDevices, immediate);
+ //dev.Disconnect();
+ //LogDebug(tempType + " Controller # " + (index + 1) + " unplugged");
useDInputOnly[index] = true;
}
}
- private SynchronizationContext uiContext = null;
- public bool Start(object tempui, bool showlog = true)
+ public bool Start(bool showlog = true)
{
startViGEm();
if (vigemTestClient != null)
//if (x360Bus.Open() && x360Bus.Start())
{
if (showlog)
- LogDebug(Properties.Resources.Starting);
+ LogDebug(DS4WinWPF.Properties.Resources.Starting);
LogDebug($"Connection to ViGEmBus {Global.vigembusVersion} established");
DS4Devices.isExclusiveMode = getUseExclusiveMode();
- uiContext = tempui as SynchronizationContext;
+ //uiContext = tempui as SynchronizationContext;
if (showlog)
{
- LogDebug(Properties.Resources.SearchingController);
- LogDebug(DS4Devices.isExclusiveMode ? Properties.Resources.UsingExclusive : Properties.Resources.UsingShared);
+ LogDebug(DS4WinWPF.Properties.Resources.SearchingController);
+ LogDebug(DS4Devices.isExclusiveMode ? DS4WinWPF.Properties.Resources.UsingExclusive : DS4WinWPF.Properties.Resources.UsingShared);
}
if (isUsingUDPServer() && _udpServer == null)
@@ -464,13 +478,14 @@ namespace DS4Windows
DS4Device device = devEnum.Current;
//DS4Device device = devices.ElementAt(i);
if (showlog)
- LogDebug(Properties.Resources.FoundController + " " + device.getMacAddress() + " (" + device.getConnectionType() + ") (" +
+ LogDebug(DS4WinWPF.Properties.Resources.FoundController + " " + device.getMacAddress() + " (" + device.getConnectionType() + ") (" +
device.DisplayName + ")");
Task task = new Task(() => { Thread.Sleep(5); WarnExclusiveModeFailure(device); });
task.Start();
DS4Controllers[i] = device;
+ slotManager.AddController(device, i);
device.Removal += this.On_DS4Removal;
device.Removal += DS4Devices.On_Removal;
device.SyncChange += this.On_SyncChange;
@@ -497,7 +512,9 @@ namespace DS4Windows
if (!getDInputOnly(i) && device.isSynced())
{
+ //useDInputOnly[i] = false;
PluginOutDev(i, device);
+
}
else
{
@@ -533,13 +550,13 @@ namespace DS4Windows
{
if (File.Exists(appdatapath + "\\Profiles\\" + ProfilePath[i] + ".xml"))
{
- string prolog = Properties.Resources.UsingProfile.Replace("*number*", (i + 1).ToString()).Replace("*Profile name*", ProfilePath[i]);
+ string prolog = DS4WinWPF.Properties.Resources.UsingProfile.Replace("*number*", (i + 1).ToString()).Replace("*Profile name*", ProfilePath[i]);
LogDebug(prolog);
AppLogger.LogToTray(prolog);
}
else
{
- string prolog = Properties.Resources.NotUsingProfile.Replace("*number*", (i + 1).ToString());
+ string prolog = DS4WinWPF.Properties.Resources.NotUsingProfile.Replace("*number*", (i + 1).ToString());
LogDebug(prolog);
AppLogger.LogToTray(prolog);
}
@@ -594,6 +611,8 @@ namespace DS4Windows
}
runHotPlug = true;
+ ServiceStarted?.Invoke(this, EventArgs.Empty);
+ RunningChanged?.Invoke(this, EventArgs.Empty);
return true;
}
@@ -603,9 +622,10 @@ namespace DS4Windows
{
running = false;
runHotPlug = false;
+ PreServiceStop?.Invoke(this, EventArgs.Empty);
if (showlog)
- LogDebug(Properties.Resources.StoppingX360);
+ LogDebug(DS4WinWPF.Properties.Resources.StoppingX360);
LogDebug("Closing connection to ViGEmBus");
@@ -661,16 +681,17 @@ namespace DS4Windows
}
if (showlog)
- LogDebug(Properties.Resources.StoppingDS4);
+ LogDebug(DS4WinWPF.Properties.Resources.StoppingDS4);
DS4Devices.stopControllers();
+ slotManager.ClearControllerList();
if (_udpServer != null)
ChangeUDPStatus(false);
//_udpServer.Stop();
if (showlog)
- LogDebug(Properties.Resources.StoppedDS4Windows);
+ LogDebug(DS4WinWPF.Properties.Resources.StoppedDS4Windows);
while (outputslotMan.RunningQueue)
{
@@ -681,6 +702,8 @@ namespace DS4Windows
}
runHotPlug = false;
+ ServiceStopped?.Invoke(this, EventArgs.Empty);
+ RunningChanged?.Invoke(this, EventArgs.Empty);
return true;
}
@@ -719,12 +742,13 @@ namespace DS4Windows
{
if (DS4Controllers[Index] == null)
{
- //LogDebug(Properties.Resources.FoundController + device.getMacAddress() + " (" + device.getConnectionType() + ")");
- LogDebug(Properties.Resources.FoundController + " " + device.getMacAddress() + " (" + device.getConnectionType() + ") (" +
+ //LogDebug(DS4WinWPF.Properties.Resources.FoundController + device.getMacAddress() + " (" + device.getConnectionType() + ")");
+ LogDebug(DS4WinWPF.Properties.Resources.FoundController + " " + device.getMacAddress() + " (" + device.getConnectionType() + ") (" +
device.DisplayName + ")");
Task task = new Task(() => { Thread.Sleep(5); WarnExclusiveModeFailure(device); });
task.Start();
DS4Controllers[Index] = device;
+ slotManager.AddController(device, Index);
device.Removal += this.On_DS4Removal;
device.Removal += DS4Devices.On_Removal;
device.SyncChange += this.On_SyncChange;
@@ -770,6 +794,7 @@ namespace DS4Windows
if (!getDInputOnly(Index) && device.isSynced())
{
+ //useDInputOnly[Index] = false;
PluginOutDev(Index, device);
}
else
@@ -785,17 +810,19 @@ namespace DS4Windows
//string filename = Path.GetFileName(ProfilePath[Index]);
if (File.Exists(appdatapath + "\\Profiles\\" + ProfilePath[Index] + ".xml"))
{
- string prolog = Properties.Resources.UsingProfile.Replace("*number*", (Index + 1).ToString()).Replace("*Profile name*", ProfilePath[Index]);
+ string prolog = DS4WinWPF.Properties.Resources.UsingProfile.Replace("*number*", (Index + 1).ToString()).Replace("*Profile name*", ProfilePath[Index]);
LogDebug(prolog);
AppLogger.LogToTray(prolog);
}
else
{
- string prolog = Properties.Resources.NotUsingProfile.Replace("*number*", (Index + 1).ToString());
+ string prolog = DS4WinWPF.Properties.Resources.NotUsingProfile.Replace("*number*", (Index + 1).ToString());
LogDebug(prolog);
AppLogger.LogToTray(prolog);
}
+ HotplugController?.Invoke(this, device, Index);
+
break;
}
}
@@ -993,20 +1020,20 @@ namespace DS4Windows
{
if (!d.IsAlive())
{
- return Properties.Resources.Connecting;
+ return DS4WinWPF.Properties.Resources.Connecting;
}
string battery;
if (d.isCharging())
{
if (d.getBattery() >= 100)
- battery = Properties.Resources.Charged;
+ battery = DS4WinWPF.Properties.Resources.Charged;
else
- battery = Properties.Resources.Charging.Replace("*number*", d.getBattery().ToString());
+ battery = DS4WinWPF.Properties.Resources.Charging.Replace("*number*", d.getBattery().ToString());
}
else
{
- battery = Properties.Resources.Battery.Replace("*number*", d.getBattery().ToString());
+ battery = DS4WinWPF.Properties.Resources.Battery.Replace("*number*", d.getBattery().ToString());
}
return d.getMacAddress() + " (" + d.getConnectionType() + "), " + battery;
@@ -1023,7 +1050,7 @@ namespace DS4Windows
{
if (!d.IsAlive())
{
- return Properties.Resources.Connecting;
+ return DS4WinWPF.Properties.Resources.Connecting;
}
return d.getMacAddress();
@@ -1044,7 +1071,7 @@ namespace DS4Windows
if (d.isCharging())
{
if (d.getBattery() >= 100)
- battery = Properties.Resources.Full;
+ battery = DS4WinWPF.Properties.Resources.Full;
else
battery = d.getBattery() + "%+";
}
@@ -1056,7 +1083,7 @@ namespace DS4Windows
return (d.getConnectionType() + " " + battery);
}
else
- return Properties.Resources.NoneText;
+ return DS4WinWPF.Properties.Resources.NoneText;
}
public string getDS4Battery(int index)
@@ -1071,7 +1098,7 @@ namespace DS4Windows
if (d.isCharging())
{
if (d.getBattery() >= 100)
- battery = Properties.Resources.Full;
+ battery = DS4WinWPF.Properties.Resources.Full;
else
battery = d.getBattery() + "%+";
}
@@ -1083,7 +1110,7 @@ namespace DS4Windows
return battery;
}
else
- return Properties.Resources.NA;
+ return DS4WinWPF.Properties.Resources.NA;
}
public string getDS4Status(int index)
@@ -1094,7 +1121,7 @@ namespace DS4Windows
return d.getConnectionType() + "";
}
else
- return Properties.Resources.NoneText;
+ return DS4WinWPF.Properties.Resources.NoneText;
}
protected void On_SerialChange(object sender, EventArgs e)
@@ -1147,6 +1174,39 @@ namespace DS4Windows
if (!getDInputOnly(ind))
{
PluginOutDev(ind, device);
+ /*OutContType conType = Global.OutContType[ind];
+ if (conType == OutContType.X360)
+ {
+ LogDebug("Plugging in X360 Controller #" + (ind + 1));
+ Global.activeOutDevType[ind] = OutContType.X360;
+ Xbox360OutDevice tempXbox = new Xbox360OutDevice(vigemTestClient);
+ outputDevices[ind] = tempXbox;
+ tempXbox.cont.FeedbackReceived += (eventsender, args) =>
+ {
+ SetDevRumble(device, args.LargeMotor, args.SmallMotor, ind);
+ };
+
+ tempXbox.Connect();
+ LogDebug("X360 Controller #" + (ind + 1) + " connected");
+ }
+ else if (conType == OutContType.DS4)
+ {
+ LogDebug("Plugging in DS4 Controller #" + (ind + 1));
+ Global.activeOutDevType[ind] = OutContType.DS4;
+ DS4OutDevice tempDS4 = new DS4OutDevice(vigemTestClient);
+ outputDevices[ind] = tempDS4;
+ int devIndex = ind;
+ tempDS4.cont.FeedbackReceived += (eventsender, args) =>
+ {
+ SetDevRumble(device, args.LargeMotor, args.SmallMotor, devIndex);
+ };
+
+ tempDS4.Connect();
+ LogDebug("DS4 Controller #" + (ind + 1) + " connected");
+ }
+ */
+
+ //useDInputOnly[ind] = false;
}
}
}
@@ -1181,12 +1241,6 @@ namespace DS4Windows
if (!useDInputOnly[ind])
{
UnplugOutDev(ind, device);
- //string tempType = outputDevices[ind].GetDeviceType();
- //outputDevices[ind].Disconnect();
- //outputDevices[ind] = null;
- //x360controls[ind].Disconnect();
- //x360controls[ind] = null;
- //LogDebug(tempType + " Controller # " + (ind + 1) + " unplugged");
}
// Use Task to reset device synth state and commit it
@@ -1195,11 +1249,11 @@ namespace DS4Windows
Mapping.Commit(ind);
}).Wait();
- string removed = Properties.Resources.ControllerWasRemoved.Replace("*Mac address*", (ind + 1).ToString());
+ string removed = DS4WinWPF.Properties.Resources.ControllerWasRemoved.Replace("*Mac address*", (ind + 1).ToString());
if (device.getBattery() <= 20 &&
device.getConnectionType() == ConnectionType.BT && !device.isCharging())
{
- removed += ". " + Properties.Resources.ChargeController;
+ removed += ". " + DS4WinWPF.Properties.Resources.ChargeController;
}
LogDebug(removed);
@@ -1218,15 +1272,17 @@ namespace DS4Windows
device.IsRemoved = true;
device.Synced = false;
DS4Controllers[ind] = null;
+ slotManager.RemoveController(device, ind);
touchPad[ind] = null;
lag[ind] = false;
inWarnMonitor[ind] = false;
useDInputOnly[ind] = true;
Global.activeOutDevType[ind] = OutContType.None;
- uiContext?.Post(new SendOrPostCallback((state) =>
+ /*uiContext?.Post(new SendOrPostCallback((state) =>
{
OnControllerRemoved(this, ind);
}), null);
+ */
//Thread.Sleep(XINPUT_UNPLUG_SETTLE_TIME);
}
}
@@ -1252,10 +1308,12 @@ namespace DS4Windows
string devError = tempStrings[ind] = device.error;
if (!string.IsNullOrEmpty(devError))
{
- uiContext?.Post(new SendOrPostCallback(delegate (object state)
+ LogDebug(devError);
+ /*uiContext?.Post(new SendOrPostCallback(delegate (object state)
{
LogDebug(devError);
}), null);
+ */
}
if (inWarnMonitor[ind])
@@ -1264,18 +1322,22 @@ namespace DS4Windows
if (!lag[ind] && device.Latency >= flashWhenLateAt)
{
lag[ind] = true;
- uiContext?.Post(new SendOrPostCallback(delegate (object state)
+ LagFlashWarning(ind, true);
+ /*uiContext?.Post(new SendOrPostCallback(delegate (object state)
{
LagFlashWarning(ind, true);
}), null);
+ */
}
else if (lag[ind] && device.Latency < flashWhenLateAt)
{
lag[ind] = false;
- uiContext?.Post(new SendOrPostCallback(delegate (object state)
+ LagFlashWarning(ind, false);
+ /*uiContext?.Post(new SendOrPostCallback(delegate (object state)
{
LagFlashWarning(ind, false);
}), null);
+ */
}
}
else
@@ -1295,20 +1357,22 @@ namespace DS4Windows
if (device.firstReport && device.IsAlive())
{
device.firstReport = false;
- uiContext?.Post(new SendOrPostCallback(delegate (object state)
+ /*uiContext?.Post(new SendOrPostCallback(delegate (object state)
{
OnDeviceStatusChanged(this, ind);
}), null);
+ */
}
- else if (pState.Battery != cState.Battery || device.oldCharging != device.isCharging())
- {
- byte tempBattery = currentBattery[ind] = cState.Battery;
- bool tempCharging = charging[ind] = device.isCharging();
- uiContext?.Post(new SendOrPostCallback(delegate (object state)
- {
- OnBatteryStatusChange(this, ind, tempBattery, tempCharging);
- }), null);
- }
+ //else if (pState.Battery != cState.Battery || device.oldCharging != device.isCharging())
+ //{
+ // byte tempBattery = currentBattery[ind] = cState.Battery;
+ // bool tempCharging = charging[ind] = device.isCharging();
+ // /*uiContext?.Post(new SendOrPostCallback(delegate (object state)
+ // {
+ // OnBatteryStatusChange(this, ind, tempBattery, tempCharging);
+ // }), null);
+ // */
+ //}
if (getEnableTouchToggle(ind))
CheckForTouchToggle(ind, cState, pState);
@@ -1384,7 +1448,7 @@ namespace DS4Windows
if (on)
{
lag[ind] = true;
- LogDebug(Properties.Resources.LatencyOverTen.Replace("*number*", (ind + 1).ToString()), true);
+ LogDebug(DS4WinWPF.Properties.Resources.LatencyOverTen.Replace("*number*", (ind + 1).ToString()), true);
if (getFlashWhenLate())
{
DS4Color color = new DS4Color { red = 50, green = 0, blue = 0 };
@@ -1396,7 +1460,7 @@ namespace DS4Windows
else
{
lag[ind] = false;
- LogDebug(Properties.Resources.LatencyNotOverTen.Replace("*number*", (ind + 1).ToString()));
+ LogDebug(DS4WinWPF.Properties.Resources.LatencyNotOverTen.Replace("*number*", (ind + 1).ToString()));
DS4LightBar.forcelight[ind] = false;
DS4LightBar.forcedFlash[ind] = 0;
}
@@ -1484,15 +1548,15 @@ namespace DS4Windows
if (GetTouchActive(deviceID) && touchreleased[deviceID])
{
TouchActive[deviceID] = false;
- LogDebug(Properties.Resources.TouchpadMovementOff);
- AppLogger.LogToTray(Properties.Resources.TouchpadMovementOff);
+ LogDebug(DS4WinWPF.Properties.Resources.TouchpadMovementOff);
+ AppLogger.LogToTray(DS4WinWPF.Properties.Resources.TouchpadMovementOff);
touchreleased[deviceID] = false;
}
else if (touchreleased[deviceID])
{
TouchActive[deviceID] = true;
- LogDebug(Properties.Resources.TouchpadMovementOn);
- AppLogger.LogToTray(Properties.Resources.TouchpadMovementOn);
+ LogDebug(DS4WinWPF.Properties.Resources.TouchpadMovementOn);
+ AppLogger.LogToTray(DS4WinWPF.Properties.Resources.TouchpadMovementOn);
touchreleased[deviceID] = false;
}
}
diff --git a/DS4Windows/DS4Control/ControllerSlotManager.cs b/DS4Windows/DS4Control/ControllerSlotManager.cs
new file mode 100644
index 0000000..df4cffb
--- /dev/null
+++ b/DS4Windows/DS4Control/ControllerSlotManager.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+
+namespace DS4Windows
+{
+ public class ControllerSlotManager
+ {
+ private List controllerColl;
+ public List ControllerColl { get => controllerColl; set => controllerColl = value; }
+
+ private Dictionary controllerDict;
+ private Dictionary reverseControllerDict;
+ public Dictionary ControllerDict { get => controllerDict; }
+ public Dictionary ReverseControllerDict { get => reverseControllerDict; }
+
+ public ControllerSlotManager()
+ {
+ controllerColl = new List();
+ controllerDict = new Dictionary();
+ reverseControllerDict = new Dictionary();
+ }
+
+ public void AddController(DS4Device device, int slotIdx)
+ {
+ controllerColl.Add(device);
+ controllerDict.Add(slotIdx, device);
+ reverseControllerDict.Add(device, slotIdx);
+ }
+
+ public void RemoveController(DS4Device device, int slotIdx)
+ {
+ controllerColl.Remove(device);
+ controllerDict.Remove(slotIdx);
+ reverseControllerDict.Remove(device);
+ }
+
+ public void ClearControllerList()
+ {
+ controllerColl.Clear();
+ controllerDict.Clear();
+ reverseControllerDict.Clear();
+ }
+ }
+}
diff --git a/DS4Windows/DS4Control/MacroParser.cs b/DS4Windows/DS4Control/MacroParser.cs
new file mode 100644
index 0000000..0950eb7
--- /dev/null
+++ b/DS4Windows/DS4Control/MacroParser.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace DS4Windows
+{
+ public class MacroParser
+ {
+ private bool loaded;
+ private List macroSteps;
+ private int[] inputMacro;
+ private Dictionary keydown = new Dictionary();
+ public static Dictionary macroInputNames = new Dictionary()
+ {
+ [256] = "Left Mouse Button", [257] = "Right Mouse Button",
+ [258] = "Middle Mouse Button", [259] = "4th Mouse Button",
+ [260] = "5th Mouse Button", [261] = "A Button",
+ [262] = "B Button", [263] = "X Button",
+ [264] = "Y Button", [265] = "Start",
+ [266] = "Back", [267] = "Up Button",
+ [268] = "Down Button", [269] = "Left Button",
+ [270] = "Right Button", [271] = "Guide",
+ [272] = "Left Bumper", [273] = "Right Bumper",
+ [274] = "Left Trigger", [275] = "Right Trigger",
+ [276] = "Left Stick", [277] = "Right Stick",
+ [278] = "LS Right", [279] = "LS Left",
+ [280] = "LS Down", [281] = "LS Up",
+ [282] = "RS Right", [283] = "RS Left",
+ [284] = "RS Down", [285] = "RS Up",
+ };
+
+ public List MacroSteps { get => macroSteps; }
+
+ public MacroParser(int[] macro)
+ {
+ macroSteps = new List();
+ inputMacro = macro;
+ }
+
+ public void LoadMacro()
+ {
+ if (loaded)
+ {
+ return;
+ }
+
+ keydown.Clear();
+ for(int i = 0; i < inputMacro.Length; i++)
+ {
+ int value = inputMacro[i];
+ MacroStep step = ParseStep(value);
+ macroSteps.Add(step);
+ }
+
+ loaded = true;
+ }
+
+ public List GetMacroStrings()
+ {
+ if (!loaded)
+ {
+ LoadMacro();
+ }
+
+ List result = new List();
+ foreach(MacroStep step in macroSteps)
+ {
+ result.Add(step.Name);
+ }
+
+ return result;
+ }
+
+ private MacroStep ParseStep(int value)
+ {
+ string name = string.Empty;
+ MacroStep.StepType type = MacroStep.StepType.ActDown;
+ MacroStep.StepOutput outType = MacroStep.StepOutput.Key;
+
+ if (value >= 1000000000)
+ {
+ outType = MacroStep.StepOutput.Lightbar;
+ if (value > 1000000000)
+ {
+ type = MacroStep.StepType.ActDown;
+ string lb = value.ToString().Substring(1);
+ byte r = (byte)(int.Parse(lb[0].ToString()) * 100 + int.Parse(lb[1].ToString()) * 10 + int.Parse(lb[2].ToString()));
+ byte g = (byte)(int.Parse(lb[3].ToString()) * 100 + int.Parse(lb[4].ToString()) * 10 + int.Parse(lb[5].ToString()));
+ byte b = (byte)(int.Parse(lb[6].ToString()) * 100 + int.Parse(lb[7].ToString()) * 10 + int.Parse(lb[8].ToString()));
+ name = $"Lightbar Color: {r},{g},{b}";
+ }
+ else
+ {
+ type = MacroStep.StepType.ActUp;
+ name = "Reset Lightbar";
+ }
+ }
+ else if (value >= 1000000)
+ {
+ outType = MacroStep.StepOutput.Rumble;
+ if (value > 1000000)
+ {
+ type = MacroStep.StepType.ActDown;
+ string r = value.ToString().Substring(1);
+ byte heavy = (byte)(int.Parse(r[0].ToString()) * 100 + int.Parse(r[1].ToString()) * 10 + int.Parse(r[2].ToString()));
+ byte light = (byte)(int.Parse(r[3].ToString()) * 100 + int.Parse(r[4].ToString()) * 10 + int.Parse(r[5].ToString()));
+ name = $"Rumble {heavy}, {light} ({Math.Round((heavy * .75f + light * .25f) / 2.55f, 1)}%)";
+ }
+ else
+ {
+ type = MacroStep.StepType.ActUp;
+ name = "Stop Rumble";
+ }
+ }
+ else if (value >= 300) // ints over 300 used to delay
+ {
+ type = MacroStep.StepType.Wait;
+ outType = MacroStep.StepOutput.None;
+ name = $"Wait {(value - 300).ToString()} ms";
+ }
+ else
+ {
+ // anything above 255 is not a key value
+ outType = value <= 255 ? MacroStep.StepOutput.Key : MacroStep.StepOutput.Button;
+ keydown.TryGetValue(value, out bool isdown);
+ if (!isdown)
+ {
+ type = MacroStep.StepType.ActDown;
+ keydown.Add(value, true);
+ if (outType == MacroStep.StepOutput.Key)
+ {
+ name = KeyInterop.KeyFromVirtualKey(value).ToString();
+ }
+ else
+ {
+ macroInputNames.TryGetValue(value, out name);
+ }
+ }
+ else
+ {
+ type = MacroStep.StepType.ActUp;
+ keydown.Remove(value);
+ if (outType == MacroStep.StepOutput.Key)
+ {
+ name = KeyInterop.KeyFromVirtualKey(value).ToString();
+ }
+ else
+ {
+ macroInputNames.TryGetValue(value, out name);
+ }
+ }
+ }
+
+ MacroStep step = new MacroStep(value, name, type, outType);
+ return step;
+ }
+
+ public void Reset()
+ {
+ loaded = false;
+ }
+ }
+
+ public class MacroStep
+ {
+ public enum StepType : uint
+ {
+ ActDown,
+ ActUp,
+ Wait,
+ }
+
+ public enum StepOutput : uint
+ {
+ None,
+ Key,
+ Button,
+ Rumble,
+ Lightbar,
+ }
+
+ private string name;
+ private int value;
+ private StepType actType;
+ private StepOutput outputType;
+
+ public string Name
+ {
+ get => name;
+ set
+ {
+ name = value;
+ NameChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ public event EventHandler NameChanged;
+ public int Value
+ {
+ get => value;
+ set
+ {
+ this.value = value;
+ ValueChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ public event EventHandler ValueChanged;
+ public StepType ActType { get => actType; }
+ public StepOutput OutputType { get => outputType; }
+
+ public MacroStep(int value, string name, StepType act, StepOutput output)
+ {
+ this.value = value;
+ this.name = name;
+ actType = act;
+ outputType = output;
+
+ ValueChanged += MacroStep_ValueChanged;
+ }
+
+ private void MacroStep_ValueChanged(object sender, EventArgs e)
+ {
+ if (actType == StepType.Wait)
+ {
+ Name = $"Wait {value-300}ms";
+ }
+ else if (outputType == StepOutput.Rumble)
+ {
+ int result = value;
+ result -= 1000000;
+ int curHeavy = result / 1000;
+ int curLight = result - (curHeavy * 1000);
+ Name = $"Rumble {curHeavy},{curLight}";
+ }
+ else if (outputType == StepOutput.Lightbar)
+ {
+ int temp = value - 1000000000;
+ int r = temp / 1000000;
+ temp -= (r * 1000000);
+ int g = temp / 1000;
+ temp -= (g * 1000);
+ int b = temp;
+ Name = $"Lightbar Color: {r},{g},{b}";
+ }
+ }
+ }
+}
diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs
index 2950896..65c25d5 100644
--- a/DS4Windows/DS4Control/Mapping.cs
+++ b/DS4Windows/DS4Control/Mapping.cs
@@ -2105,7 +2105,7 @@ namespace DS4Windows
}
}
- string prolog = Properties.Resources.UsingProfile.Replace("*number*", (device + 1).ToString()).Replace("*Profile name*", action.details);
+ string prolog = DS4WinWPF.Properties.Resources.UsingProfile.Replace("*number*", (device + 1).ToString()).Replace("*Profile name*", action.details);
AppLogger.LogToGui(prolog, false);
LoadTempProfile(device, action.details, true, ctrl);
@@ -2559,7 +2559,7 @@ namespace DS4Windows
}
string profileName = untriggeraction[device].prevProfileName;
- string prolog = Properties.Resources.UsingProfile.Replace("*number*", (device + 1).ToString()).Replace("*Profile name*", (profileName == string.Empty ? ProfilePath[device] : profileName));
+ string prolog = DS4WinWPF.Properties.Resources.UsingProfile.Replace("*number*", (device + 1).ToString()).Replace("*Profile name*", (profileName == string.Empty ? ProfilePath[device] : profileName));
AppLogger.LogToGui(prolog, false);
untriggeraction[device] = null;
diff --git a/DS4Windows/DS4Control/Mouse.cs b/DS4Windows/DS4Control/Mouse.cs
index f0cc9e4..d21c15d 100644
--- a/DS4Windows/DS4Control/Mouse.cs
+++ b/DS4Windows/DS4Control/Mouse.cs
@@ -359,7 +359,7 @@ namespace DS4Windows
tempBool = true;
for (int i = 0, arlen = disArray.Length; tempBool && i < arlen; i++)
{
- if (disArray[i] == -1 || getDS4ControlsByName(disArray[i]) == false)
+ if (getDS4ControlsByName(disArray[i]) == false)
tempBool = false;
}
diff --git a/DS4Windows/DS4Control/Program.cs b/DS4Windows/DS4Control/Program.cs
new file mode 100644
index 0000000..c78e761
--- /dev/null
+++ b/DS4Windows/DS4Control/Program.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DS4Windows
+{
+ public class Program
+ {
+ public static ControlService rootHub;
+ }
+}
diff --git a/DS4Windows/DS4Control/ScpUtil.cs b/DS4Windows/DS4Control/ScpUtil.cs
index 9edadc8..e349a00 100644
--- a/DS4Windows/DS4Control/ScpUtil.cs
+++ b/DS4Windows/DS4Control/ScpUtil.cs
@@ -11,6 +11,7 @@ using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Globalization;
+using System.Diagnostics;
namespace DS4Windows
{
@@ -238,7 +239,10 @@ namespace DS4Windows
{
protected static BackingStore m_Config = new BackingStore();
protected static Int32 m_IdleTimeout = 600000;
- public static readonly string exepath = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName;
+ public static string exelocation = Assembly.GetExecutingAssembly().Location;
+ public static string exedirpath = Directory.GetParent(exelocation).FullName;
+ public static FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(exelocation);
+ public static string exeversion = fileVersion.ProductVersion;
public static string appdatapath;
public static bool firstRun = false;
public static bool multisavespots = false;
@@ -265,7 +269,7 @@ namespace DS4Windows
X360Controls.LYNeg, X360Controls.LYPos, X360Controls.RXNeg, X360Controls.RXPos, X360Controls.RYNeg, X360Controls.RYPos,
X360Controls.LB, X360Controls.LT, X360Controls.LS, X360Controls.RB, X360Controls.RT, X360Controls.RS, X360Controls.X,
X360Controls.Y, X360Controls.B, X360Controls.A, X360Controls.DpadUp, X360Controls.DpadRight, X360Controls.DpadDown,
- X360Controls.DpadLeft, X360Controls.Guide, X360Controls.None, X360Controls.None, X360Controls.None, X360Controls.None,
+ X360Controls.DpadLeft, X360Controls.Guide, X360Controls.LeftMouse, X360Controls.RightMouse, X360Controls.MiddleMouse, X360Controls.LeftMouse,
X360Controls.Back, X360Controls.Start, X360Controls.None, X360Controls.None, X360Controls.None, X360Controls.None,
X360Controls.None, X360Controls.None, X360Controls.None, X360Controls.None
};
@@ -286,6 +290,162 @@ namespace DS4Windows
return temp;
})();
+ public static Dictionary xboxDefaultNames = new Dictionary()
+ {
+ [X360Controls.LXNeg] = "Left X-Axis-",
+ [X360Controls.LXPos] = "Left X-Axis+",
+ [X360Controls.LYNeg] = "Left Y-Axis-",
+ [X360Controls.LYPos] = "Left Y-Axis+",
+ [X360Controls.RXNeg] = "Right X-Axis-",
+ [X360Controls.RXPos] = "Right X-Axis+",
+ [X360Controls.RYNeg] = "Right Y-Axis-",
+ [X360Controls.RYPos] = "Right Y-Axis+",
+ [X360Controls.LB] = "Left Bumper",
+ [X360Controls.LT] = "Left Trigger",
+ [X360Controls.LS] = "Left Stick",
+ [X360Controls.RB] = "Right Bumper",
+ [X360Controls.RT] = "Right Trigger",
+ [X360Controls.RS] = "Right Stick",
+ [X360Controls.X] = "X Button",
+ [X360Controls.Y] = "Y Button",
+ [X360Controls.B] = "B Button",
+ [X360Controls.A] = "A Button",
+ [X360Controls.DpadUp] = "Up Button",
+ [X360Controls.DpadRight] = "Right Button",
+ [X360Controls.DpadDown] = "Down Button",
+ [X360Controls.DpadLeft] = "Left Button",
+ [X360Controls.Guide] = "Guide",
+ [X360Controls.Back] = "Back",
+ [X360Controls.Start] = "Start",
+ [X360Controls.LeftMouse] = "Left Mouse Button",
+ [X360Controls.RightMouse] = "Right Mouse Button",
+ [X360Controls.MiddleMouse] = "Middle Mouse Button",
+ [X360Controls.FourthMouse] = "4th Mouse Button",
+ [X360Controls.FifthMouse] = "5th Mouse Button",
+ [X360Controls.WUP] = "Mouse Wheel Up",
+ [X360Controls.WDOWN] = "Mouse Wheel Down",
+ [X360Controls.MouseUp] = "Mouse Up",
+ [X360Controls.MouseDown] = "Mouse Down",
+ [X360Controls.MouseLeft] = "Mouse Left",
+ [X360Controls.MouseRight] = "Mouse Right",
+ [X360Controls.Unbound] = "Unbound",
+ };
+
+ public static Dictionary ds4DefaultNames = new Dictionary()
+ {
+ [X360Controls.LXNeg] = "Left X-Axis-",
+ [X360Controls.LXPos] = "Left X-Axis+",
+ [X360Controls.LYNeg] = "Left Y-Axis-",
+ [X360Controls.LYPos] = "Left Y-Axis+",
+ [X360Controls.RXNeg] = "Right X-Axis-",
+ [X360Controls.RXPos] = "Right X-Axis+",
+ [X360Controls.RYNeg] = "Right Y-Axis-",
+ [X360Controls.RYPos] = "Right Y-Axis+",
+ [X360Controls.LB] = "L1",
+ [X360Controls.LT] = "L2",
+ [X360Controls.LS] = "L3",
+ [X360Controls.RB] = "R1",
+ [X360Controls.RT] = "R2",
+ [X360Controls.RS] = "R3",
+ [X360Controls.X] = "Square",
+ [X360Controls.Y] = "Triangle",
+ [X360Controls.B] = "Circle",
+ [X360Controls.A] = "Cross",
+ [X360Controls.DpadUp] = "Dpad Up",
+ [X360Controls.DpadRight] = "Dpad Right",
+ [X360Controls.DpadDown] = "Dpad Down",
+ [X360Controls.DpadLeft] = "Dpad Left",
+ [X360Controls.Guide] = "PS",
+ [X360Controls.Back] = "Share",
+ [X360Controls.Start] = "Options",
+ [X360Controls.LeftMouse] = "Left Mouse Button",
+ [X360Controls.RightMouse] = "Right Mouse Button",
+ [X360Controls.MiddleMouse] = "Middle Mouse Button",
+ [X360Controls.FourthMouse] = "4th Mouse Button",
+ [X360Controls.FifthMouse] = "5th Mouse Button",
+ [X360Controls.WUP] = "Mouse Wheel Up",
+ [X360Controls.WDOWN] = "Mouse Wheel Down",
+ [X360Controls.MouseUp] = "Mouse Up",
+ [X360Controls.MouseDown] = "Mouse Down",
+ [X360Controls.MouseLeft] = "Mouse Left",
+ [X360Controls.MouseRight] = "Mouse Right",
+ [X360Controls.Unbound] = "Unbound",
+ };
+
+ public static string getX360ControlString(X360Controls key, OutContType conType)
+ {
+ string result = string.Empty;
+ if (conType == DS4Windows.OutContType.X360)
+ {
+ xboxDefaultNames.TryGetValue(key, out result);
+ }
+ else if (conType == DS4Windows.OutContType.DS4)
+ {
+ ds4DefaultNames.TryGetValue(key, out result);
+ }
+
+ return result;
+ }
+
+ public static Dictionary ds4inputNames = new Dictionary()
+ {
+ [DS4Controls.LXNeg] = "Left X-Axis-",
+ [DS4Controls.LXPos] = "Left X-Axis+",
+ [DS4Controls.LYNeg] = "Left Y-Axis-",
+ [DS4Controls.LYPos] = "Left Y-Axis+",
+ [DS4Controls.RXNeg] = "Right X-Axis-",
+ [DS4Controls.RXPos] = "Right X-Axis+",
+ [DS4Controls.RYNeg] = "Right Y-Axis-",
+ [DS4Controls.RYPos] = "Right Y-Axis+",
+ [DS4Controls.L1] = "L1",
+ [DS4Controls.L2] = "L2",
+ [DS4Controls.L3] = "L3",
+ [DS4Controls.R1] = "R1",
+ [DS4Controls.R2] = "R2",
+ [DS4Controls.R3] = "R3",
+ [DS4Controls.Square] = "Square",
+ [DS4Controls.Triangle] = "Triangle",
+ [DS4Controls.Circle] = "Circle",
+ [DS4Controls.Cross] = "Cross",
+ [DS4Controls.DpadUp] = "Dpad Up",
+ [DS4Controls.DpadRight] = "Dpad Right",
+ [DS4Controls.DpadDown] = "Dpad Down",
+ [DS4Controls.DpadLeft] = "Dpad Left",
+ [DS4Controls.PS] = "PS",
+ [DS4Controls.Share] = "Share",
+ [DS4Controls.Options] = "Options",
+ [DS4Controls.TouchLeft] = "Left Touch",
+ [DS4Controls.TouchUpper] = "Upper Touch",
+ [DS4Controls.TouchMulti] = "Multitouch",
+ [DS4Controls.TouchRight] = "Right Touch",
+ [DS4Controls.GyroXPos] = "Gyro X+",
+ [DS4Controls.GyroXNeg] = "Gyro X-",
+ [DS4Controls.GyroZPos] = "Gyro Z+",
+ [DS4Controls.GyroZNeg] = "Gyro Z+-",
+ [DS4Controls.SwipeLeft] = "Swipe Left",
+ [DS4Controls.SwipeRight] = "Swipe Right",
+ [DS4Controls.SwipeUp] = "Swipe Up",
+ [DS4Controls.SwipeUp] = "Swipe Up",
+ [DS4Controls.SwipeDown] = "None",
+ };
+
+ public static Dictionary macroDS4Values = new Dictionary()
+ {
+ [DS4Controls.Cross] = 261, [DS4Controls.Circle] = 262,
+ [DS4Controls.Square] = 263, [DS4Controls.Triangle] = 264,
+ [DS4Controls.Options] = 265, [DS4Controls.Share] = 266,
+ [DS4Controls.DpadUp] = 267, [DS4Controls.DpadDown] = 268,
+ [DS4Controls.DpadLeft] = 269, [DS4Controls.DpadRight] = 270,
+ [DS4Controls.PS] = 271, [DS4Controls.L1] = 272,
+ [DS4Controls.R1] = 273, [DS4Controls.L2] = 274,
+ [DS4Controls.R2] = 275, [DS4Controls.L3] = 276,
+ [DS4Controls.R3] = 277, [DS4Controls.LXPos] = 278,
+ [DS4Controls.LXNeg] = 279, [DS4Controls.LYPos] = 280,
+ [DS4Controls.LYNeg] = 281, [DS4Controls.RXPos] = 282,
+ [DS4Controls.RXNeg] = 283, [DS4Controls.RYPos] = 284,
+ [DS4Controls.RYNeg] = 285,
+ };
+
public static void SaveWhere(string path)
{
appdatapath = path;
@@ -295,6 +455,36 @@ namespace DS4Windows
m_Config.m_controllerConfigs = Global.appdatapath + "\\ControllerConfigs.xml";
}
+ public static bool SaveDefault(string path)
+ {
+ Boolean Saved = true;
+ XmlDocument m_Xdoc = new XmlDocument();
+ try
+ {
+ XmlNode Node;
+
+ m_Xdoc.RemoveAll();
+
+ Node = m_Xdoc.CreateXmlDeclaration("1.0", "utf-8", String.Empty);
+ m_Xdoc.AppendChild(Node);
+
+ Node = m_Xdoc.CreateComment(string.Format(" Profile Configuration Data. {0} ", DateTime.Now));
+ m_Xdoc.AppendChild(Node);
+
+ Node = m_Xdoc.CreateWhitespace("\r\n");
+ m_Xdoc.AppendChild(Node);
+
+ Node = m_Xdoc.CreateNode(XmlNodeType.Element, "Profile", null);
+
+ m_Xdoc.AppendChild(Node);
+
+ m_Xdoc.Save(path);
+ }
+ catch { Saved = false; }
+
+ return Saved;
+ }
+
///
/// Check if Admin Rights are needed to write in Appliplation Directory
///
@@ -303,8 +493,8 @@ namespace DS4Windows
{
try
{
- File.WriteAllText(exepath + "\\test.txt", "test");
- File.Delete(exepath + "\\test.txt");
+ File.WriteAllText(exedirpath + "\\test.txt", "test");
+ File.Delete(exedirpath + "\\test.txt");
return false;
}
catch (UnauthorizedAccessException)
@@ -451,17 +641,17 @@ namespace DS4Windows
public static void FindConfigLocation()
{
- if (File.Exists(exepath + "\\Auto Profiles.xml")
+ if (File.Exists(exedirpath + "\\Auto Profiles.xml")
&& File.Exists(appDataPpath + "\\Auto Profiles.xml"))
{
Global.firstRun = true;
Global.multisavespots = true;
}
- else if (File.Exists(exepath + "\\Auto Profiles.xml"))
- SaveWhere(exepath);
+ else if (File.Exists(exedirpath + "\\Auto Profiles.xml"))
+ SaveWhere(exedirpath);
else if (File.Exists(appDataPpath + "\\Auto Profiles.xml"))
SaveWhere(appDataPpath);
- else if (!File.Exists(exepath + "\\Auto Profiles.xml")
+ else if (!File.Exists(exedirpath + "\\Auto Profiles.xml")
&& !File.Exists(appDataPpath + "\\Auto Profiles.xml"))
{
Global.firstRun = true;
@@ -517,6 +707,33 @@ namespace DS4Windows
catch { }
}
+ public static bool CreateAutoProfiles(string m_Profile)
+ {
+ bool Saved = true;
+
+ try
+ {
+ XmlNode Node;
+ XmlDocument doc = new XmlDocument();
+
+ Node = doc.CreateXmlDeclaration("1.0", "utf-8", String.Empty);
+ doc.AppendChild(Node);
+
+ Node = doc.CreateComment(string.Format(" Auto-Profile Configuration Data. {0} ", DateTime.Now));
+ doc.AppendChild(Node);
+
+ Node = doc.CreateWhitespace("\r\n");
+ doc.AppendChild(Node);
+
+ Node = doc.CreateNode(XmlNodeType.Element, "Programs", "");
+ doc.AppendChild(Node);
+ doc.Save(m_Profile);
+ }
+ catch { Saved = false; }
+
+ return Saved;
+ }
+
public static event EventHandler ControllerStatusChange; // called when a controller is added/removed/battery or touchpad mode changes/etc.
public static void ControllerStatusChanged(object sender)
{
@@ -1554,6 +1771,15 @@ namespace DS4Windows
tempprofileDistance[device] = name.ToLower().Contains("distance");
}
+ public static void LoadBlankDevProfile(int device, bool launchprogram, ControlService control,
+ bool xinputChange = true, bool postLoad = true)
+ {
+ m_Config.LoadBlankProfile(device, launchprogram, control, "", xinputChange, postLoad);
+ tempprofilename[device] = string.Empty;
+ useTempProfile[device] = false;
+ tempprofileDistance[device] = false;
+ }
+
public static bool Save()
{
return m_Config.Save();
@@ -3346,31 +3572,8 @@ namespace DS4Windows
// performing this upon program startup before loading devices.
if (xinputChange)
{
- if (device < 4)
- {
- DS4Device tempDevice = control.DS4Controllers[device];
- bool exists = tempBool = (tempDevice != null);
- bool synced = tempBool = exists ? tempDevice.isSynced() : false;
- bool isAlive = tempBool = exists ? tempDevice.IsAlive() : false;
- if (dinputOnly[device] != oldUseDInputOnly)
- {
- if (dinputOnly[device] == true)
- {
- xinputPlug = false;
- xinputStatus = true;
- }
- else if (synced && isAlive)
- {
- xinputPlug = true;
- xinputStatus = true;
- }
- }
- else if (oldContType != outputDevType[device])
- {
- xinputPlug = true;
- xinputStatus = true;
- }
- }
+ CheckOldDevicestatus(device, control, oldContType,
+ out xinputPlug, out xinputStatus);
}
try
@@ -3630,41 +3833,7 @@ namespace DS4Windows
// options to device instance
if (postLoad && device < 4)
{
- DS4Device tempDev = control.DS4Controllers[device];
- if (tempDev != null && tempDev.isSynced())
- {
- tempDev.queueEvent(() =>
- {
- tempDev.setIdleTimeout(idleDisconnectTimeout[device]);
- tempDev.setBTPollRate(btPollRate[device]);
- if (xinputStatus && xinputPlug)
- {
- OutputDevice tempOutDev = control.outputDevices[device];
- if (tempOutDev != null)
- {
- //string tempType = tempOutDev.GetDeviceType();
- //AppLogger.LogToGui("Unplug " + tempType + " Controller #" + (device + 1), false);
- //tempOutDev.Disconnect();
- tempOutDev = null;
- //control.outputDevices[device] = null;
- Global.activeOutDevType[device] = OutContType.None;
- control.UnplugOutDev(device, tempDev);
- }
-
- OutContType tempContType = outputDevType[device];
- control.PluginOutDev(device, tempDev);
- }
- else if (xinputStatus && !xinputPlug)
- {
- Global.activeOutDevType[device] = OutContType.None;
- control.UnplugOutDev(device, tempDev);
- }
-
- tempDev.setRumble(0, 0);
- });
-
- Program.rootHub.touchPad[device]?.ResetTrackAccel(trackballFriction[device]);
- }
+ PostLoadSnippet(device, control, xinputStatus, xinputPlug);
}
return Loaded;
@@ -4630,6 +4799,111 @@ namespace DS4Windows
outputDevType[device] = OutContType.X360;
ds4Mapping = false;
}
+
+ public void LoadBlankProfile(int device, bool launchprogram, ControlService control,
+ string propath = "", bool xinputChange = true, bool postLoad = true)
+ {
+ bool xinputPlug = false;
+ bool xinputStatus = false;
+
+ OutContType oldContType = Global.activeOutDevType[device];
+
+ // Make sure to reset currently set profile values before parsing
+ ResetProfile(device);
+
+ // Only change xinput devices under certain conditions. Avoid
+ // performing this upon program startup before loading devices.
+ if (xinputChange)
+ {
+ CheckOldDevicestatus(device, control, oldContType,
+ out xinputPlug, out xinputStatus);
+ }
+
+ foreach (DS4ControlSettings dcs in ds4settings[device])
+ dcs.Reset();
+
+ profileActions[device].Clear();
+ containsCustomAction[device] = false;
+ containsCustomExtras[device] = false;
+
+ // If a device exists, make sure to transfer relevant profile device
+ // options to device instance
+ if (postLoad && device < 4)
+ {
+ PostLoadSnippet(device, control, xinputStatus, xinputPlug);
+ }
+ }
+
+ private void CheckOldDevicestatus(int device, ControlService control,
+ OutContType oldContType, out bool xinputPlug, out bool xinputStatus)
+ {
+ xinputPlug = false;
+ xinputStatus = false;
+
+ if (device < 4)
+ {
+ bool oldUseDInputOnly = Global.useDInputOnly[device];
+ DS4Device tempDevice = control.DS4Controllers[device];
+ bool exists = tempBool = (tempDevice != null);
+ bool synced = tempBool = exists ? tempDevice.isSynced() : false;
+ bool isAlive = tempBool = exists ? tempDevice.IsAlive() : false;
+ if (dinputOnly[device] != oldUseDInputOnly)
+ {
+ if (dinputOnly[device] == true)
+ {
+ xinputPlug = false;
+ xinputStatus = true;
+ }
+ else if (synced && isAlive)
+ {
+ xinputPlug = true;
+ xinputStatus = true;
+ }
+ }
+ else if (oldContType != outputDevType[device])
+ {
+ xinputPlug = true;
+ xinputStatus = true;
+ }
+ }
+ }
+
+ private void PostLoadSnippet(int device, ControlService control, bool xinputStatus, bool xinputPlug)
+ {
+ DS4Device tempDev = control.DS4Controllers[device];
+ if (tempDev != null && tempDev.isSynced())
+ {
+ tempDev.queueEvent(() =>
+ {
+ tempDev.setIdleTimeout(idleDisconnectTimeout[device]);
+ tempDev.setBTPollRate(btPollRate[device]);
+ if (xinputStatus && xinputPlug)
+ {
+ OutputDevice tempOutDev = control.outputDevices[device];
+ if (tempOutDev != null)
+ {
+ tempOutDev = null;
+ Global.activeOutDevType[device] = OutContType.None;
+ control.UnplugOutDev(device, tempDev);
+ }
+
+ OutContType tempContType = outputDevType[device];
+ control.PluginOutDev(device, tempDev);
+ //Global.useDInputOnly[device] = false;
+
+ }
+ else if (xinputStatus && !xinputPlug)
+ {
+ Global.activeOutDevType[device] = OutContType.None;
+ control.UnplugOutDev(device, tempDev);
+ }
+
+ tempDev.setRumble(0, 0);
+ });
+
+ Program.rootHub.touchPad[device]?.ResetTrackAccel(trackballFriction[device]);
+ }
+ }
}
public class SpecialAction
diff --git a/DS4Windows/DS4Forms/About.xaml b/DS4Windows/DS4Forms/About.xaml
new file mode 100644
index 0000000..0e9cc06
--- /dev/null
+++ b/DS4Windows/DS4Forms/About.xaml
@@ -0,0 +1,75 @@
+
+
+
+
+ Changelog
+
+
+
+ Hide DS4 Controller: Hides the DS4's regular input (Dinput) from other programs, check if you are getting double input in games or R2 pauses games
+Click left side of touchpad: Left Touch
+Click right side of touchpad: Right Touch
+Click touchpad with 2 fingers: Multitouch
+Click upper part of touchpad: Upper Touch
+PS + Options or hold PS for 10 secs: Disconnect Controller (Only on Bluetooth)
+Touch Touchpad + PS: Turn off touchpad movement (clicking still works)
+Pad click on lower right: Right click (Best used when right side is used as a mouse button)
+Two fingers up/down on touchpad*: Scroll Up/Down
+Tap then hold touchpad*: Left mouse drag
+2 finger touchpad swipe left or right: Cycle through profiles
+Shift Modifer: Hold an action to use another set of controls
+When mapping keyboard and mouse:
+Toggle: The key will remain in a "held down" state until pressed again
+Macro: Assign multiple keys to one input
+Scan Code: Keys are interpreted differently. May be needed for certain games
+*If enabled
+
+
+
+
+
+ Site
+ Source
+
+
+ Jays2Kings
+ InhexSTER (Starter of DS4Tool)
+
+
+ electrobrains (Branched off of)
+
+
+
+
+
+
+
+
+
+
+ Bitcoin: 1DnMJwjdd7JRfHJap2mmTmADYm38SzR2z9
+Litecoin: D9fhbXp9bCHEhuS8vX1BmVu6t7Y2nVNUCK
+Dogecoin: La5mniW7SFMH2RhqDgUty3RwkBSYbjbnJ6
+Monero: 49RvRMiMewaeez1Y2auxHmfMaAUYfhUpBem4ohzRJd9b5acPcxzh1icjnhZfjnYd1S7NQ57reQ7cP1swGre3rpfzUgJhEB7
+
+ Tip via Paypal
+
+
+ Patreon
+
+
+ SubscribeStar
+
+
+
+
+
+
diff --git a/DS4Windows/DS4Forms/About.xaml.cs b/DS4Windows/DS4Forms/About.xaml.cs
new file mode 100644
index 0000000..156b672
--- /dev/null
+++ b/DS4Windows/DS4Forms/About.xaml.cs
@@ -0,0 +1,64 @@
+using System.Diagnostics;
+using System.Windows;
+
+namespace DS4WinWPF.DS4Forms
+{
+ ///
+ /// Interaction logic for About.xaml
+ ///
+ public partial class About : Window
+ {
+ public About()
+ {
+ InitializeComponent();
+
+ string version = DS4Windows.Global.exeversion;
+ headerLb.Content += version + ")";
+ }
+
+ private void ChangeLogLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://docs.google.com/document/d/1CovpH08fbPSXrC6TmEprzgPwCe0tTjQ_HTFfDotpmxk/edit?usp=sharing");
+ }
+
+ private void PaypalLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://paypal.me/ryochan7");
+ }
+
+ private void PatreonLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://patreon.com/user?u=501036");
+ }
+
+ private void SubscribeStartLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://subscribestar.com/ryochan7");
+ }
+
+ private void SiteLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://ryochan7.github.io/ds4windows-site/");
+ }
+
+ private void SourceLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://github.com/Ryochan7/DS4Windows");
+ }
+
+ private void Jays2KingsLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://github.com/Jays2Kings/");
+ }
+
+ private void InhexSTERLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://code.google.com/p/ds4-tool/");
+ }
+
+ private void ElectrobrainsLink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start("https://code.google.com/r/brianfundakowskifeldman-ds4windows/");
+ }
+ }
+}
diff --git a/DS4Windows/DS4Forms/AdvancedColorDialog.cs b/DS4Windows/DS4Forms/AdvancedColorDialog.cs
deleted file mode 100644
index d70a558..0000000
--- a/DS4Windows/DS4Forms/AdvancedColorDialog.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Windows.Forms;
-
-namespace DS4Windows
-{
- [SuppressUnmanagedCodeSecurity]
- public class AdvancedColorDialog : ColorDialog
- {
- #region WinAPI
- internal class ApiWindow
- {
- public IntPtr hWnd;
- public string ClassName;
- public string MainWindowTitle;
- }
- [SuppressUnmanagedCodeSecurity]
- internal class WindowsEnumerator
- {
- private delegate int EnumCallBackDelegate(IntPtr hwnd, int lParam);
-
- [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- private static extern int EnumWindows(EnumCallBackDelegate lpEnumFunc, int lParam);
-
- [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- private static extern int EnumChildWindows(IntPtr hWndParent, EnumCallBackDelegate lpEnumFunc, int lParam);
-
- [DllImport("user32", EntryPoint = "GetClassNameA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- private static extern int GetClassName(IntPtr hwnd, System.Text.StringBuilder lpClassName, int nMaxCount);
-
- [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- private static extern int IsWindowVisible(IntPtr hwnd);
-
- [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- private static extern int GetParent(IntPtr hwnd);
-
- [DllImport("user32", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
-
- [DllImport("user32", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, System.Text.StringBuilder lParam);
-
- private List _listChildren = new List();
-
- private List _listTopLevel = new List();
- private string _topLevelClass = string.Empty;
-
- private string _childClass = string.Empty;
- public ApiWindow[] GetTopLevelWindows()
- {
- EnumWindows(EnumWindowProc, 0x0);
- return _listTopLevel.ToArray();
- }
-
- public ApiWindow[] GetTopLevelWindows(string className)
- {
- _topLevelClass = className;
- return this.GetTopLevelWindows();
- }
-
- public ApiWindow[] GetChildWindows(IntPtr hwnd)
- {
- _listChildren.Clear();
- EnumChildWindows(hwnd, EnumChildWindowProc, 0x0);
- return _listChildren.ToArray();
- }
-
- public ApiWindow[] GetChildWindows(IntPtr hwnd, string childClass)
- {
- _childClass = childClass;
- return this.GetChildWindows(hwnd);
- }
-
- private Int32 EnumWindowProc(IntPtr hwnd, Int32 lParam)
- {
- if (GetParent(hwnd) == 0 && IsWindowVisible(hwnd) != 0)
- {
- ApiWindow window = GetWindowIdentification(hwnd);
- if (_topLevelClass.Length == 0 || window.ClassName.ToLower() == _topLevelClass.ToLower())
- {
- _listTopLevel.Add(window);
- }
- }
- return 1;
- }
-
- private Int32 EnumChildWindowProc(IntPtr hwnd, Int32 lParam)
- {
- ApiWindow window = GetWindowIdentification(hwnd);
- if (_childClass.Length == 0 || window.ClassName.ToLower() == _childClass.ToLower())
- {
- _listChildren.Add(window);
- }
- return 1;
- }
-
- private ApiWindow GetWindowIdentification(IntPtr hwnd)
- {
- System.Text.StringBuilder classBuilder = new System.Text.StringBuilder(64);
- GetClassName(hwnd, classBuilder, 64);
-
- ApiWindow window = new ApiWindow();
- window.ClassName = classBuilder.ToString();
- window.MainWindowTitle = WindowText(hwnd);
- window.hWnd = hwnd;
- return window;
- }
-
- public static string WindowText(IntPtr hwnd)
- {
- const int W_GETTEXT = 0xd;
- const int W_GETTEXTLENGTH = 0xe;
-
- System.Text.StringBuilder SB = new System.Text.StringBuilder();
- int length = SendMessage(hwnd, W_GETTEXTLENGTH, 0, 0);
- if (length > 0)
- {
- SB = new System.Text.StringBuilder(length + 1);
- SendMessage(hwnd, W_GETTEXT, SB.Capacity, SB);
- }
- return SB.ToString();
- }
-
- }
- #endregion
- private const int GA_ROOT = 2;
- private const int WM_CTLCOLOREDIT = 0x133;
- private const int WM_LBUTTONDOWN = 0x0201;
- private const int WM_INITDIALOG = 0x0110;
-
- private List EditWindows = null;
- public delegate void ColorUpdateHandler(Color colValue, EventArgs e);
- public event ColorUpdateHandler OnUpdateColor;
-
- [DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
- public static extern IntPtr GetAncestor(IntPtr hWnd, int gaFlags);
-
- // Overrides the base class hook procedure...
- //[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
- protected override IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
- {
- if (msg == WM_INITDIALOG)
- {
- IntPtr mainWindow = GetAncestor(hWnd, GA_ROOT);
- if (!mainWindow.Equals(IntPtr.Zero))
- EditWindows = new List((new WindowsEnumerator()).GetChildWindows(mainWindow, "Edit"));
- }
- else if (msg == WM_CTLCOLOREDIT)
- {
- if ((EditWindows == null))
- {
- IntPtr mainWindow = GetAncestor(hWnd, GA_ROOT);
- if (!mainWindow.Equals(IntPtr.Zero))
- EditWindows = new List((new WindowsEnumerator()).GetChildWindows(mainWindow, "Edit"));
- }
- if ((EditWindows != null) && EditWindows.Count == 6)
- {
- byte red = 0, green = 0, blue = 0;
- if (Byte.TryParse(WindowsEnumerator.WindowText(EditWindows[3].hWnd), out red))
- if (Byte.TryParse(WindowsEnumerator.WindowText(EditWindows[4].hWnd), out green))
- if (Byte.TryParse(WindowsEnumerator.WindowText(EditWindows[5].hWnd), out blue))
- OnUpdateColor?.Invoke(Color.FromArgb(red, green, blue), EventArgs.Empty);
- }
- }
- // Always call the base class hook procedure.
- return base.HookProc(hWnd, msg, wParam, lParam);
- }
- }
-}
diff --git a/DS4Windows/DS4Forms/AutoProfiles.xaml b/DS4Windows/DS4Forms/AutoProfiles.xaml
new file mode 100644
index 0000000..90e6df4
--- /dev/null
+++ b/DS4Windows/DS4Forms/AutoProfiles.xaml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DS4Windows/DS4Forms/AutoProfiles.xaml.cs b/DS4Windows/DS4Forms/AutoProfiles.xaml.cs
new file mode 100644
index 0000000..2360442
--- /dev/null
+++ b/DS4Windows/DS4Forms/AutoProfiles.xaml.cs
@@ -0,0 +1,279 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Ookii.Dialogs.Wpf;
+using DS4WinWPF.DS4Forms.ViewModels;
+using Microsoft.Win32;
+
+namespace DS4WinWPF.DS4Forms
+{
+ ///
+ /// Interaction logic for AutoProfiles.xaml
+ ///
+ public partial class AutoProfiles : UserControl
+ {
+ protected String m_Profile = DS4Windows.Global.appdatapath + "\\Auto Profiles.xml";
+ public const string steamCommx86Loc = @"C:\Program Files (x86)\Steam\steamapps\common";
+ public const string steamCommLoc = @"C:\Program Files\Steam\steamapps\common";
+ private string steamgamesdir;
+ private AutoProfilesViewModel autoProfVM;
+ private AutoProfileHolder autoProfileHolder;
+ private ProfileList profileList;
+ private bool autoDebug;
+
+ public AutoProfileHolder AutoProfileHolder { get => autoProfileHolder;
+ set => autoProfileHolder = value; }
+ public AutoProfilesViewModel AutoProfVM { get => autoProfVM; }
+ public bool AutoDebug { get => autoDebug; }
+ public event EventHandler AutoDebugChanged;
+
+ public AutoProfiles()
+ {
+ InitializeComponent();
+
+ if (!File.Exists(DS4Windows.Global.appdatapath + @"\Auto Profiles.xml"))
+ DS4Windows.Global.CreateAutoProfiles(m_Profile);
+
+ //LoadP();
+
+ if (DS4Windows.Global.UseCustomSteamFolder &&
+ Directory.Exists(DS4Windows.Global.CustomSteamFolder))
+ steamgamesdir = DS4Windows.Global.CustomSteamFolder;
+ else if (Directory.Exists(steamCommx86Loc))
+ steamgamesdir = steamCommx86Loc;
+ else if (Directory.Exists(steamCommLoc))
+ steamgamesdir = steamCommLoc;
+ else
+ addProgramsBtn.ContextMenu.Items.Remove(steamMenuItem);
+
+ autoProfileHolder = new AutoProfileHolder();
+ }
+
+ public void SetupDataContext(ProfileList profileList)
+ {
+ autoProfVM = new AutoProfilesViewModel(autoProfileHolder, profileList);
+ programListLV.DataContext = autoProfVM;
+ programListLV.ItemsSource = autoProfVM.ProgramColl;
+
+ autoProfVM.SearchFinished += AutoProfVM_SearchFinished;
+ autoProfVM.CurrentItemChange += AutoProfVM_CurrentItemChange;
+
+ this.profileList = profileList;
+ cont1AutoProfCol.Collection = profileList.ProfileListCol;
+ cont2AutoProfCol.Collection = profileList.ProfileListCol;
+ cont3AutoProfCol.Collection = profileList.ProfileListCol;
+ cont4AutoProfCol.Collection = profileList.ProfileListCol;
+ }
+
+ private void AutoProfVM_CurrentItemChange(AutoProfilesViewModel sender, ProgramItem item)
+ {
+ if (item != null)
+ {
+ if (item.MatchedAutoProfile != null)
+ {
+ ProfileEntity tempProf = null;
+ string profileName = item.MatchedAutoProfile.ProfileNames[0];
+ if (!string.IsNullOrEmpty(profileName) && profileName != "(none)")
+ {
+ tempProf = profileList.ProfileListCol.SingleOrDefault(x => x.Name == profileName);
+ if (tempProf != null)
+ {
+ item.SelectedIndexCon1 = profileList.ProfileListCol.IndexOf(tempProf) + 1;
+ }
+ }
+ else
+ {
+ item.SelectedIndexCon1 = 0;
+ }
+
+ profileName = item.MatchedAutoProfile.ProfileNames[1];
+ if (!string.IsNullOrEmpty(profileName) && profileName != "(none)")
+ {
+ tempProf = profileList.ProfileListCol.SingleOrDefault(x => x.Name == profileName);
+ if (tempProf != null)
+ {
+ item.SelectedIndexCon2 = profileList.ProfileListCol.IndexOf(tempProf) + 1;
+ }
+ }
+ else
+ {
+ item.SelectedIndexCon2 = 0;
+ }
+
+ profileName = item.MatchedAutoProfile.ProfileNames[2];
+ if (!string.IsNullOrEmpty(profileName) && profileName != "(none)")
+ {
+ tempProf = profileList.ProfileListCol.SingleOrDefault(x => x.Name == profileName);
+ if (tempProf != null)
+ {
+ item.SelectedIndexCon3 = profileList.ProfileListCol.IndexOf(tempProf) + 1;
+ }
+ }
+ else
+ {
+ item.SelectedIndexCon3 = 0;
+ }
+
+ profileName = item.MatchedAutoProfile.ProfileNames[3];
+ if (!string.IsNullOrEmpty(profileName) && profileName != "(none)")
+ {
+ tempProf = profileList.ProfileListCol.SingleOrDefault(x => x.Name == profileName);
+ if (tempProf != null)
+ {
+ item.SelectedIndexCon4 = profileList.ProfileListCol.IndexOf(tempProf) + 1;
+ }
+ }
+ else
+ {
+ item.SelectedIndexCon4 = 0;
+ }
+ }
+
+ editControlsPanel.DataContext = item;
+ editControlsPanel.IsEnabled = true;
+ }
+ else
+ {
+ editControlsPanel.DataContext = null;
+ editControlsPanel.IsEnabled = false;
+
+ cont1AutoProf.SelectedIndex = 0;
+ cont2AutoProf.SelectedIndex = 0;
+ cont3AutoProf.SelectedIndex = 0;
+ cont4AutoProf.SelectedIndex = 0;
+ }
+ }
+
+ private void AutoProfVM_SearchFinished(object sender, EventArgs e)
+ {
+ IsEnabled = true;
+ }
+
+ private void SteamMenuItem_Click(object sender, RoutedEventArgs e)
+ {
+ this.IsEnabled = false;
+ steamMenuItem.Visibility = Visibility.Collapsed;
+ programListLV.ItemsSource = null;
+ autoProfVM.SearchFinished += AppsSearchFinished;
+ autoProfVM.AddProgramsFromSteam(steamgamesdir);
+ }
+
+ private void BrowseProgsMenuItem_Click(object sender, RoutedEventArgs e)
+ {
+ this.IsEnabled = false;
+ VistaFolderBrowserDialog dialog = new VistaFolderBrowserDialog();
+ if (dialog.ShowDialog() == true)
+ {
+ //browseProgsMenuItem.Visibility = Visibility.Collapsed;
+ programListLV.ItemsSource = null;
+ autoProfVM.SearchFinished += AppsSearchFinished;
+ autoProfVM.AddProgramsFromDir(dialog.SelectedPath);
+ }
+ else
+ {
+ this.IsEnabled = true;
+ }
+ }
+
+ private void StartMenuItem_Click(object sender, RoutedEventArgs e)
+ {
+ this.IsEnabled = false;
+ startMenuItem.Visibility = Visibility.Collapsed;
+ programListLV.ItemsSource = null;
+ autoProfVM.SearchFinished += AppsSearchFinished;
+ autoProfVM.AddProgramsFromStartMenu();
+ }
+
+ private void AppsSearchFinished(object sender, EventArgs e)
+ {
+ autoProfVM.SearchFinished -= AppsSearchFinished;
+ programListLV.ItemsSource = autoProfVM.ProgramColl;
+ }
+
+ private void AddProgramsBtn_Click(object sender, RoutedEventArgs e)
+ {
+ addProgramsBtn.ContextMenu.IsOpen = true;
+ e.Handled = true;
+ }
+
+ private void HideUncheckedBtn_Click(object sender, RoutedEventArgs e)
+ {
+ programListLV.ItemsSource = null;
+ autoProfVM.RemoveUnchecked();
+ steamMenuItem.Visibility = Visibility.Visible;
+ startMenuItem.Visibility = Visibility.Visible;
+ browseProgsMenuItem.Visibility = Visibility.Visible;
+ programListLV.ItemsSource = autoProfVM.ProgramColl;
+ }
+
+ private void ShowAutoDebugCk_Click(object sender, RoutedEventArgs e)
+ {
+ bool state = showAutoDebugCk.IsChecked == true;
+ autoDebug = state;
+ AutoDebugChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void RemoveAutoBtn_Click(object sender, RoutedEventArgs e)
+ {
+ if (autoProfVM.SelectedItem != null)
+ {
+ editControlsPanel.DataContext = null;
+ autoProfVM.RemoveAutoProfileEntry(autoProfVM.SelectedItem);
+ autoProfVM.AutoProfileHolder.Save(DS4Windows.Global.appdatapath + @"\Auto Profiles.xml");
+ autoProfVM.SelectedItem = null;
+ }
+ }
+
+ private void SaveAutoBtn_Click(object sender, RoutedEventArgs e)
+ {
+ if (autoProfVM.SelectedItem != null)
+ {
+ if (autoProfVM.SelectedItem.MatchedAutoProfile == null)
+ {
+ autoProfVM.CreateAutoProfileEntry(autoProfVM.SelectedItem);
+ }
+ else
+ {
+ autoProfVM.PersistAutoProfileEntry(autoProfVM.SelectedItem);
+ }
+
+ autoProfVM.AutoProfileHolder.Save(DS4Windows.Global.appdatapath + @"\Auto Profiles.xml");
+ }
+ }
+
+ private void BrowseAddProgMenuItem_Click(object sender, RoutedEventArgs e)
+ {
+ this.IsEnabled = false;
+ OpenFileDialog dialog = new OpenFileDialog();
+ dialog.Multiselect = false;
+ dialog.AddExtension = true;
+ dialog.DefaultExt = ".exe";
+ dialog.Filter = "Program (*.exe)|*.exe";
+ dialog.Title = "Select Program";
+
+ dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
+ if (dialog.ShowDialog() == true)
+ {
+ programListLV.ItemsSource = null;
+ autoProfVM.SearchFinished += AppsSearchFinished;
+ autoProfVM.AddProgramExeLocation(dialog.FileName);
+ }
+ else
+ {
+ this.IsEnabled = true;
+ }
+ }
+ }
+}
diff --git a/DS4Windows/DS4Forms/BindingWindow.xaml b/DS4Windows/DS4Forms/BindingWindow.xaml
new file mode 100644
index 0000000..d6c5301
--- /dev/null
+++ b/DS4Windows/DS4Forms/BindingWindow.xaml
@@ -0,0 +1,559 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DS4Windows/DS4Forms/BindingWindow.xaml.cs b/DS4Windows/DS4Forms/BindingWindow.xaml.cs
new file mode 100644
index 0000000..94129c7
--- /dev/null
+++ b/DS4Windows/DS4Forms/BindingWindow.xaml.cs
@@ -0,0 +1,891 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using DS4WinWPF.DS4Forms.ViewModels;
+
+namespace DS4WinWPF.DS4Forms
+{
+ ///
+ /// Interaction logic for BindingWindow.xaml
+ ///
+ public partial class BindingWindow : Window
+ {
+ private Dictionary