mirror of
https://github.com/chibicitiberiu/rainmeter-studio.git
synced 2024-02-24 04:33:31 +00:00
InputText: Fixed multi-threading issues
This commit is contained in:
parent
76e504d1e5
commit
167123bcf7
@ -294,6 +294,9 @@ namespace InputText
|
|||||||
|
|
||||||
public bool ShowInputBox()
|
public bool ShowInputBox()
|
||||||
{
|
{
|
||||||
|
if (this.drBackup != DialogResult.None)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (this._FocusDismiss)
|
if (this._FocusDismiss)
|
||||||
{
|
{
|
||||||
this.Show(this.parent);
|
this.Show(this.parent);
|
||||||
@ -324,6 +327,12 @@ namespace InputText
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Abort()
|
||||||
|
{
|
||||||
|
this.drBackup = DialogResult.Cancel;
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
private void txtInput_Leave(object sender, EventArgs e)
|
private void txtInput_Leave(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (this._FocusDismiss)
|
if (this._FocusDismiss)
|
||||||
|
@ -71,17 +71,51 @@ namespace InputText
|
|||||||
|
|
||||||
if (go)
|
if (go)
|
||||||
{
|
{
|
||||||
ThreadPool.QueueUserWorkItem(ExecuteBangThread, args);
|
ExecuteBangParam param = new ExecuteBangParam(args);
|
||||||
|
if (ReadOptions(param)) // Read all options in advance for thread-safety
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(ExecuteBangThread, param);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No need to continue
|
||||||
|
lock (this.locker)
|
||||||
|
{
|
||||||
|
this.IsExecuteBangRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ExecuteBangParam
|
||||||
|
{
|
||||||
|
internal enum BangType
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
SetVariable,
|
||||||
|
ExecuteBatch
|
||||||
|
};
|
||||||
|
internal Dictionary<string, string> Options;
|
||||||
|
internal List<string> Commands;
|
||||||
|
internal string Command;
|
||||||
|
internal BangType Type;
|
||||||
|
|
||||||
|
internal ExecuteBangParam(string args)
|
||||||
|
{
|
||||||
|
this.Options = new Dictionary<string, string>();
|
||||||
|
this.Commands = new List<string>();
|
||||||
|
this.Command = args.Trim();
|
||||||
|
this.Type = BangType.Unknown;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private void ExecuteBangThread(object state)
|
private void ExecuteBangThread(object state)
|
||||||
{
|
{
|
||||||
string command = (string)state;
|
ExecuteBangParam param = (ExecuteBangParam)state;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ExecuteCommands(command);
|
ExecuteCommands(param);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -93,6 +127,11 @@ namespace InputText
|
|||||||
this.IsExecuteBangRunning = false;
|
this.IsExecuteBangRunning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void Dispose()
|
||||||
|
{
|
||||||
|
CloseInputBox();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Plugin
|
#region Plugin
|
||||||
@ -112,6 +151,7 @@ namespace InputText
|
|||||||
public unsafe static void Finalize(void* data)
|
public unsafe static void Finalize(void* data)
|
||||||
{
|
{
|
||||||
uint id = (uint)data;
|
uint id = (uint)data;
|
||||||
|
Measures[id].Dispose();
|
||||||
Measures.Remove(id);
|
Measures.Remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,59 +25,45 @@ namespace InputText
|
|||||||
{
|
{
|
||||||
internal partial class Measure
|
internal partial class Measure
|
||||||
{
|
{
|
||||||
private void ExecuteCommands(string Command)
|
private bool ReadOptions(ExecuteBangParam param)
|
||||||
{
|
{
|
||||||
Command = Command.Trim();
|
|
||||||
|
|
||||||
// Get default options
|
// Get default options
|
||||||
Dictionary<string, string> Options = new Dictionary<string, string>();
|
ReadOption("DefaultValue", param.Options);
|
||||||
ReadOption("DefaultValue", Options);
|
ReadOption("X", param.Options);
|
||||||
ReadOption("X", Options);
|
ReadOption("Y", param.Options);
|
||||||
ReadOption("Y", Options);
|
ReadOption("W", param.Options);
|
||||||
ReadOption("W", Options);
|
ReadOption("H", param.Options);
|
||||||
ReadOption("H", Options);
|
ReadOption("StringStyle", param.Options);
|
||||||
ReadOption("StringStyle", Options);
|
ReadOption("StringAlign", param.Options);
|
||||||
ReadOption("StringAlign", Options);
|
ReadOption("FocusDismiss", param.Options);
|
||||||
ReadOption("FocusDismiss", Options);
|
ReadOption("FontColor", param.Options);
|
||||||
ReadOption("FontColor", Options);
|
ReadOption("FontFace", param.Options);
|
||||||
ReadOption("FontFace", Options);
|
ReadOption("FontSize", param.Options);
|
||||||
ReadOption("FontSize", Options);
|
ReadOption("SolidColor", param.Options);
|
||||||
ReadOption("SolidColor", Options);
|
ReadOption("Password", param.Options);
|
||||||
ReadOption("Password", Options);
|
ReadOption("TopMost", param.Options);
|
||||||
ReadOption("TopMost", Options);
|
|
||||||
|
|
||||||
#region Handle a single parameter
|
#region Handle a single parameter
|
||||||
|
|
||||||
// If our parameter list only contains a single word, then open a textbox immediately
|
// If our parameter list only contains a single word, then open a textbox immediately
|
||||||
// and set a value. This mode does not do any batching.
|
// and set a value. This mode does not do any batching.
|
||||||
if (!Command.Contains(" "))
|
if (!param.Command.Contains(" "))
|
||||||
{
|
{
|
||||||
// Assume that the parameter is the name of the variable
|
param.Type = ExecuteBangParam.BangType.SetVariable;
|
||||||
|
return true;
|
||||||
// Ask for input
|
|
||||||
string sInput = GetUserInput(Options);
|
|
||||||
|
|
||||||
// If the user cancelled out of the inputbox (ESC key, etc.), then abort
|
|
||||||
if (sInput == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ask Rainmeter to set the variable using a bang (http://rainmeter.net/RainCMS/?q=Bangs)
|
|
||||||
API.Execute(rm.GetSkin(), "!SetVariable " + Command + " \"" + sInput + "\"");
|
|
||||||
|
|
||||||
// Note that the skin needs DynamicVariables=1 in the measure's settings or the above
|
|
||||||
// code will have no effect.
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Handle multiple parameters
|
#region Handle multiple parameters
|
||||||
|
|
||||||
// Our parameter list contains at least two words, so split them up
|
// Our parameter list contains at least two words, so split them up
|
||||||
string[] sParts = Command.Split(new string[] { " " }, StringSplitOptions.None);
|
string[] sParts = param.Command.Split(new string[] { " " }, StringSplitOptions.None);
|
||||||
|
|
||||||
// If the first parameter is 'ExecuteBatch' (not case sensitive)...
|
// If the first parameter is 'ExecuteBatch' (not case sensitive)...
|
||||||
if (sParts[0].Trim().ToUpper() == "EXECUTEBATCH")
|
if (sParts[0].Trim().ToUpper() == "EXECUTEBATCH")
|
||||||
{
|
{
|
||||||
|
param.Type = ExecuteBangParam.BangType.ExecuteBatch;
|
||||||
|
|
||||||
// ExecuteBatch tells this plugin to go through the measure's settings to look
|
// ExecuteBatch tells this plugin to go through the measure's settings to look
|
||||||
// for lines beginning with "Command" and executing Rainmeter bangs for each one.
|
// for lines beginning with "Command" and executing Rainmeter bangs for each one.
|
||||||
// If a line contains $UserInput$, then an input textbox is opened and command
|
// If a line contains $UserInput$, then an input textbox is opened and command
|
||||||
@ -131,8 +117,6 @@ namespace InputText
|
|||||||
#region Parse commands in range
|
#region Parse commands in range
|
||||||
// Parse each command in the range, aborting if any line returns 'false' or
|
// Parse each command in the range, aborting if any line returns 'false' or
|
||||||
// the requested command line does not exist in the config for that measure.
|
// the requested command line does not exist in the config for that measure.
|
||||||
List<string> commands = new List<string>();
|
|
||||||
|
|
||||||
for (int i = iMin; i <= iMax; i++)
|
for (int i = iMin; i <= iMax; i++)
|
||||||
{
|
{
|
||||||
// Read this command's line
|
// Read this command's line
|
||||||
@ -142,28 +126,59 @@ namespace InputText
|
|||||||
if (string.IsNullOrEmpty(sCurrentLine))
|
if (string.IsNullOrEmpty(sCurrentLine))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
commands.Add(sCurrentLine);
|
param.Commands.Add(sCurrentLine);
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string sCurrentLine in commands)
|
|
||||||
{
|
|
||||||
// Execute the line, but if there's a problem (error or they cancel the
|
|
||||||
// input textbox), then abort
|
|
||||||
if (!ExecuteLine(sCurrentLine, Options))
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Continue to the next line, if there is any
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unhandled command, log the message but otherwise do nothing
|
return param.Commands.Count > 0;
|
||||||
API.Log(API.LogType.Debug, "InputText: Received command \"" + sParts[0].Trim() + "\", left unhandled");
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
return;
|
// Unhandled command, log the message but otherwise do nothing
|
||||||
|
param.Type = ExecuteBangParam.BangType.Unknown;
|
||||||
|
API.Log(API.LogType.Debug, "InputText: Received command \"" + sParts[0].Trim() + "\", left unhandled");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteCommands(ExecuteBangParam param)
|
||||||
|
{
|
||||||
|
switch (param.Type)
|
||||||
|
{
|
||||||
|
case ExecuteBangParam.BangType.SetVariable:
|
||||||
|
{
|
||||||
|
// Assume that the parameter is the name of the variable
|
||||||
|
|
||||||
|
// Ask for input
|
||||||
|
string sInput = GetUserInput(param.Options);
|
||||||
|
|
||||||
|
// If the user cancelled out of the inputbox (ESC key, etc.), then abort
|
||||||
|
if (sInput == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Ask Rainmeter to set the variable using a bang (http://rainmeter.net/RainCMS/?q=Bangs)
|
||||||
|
API.Execute(rm.GetSkin(), "!SetVariable " + param.Command + " \"" + sInput + "\"");
|
||||||
|
|
||||||
|
// Note that the skin needs DynamicVariables=1 in the measure's settings or the above
|
||||||
|
// code will have no effect.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ExecuteBangParam.BangType.ExecuteBatch:
|
||||||
|
{
|
||||||
|
foreach (string sCurrentLine in param.Commands)
|
||||||
|
{
|
||||||
|
// Execute the line, but if there's a problem (error or they cancel the
|
||||||
|
// input textbox), then abort
|
||||||
|
if (!ExecuteLine(sCurrentLine, param.Options))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Continue to the next line, if there is any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region This is all code custom to this plugin
|
#region This is all code custom to this plugin
|
||||||
@ -235,6 +250,9 @@ namespace InputText
|
|||||||
delegate void ChangeSettingFromString(string value);
|
delegate void ChangeSettingFromString(string value);
|
||||||
delegate void ChangeInputBoxSetting(string option, ChangeSettingFromString method);
|
delegate void ChangeInputBoxSetting(string option, ChangeSettingFromString method);
|
||||||
|
|
||||||
|
private InputBox _InputBox = null;
|
||||||
|
private object _InputBoxLocker = new object();
|
||||||
|
|
||||||
private string GetUserInput(Dictionary<string, string> Options)
|
private string GetUserInput(Dictionary<string, string> Options)
|
||||||
{
|
{
|
||||||
// No INI overrides provided, so create an empty list
|
// No INI overrides provided, so create an empty list
|
||||||
@ -242,78 +260,108 @@ namespace InputText
|
|||||||
}
|
}
|
||||||
private string GetUserInput(Dictionary<string, string> Options, Dictionary<string, string> Overrides)
|
private string GetUserInput(Dictionary<string, string> Options, Dictionary<string, string> Overrides)
|
||||||
{
|
{
|
||||||
SkinWindow skin = new SkinWindow(rm);
|
InputBox input = null;
|
||||||
|
|
||||||
// Create the form. 'InputBox' is a .NET form with a textbox and two button controls on it.
|
lock (this._InputBoxLocker)
|
||||||
InputBox input = new InputBox(skin);
|
|
||||||
input.ChangeX("0");
|
|
||||||
input.ChangeY("0");
|
|
||||||
|
|
||||||
// Change the styles of the InputBox form based on overrides or INI values
|
|
||||||
#region Style and preference tweaks (INI and override settings)
|
|
||||||
|
|
||||||
ChangeInputBoxSetting changeSetting = (opt, change) =>
|
|
||||||
{
|
{
|
||||||
if (Overrides.ContainsKey(opt))
|
SkinWindow skin = new SkinWindow(rm);
|
||||||
change(Overrides[opt]);
|
|
||||||
else if (Options.ContainsKey(opt))
|
|
||||||
change(Options[opt]);
|
|
||||||
};
|
|
||||||
|
|
||||||
changeSetting("FontFace", input.ChangeFontFace);
|
// Create the form. 'InputBox' is a .NET form with a textbox and two button controls on it.
|
||||||
changeSetting("FontSize", input.ChangeFontSize);
|
this._InputBox = new InputBox(skin);
|
||||||
|
input = this._InputBox;
|
||||||
|
|
||||||
changeSetting("W", input.ChangeW);
|
input.ChangeX("0");
|
||||||
changeSetting("H", input.ChangeH);
|
input.ChangeY("0");
|
||||||
changeSetting("X", input.ChangeX);
|
|
||||||
changeSetting("Y", input.ChangeY);
|
|
||||||
|
|
||||||
changeSetting("StringStyle", input.ChangeFontStringStyle);
|
// Change the styles of the InputBox form based on overrides or INI values
|
||||||
changeSetting("StringAlign", input.ChangeStringAlign);
|
#region Style and preference tweaks (INI and override settings)
|
||||||
|
|
||||||
changeSetting("FontColor", input.ChangeFontColor);
|
ChangeInputBoxSetting changeSetting = (opt, change) =>
|
||||||
changeSetting("SolidColor", input.ChangeBackColor);
|
{
|
||||||
|
if (Overrides.ContainsKey(opt))
|
||||||
|
change(Overrides[opt]);
|
||||||
|
else if (Options.ContainsKey(opt))
|
||||||
|
change(Options[opt]);
|
||||||
|
};
|
||||||
|
|
||||||
if (Overrides.ContainsKey("FocusDismiss"))
|
changeSetting("FontFace", input.ChangeFontFace);
|
||||||
input.MakeFocusDismiss(Overrides["FocusDismiss"] == "1");
|
changeSetting("FontSize", input.ChangeFontSize);
|
||||||
else if (Options.ContainsKey("FocusDismiss"))
|
|
||||||
input.MakeFocusDismiss(Options["FocusDismiss"].Trim() == "1");
|
|
||||||
|
|
||||||
if (Overrides.ContainsKey("Password"))
|
changeSetting("W", input.ChangeW);
|
||||||
input.MakePassword(Overrides["Password"] == "1");
|
changeSetting("H", input.ChangeH);
|
||||||
else if (Options.ContainsKey("Password"))
|
changeSetting("X", input.ChangeX);
|
||||||
input.MakePassword(Options["Password"].Trim() == "1");
|
changeSetting("Y", input.ChangeY);
|
||||||
|
|
||||||
string topmost = null;
|
changeSetting("StringStyle", input.ChangeFontStringStyle);
|
||||||
if (Overrides.ContainsKey("TopMost"))
|
changeSetting("StringAlign", input.ChangeStringAlign);
|
||||||
topmost = Overrides["TopMost"];
|
|
||||||
else if (Options.ContainsKey("TopMost"))
|
changeSetting("FontColor", input.ChangeFontColor);
|
||||||
topmost = Options["TopMost"].Trim();
|
changeSetting("SolidColor", input.ChangeBackColor);
|
||||||
switch (topmost)
|
|
||||||
{
|
if (Overrides.ContainsKey("FocusDismiss"))
|
||||||
case "1":
|
input.MakeFocusDismiss(Overrides["FocusDismiss"] == "1");
|
||||||
input.MakeTopmost();
|
else if (Options.ContainsKey("FocusDismiss"))
|
||||||
break;
|
input.MakeFocusDismiss(Options["FocusDismiss"].Trim() == "1");
|
||||||
case "0":
|
|
||||||
break;
|
if (Overrides.ContainsKey("Password"))
|
||||||
default: // AUTO
|
input.MakePassword(Overrides["Password"] == "1");
|
||||||
if (skin.IsTopmost)
|
else if (Options.ContainsKey("Password"))
|
||||||
|
input.MakePassword(Options["Password"].Trim() == "1");
|
||||||
|
|
||||||
|
string topmost = null;
|
||||||
|
if (Overrides.ContainsKey("TopMost"))
|
||||||
|
topmost = Overrides["TopMost"];
|
||||||
|
else if (Options.ContainsKey("TopMost"))
|
||||||
|
topmost = Options["TopMost"].Trim();
|
||||||
|
switch (topmost)
|
||||||
|
{
|
||||||
|
case "1":
|
||||||
input.MakeTopmost();
|
input.MakeTopmost();
|
||||||
break;
|
break;
|
||||||
|
case "0":
|
||||||
|
break;
|
||||||
|
default: // AUTO
|
||||||
|
if (skin.IsTopmost)
|
||||||
|
input.MakeTopmost();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSetting("DefaultValue", input.DefaultValue);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
changeSetting("DefaultValue", input.DefaultValue);
|
string result = null;
|
||||||
|
|
||||||
#endregion
|
if (input.ShowInputBox())
|
||||||
|
|
||||||
if (!input.ShowInputBox())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
lock (this.locker)
|
|
||||||
{
|
{
|
||||||
this.LastInput = input.TextValue;
|
lock (this.locker)
|
||||||
|
{
|
||||||
|
this.LastInput = input.TextValue;
|
||||||
|
result = this.LastInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispose
|
||||||
|
input = null;
|
||||||
|
lock (this._InputBoxLocker)
|
||||||
|
{
|
||||||
|
this._InputBox.Dispose();
|
||||||
|
this._InputBox = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseInputBox()
|
||||||
|
{
|
||||||
|
lock (this._InputBoxLocker)
|
||||||
|
{
|
||||||
|
if (this._InputBox != null)
|
||||||
|
{
|
||||||
|
this._InputBox.Abort();
|
||||||
|
System.Threading.Thread.Sleep(50); // Wait for closing input box
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return input.TextValue;
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user