using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Data; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; namespace DS4WinWPF.DS4Forms.ViewModels { public class AutoProfilesViewModel { private object _colLockobj = new object(); private ObservableCollection programColl; private AutoProfileHolder autoProfileHolder; private ProfileList profileList; private int selectedIndex = -1; private ProgramItem selectedItem; private HashSet existingapps; public ObservableCollection ProgramColl { get => programColl; } public AutoProfileHolder AutoProfileHolder { get => autoProfileHolder; } public int SelectedIndex { get => selectedIndex; set => selectedIndex = value; } public ProgramItem SelectedItem { get => selectedItem; set { selectedItem = value; CurrentItemChange?.Invoke(this, value); } } public ProfileList ProfileList { get => profileList; } public delegate void CurrentItemChangeHandler(AutoProfilesViewModel sender, ProgramItem item); public event CurrentItemChangeHandler CurrentItemChange; public event EventHandler SearchFinished; public delegate void AutoProfileStateHandler(AutoProfilesViewModel sender, bool state); public event AutoProfileStateHandler AutoProfileSystemChange; public bool RevertDefaultProfileOnUnknown { get => DS4Windows.Global.AutoProfileRevertDefaultProfile; set => DS4Windows.Global.AutoProfileRevertDefaultProfile = value; } public AutoProfilesViewModel(AutoProfileHolder autoProfileHolder, ProfileList profileList) { programColl = new ObservableCollection(); existingapps = new HashSet(); this.autoProfileHolder = autoProfileHolder; this.profileList = profileList; PopulateCurrentEntries(); BindingOperations.EnableCollectionSynchronization(programColl, _colLockobj); } private void PopulateCurrentEntries() { foreach(AutoProfileEntity entry in autoProfileHolder.AutoProfileColl) { ProgramItem item = new ProgramItem(entry.Path, entry); programColl.Add(item); existingapps.Add(entry.Path); } } public void RemoveUnchecked() { AutoProfileSystemChange?.Invoke(this, false); programColl.Clear(); existingapps.Clear(); PopulateCurrentEntries(); AutoProfileSystemChange?.Invoke(this, true); } public async void AddProgramsFromStartMenu() { AutoProfileSystemChange?.Invoke(this, false); await Task.Run(() => { AddFromStartMenu(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu) + "\\Programs"); }); SearchFinished?.Invoke(this, EventArgs.Empty); AutoProfileSystemChange?.Invoke(this, true); } public async void AddProgramsFromSteam(string location) { AutoProfileSystemChange?.Invoke(this, false); await Task.Run(() => { AddAppsFromLocation(location); }); SearchFinished?.Invoke(this, EventArgs.Empty); AutoProfileSystemChange?.Invoke(this, true); } public async void AddProgramsFromDir(string location) { AutoProfileSystemChange?.Invoke(this, false); await Task.Run(() => { AddAppsFromLocation(location); }); SearchFinished?.Invoke(this, EventArgs.Empty); AutoProfileSystemChange?.Invoke(this, true); } public async void AddProgramExeLocation(string location) { AutoProfileSystemChange?.Invoke(this, false); await Task.Run(() => { AddAppExeLocation(location); }); SearchFinished?.Invoke(this, EventArgs.Empty); AutoProfileSystemChange?.Invoke(this, true); } private void AddFromStartMenu(string path) { List lnkpaths = new List(); lnkpaths.AddRange(Directory.GetFiles(path, "*.lnk", SearchOption.AllDirectories)); lnkpaths.AddRange(Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu) + "\\Programs", "*.lnk", SearchOption.AllDirectories)); List exepaths = new List(); foreach(string link in lnkpaths) { string target = GetTargetPath(link); exepaths.Add(target); } ScanApps(exepaths); } private void AddAppsFromLocation(string path) { List exepaths = new List(); exepaths.AddRange(Directory.GetFiles(path, "*.exe", SearchOption.AllDirectories)); ScanApps(exepaths); } private void AddAppExeLocation(string path) { List exepaths = new List(); exepaths.Add(path); ScanApps(exepaths, checkexisting: false, skipsetupapps: false); } private void ScanApps(List exepaths, bool checkexisting = true, bool skipsetupapps = true) { foreach (string target in exepaths) { bool skip = !File.Exists(target) || Path.GetExtension(target) != ".exe"; skip = skip || (skipsetupapps && (target.Contains("etup") || target.Contains("dotnet") || target.Contains("SETUP") || target.Contains("edist") || target.Contains("nstall") || string.IsNullOrEmpty(target))); skip = skip || (checkexisting && existingapps.Contains(target)); if (!skip) { ProgramItem item = new ProgramItem(target); /*if (autoProfileHolder.AutoProfileDict.TryGetValue(target, out AutoProfileEntity autoEntity)) { item.MatchedAutoProfile = autoEntity; } */ programColl.Add(item); existingapps.Add(target); } } } public void CreateAutoProfileEntry(ProgramItem item) { if (item.MatchedAutoProfile == null) { AutoProfileEntity tempEntry = new AutoProfileEntity(item.Path, item.Title); tempEntry.Turnoff = item.Turnoff; int tempindex = item.SelectedIndexCon1; tempEntry.ProfileNames[0] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; tempindex = item.SelectedIndexCon2; tempEntry.ProfileNames[1] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; tempindex = item.SelectedIndexCon3; tempEntry.ProfileNames[2] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; tempindex = item.SelectedIndexCon4; tempEntry.ProfileNames[3] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; item.MatchedAutoProfile = tempEntry; autoProfileHolder.AutoProfileColl.Add(item.MatchedAutoProfile); } } public void PersistAutoProfileEntry(ProgramItem item) { if (item.MatchedAutoProfile != null) { AutoProfileEntity tempEntry = item.MatchedAutoProfile; int tempindex = item.SelectedIndexCon1; tempEntry.ProfileNames[0] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; tempindex = item.SelectedIndexCon2; tempEntry.ProfileNames[1] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; tempindex = item.SelectedIndexCon3; tempEntry.ProfileNames[2] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; tempindex = item.SelectedIndexCon4; tempEntry.ProfileNames[3] = tempindex > 0 ? profileList.ProfileListCol[tempindex - 1].Name : AutoProfileEntity.NONE_STRING; } } public void RemoveAutoProfileEntry(ProgramItem item) { autoProfileHolder.AutoProfileColl.Remove(item.MatchedAutoProfile); item.MatchedAutoProfile = null; } private string GetTargetPath(string filePath) { string targetPath = ResolveMsiShortcut(filePath); if (targetPath == null) { targetPath = ResolveShortcut(filePath); } return targetPath; } public string ResolveShortcutAndArgument(string filePath) { Type t = Type.GetTypeFromCLSID(new Guid("72C24DD5-D70A-438B-8A42-98424B88AFB8")); // Windows Script Host Shell Object dynamic shell = Activator.CreateInstance(t); string result; try { var shortcut = shell.CreateShortcut(filePath); result = shortcut.TargetPath + " " + shortcut.Arguments; Marshal.FinalReleaseComObject(shortcut); } catch (COMException) { // A COMException is thrown if the file is not a valid shortcut (.lnk) file result = null; } finally { Marshal.FinalReleaseComObject(shell); } return result; } public string ResolveMsiShortcut(string file) { StringBuilder product = new StringBuilder(NativeMethods2.MaxGuidLength + 1); StringBuilder feature = new StringBuilder(NativeMethods2.MaxFeatureLength + 1); StringBuilder component = new StringBuilder(NativeMethods2.MaxGuidLength + 1); NativeMethods2.MsiGetShortcutTarget(file, product, feature, component); int pathLength = NativeMethods2.MaxPathLength; StringBuilder path = new StringBuilder(pathLength); NativeMethods2.InstallState installState = NativeMethods2.MsiGetComponentPath(product.ToString(), component.ToString(), path, ref pathLength); if (installState == NativeMethods2.InstallState.Local) { return path.ToString(); } else { return null; } } public string ResolveShortcut(string filePath) { Type t = Type.GetTypeFromCLSID(new Guid("72C24DD5-D70A-438B-8A42-98424B88AFB8")); // Windows Script Host Shell Object dynamic shell = Activator.CreateInstance(t); string result; try { var shortcut = shell.CreateShortcut(filePath); result = shortcut.TargetPath; Marshal.FinalReleaseComObject(shortcut); } catch (COMException) { // A COMException is thrown if the file is not a valid shortcut (.lnk) file result = null; } finally { Marshal.FinalReleaseComObject(shell); } return result; } } public class ProgramItem { private string path; private string path_lowercase; private string filename; private string title; private string title_lowercase; private AutoProfileEntity matchedAutoProfile; private ImageSource exeicon; private bool turnoff; public string Path { get => path; set => path = value; } public string Title { get => title; set { if (title == value) return; title = value; if (matchedAutoProfile != null) { matchedAutoProfile.Title = value; } TitleChanged?.Invoke(this, EventArgs.Empty); } } public event EventHandler TitleChanged; public AutoProfileEntity MatchedAutoProfile { get => matchedAutoProfile; set { matchedAutoProfile = value; if (matchedAutoProfile != null) { title = matchedAutoProfile.Title ?? string.Empty; title_lowercase = title.ToLower(); } MatchedAutoProfileChanged?.Invoke(this, EventArgs.Empty); } } public event EventHandler MatchedAutoProfileChanged; public delegate void AutoProfileHandler(ProgramItem sender, bool added); public event AutoProfileHandler AutoProfileAction; public string Filename { get => filename; } public ImageSource Exeicon { get => exeicon; } public bool Turnoff { get { bool result = turnoff; if (matchedAutoProfile != null) { result = matchedAutoProfile.Turnoff; } return result; } set { turnoff = value; if (matchedAutoProfile != null) { matchedAutoProfile.Turnoff = value; } TurnoffChanged?.Invoke(this, EventArgs.Empty); } } public event EventHandler TurnoffChanged; public bool Exists { get => matchedAutoProfile != null; } public event EventHandler ExistsChanged; private int selectedIndexCon1 = 0; private int selectedIndexCon2 = 0; private int selectedIndexCon3 = 0; private int selectedIndexCon4 = 0; public int SelectedIndexCon1 { get => selectedIndexCon1; set { if (selectedIndexCon1 == value) return; selectedIndexCon1 = value; } } public int SelectedIndexCon2 { get => selectedIndexCon2; set { if (selectedIndexCon2 == value) return; selectedIndexCon2 = value; } } public int SelectedIndexCon3 { get => selectedIndexCon3; set { if (selectedIndexCon3 == value) return; selectedIndexCon3 = value; } } public int SelectedIndexCon4 { get => selectedIndexCon4; set { if (selectedIndexCon4 == value) return; selectedIndexCon4 = value; } } public ProgramItem(string path, AutoProfileEntity autoProfileEntity = null) { this.path = path; this.path_lowercase = path.ToLower(); filename = System.IO.Path.GetFileNameWithoutExtension(path); this.matchedAutoProfile = autoProfileEntity; if (autoProfileEntity != null) { title = autoProfileEntity.Title; title_lowercase = title.ToLower(); turnoff = autoProfileEntity.Turnoff; } if (File.Exists(path)) { using (Icon ico = Icon.ExtractAssociatedIcon(path)) { exeicon = Imaging.CreateBitmapSourceFromHIcon(ico.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); exeicon.Freeze(); } } MatchedAutoProfileChanged += ProgramItem_MatchedAutoProfileChanged; } private void ProgramItem_MatchedAutoProfileChanged(object sender, EventArgs e) { if (matchedAutoProfile == null) { selectedIndexCon1 = 0; selectedIndexCon2 = 0; selectedIndexCon3 = 0; selectedIndexCon4 = 0; } ExistsChanged?.Invoke(this, EventArgs.Empty); } } [SuppressUnmanagedCodeSecurity] class NativeMethods2 { [DllImport("msi.dll", CharSet = CharSet.Auto)] public static extern uint MsiGetShortcutTarget(string targetFile, StringBuilder productCode, StringBuilder featureID, StringBuilder componentCode); [DllImport("msi.dll", CharSet = CharSet.Auto)] public static extern InstallState MsiGetComponentPath(string productCode, string componentCode, StringBuilder componentPath, ref int componentPathBufferSize); public const int MaxFeatureLength = 38; public const int MaxGuidLength = 38; public const int MaxPathLength = 1024; public enum InstallState { NotUsed = -7, BadConfig = -6, Incomplete = -5, SourceAbsent = -4, MoreData = -3, InvalidArg = -2, Unknown = -1, Broken = 0, Advertised = 1, Removed = 1, Absent = 2, Local = 3, Source = 4, Default = 5 } } }