Support auto-profile matching using both program path and application window title values. Support wildchar string match logic in auto-profile path and window title values. Re-wrote auto-profile checking thread to be less RAM hungry and better performance (caching to remember the previous application and window handle values)

This commit is contained in:
mika-n 2019-07-11 23:58:03 +03:00
parent 19a96973ff
commit e9d9e9f428
12 changed files with 947 additions and 296 deletions

View File

@ -39,12 +39,20 @@ namespace DS4Windows.Forms
protected CheckBox[] linkedProfileCB;
NonFormTimer hotkeysTimer = null;// new NonFormTimer();
NonFormTimer autoProfilesTimer = null;// new NonFormTimer();
string tempProfileProgram = string.Empty;
double dpix, dpiy;
List<ProgramPathItem> programpaths = new List<ProgramPathItem>();
List<string> profilenames = new List<string>();
List<string> programpaths = new List<string>();
List<string>[] proprofiles;
List<bool> turnOffTempProfiles;
ProgramPathItem tempProfileProgram = null;
public static int autoProfileDebugLogLevel = 0; // 0=Dont log debug messages about active process and window titles to GUI Log screen. 1=Show debug log messages
private static IntPtr prevForegroundWnd = IntPtr.Zero;
private static uint prevForegroundProcessID = 0;
private static string prevForegroundWndTitleName = string.Empty;
private static string prevForegroundProcessName = string.Empty;
private static StringBuilder autoProfileCheckTextBuilder = null;
private bool systemShutdown = false;
private bool wasrunning = false;
@ -87,6 +95,9 @@ namespace DS4Windows.Forms
[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);
public DS4Form(string[] args)
{
Global.FindConfigLocation();
@ -546,20 +557,87 @@ namespace DS4Windows.Forms
}
}
public static string GetTopWindowName()
public static bool GetTopWindowName(out string topProcessName, out string topWndTitleName, bool autoProfileTimerCheck = false)
{
IntPtr hWnd = GetForegroundWindow();
uint lpdwProcessId;
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(autoProfileTimerCheck)
{
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);
IntPtr hProcess = OpenProcess(0x0410, false, lpdwProcessId);
if (autoProfileTimerCheck)
{
if (autoProfileCheckTextBuilder == null) autoProfileCheckTextBuilder = new StringBuilder(1000);
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();
}
else
{
// Caller function was not the autoprofile timer check thread, so create a new buffer to make this call thread safe and always query process and window title names.
// Note! At the moment DS4Win app doesn't call this method with autoProfileTimerCheck=false option, but this is here just for potential future usage.
StringBuilder text = new StringBuilder(1000);
GetModuleFileNameEx(hProcess, IntPtr.Zero, text, text.Capacity);
CloseHandle(hProcess);
hProcess = OpenProcess(0x0410, false, lpdwProcessId);
if (hProcess != IntPtr.Zero) GetModuleFileNameEx(hProcess, IntPtr.Zero, text, text.Capacity);
else text.Clear();
topProcessName = text.ToString();
return text.ToString();
GetWindowText(hWnd, text, text.Capacity);
topWndTitleName = text.ToString();
}
if (hProcess != IntPtr.Zero) CloseHandle(hProcess);
if(DS4Form.autoProfileDebugLogLevel > 0 )
AppLogger.LogToGui($"DEBUG: Auto-Profile. PID={lpdwProcessId} Path={topProcessName} | WND={hWnd} Title={topWndTitleName}", false);
return true;
}
private void PowerEventArrive(object sender, EventArrivedEventArgs e)
@ -651,27 +729,63 @@ namespace DS4Windows.Forms
private void CheckAutoProfiles(object sender, EventArgs e)
{
string topProcessName, topWindowTitle;
string[] newProfileName = new string[4] { String.Empty, String.Empty, String.Empty, String.Empty };
bool turnOffDS4WinApp = false;
ProgramPathItem matchingProgramPathItem = null;
autoProfilesTimer.Stop();
//Check for process for auto profiles
if (string.IsNullOrEmpty(tempProfileProgram))
if (GetTopWindowName(out topProcessName, out topWindowTitle, true))
{
string windowName = GetTopWindowName().ToLower().Replace('/', '\\');
// 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 = programpaths.Count; i < pathsLen; i++)
{
string name = programpaths[i].ToLower().Replace('/', '\\');
if (name == windowName)
if (programpaths[i].IsMatch(topProcessName, topWindowTitle))
{
if (DS4Form.autoProfileDebugLogLevel > 0)
AppLogger.LogToGui($"DEBUG: Auto-Profile. Rule#{i+1} Path={programpaths[i].path} Title={programpaths[i].title}", false);
for (int j = 0; j < 4; j++)
{
if (proprofiles[j][i] != "(none)" && proprofiles[j][i] != Properties.Resources.noneProfile)
{
LoadTempProfile(j, proprofiles[j][i], true, Program.rootHub); // j is controller index, i is filename
//if (LaunchProgram[j] != string.Empty) Process.Start(LaunchProgram[j]);
newProfileName[j] = proprofiles[j][i]; // j is controller index, i is filename
}
}
if (turnOffTempProfiles[i])
// Matching autoprofile rule found
turnOffDS4WinApp = turnOffTempProfiles[i];
matchingProgramPathItem = programpaths[i];
break;
}
}
if (matchingProgramPathItem != 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++)
{
if (newProfileName[j] != String.Empty)
{
if ((Global.useTempProfile[j] && newProfileName[j] != Global.tempprofilename[j]) || (!Global.useTempProfile[j] && newProfileName[j] != Global.ProfilePath[j]))
{
if (DS4Form.autoProfileDebugLogLevel > 0)
AppLogger.LogToGui($"DEBUG: Auto-Profile. LoadProfile Controller {j+1}={newProfileName[j]}", false);
LoadTempProfile(j, newProfileName[j], true, Program.rootHub); // j is controller index, i is filename
//if (LaunchProgram[j] != string.Empty) Process.Start(LaunchProgram[j]);
}
else
{
if (DS4Form.autoProfileDebugLogLevel > 0)
AppLogger.LogToGui($"DEBUG: Auto-Profile. LoadProfile Controller {j + 1}={newProfileName[j]} (already loaded)", false);
}
}
}
if (turnOffDS4WinApp)
{
turnOffTemp = true;
if (btnStartStop.Text == Properties.Resources.StopText)
@ -681,7 +795,8 @@ namespace DS4Windows.Forms
ChangeAutoProfilesStatus(false);
ChangeHotkeysStatus(false);
this.Invoke((System.Action)(() => {
this.Invoke((System.Action)(() =>
{
this.changingService = true;
BtnStartStop_Clicked();
}));
@ -701,19 +816,23 @@ namespace DS4Windows.Forms
}
}
tempProfileProgram = name;
break;
tempProfileProgram = matchingProgramPathItem;
}
}
}
else
else if (tempProfileProgram != null)
{
string windowName = GetTopWindowName().ToLower().Replace('/', '\\');
if (tempProfileProgram != windowName)
{
tempProfileProgram = string.Empty;
// The current active program doesn't match any of the programs in autoprofile path list.
// Unload temp profile if controller is not using the default profile already.
tempProfileProgram = null;
for (int j = 0; j < 4; j++)
{
if (Global.useTempProfile[j])
{
if (DS4Form.autoProfileDebugLogLevel > 0)
AppLogger.LogToGui($"DEBUG: Auto-Profile. RestoreProfile Controller {j + 1}={Global.ProfilePath[j]} (default)", false);
LoadProfile(j, false, Program.rootHub);
}
}
if (turnOffTemp)
{
@ -746,18 +865,25 @@ namespace DS4Windows.Forms
doc.Load(appdatapath + "\\Auto Profiles.xml");
XmlNodeList programslist = doc.SelectNodes("Programs/Program");
foreach (XmlNode x in programslist)
programpaths.Add(x.Attributes["path"].Value);
programpaths.Add(new ProgramPathItem(x.Attributes["path"]?.Value, x.Attributes["title"]?.Value));
foreach (string s in programpaths)
int nodeIdx=0;
foreach (ProgramPathItem pathItem in programpaths)
{
XmlNode item;
nodeIdx++;
for (int i = 0; i < 4; i++)
{
proprofiles[i].Add(doc.SelectSingleNode("/Programs/Program[@path=\"" + s + "\"]"
+ "/Controller" + (i + 1)).InnerText);
item = doc.SelectSingleNode($"/Programs/Program[{nodeIdx}]/Controller{i+1}");
if (item != null)
proprofiles[i].Add(item.InnerText);
else
proprofiles[i].Add("(none)");
}
XmlNode item = doc.SelectSingleNode("/Programs/Program[@path=\"" + s + "\"]"
+ "/TurnOff");
item = doc.SelectSingleNode($"/Programs/Program[{nodeIdx}]/TurnOff");
bool turnOff;
if (item != null && bool.TryParse(item.InnerText, out turnOff))
turnOffTempProfiles.Add(turnOff);
@ -2701,4 +2827,78 @@ Properties.Resources.DS4Update, MessageBoxButtons.YesNo, MessageBoxIcon.Question
FlashWhenLateAt = (int)Math.Round(nUDLatency.Value);
}
}
//
// Class to store autoprofile path and title data. Path and Title are pre-stored as lowercase versions (case insensitive search) to speed up IsMatch method in autoprofile timer calls.
// AutoProfile thread monitors active processes and windows. Autoprofile search rule can define just a process path or both path and window title search keywords.
// Keyword syntax: xxxx = exact matach, ^xxxx = match to beginning of path or title string. xxxx$ = match to end of string. *xxxx = contains in a string search
//
public class ProgramPathItem
{
public string path;
public string title;
private string path_lowercase;
private string title_lowercase;
public ProgramPathItem(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.
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;
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;
}
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;
}
}
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -631,4 +631,58 @@
<data name="lbID.Text" xml:space="preserve">
<value>ID</value>
</data>
<data name="lbPad2.Text" xml:space="preserve">
<value>Ohjain 2</value>
</data>
<data name="lbPad3.Text" xml:space="preserve">
<value>Ohjain 3</value>
</data>
<data name="lbPad4.Text" xml:space="preserve">
<value>Ohjain 4</value>
</data>
<data name="discon1toolStripMenuItem.Text" xml:space="preserve">
<value>Katkaise yhteys ohjain 1</value>
</data>
<data name="discon2ToolStripMenuItem.Text" xml:space="preserve">
<value>Katkaise yhteys ohjain 2</value>
</data>
<data name="discon3ToolStripMenuItem.Text" xml:space="preserve">
<value>Katkaise yhteys ohjain 3</value>
</data>
<data name="discon4ToolStripMenuItem.Text" xml:space="preserve">
<value>Katkaise yhteys ohjain 4</value>
</data>
<data name="lbLinkProfile.Text" xml:space="preserve">
<value>Linkitä profiili/ID</value>
</data>
<data name="mintoTaskCheckBox.Text" xml:space="preserve">
<value>Minimoi tehtäväpalkkiin</value>
</data>
<data name="openProgramFolderToolStripMenuItem.Text" xml:space="preserve">
<value>Avaa ohjelmakansio</value>
</data>
<data name="label2.Text" xml:space="preserve">
<value>Portti</value>
</data>
<data name="runStartProgRadio.Text" xml:space="preserve">
<value>Ohjelmana</value>
</data>
<data name="label1.Text" xml:space="preserve">
<value>Avaustapa:</value>
</data>
<data name="ckUdpServ.Text" xml:space="preserve">
<value>UDP palvelin</value>
</data>
<data name="runStartTaskRadio.Text" xml:space="preserve">
<value>Ajoitettuna tehtävänä</value>
</data>
<data name="cBCustomSteam.Text" xml:space="preserve">
<value>Käytä vaihtoehtoista Steam-kansiota</value>
</data>
<data name="cBUseWhiteIcon.Text" xml:space="preserve">
<value>Valkoinen DS4Windows-kuvake</value>
</data>
<data name="disconToolStripMenuItem.Text" xml:space="preserve">
<value>Yhteyskatkaisun menu</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -126,4 +126,7 @@
<data name="tBProfile.Text" xml:space="preserve">
<value>&lt;Uusi nimi tähän&gt;</value>
</data>
<data name="$this.Text" xml:space="preserve">
<value>Kirjoita uuden profiilin nimi</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -640,4 +640,19 @@
<data name="tPDeadzone.Text" xml:space="preserve">
<value>Deadzone</value>
</data>
<data name="lblSteeringWheelEmulationRange.Text" xml:space="preserve">
<value>Rattiohjaimen kääntösäde</value>
</data>
<data name="lblSteeringWheelEmulationAxis.Text" xml:space="preserve">
<value>Rattiohjaimen akseli</value>
</data>
<data name="lbL2TrackS.Text" xml:space="preserve">
<value>Ohjaimen lukemat</value>
</data>
<data name="btnSteeringWheelEmulationCalibrate.Text" xml:space="preserve">
<value>Kalibroi...</value>
</data>
<data name="rBTPMouse.Text" xml:space="preserve">
<value>Käytä hiirenä</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -201,4 +201,7 @@
<data name="lbUnloadTipKey.Text" xml:space="preserve">
<value>Kytke pois painamalla</value>
</data>
<data name="cbProfileAutoUntrigger.Text" xml:space="preserve">
<value>Poiskytkentä liipaisimen vapautuksella</value>
</data>
</root>

View File

@ -31,7 +31,6 @@
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(WinProgs));
this.bnAddPrograms = new System.Windows.Forms.Button();
this.lBProgramPath = new System.Windows.Forms.Label();
this.cBProfile1 = new System.Windows.Forms.ComboBox();
this.cBProfile2 = new System.Windows.Forms.ComboBox();
this.cBProfile3 = new System.Windows.Forms.ComboBox();
@ -47,6 +46,7 @@
this.lVPrograms = new System.Windows.Forms.ListView();
this.nameHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.PathHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.WndTitleHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.pBProfilesTip = new System.Windows.Forms.Label();
this.bnHideUnchecked = new System.Windows.Forms.Button();
this.cMSPrograms = new System.Windows.Forms.ContextMenuStrip(this.components);
@ -56,6 +56,10 @@
this.addDirectoryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.browseForOtherProgramsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.cBTurnOffDS4W = new System.Windows.Forms.CheckBox();
this.cBAutoProfileDebugLog = new System.Windows.Forms.CheckBox();
this.tBPath = new System.Windows.Forms.TextBox();
this.tBWndTitle = new System.Windows.Forms.TextBox();
this.lBDividerLine1 = new System.Windows.Forms.Label();
this.cMSPrograms.SuspendLayout();
this.SuspendLayout();
//
@ -66,12 +70,6 @@
this.bnAddPrograms.UseVisualStyleBackColor = true;
this.bnAddPrograms.Click += new System.EventHandler(this.bnAddPrograms_Click);
//
// lBProgramPath
//
resources.ApplyResources(this.lBProgramPath, "lBProgramPath");
this.lBProgramPath.Name = "lBProgramPath";
this.lBProgramPath.TextChanged += new System.EventHandler(this.lBProgramPath_TextChanged);
//
// cBProfile1
//
resources.ApplyResources(this.cBProfile1, "cBProfile1");
@ -154,7 +152,8 @@
this.lVPrograms.CheckBoxes = true;
this.lVPrograms.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.nameHeader,
this.PathHeader});
this.PathHeader,
this.WndTitleHeader});
this.lVPrograms.FullRowSelect = true;
this.lVPrograms.HideSelection = false;
this.lVPrograms.LargeImageList = this.iLIcons;
@ -164,7 +163,6 @@
this.lVPrograms.SmallImageList = this.iLIcons;
this.lVPrograms.UseCompatibleStateImageBehavior = false;
this.lVPrograms.View = System.Windows.Forms.View.Details;
this.lVPrograms.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.listView1_ItemCheck);
this.lVPrograms.SelectedIndexChanged += new System.EventHandler(this.lBProgramPath_SelectedIndexChanged);
//
// nameHeader
@ -175,6 +173,10 @@
//
resources.ApplyResources(this.PathHeader, "PathHeader");
//
// WndTitleHeader
//
resources.ApplyResources(this.WndTitleHeader, "WndTitleHeader");
//
// pBProfilesTip
//
resources.ApplyResources(this.pBProfilesTip, "pBProfilesTip");
@ -239,13 +241,42 @@
this.cBTurnOffDS4W.UseVisualStyleBackColor = true;
this.cBTurnOffDS4W.CheckedChanged += new System.EventHandler(this.cBTurnOffDS4W_CheckedChanged);
//
// cBAutoProfileDebugLog
//
resources.ApplyResources(this.cBAutoProfileDebugLog, "cBAutoProfileDebugLog");
this.cBAutoProfileDebugLog.Name = "cBAutoProfileDebugLog";
this.cBAutoProfileDebugLog.UseVisualStyleBackColor = true;
this.cBAutoProfileDebugLog.CheckedChanged += new System.EventHandler(this.cBAutoProfileDebugLog_CheckedChanged);
//
// tBPath
//
resources.ApplyResources(this.tBPath, "tBPath");
this.tBPath.Name = "tBPath";
this.tBPath.TextChanged += new System.EventHandler(this.tBPath_TextChanged);
//
// tBWndTitle
//
resources.ApplyResources(this.tBWndTitle, "tBWndTitle");
this.tBWndTitle.Name = "tBWndTitle";
this.tBWndTitle.TextChanged += new System.EventHandler(this.tBPath_TextChanged);
//
// lBDividerLine1
//
resources.ApplyResources(this.lBDividerLine1, "lBDividerLine1");
this.lBDividerLine1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.lBDividerLine1.Name = "lBDividerLine1";
//
// WinProgs
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.Control;
this.Controls.Add(this.cBTurnOffDS4W);
this.Controls.Add(this.pBProfilesTip);
this.Controls.Add(this.lBDividerLine1);
this.Controls.Add(this.tBWndTitle);
this.Controls.Add(this.tBPath);
this.Controls.Add(this.cBAutoProfileDebugLog);
this.Controls.Add(this.cBTurnOffDS4W);
this.Controls.Add(this.bnHideUnchecked);
this.Controls.Add(this.bnAddPrograms);
this.Controls.Add(this.lVPrograms);
@ -257,7 +288,6 @@
this.Controls.Add(this.cBProfile3);
this.Controls.Add(this.cBProfile2);
this.Controls.Add(this.cBProfile1);
this.Controls.Add(this.lBProgramPath);
this.Controls.Add(this.bnDelete);
this.Controls.Add(this.bnSave);
this.Name = "WinProgs";
@ -270,7 +300,6 @@
#endregion
private System.Windows.Forms.Button bnAddPrograms;
private System.Windows.Forms.Label lBProgramPath;
private System.Windows.Forms.ComboBox cBProfile1;
private System.Windows.Forms.ComboBox cBProfile2;
private System.Windows.Forms.ComboBox cBProfile3;
@ -295,5 +324,10 @@
private System.Windows.Forms.ToolStripMenuItem browseForOtherProgramsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem addDirectoryToolStripMenuItem;
private System.Windows.Forms.CheckBox cBTurnOffDS4W;
private System.Windows.Forms.CheckBox cBAutoProfileDebugLog;
private System.Windows.Forms.ColumnHeader WndTitleHeader;
private System.Windows.Forms.TextBox tBPath;
private System.Windows.Forms.TextBox tBWndTitle;
private System.Windows.Forms.Label lBDividerLine1;
}
}

View File

@ -16,14 +16,13 @@ namespace DS4Windows.Forms
{
public partial class WinProgs : Form
{
ToolTip tp = new ToolTip();
ComboBox[] cbs;
public DS4Form form;
//C:\ProgramData\Microsoft\Windows\Start Menu\Programs
string steamgamesdir, origingamesdir;
protected String m_Profile = Global.appdatapath + "\\Auto Profiles.xml";
protected XmlDocument m_Xdoc = new XmlDocument();
List<string> programpaths = new List<string>();
ProgramPathItem selectedProgramPathItem = null;
List<string> lodsf = new List<string>();
bool appsloaded = false;
public const string steamCommx86Loc = @"C:\Program Files (x86)\Steam\steamapps\common";
@ -33,7 +32,9 @@ namespace DS4Windows.Forms
public WinProgs(string[] oc, DS4Form main)
{
ToolTip tp = new ToolTip();
InitializeComponent();
openProgram.Filter = Properties.Resources.Programs+"|*.exe|" + Properties.Resources.Shortcuts + "|*.lnk";
form = main;
cbs = new ComboBox[4] { cBProfile1, cBProfile2, cBProfile3, cBProfile4 };
@ -44,6 +45,10 @@ namespace DS4Windows.Forms
cbs[i].SelectedIndex = cbs[i].Items.Count - 1;
}
tp.SetToolTip(cBAutoProfileDebugLog, Properties.Resources.ShowAutoProfileDebugLogTip);
tp.SetToolTip(tBPath, Properties.Resources.AutoProfilePathAndWindowTitleEditTip);
tp.SetToolTip(tBWndTitle, Properties.Resources.AutoProfilePathAndWindowTitleEditTip);
if (!File.Exists(Global.appdatapath + @"\Auto Profiles.xml"))
Create();
@ -64,6 +69,10 @@ namespace DS4Windows.Forms
origingamesdir = originLoc;
else
cMSPrograms.Items.Remove(addOriginGamesToolStripMenuItem);
cBAutoProfileDebugLog.Checked = (DS4Form.autoProfileDebugLogLevel > 0);
selectedProgramPathItem = null;
UpdateProfileComboListValues(null);
}
public bool Create()
@ -73,19 +82,20 @@ namespace DS4Windows.Forms
try
{
XmlNode Node;
XmlDocument doc = new XmlDocument();
Node = m_Xdoc.CreateXmlDeclaration("1.0", "utf-8", String.Empty);
m_Xdoc.AppendChild(Node);
Node = doc.CreateXmlDeclaration("1.0", "utf-8", String.Empty);
doc.AppendChild(Node);
Node = m_Xdoc.CreateComment(String.Format(" Auto-Profile Configuration Data. {0} ", DateTime.Now));
m_Xdoc.AppendChild(Node);
Node = doc.CreateComment(String.Format(" Auto-Profile Configuration Data. {0} ", DateTime.Now));
doc.AppendChild(Node);
Node = m_Xdoc.CreateWhitespace("\r\n");
m_Xdoc.AppendChild(Node);
Node = doc.CreateWhitespace("\r\n");
doc.AppendChild(Node);
Node = m_Xdoc.CreateNode(XmlNodeType.Element, "Programs", "");
m_Xdoc.AppendChild(Node);
m_Xdoc.Save(m_Profile);
Node = doc.CreateNode(XmlNodeType.Element, "Programs", "");
doc.AppendChild(Node);
doc.Save(m_Profile);
}
catch { Saved = false; }
@ -100,40 +110,61 @@ namespace DS4Windows.Forms
}
public void LoadP()
{
try
{
XmlDocument doc = new XmlDocument();
programpaths.Clear();
iLIcons.Images.Clear();
if (!File.Exists(Global.appdatapath + "\\Auto Profiles.xml"))
return;
doc.Load(Global.appdatapath + "\\Auto Profiles.xml");
XmlNodeList programslist = doc.SelectNodes("Programs/Program");
foreach (XmlNode x in programslist)
programpaths.Add(x.Attributes["path"].Value);
int index;
ListViewItem lvi;
string progPath, progTitle;
lVPrograms.BeginUpdate();
int index = 0;
foreach (string st in programpaths)
foreach (XmlNode progNode in programslist)
{
if (!string.IsNullOrEmpty(st))
progPath = progNode.Attributes["path"]?.Value;
progTitle = progNode.Attributes["title"]?.Value;
if (!string.IsNullOrEmpty(progPath))
{
if (File.Exists(st))
index = iLIcons.Images.IndexOfKey(progPath);
if (index < 0 && File.Exists(progPath))
{
iLIcons.Images.Add(Icon.ExtractAssociatedIcon(st));
iLIcons.Images.Add(progPath, Icon.ExtractAssociatedIcon(progPath));
index = iLIcons.Images.Count - 1;
}
ListViewItem lvi = new ListViewItem(Path.GetFileNameWithoutExtension(st), index);
if(index >= 0)
lvi = new ListViewItem(Path.GetFileNameWithoutExtension(progPath), index);
else
lvi = new ListViewItem(Path.GetFileNameWithoutExtension(progPath));
lvi.Checked = true;
lvi.ToolTipText = st;
lvi.SubItems.Add(st);
lvi.SubItems.Add(progPath);
if (!String.IsNullOrEmpty(progTitle))
lvi.SubItems.Add(progTitle);
lVPrograms.Items.Add(lvi);
}
index++;
}
}
catch (Exception e)
{
// Eat all exceptions while reading auto-profile file because we don't want to crash DS4Win app just because there are some permissions or other issues with the file
AppLogger.LogToGui($"ERROR. Auto-profile XML file {Global.appdatapath}\\Auto Profiles.xml reading failed. {e.Message}", true);
}
finally
{
lVPrograms.EndUpdate();
}
}
private void GetApps(string path)
{
@ -179,11 +210,16 @@ namespace DS4Windows.Forms
appsloaded = true;
}
void AddLoadedApps()
int AddLoadedApps(bool autoremoveSetupApps = true)
{
int numOfAppsAdded = 0;
if (appsloaded)
{
bnAddPrograms.Text = Properties.Resources.AddingToList;
if (autoremoveSetupApps)
{
for (int i = lodsf.Count - 1; i >= 0; i--)
{
if (lodsf[i].Contains("etup") || lodsf[i].Contains("dotnet") || lodsf[i].Contains("SETUP")
@ -191,28 +227,56 @@ namespace DS4Windows.Forms
lodsf.RemoveAt(i);
}
// Remove existing program entries from a list of new app paths (no need to add the same path twice)
for (int i = lodsf.Count - 1; i >= 0; i--)
{
for (int j = programpaths.Count - 1; j >= 0; j--)
for (int j = 0; j < lVPrograms.Items.Count; j++)
{
if(string.Equals(lVPrograms.Items[j].SubItems[1].Text, lodsf[i], StringComparison.OrdinalIgnoreCase))
{
if (lodsf[i].ToLower().Replace('/', '\\') == programpaths[j].ToLower().Replace('/', '\\'))
lodsf.RemoveAt(i);
break;
}
}
}
}
try
{
ListViewItem lvi;
int index;
lVPrograms.BeginUpdate();
foreach (string st in lodsf)
{
if (File.Exists(st))
{
int index = programpaths.IndexOf(st);
iLIcons.Images.Add(Icon.ExtractAssociatedIcon(st));
ListViewItem lvi = new ListViewItem(Path.GetFileNameWithoutExtension(st), iLIcons.Images.Count + index);
index = iLIcons.Images.IndexOfKey(st);
if (index < 0)
{
iLIcons.Images.Add(st, Icon.ExtractAssociatedIcon(st));
index = iLIcons.Images.Count - 1;
}
if(index >= 0)
lvi = new ListViewItem(Path.GetFileNameWithoutExtension(st), index);
else
lvi = new ListViewItem(Path.GetFileNameWithoutExtension(st));
lvi.SubItems.Add(st);
lvi.ToolTipText = st;
lVPrograms.Items.Add(lvi);
numOfAppsAdded++;
}
}
}
catch (Exception e)
{
// Eat all exceptions while processing added apps because we don't want to crash DS4Win app just because there are some permissions or other issues with the file
AppLogger.LogToGui($"ERROR. Failed to add selected applications to an auto-profile list. {e.Message}", true);
}
finally
{
lVPrograms.EndUpdate();
bnAddPrograms.Text = Properties.Resources.AddPrograms;
@ -221,61 +285,127 @@ namespace DS4Windows.Forms
}
}
public void Save(string name)
return numOfAppsAdded;
}
private XmlNode FindProgramXMLItem(XmlDocument doc, string programPath, string windowTitle)
{
m_Xdoc.Load(m_Profile);
// Try to find a specified programPath+windowTitle program entry from an autoprofile XML file list
foreach (XmlNode item in doc.SelectNodes("/Programs/Program"))
{
XmlAttribute xmlAttrTitle = item.Attributes["title"];
if(item.Attributes["path"].InnerText == programPath)
{
if (String.IsNullOrEmpty(windowTitle) && (xmlAttrTitle == null || String.IsNullOrEmpty(xmlAttrTitle.InnerText)))
return item;
else if (!String.IsNullOrEmpty(windowTitle) && xmlAttrTitle != null && xmlAttrTitle.InnerText == windowTitle)
return item;
}
}
return null;
}
public void Save(ProgramPathItem progPathItem)
{
XmlDocument doc = new XmlDocument();
XmlNode Node;
string newPath, newTitle;
Node = m_Xdoc.CreateComment(String.Format(" Auto-Profile Configuration Data. {0} ", DateTime.Now));
foreach (XmlNode node in m_Xdoc.SelectNodes("//comment()"))
node.ParentNode.ReplaceChild(Node, node);
newPath = tBPath.Text.Trim();
newTitle = tBWndTitle.Text;
Node = m_Xdoc.SelectSingleNode("Programs");
string programname;
programname = Path.GetFileNameWithoutExtension(name);
XmlElement el = m_Xdoc.CreateElement("Program");
el.SetAttribute("path", name);
el.AppendChild(m_Xdoc.CreateElement("Controller1")).InnerText = cBProfile1.Text;
el.AppendChild(m_Xdoc.CreateElement("Controller2")).InnerText = cBProfile2.Text;
el.AppendChild(m_Xdoc.CreateElement("Controller3")).InnerText = cBProfile3.Text;
el.AppendChild(m_Xdoc.CreateElement("Controller4")).InnerText = cBProfile4.Text;
el.AppendChild(m_Xdoc.CreateElement("TurnOff")).InnerText = cBTurnOffDS4W.Checked.ToString();
if (progPathItem == null || String.IsNullOrEmpty(progPathItem.path) || String.IsNullOrEmpty(newPath))
return;
try
{
XmlNode oldxmlprocess = m_Xdoc.SelectSingleNode("/Programs/Program[@path=\"" + lBProgramPath.Text + "\"]");
Node.ReplaceChild(el, oldxmlprocess);
}
catch { Node.AppendChild(el); }
doc.Load(m_Profile);
Node = doc.CreateComment(String.Format(" Auto-Profile Configuration Data. {0} ", DateTime.Now));
foreach (XmlNode node in doc.SelectNodes("//comment()"))
node.ParentNode.ReplaceChild(Node, node);
m_Xdoc.AppendChild(Node);
m_Xdoc.Save(m_Profile);
// Find the existing XML program entry using the old path and title value as a search key and replace it with new path and title values (or add a new entry if this was a new program path)
XmlNode oldxmlprocess = FindProgramXMLItem(doc, progPathItem.path, progPathItem.title);
Node = doc.SelectSingleNode("Programs");
XmlElement el = doc.CreateElement("Program");
el.SetAttribute("path", newPath);
if (!String.IsNullOrEmpty(newTitle))
el.SetAttribute("title", newTitle);
el.AppendChild(doc.CreateElement("Controller1")).InnerText = cBProfile1.Text;
el.AppendChild(doc.CreateElement("Controller2")).InnerText = cBProfile2.Text;
el.AppendChild(doc.CreateElement("Controller3")).InnerText = cBProfile3.Text;
el.AppendChild(doc.CreateElement("Controller4")).InnerText = cBProfile4.Text;
el.AppendChild(doc.CreateElement("TurnOff")).InnerText = cBTurnOffDS4W.Checked.ToString();
if (oldxmlprocess != null)
Node.ReplaceChild(el, oldxmlprocess);
else
Node.AppendChild(el);
doc.AppendChild(Node);
doc.Save(m_Profile);
if(selectedProgramPathItem != null)
{
selectedProgramPathItem.path = newPath;
selectedProgramPathItem.title = newTitle;
}
if (lVPrograms.SelectedItems.Count > 0)
{
lVPrograms.SelectedItems[0].Checked = true;
lVPrograms.SelectedItems[0].SubItems[0].Text = Path.GetFileNameWithoutExtension(newPath);
lVPrograms.SelectedItems[0].SubItems[1].Text = newPath;
if (lVPrograms.SelectedItems[0].SubItems.Count < 3)
if(!String.IsNullOrEmpty(newTitle))
lVPrograms.SelectedItems[0].SubItems.Add(newTitle);
else
lVPrograms.SelectedItems[0].SubItems[2].Text = newTitle;
if (!String.IsNullOrEmpty(newTitle))
lVPrograms.SelectedItems[0].ToolTipText = $"{newPath} [{newTitle}]";
else
lVPrograms.SelectedItems[0].ToolTipText = newPath;
}
form.LoadP();
}
catch (Exception e)
{
// Eat all exceptions while writing auto-profile file because we don't want to crash DS4Win app just because there are some permissions or other issues with the file
AppLogger.LogToGui($"ERROR. Auto-profile XML file {Global.appdatapath}\\Auto Profiles.xml writing failed. {e.Message}", true);
}
}
public void LoadP(string name)
public void LoadP(ProgramPathItem loadProgPathItem)
{
if (loadProgPathItem == null)
return;
try
{
XmlDocument doc = new XmlDocument();
doc.Load(m_Profile);
XmlNodeList programs = doc.SelectNodes("Programs/Program");
XmlNode Item = doc.SelectSingleNode("/Programs/Program[@path=\"" + name + "\"]");
if (Item != null)
XmlNode programItem = FindProgramXMLItem(doc, loadProgPathItem.path, loadProgPathItem.title);
if (programItem != null)
{
XmlNode profileItem;
for (int i = 0; i < 4; i++)
{
Item = doc.SelectSingleNode("/Programs/Program[@path=\"" + name + "\"]" + "/Controller" + (i + 1));
if (Item != null)
profileItem = programItem.SelectSingleNode($".//Controller{i + 1}");
if (profileItem != null)
{
for (int j = 0; j < cbs[i].Items.Count; j++)
{
if (cbs[i].Items[j].ToString() == Item.InnerText)
if (cbs[i].Items[j].ToString() == profileItem.InnerText)
{
cbs[i].SelectedIndex = j;
bnSave.Enabled = false;
break;
}
else
@ -286,61 +416,108 @@ namespace DS4Windows.Forms
cbs[i].SelectedIndex = cbs[i].Items.Count - 1;
}
Item = doc.SelectSingleNode("/Programs/Program[@path=\"" + name + "\"]" + "/TurnOff");
bool turnOff;
if (Item != null && bool.TryParse(Item.InnerText, out turnOff))
profileItem = programItem.SelectSingleNode($".//TurnOff");
if (profileItem != null && bool.TryParse(profileItem.InnerText, out turnOff))
{
cBTurnOffDS4W.Checked = turnOff;
}
else
cBTurnOffDS4W.Checked = false;
}
else
{
for (int i = 0; i < 4; i++)
cbs[i].SelectedIndex = cbs[i].Items.Count - 1;
cBTurnOffDS4W.Checked = false;
tBPath.Text = loadProgPathItem.path;
tBWndTitle.Text = loadProgPathItem.title;
}
catch (Exception e)
{
// Eat all exceptions while reading auto-profile file because we don't want to crash DS4Win app just because there are some permissions or other issues with the file
AppLogger.LogToGui($"ERROR. Failed to read {loadProgPathItem.path} {loadProgPathItem.title} XML entry. {e.Message}", true);
}
bnSave.Enabled = false;
}
}
public void RemoveP(string name, bool uncheck)
public void RemoveP(ProgramPathItem removeProgPathItem, bool uncheck)
{
bnSave.Enabled = false;
if (removeProgPathItem == null)
return;
try
{
XmlDocument doc = new XmlDocument();
doc.Load(m_Profile);
XmlNode Node = doc.SelectSingleNode("Programs");
XmlNode Item = doc.SelectSingleNode("/Programs/Program[@path=\"" + name + "\"]");
if (Item != null)
Node.RemoveChild(Item);
doc.AppendChild(Node);
XmlNode programItem = FindProgramXMLItem(doc, removeProgPathItem.path, removeProgPathItem.title);
if (programItem != null)
{
XmlNode parentNode = programItem.ParentNode;
if (parentNode != null)
{
parentNode.RemoveChild(programItem);
doc.AppendChild(parentNode);
doc.Save(m_Profile);
}
}
if (lVPrograms.SelectedItems.Count > 0 && uncheck)
lVPrograms.SelectedItems[0].Checked = false;
}
catch (Exception e)
{
// Eat all exceptions while updating auto-profile file because we don't want to crash DS4Win app just because there are some permissions or other issues with the file
AppLogger.LogToGui($"ERROR. Failed to remove {removeProgPathItem.path} {removeProgPathItem.title} XML entry. {e.Message}", true);
}
UpdateProfileComboListValues(null);
}
private void UpdateProfileComboListValues(ProgramPathItem progItem)
{
if (progItem != null)
{
for (int i = 0; i < 4; i++)
cbs[i].SelectedIndex = cbs[i].Items.Count - 1;
cbs[i].Enabled = true;
bnSave.Enabled = false;
tBPath.Enabled = tBWndTitle.Enabled = cBTurnOffDS4W.Enabled = true;
LoadP(progItem);
bnDelete.Enabled = true;
}
else
{
int last = cbs[0].Items.Count - 1;
// Set all profile combox values to "none" value (ie. the last item in a controller combobox list)
for (int i = 0; i < 4; i++)
{
cbs[i].Enabled = false;
if (cbs[i].SelectedIndex != last)
cbs[i].SelectedIndex = last;
}
bnSave.Enabled = bnDelete.Enabled = false;
tBPath.Enabled = tBWndTitle.Enabled = cBTurnOffDS4W.Enabled = false;
tBPath.Text = tBWndTitle.Text = "";
cBTurnOffDS4W.Checked = false;
}
}
private void CBProfile_IndexChanged(object sender, EventArgs e)
{
int last = cbs[0].Items.Count - 1;
if (lBProgramPath.Text != string.Empty)
if (selectedProgramPathItem != null && lVPrograms.SelectedItems.Count > 0)
bnSave.Enabled = true;
if (cbs[0].SelectedIndex == last && cbs[1].SelectedIndex == last &&
cbs[2].SelectedIndex == last && cbs[3].SelectedIndex == last && !cBTurnOffDS4W.Checked)
bnSave.Enabled = false;
}
private void bnSave_Click(object sender, EventArgs e)
{
if (lBProgramPath.Text != "")
Save(lBProgramPath.Text);
if (selectedProgramPathItem != null)
{
// Path cannot be empty. If user tried to clear it then re-use the original path value
if (String.IsNullOrEmpty(tBPath.Text))
tBPath.Text = selectedProgramPathItem.path;
Save(selectedProgramPathItem);
}
bnSave.Enabled = false;
}
@ -349,41 +526,48 @@ namespace DS4Windows.Forms
cMSPrograms.Show(bnAddPrograms, new Point(0, bnAddPrograms.Height));
}
private void lBProgramPath_TextChanged(object sender, EventArgs e)
{
if (lBProgramPath.Text != "")
LoadP(lBProgramPath.Text);
else
{
for (int i = 0; i < 4; i++)
cbs[i].SelectedIndex = cbs[i].Items.Count - 1;
}
}
private void bnDelete_Click(object sender, EventArgs e)
{
RemoveP(lBProgramPath.Text, true);
RemoveP(selectedProgramPathItem, true);
selectedProgramPathItem = null;
bnSave.Enabled = bnDelete.Enabled = false;
if (lVPrograms.SelectedItems.Count > 0)
lVPrograms.SelectedItems[0].Selected = lVPrograms.SelectedItems[0].Focused = false;
}
private void lBProgramPath_SelectedIndexChanged(object sender, EventArgs e)
{
if (lVPrograms.SelectedItems.Count > 0)
if (lVPrograms.SelectedItems.Count > 0 && lVPrograms.SelectedIndices[0] > -1)
{
if (lVPrograms.SelectedIndices[0] > -1)
lBProgramPath.Text = lVPrograms.SelectedItems[0].SubItems[1].Text;
if (selectedProgramPathItem == null) selectedProgramPathItem = new ProgramPathItem(String.Empty, String.Empty);
selectedProgramPathItem.path = lVPrograms.SelectedItems[0].SubItems[1].Text;
if (lVPrograms.SelectedItems[0].SubItems.Count >= 3)
selectedProgramPathItem.title = lVPrograms.SelectedItems[0].SubItems[2].Text;
else
selectedProgramPathItem.title = String.Empty;
UpdateProfileComboListValues(selectedProgramPathItem);
}
else
lBProgramPath.Text = "";
}
private void listView1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (lVPrograms.Items[e.Index].Checked)
RemoveP(lVPrograms.Items[e.Index].SubItems[1].Text, false);
selectedProgramPathItem = null;
UpdateProfileComboListValues(null);
}
}
private void bnHideUnchecked_Click(object sender, EventArgs e)
{
// Remove all unchecked items from autoprofile XML file and listView list
foreach (ListViewItem lvi in lVPrograms.Items)
{
if (!lvi.Checked)
{
RemoveP(new ProgramPathItem(lvi.SubItems[1]?.Text, (lvi.SubItems.Count >=3 ? lvi.SubItems[2]?.Text : null)), false);
}
}
selectedProgramPathItem = null;
form.RefreshAutoProfilesPage();
}
@ -418,11 +602,18 @@ namespace DS4Windows.Forms
file = GetTargetPath(file);
}
lBProgramPath.Text = file;
iLIcons.Images.Add(Icon.ExtractAssociatedIcon(file));
ListViewItem lvi = new ListViewItem(Path.GetFileNameWithoutExtension(file), lVPrograms.Items.Count);
lvi.SubItems.Add(file);
lVPrograms.Items.Insert(0, lvi);
if (lVPrograms.SelectedItems.Count > 0)
lVPrograms.SelectedItems[0].Selected = lVPrograms.SelectedItems[0].Focused = false;
lodsf.Clear();
lodsf.Add(file);
appsloaded = true;
if (AddLoadedApps(false) > 0 && lVPrograms.Items.Count > 0)
{
lVPrograms.Items[lVPrograms.Items.Count - 1].Focused = true;
lVPrograms.Items[lVPrograms.Items.Count - 1].Selected = true;
}
}
}
@ -534,6 +725,22 @@ namespace DS4Windows.Forms
CBProfile_IndexChanged(sender, e);
}
private void cBAutoProfileDebugLog_CheckedChanged(object sender, EventArgs e)
{
DS4Form.autoProfileDebugLogLevel = cBAutoProfileDebugLog.Checked ? 1 : 0;
}
private void tBPath_TextChanged(object sender, EventArgs e)
{
int last = cbs[0].Items.Count - 1;
if (cbs[0].SelectedIndex != last || cbs[1].SelectedIndex != last || cbs[2].SelectedIndex != last || cbs[3].SelectedIndex != last || !cBTurnOffDS4W.Checked)
{
// Content of path or wndTitle editbox changed. Enable SAVE button if it is disabled at the moment and there is an active selection in a listView
if (selectedProgramPathItem != null && bnSave.Enabled == false && lVPrograms.SelectedItems.Count > 0)
bnSave.Enabled = true;
}
}
public static string ResolveMsiShortcut(string file)
{
StringBuilder product = new StringBuilder(NativeMethods2.MaxGuidLength + 1);

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -165,4 +165,16 @@
<data name="pBProfilesTip.Text" xml:space="preserve">
<value>Valitse profiilit tästä</value>
</data>
<data name="WndTitleHeader.Text" xml:space="preserve">
<value>Ikkunan otsikko</value>
</data>
<data name="cBTurnOffDS4W.Text" xml:space="preserve">
<value>Pysäytä DS4Windows väliaikaisesti</value>
</data>
<data name="cBAutoProfileDebugLog.Text" xml:space="preserve">
<value>Näytä auto-profiilien virheseurannan viestit</value>
</data>
<data name="$this.Text" xml:space="preserve">
<value>Auto-Profiilit</value>
</data>
</root>

View File

@ -141,36 +141,9 @@
<value>$this</value>
</data>
<data name="&gt;&gt;bnAddPrograms.ZOrder" xml:space="preserve">
<value>4</value>
<value>8</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="lBProgramPath.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
<data name="lBProgramPath.Location" type="System.Drawing.Point, System.Drawing">
<value>519, 195</value>
</data>
<data name="lBProgramPath.Size" type="System.Drawing.Size, System.Drawing">
<value>205, 18</value>
</data>
<data name="lBProgramPath.TabIndex" type="System.Int32, mscorlib">
<value>3</value>
</data>
<data name="lBProgramPath.Visible" type="System.Boolean, mscorlib">
<value>False</value>
</data>
<data name="&gt;&gt;lBProgramPath.Name" xml:space="preserve">
<value>lBProgramPath</value>
</data>
<data name="&gt;&gt;lBProgramPath.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;lBProgramPath.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;lBProgramPath.ZOrder" xml:space="preserve">
<value>14</value>
</data>
<data name="cBProfile1.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
@ -193,7 +166,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;cBProfile1.ZOrder" xml:space="preserve">
<value>13</value>
<value>17</value>
</data>
<data name="cBProfile2.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -217,7 +190,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;cBProfile2.ZOrder" xml:space="preserve">
<value>12</value>
<value>16</value>
</data>
<data name="cBProfile3.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -241,7 +214,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;cBProfile3.ZOrder" xml:space="preserve">
<value>11</value>
<value>15</value>
</data>
<data name="cBProfile4.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -265,7 +238,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;cBProfile4.ZOrder" xml:space="preserve">
<value>10</value>
<value>14</value>
</data>
<data name="bnSave.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -295,7 +268,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;bnSave.ZOrder" xml:space="preserve">
<value>16</value>
<value>19</value>
</data>
<data name="lBController1.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -325,7 +298,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;lBController1.ZOrder" xml:space="preserve">
<value>9</value>
<value>13</value>
</data>
<data name="lBController2.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -355,7 +328,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;lBController2.ZOrder" xml:space="preserve">
<value>8</value>
<value>12</value>
</data>
<data name="lBController3.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -385,7 +358,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;lBController3.ZOrder" xml:space="preserve">
<value>7</value>
<value>11</value>
</data>
<data name="lBController4.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -415,7 +388,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;lBController4.ZOrder" xml:space="preserve">
<value>6</value>
<value>10</value>
</data>
<metadata name="openProgram.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
@ -448,7 +421,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;bnDelete.ZOrder" xml:space="preserve">
<value>15</value>
<value>18</value>
</data>
<metadata name="iLIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>143, 17</value>
@ -469,13 +442,19 @@
<value>Path</value>
</data>
<data name="PathHeader.Width" type="System.Int32, mscorlib">
<value>358</value>
<value>400</value>
</data>
<data name="WndTitleHeader.Text" xml:space="preserve">
<value>Window Title</value>
</data>
<data name="WndTitleHeader.Width" type="System.Int32, mscorlib">
<value>140</value>
</data>
<data name="lVPrograms.Location" type="System.Drawing.Point, System.Drawing">
<value>5, 28</value>
</data>
<data name="lVPrograms.Size" type="System.Drawing.Size, System.Drawing">
<value>505, 190</value>
<value>505, 261</value>
</data>
<data name="lVPrograms.TabIndex" type="System.Int32, mscorlib">
<value>12</value>
@ -490,7 +469,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;lVPrograms.ZOrder" xml:space="preserve">
<value>5</value>
<value>9</value>
</data>
<data name="pBProfilesTip.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
@ -520,7 +499,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;pBProfilesTip.ZOrder" xml:space="preserve">
<value>2</value>
<value>1</value>
</data>
<data name="bnHideUnchecked.Location" type="System.Drawing.Point, System.Drawing">
<value>99, 2</value>
@ -544,7 +523,7 @@
<value>$this</value>
</data>
<data name="&gt;&gt;bnHideUnchecked.ZOrder" xml:space="preserve">
<value>3</value>
<value>7</value>
</data>
<metadata name="cMSPrograms.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>232, 17</value>
@ -592,13 +571,13 @@
<value>Top, Right</value>
</data>
<data name="cBTurnOffDS4W.Location" type="System.Drawing.Point, System.Drawing">
<value>514, 152</value>
<value>514, 151</value>
</data>
<data name="cBTurnOffDS4W.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="cBTurnOffDS4W.Size" type="System.Drawing.Size, System.Drawing">
<value>215, 41</value>
<value>215, 24</value>
</data>
<data name="cBTurnOffDS4W.TabIndex" type="System.Int32, mscorlib">
<value>13</value>
@ -616,7 +595,109 @@
<value>$this</value>
</data>
<data name="&gt;&gt;cBTurnOffDS4W.ZOrder" xml:space="preserve">
<value>1</value>
<value>6</value>
</data>
<data name="cBAutoProfileDebugLog.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
<data name="cBAutoProfileDebugLog.Location" type="System.Drawing.Point, System.Drawing">
<value>514, 255</value>
</data>
<data name="cBAutoProfileDebugLog.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>2, 2, 2, 2</value>
</data>
<data name="cBAutoProfileDebugLog.Size" type="System.Drawing.Size, System.Drawing">
<value>215, 20</value>
</data>
<data name="cBAutoProfileDebugLog.TabIndex" type="System.Int32, mscorlib">
<value>14</value>
</data>
<data name="cBAutoProfileDebugLog.Text" xml:space="preserve">
<value>Show auto-profile debug messages</value>
</data>
<data name="&gt;&gt;cBAutoProfileDebugLog.Name" xml:space="preserve">
<value>cBAutoProfileDebugLog</value>
</data>
<data name="&gt;&gt;cBAutoProfileDebugLog.Type" xml:space="preserve">
<value>System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;cBAutoProfileDebugLog.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;cBAutoProfileDebugLog.ZOrder" xml:space="preserve">
<value>5</value>
</data>
<data name="tBPath.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
<data name="tBPath.Location" type="System.Drawing.Point, System.Drawing">
<value>514, 186</value>
</data>
<data name="tBPath.Size" type="System.Drawing.Size, System.Drawing">
<value>216, 20</value>
</data>
<data name="tBPath.TabIndex" type="System.Int32, mscorlib">
<value>15</value>
</data>
<data name="&gt;&gt;tBPath.Name" xml:space="preserve">
<value>tBPath</value>
</data>
<data name="&gt;&gt;tBPath.Type" xml:space="preserve">
<value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tBPath.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;tBPath.ZOrder" xml:space="preserve">
<value>4</value>
</data>
<data name="tBWndTitle.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
<data name="tBWndTitle.Location" type="System.Drawing.Point, System.Drawing">
<value>514, 212</value>
</data>
<data name="tBWndTitle.Size" type="System.Drawing.Size, System.Drawing">
<value>215, 20</value>
</data>
<data name="tBWndTitle.TabIndex" type="System.Int32, mscorlib">
<value>16</value>
</data>
<data name="&gt;&gt;tBWndTitle.Name" xml:space="preserve">
<value>tBWndTitle</value>
</data>
<data name="&gt;&gt;tBWndTitle.Type" xml:space="preserve">
<value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;tBWndTitle.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;tBWndTitle.ZOrder" xml:space="preserve">
<value>3</value>
</data>
<data name="lBDividerLine1.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
<value>Top, Right</value>
</data>
<data name="lBDividerLine1.Location" type="System.Drawing.Point, System.Drawing">
<value>514, 248</value>
</data>
<data name="lBDividerLine1.Size" type="System.Drawing.Size, System.Drawing">
<value>218, 5</value>
</data>
<data name="lBDividerLine1.TabIndex" type="System.Int32, mscorlib">
<value>17</value>
</data>
<data name="&gt;&gt;lBDividerLine1.Name" xml:space="preserve">
<value>lBDividerLine1</value>
</data>
<data name="&gt;&gt;lBDividerLine1.Type" xml:space="preserve">
<value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;lBDividerLine1.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;lBDividerLine1.ZOrder" xml:space="preserve">
<value>2</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
@ -625,7 +706,7 @@
<value>6, 13</value>
</data>
<data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
<value>736, 222</value>
<value>736, 293</value>
</data>
<data name="$this.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>4, 4, 4, 4</value>
@ -657,6 +738,12 @@
<data name="&gt;&gt;PathHeader.Type" xml:space="preserve">
<value>System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;WndTitleHeader.Name" xml:space="preserve">
<value>WndTitleHeader</value>
</data>
<data name="&gt;&gt;WndTitleHeader.Type" xml:space="preserve">
<value>System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="&gt;&gt;addProgramsFromStartMenuToolStripMenuItem.Name" xml:space="preserve">
<value>addProgramsFromStartMenuToolStripMenuItem</value>
</data>

View File

@ -154,6 +154,15 @@ namespace DS4Windows.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to ^ABC = Match at the beginning of string (^) | ABC$ = Match at the end of string ($) | *ABC =Contains a string (*).
/// </summary>
public static string AutoProfilePathAndWindowTitleEditTip {
get {
return ResourceManager.GetString("AutoProfilePathAndWindowTitleEditTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -1800,6 +1809,15 @@ namespace DS4Windows.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to If enabled then Log tab page shows detailed messages of auto-profile events..
/// </summary>
public static string ShowAutoProfileDebugLogTip {
get {
return ResourceManager.GetString("ShowAutoProfileDebugLogTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Click for advanced Sixaxis reading.
/// </summary>

View File

@ -603,4 +603,16 @@
<data name="UdpServer" xml:space="preserve">
<value>Aktivoi UDP palvelu. Palvelun osoite ja portti. Osoitevaihtoehdot: 127.0.0.1 paikallinen | 0.0.0.0 kaikki liittymät | Tietty palvelinnimi tai IP numero.</value>
</data>
<data name="AutoProfilePathAndWindowTitleEditTip" xml:space="preserve">
<value>^ABC = Täsmää merkkijonon alusta (^) | ABC$ = Täsmää merkkijonon lopusta ($) | *ABC = Sisältää merkkijonon.</value>
</data>
<data name="ShowAutoProfileDebugLogTip" xml:space="preserve">
<value>Näytä automaattiprofiilivalintojen tarkempia valvontaviestejä Loki-välilehdellä.</value>
</data>
<data name="OutContNotice" xml:space="preserve">
<value>Valitse virtuaaliohjaimen tyyppi (muutos tulee voimaan profiilin tallennuksen jälkeen)</value>
</data>
<data name="EnableTouchToggle" xml:space="preserve">
<value>Salli kosketuslevyhiiriohjaimen päälle-pois kytkentä PS + kosketuslevynapautuksella.</value>
</data>
</root>

View File

@ -826,4 +826,10 @@
<data name="UdpServer" xml:space="preserve">
<value>Enable UDP server. Server listen address and port. Address value options: 127.0.0.1 localhost only | 0.0.0.0 all addresses | Specific host name or IP address.</value>
</data>
<data name="AutoProfilePathAndWindowTitleEditTip" xml:space="preserve">
<value>^ABC = Match at the beginning of string (^) | ABC$ = Match at the end of string ($) | *ABC =Contains a string (*)</value>
</data>
<data name="ShowAutoProfileDebugLogTip" xml:space="preserve">
<value>If enabled then Log tab page shows detailed messages of auto-profile events.</value>
</data>
</root>