From 167123bcf79def96918dffa7244f36bb78e7e704 Mon Sep 17 00:00:00 2001 From: spx Date: Sat, 2 Mar 2013 00:29:25 +0900 Subject: [PATCH] InputText: Fixed multi-threading issues --- Plugins/PluginInputText/InputBox.cs | 9 + Plugins/PluginInputText/Main.cs | 46 ++++- Plugins/PluginInputText/PluginCode.cs | 268 +++++++++++++++----------- 3 files changed, 210 insertions(+), 113 deletions(-) diff --git a/Plugins/PluginInputText/InputBox.cs b/Plugins/PluginInputText/InputBox.cs index d7967808..ffdbe6df 100644 --- a/Plugins/PluginInputText/InputBox.cs +++ b/Plugins/PluginInputText/InputBox.cs @@ -294,6 +294,9 @@ namespace InputText public bool ShowInputBox() { + if (this.drBackup != DialogResult.None) + return false; + if (this._FocusDismiss) { this.Show(this.parent); @@ -324,6 +327,12 @@ namespace InputText return true; } + public void Abort() + { + this.drBackup = DialogResult.Cancel; + this.Close(); + } + private void txtInput_Leave(object sender, EventArgs e) { if (this._FocusDismiss) diff --git a/Plugins/PluginInputText/Main.cs b/Plugins/PluginInputText/Main.cs index 81629e57..690d1832 100644 --- a/Plugins/PluginInputText/Main.cs +++ b/Plugins/PluginInputText/Main.cs @@ -71,17 +71,51 @@ namespace InputText 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 Options; + internal List Commands; + internal string Command; + internal BangType Type; + + internal ExecuteBangParam(string args) + { + this.Options = new Dictionary(); + this.Commands = new List(); + this.Command = args.Trim(); + this.Type = BangType.Unknown; + } + }; + private void ExecuteBangThread(object state) { - string command = (string)state; + ExecuteBangParam param = (ExecuteBangParam)state; try { - ExecuteCommands(command); + ExecuteCommands(param); } catch (Exception ex) { @@ -93,6 +127,11 @@ namespace InputText this.IsExecuteBangRunning = false; } } + + internal void Dispose() + { + CloseInputBox(); + } } #region Plugin @@ -112,6 +151,7 @@ namespace InputText public unsafe static void Finalize(void* data) { uint id = (uint)data; + Measures[id].Dispose(); Measures.Remove(id); } diff --git a/Plugins/PluginInputText/PluginCode.cs b/Plugins/PluginInputText/PluginCode.cs index 1c8f6574..b7ccd57a 100644 --- a/Plugins/PluginInputText/PluginCode.cs +++ b/Plugins/PluginInputText/PluginCode.cs @@ -25,59 +25,45 @@ namespace InputText { internal partial class Measure { - private void ExecuteCommands(string Command) + private bool ReadOptions(ExecuteBangParam param) { - Command = Command.Trim(); - // Get default options - Dictionary Options = new Dictionary(); - ReadOption("DefaultValue", Options); - ReadOption("X", Options); - ReadOption("Y", Options); - ReadOption("W", Options); - ReadOption("H", Options); - ReadOption("StringStyle", Options); - ReadOption("StringAlign", Options); - ReadOption("FocusDismiss", Options); - ReadOption("FontColor", Options); - ReadOption("FontFace", Options); - ReadOption("FontSize", Options); - ReadOption("SolidColor", Options); - ReadOption("Password", Options); - ReadOption("TopMost", Options); + ReadOption("DefaultValue", param.Options); + ReadOption("X", param.Options); + ReadOption("Y", param.Options); + ReadOption("W", param.Options); + ReadOption("H", param.Options); + ReadOption("StringStyle", param.Options); + ReadOption("StringAlign", param.Options); + ReadOption("FocusDismiss", param.Options); + ReadOption("FontColor", param.Options); + ReadOption("FontFace", param.Options); + ReadOption("FontSize", param.Options); + ReadOption("SolidColor", param.Options); + ReadOption("Password", param.Options); + ReadOption("TopMost", param.Options); #region Handle a single parameter // 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. - if (!Command.Contains(" ")) + if (!param.Command.Contains(" ")) { - // Assume that the parameter is the name of the variable - - // 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; + param.Type = ExecuteBangParam.BangType.SetVariable; + return true; } #endregion #region Handle multiple parameters // 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 (sParts[0].Trim().ToUpper() == "EXECUTEBATCH") { + param.Type = ExecuteBangParam.BangType.ExecuteBatch; + // 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. // If a line contains $UserInput$, then an input textbox is opened and command @@ -131,8 +117,6 @@ namespace InputText #region Parse commands in range // 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. - List commands = new List(); - for (int i = iMin; i <= iMax; i++) { // Read this command's line @@ -142,28 +126,59 @@ namespace InputText if (string.IsNullOrEmpty(sCurrentLine)) break; - 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 + param.Commands.Add(sCurrentLine); } #endregion - return; - } - // Unhandled command, log the message but otherwise do nothing - API.Log(API.LogType.Debug, "InputText: Received command \"" + sParts[0].Trim() + "\", left unhandled"); + return param.Commands.Count > 0; + } #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 @@ -235,6 +250,9 @@ namespace InputText delegate void ChangeSettingFromString(string value); delegate void ChangeInputBoxSetting(string option, ChangeSettingFromString method); + private InputBox _InputBox = null; + private object _InputBoxLocker = new object(); + private string GetUserInput(Dictionary Options) { // No INI overrides provided, so create an empty list @@ -242,78 +260,108 @@ namespace InputText } private string GetUserInput(Dictionary Options, Dictionary 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. - 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) => + lock (this._InputBoxLocker) { - if (Overrides.ContainsKey(opt)) - change(Overrides[opt]); - else if (Options.ContainsKey(opt)) - change(Options[opt]); - }; + SkinWindow skin = new SkinWindow(rm); - changeSetting("FontFace", input.ChangeFontFace); - changeSetting("FontSize", input.ChangeFontSize); + // Create the form. 'InputBox' is a .NET form with a textbox and two button controls on it. + this._InputBox = new InputBox(skin); + input = this._InputBox; - changeSetting("W", input.ChangeW); - changeSetting("H", input.ChangeH); - changeSetting("X", input.ChangeX); - changeSetting("Y", input.ChangeY); + input.ChangeX("0"); + input.ChangeY("0"); - changeSetting("StringStyle", input.ChangeFontStringStyle); - changeSetting("StringAlign", input.ChangeStringAlign); + // Change the styles of the InputBox form based on overrides or INI values + #region Style and preference tweaks (INI and override settings) - changeSetting("FontColor", input.ChangeFontColor); - changeSetting("SolidColor", input.ChangeBackColor); + ChangeInputBoxSetting changeSetting = (opt, change) => + { + if (Overrides.ContainsKey(opt)) + change(Overrides[opt]); + else if (Options.ContainsKey(opt)) + change(Options[opt]); + }; - if (Overrides.ContainsKey("FocusDismiss")) - input.MakeFocusDismiss(Overrides["FocusDismiss"] == "1"); - else if (Options.ContainsKey("FocusDismiss")) - input.MakeFocusDismiss(Options["FocusDismiss"].Trim() == "1"); + changeSetting("FontFace", input.ChangeFontFace); + changeSetting("FontSize", input.ChangeFontSize); - if (Overrides.ContainsKey("Password")) - input.MakePassword(Overrides["Password"] == "1"); - else if (Options.ContainsKey("Password")) - input.MakePassword(Options["Password"].Trim() == "1"); + changeSetting("W", input.ChangeW); + changeSetting("H", input.ChangeH); + changeSetting("X", input.ChangeX); + changeSetting("Y", input.ChangeY); - 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(); - break; - case "0": - break; - default: // AUTO - if (skin.IsTopmost) + changeSetting("StringStyle", input.ChangeFontStringStyle); + changeSetting("StringAlign", input.ChangeStringAlign); + + changeSetting("FontColor", input.ChangeFontColor); + changeSetting("SolidColor", input.ChangeBackColor); + + if (Overrides.ContainsKey("FocusDismiss")) + input.MakeFocusDismiss(Overrides["FocusDismiss"] == "1"); + else if (Options.ContainsKey("FocusDismiss")) + input.MakeFocusDismiss(Options["FocusDismiss"].Trim() == "1"); + + if (Overrides.ContainsKey("Password")) + input.MakePassword(Overrides["Password"] == "1"); + 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(); - 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()) - return null; - - lock (this.locker) + if (input.ShowInputBox()) { - 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