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:
		@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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<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)
 | 
			
		||||
        {
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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<string, string> Options = new Dictionary<string, string>();
 | 
			
		||||
            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<string> commands = new List<string>();
 | 
			
		||||
 | 
			
		||||
                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);
 | 
			
		||||
                    param.Commands.Add(sCurrentLine);
 | 
			
		||||
                }
 | 
			
		||||
                #endregion
 | 
			
		||||
 | 
			
		||||
                return param.Commands.Count > 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                foreach (string sCurrentLine in commands)
 | 
			
		||||
            #endregion
 | 
			
		||||
 | 
			
		||||
            // 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, Options))
 | 
			
		||||
                            if (!ExecuteLine(sCurrentLine, param.Options))
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            // Continue to the next line, if there is any
 | 
			
		||||
                        }
 | 
			
		||||
                #endregion
 | 
			
		||||
                return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
            // Unhandled command, log the message but otherwise do nothing
 | 
			
		||||
            API.Log(API.LogType.Debug, "InputText: Received command \"" + sParts[0].Trim() + "\", left unhandled");
 | 
			
		||||
 | 
			
		||||
            #endregion
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region This is all code custom to this plugin
 | 
			
		||||
@@ -235,17 +250,26 @@ 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<string, string> Options)
 | 
			
		||||
        {
 | 
			
		||||
            // No INI overrides provided, so create an empty list
 | 
			
		||||
            return GetUserInput(Options, new Dictionary<string, string>());
 | 
			
		||||
        }
 | 
			
		||||
        private string GetUserInput(Dictionary<string, string> Options, Dictionary<string, string> Overrides)
 | 
			
		||||
        {
 | 
			
		||||
            InputBox input = null;
 | 
			
		||||
 | 
			
		||||
            lock (this._InputBoxLocker)
 | 
			
		||||
            {
 | 
			
		||||
                SkinWindow skin = new SkinWindow(rm);
 | 
			
		||||
 | 
			
		||||
                // Create the form.  'InputBox' is a .NET form with a textbox and two button controls on it.
 | 
			
		||||
            InputBox input = new InputBox(skin);
 | 
			
		||||
                this._InputBox = new InputBox(skin);
 | 
			
		||||
                input = this._InputBox;
 | 
			
		||||
 | 
			
		||||
                input.ChangeX("0");
 | 
			
		||||
                input.ChangeY("0");
 | 
			
		||||
 | 
			
		||||
@@ -305,15 +329,39 @@ namespace InputText
 | 
			
		||||
                changeSetting("DefaultValue", input.DefaultValue);
 | 
			
		||||
 | 
			
		||||
                #endregion
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!input.ShowInputBox())
 | 
			
		||||
                return null;
 | 
			
		||||
            string result = null;
 | 
			
		||||
 | 
			
		||||
            if (input.ShowInputBox())
 | 
			
		||||
            {
 | 
			
		||||
                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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user