using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; // This is a utility class / toolkit for communicating with Rainmeter and managing // logging, INI settings, bangs, window positioning, multiple instances, and temporary // data storage. // // You should not need to edit this code except to expand the toolkit support. // // Rather, most of your plugin's code should go in PluginCode.cs or additional files // that you create (such as new forms, classes, and controls). namespace InputText { public class WindowWrapper : System.Windows.Forms.IWin32Window { public WindowWrapper(IntPtr handle) { _hwnd = handle; } public IntPtr Handle { get { return _hwnd; } } private IntPtr _hwnd; } public class Rainmeter { #region Methods for getting the screen-relative location of the current skin public static IntPtr GetConfigWindow(Rainmeter.Settings.InstanceSettings Instance) { return (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", Rainmeter.PluginBridge("GetConfig", Instance.INI_File)))); } public static int ConfigX(string sSkin) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", sSkin))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + sSkin + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Left; } public static int ConfigX(Rainmeter.Settings.InstanceSettings Instance) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", Rainmeter.PluginBridge("GetConfig", Instance.INI_File)))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + Rainmeter.PluginBridge("GetConfig", Instance.INI_File) + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Left; } public static int ConfigY(string sSkin) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", sSkin))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + sSkin + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Top; } public static int ConfigY(Rainmeter.Settings.InstanceSettings Instance) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", Rainmeter.PluginBridge("GetConfig", Instance.INI_File)))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + Rainmeter.PluginBridge("GetConfig", Instance.INI_File) + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Top; } public static int ConfigWidth(string sSkin) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", sSkin))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + sSkin + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Right - rct.Left; } public static int ConfigWidth(Rainmeter.Settings.InstanceSettings Instance) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", Rainmeter.PluginBridge("GetConfig", Instance.INI_File)))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + Rainmeter.PluginBridge("GetConfig", Instance.INI_File) + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Right - rct.Left; } public static int ConfigHeight(string sSkin) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", sSkin))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + sSkin + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Bottom - rct.Top; } public static int ConfigHeight(Rainmeter.Settings.InstanceSettings Instance) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", Rainmeter.PluginBridge("GetConfig", Instance.INI_File)))); RECT rct; if (!GetWindowRect(hwnd, out rct)) { Rainmeter.Log(LogLevel.Error, "Rainmeter told us the HWND for window '" + Rainmeter.PluginBridge("GetConfig", Instance.INI_File) + "' is " + hwnd.ToInt32().ToString() + "L, but couldn't receive a proper RECT from it"); return 0; } return rct.Bottom - rct.Top; } #region GetWindowRect() platform invoke to get the size/location of a window [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); #endregion #region GetParent() platform invoke to get the handle of a parent window [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] private static extern IntPtr GetParent(IntPtr hWnd); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; // x position of upper-left corner public int Top; // y position of upper-left corner public int Right; // x position of lower-right corner public int Bottom; // y position of lower-right corner } #endregion #region SendMessage -- SendMessage (this variant is only for WM_COPYSTRUCT messages) [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref COPYDATASTRUCT lParam); [StructLayout(LayoutKind.Sequential)] struct COPYDATASTRUCT { public UInt32 dwData; public int cbData; public IntPtr lpData; } #endregion #region FindWindowByClass -- platform invoke to find a window given a class name [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)] private static extern IntPtr FindWindowByClass(string lpClassName, IntPtr ZeroOnly); #endregion #region GetWindowInfo -- platform invoke to check a window's Z-order (Topmost=Auto) [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetWindowInfo(IntPtr hwnd, ref WINDOWINFO pwi); [StructLayout(LayoutKind.Sequential)] private struct WINDOWINFO { public uint cbSize; public RECT rcWindow; public RECT rcClient; public uint dwStyle; public uint dwExStyle; public uint dwWindowStatus; public uint cxWindowBorders; public uint cyWindowBorders; public ushort atomWindowType; public ushort wCreatorVersion; public WINDOWINFO(Boolean? filler) : this() // Allows automatic initialization of "cbSize" with "new WINDOWINFO(null/true/false)". { cbSize = (UInt32)(Marshal.SizeOf(typeof(WINDOWINFO))); } } // Call this function to determine if the parent skin is topmost public static bool ParentIsTopmost(Rainmeter.Settings.InstanceSettings Instance) { IntPtr hwnd = (IntPtr)(UInt32.Parse(Rainmeter.PluginBridge("GetWindow", Rainmeter.PluginBridge("GetConfig", Instance.INI_File)))); WINDOWINFO info = new WINDOWINFO(true); GetWindowInfo(hwnd, ref info); return ((info.dwExStyle & 0x00000008L) > 0); } #endregion #region SkinName -- gets the current skin name public static string SkinName(Rainmeter.Settings.InstanceSettings Instance) { try { return Instance.ConfigName; /* if (Instance.GetTempValue("_internal_SkinPath", string.Empty).ToString() == string.Empty) { string sAppDataPath = System.Environment.GetEnvironmentVariable("AppData").Trim(); string sRainmeterINIText = System.IO.File.ReadAllText(sAppDataPath + "\\Rainmeter\\Rainmeter.ini"); string sSkinPath = Chopper(sRainmeterINIText.Replace('\n', '\r'), "SkinPath=", "\r").Trim().TrimEnd(new char[] { '\\' }); Instance.SetTempValue("_internal_SkinPath", sSkinPath); } System.IO.FileInfo fi = new System.IO.FileInfo(Instance.INI_File); return fi.DirectoryName.Replace(Instance.GetTempValue("_internal_SkinPath", string.Empty).ToString(), string.Empty).TrimEnd(new char[] { '\\' }).TrimStart(new char[] { '\\' }); */ } catch { } return string.Empty; } #endregion #endregion #region Chopper -- string manipulation public static string Chopper(string sText, string sSearch, string sEnd, int offset) { string sIntermediate = ""; try { if ((sSearch == null) || (sSearch == string.Empty)) { sIntermediate = sText.Substring(offset); } else { if (sText.Contains(sSearch) == false) return sText; sIntermediate = sText.Substring(sText.IndexOf(sSearch) + sSearch.Length + offset); } if ((sEnd == null) || (sEnd == string.Empty)) return sIntermediate; if (sIntermediate.Contains(sEnd) == false) return sIntermediate; return sIntermediate.Substring(0, sIntermediate.IndexOf(sEnd)); } catch { if (sIntermediate == "") return sText; return sIntermediate; } } public static string Chopper(string sText, string sSearch, string sEnd) { return Chopper(sText, sSearch, sEnd, 0); } #endregion #region Version helpers (converts "1.04" or "1, 4" to 1004, etc.) public static UInt32 Version(double dVersion) { return (UInt32)(dVersion * 1000.0); } public static UInt32 Version(int iMajor, int iMinor) { return (UInt32)((iMajor * 1000) + iMinor); } #endregion #region Converts a C# 'string' to a C++ 'char *' public static unsafe char* String(string s) { fixed (char* p = s) return p; } #endregion #region Export for Rainmeter's new plugin bridge [DllImport("Rainmeter.dll", CharSet = CharSet.Auto)] private extern static unsafe char* PluginBridge(char* sCommand, char* sData); private unsafe static string PluginBridge(string sCommand, string sData) { try { return new string(PluginBridge(Rainmeter.String(sCommand), Rainmeter.String(sData))); } catch { } return string.Empty; } #endregion #region Read INI values using Rainmeter's 'ReadConfigString' export // We have to use this method rather than loading the .INI file manually because Rainmeter will // replace tokens for us. See: http://rainmeter.net/RainCMS/?q=Settings_BuiltInVariables // // Please note that this is done during plugin initialization and stored in the Instance // variable. This is done automatically, so this function should not be used by plugin developers // directly, therefore the methods here are 'private'. [DllImport("Rainmeter.dll", CharSet = CharSet.Auto)] private extern static unsafe char* ReadConfigString(char* sSection, char* key, char* defValue); // If an INI is missing, a blank string will be returned to avoid raising exceptions private unsafe static string ReadConfigString(string sSection, string sKey) { try { char* szString = ReadConfigString(Rainmeter.String(sSection), Rainmeter.String(sKey), Rainmeter.String(string.Empty)); if (szString != null) return new string(szString); } catch { } return string.Empty; } #endregion #region Use Rainmeter's 'LSLog' export to log using its format and settings [DllImport("Rainmeter.dll", CharSet = CharSet.Auto)] private extern static unsafe UInt16 LSLog(int nLevel, char* pszModule, char* pszMessage); public enum LogLevel : int { Error = 1, Warning = 2, Notice = 3, Debug = 4 } // You can call this function directly to log to Rainmeter.log // // Rainmeter needs to be configured to allow debug logging, of course. public static unsafe bool Log(LogLevel level, string sText) { return (Rainmeter.LSLog((int)level, Rainmeter.String("C# plugin"), Rainmeter.String(sText)) != 0); } #endregion #region Send a bang to Rainmeter (prepends a '!' if necessary) public static void Bang(string sBang) { if (!sBang.StartsWith("!")) sBang = "!" + sBang; System.Diagnostics.Process.Start(System.Windows.Forms.Application.ExecutablePath, sBang); } #endregion #region Settings are automatically created (and set at the top of 'Main.cs'), don't edit here // WARNING: DO NOT EDIT THIS HERE. Change your author name, version, etc. in 'Main.cs' public class Settings { public string Author = "(unknown)"; public double Version = 0.01; public string Email = string.Empty; public string Comments = string.Empty; public Settings(string _Author, double _Version) { this.Author = _Author; this.Version = _Version; } public Settings(string _Author, double _Version, string _Email) { this.Author = _Author; this.Version = _Version; this.Email = _Email; } public Settings(string _Author, double _Version, string _Email, string _Comments) { this.Author = _Author; this.Version = _Version; this.Email = _Email; this.Comments = _Comments; } public Dictionary Instances = new Dictionary(); public List KeyValues = new List(); public struct SectionKey { public UInt32 id; public string INI_File; public string Section; public string Key; public string Value; } public unsafe void AddInstance(char* iniFile, char* section, UInt32 id) { InstanceSettings new_instance = new InstanceSettings(); new_instance.Initialize(this, iniFile, section, id); this.Instances.Add(id, new_instance); bool bInSection = false; foreach (string line in System.IO.File.ReadAllLines(new_instance.INI_File)) { if (line.Trim().StartsWith(";")) continue; // ignore comments if (line.Trim().StartsWith("[")) bInSection = false; // new section // section test if (line.Trim().ToLower() == ("[" + new_instance.Section.ToLower() + "]")) bInSection = true; if (!bInSection) continue; // abort this pass if not in section if (!line.Contains("=")) continue; // abort this pass if there's no INI setting here string[] sLineParts = line.Trim().Split(new char[] { '=' }); SectionKey sk = new SectionKey(); sk.id = id; sk.INI_File = new_instance.INI_File; sk.Key = sLineParts[0].Trim(); sk.Section = new_instance.Section; sk.Value = ReadConfigString(new_instance.Section, sLineParts[0].Trim()); this.KeyValues.Add(sk); } } public void RemoveInstance(UInt32 id) { try { start_over: for (int i = 0; i < this.KeyValues.Count; i++) { if (this.KeyValues[i].id == id) { this.KeyValues.RemoveAt(i); goto start_over; // start over since the IEnumerable has been modified } } this.Instances.Remove(id); } catch { } } public class InstanceSettings { private UInt32 _ID = 0; private string _INI_File = string.Empty; private string _Section = string.Empty; private Settings PluginSettings = null; private Dictionary TempData = new Dictionary(); private object locker = new object(); public object GetTempValue(string name, object oDefault) { lock (this.locker) { if (this.TempData.ContainsKey(name)) return this.TempData[name]; return oDefault; } } public void SetTempValue(string name, object o) { lock (this.locker) { if (this.TempData.ContainsKey(name)) this.TempData[name] = o; else this.TempData.Add(name, o); } } public string INI_Value(string name) { try { foreach (SectionKey sk in PluginSettings.KeyValues) if (sk.id == this.ID) if (sk.Section == this.Section) if (sk.Key.Trim().ToLower() == name.Trim().ToLower()) return sk.Value; } catch { } return string.Empty; } public unsafe void Initialize(Settings _PluginSettings, char* iniFile, char* section, UInt32 id) { this.PluginSettings = _PluginSettings; this._ID = id; this._INI_File = new string(iniFile); this._Section = new string(section); this.ConfigName = Rainmeter.PluginBridge("GetConfig", this.INI_File); } public string GetVariable(string sVariable) { return Rainmeter.PluginBridge("GetVariable", "\"" + this.ConfigName + "\" " + sVariable.Trim() + ""); } public string SetVariable(string sVariable, object oData) { return Rainmeter.PluginBridge("SetVariable", "\"" + this.ConfigName + "\" " + sVariable.Trim() + " \"" + oData.ToString().Trim() + "\""); } public string ConfigName = string.Empty; public UInt32 ID { get { return this._ID; } set { this._ID = value; } } public string INI_File { get { return this._INI_File; } } public string Section { get { return this._Section; } } } } #endregion } public class YourPlugin { #region YourPlugin class -- do not modify // This class is a simple launcher for your actual code in PluginCode.cs // // We make use of non-volatile data and threading to let your work run in another // thread, making Rainmeter nice and responsive. Checks are automatically performed // so that overlapping of execution does not occur. // Default values of a blank string for GetUpdate() and zero for Update()/Update2() // are returned. public UInt32 Update(Rainmeter.Settings Plugin, UInt32 id) { bool bAlreadyRunning = (bool)Plugin.Instances[id].GetTempValue("__RMT_U_AlreadyRunning", false); if (!bAlreadyRunning) { UpdateThread thread_details = new UpdateThread(Plugin.Instances[id]); Thread thread = new Thread(new ThreadStart(thread_details.Go)); thread.Start(); } try { return (UInt32)Plugin.Instances[id].GetTempValue("__RMT_U_LastValue", 0); } catch { return 0; } } private class UpdateThread { private Rainmeter.Settings.InstanceSettings Instance = null; public UpdateThread(Rainmeter.Settings.InstanceSettings _Instance) { this.Instance = _Instance; } public void Go() { this.Instance.SetTempValue("__RMT_U_AlreadyRunning", true); try { this.Instance.SetTempValue("__RMT_U_LastValue", new PluginCode().Update(this.Instance)); } catch (Exception ex) { Rainmeter.Log(Rainmeter.LogLevel.Error, "C# plugin in GetString(), " + ex.GetType().ToString() + ": " + ex.Message); } this.Instance.SetTempValue("__RMT_U_AlreadyRunning", false); } } public double Update2(Rainmeter.Settings Plugin, UInt32 id) { bool bAlreadyRunning = (bool)Plugin.Instances[id].GetTempValue("__RMT_U2_AlreadyRunning", false); if (!bAlreadyRunning) { Update2Thread thread_details = new Update2Thread(Plugin.Instances[id]); Thread thread = new Thread(new ThreadStart(thread_details.Go)); thread.Start(); } try { return (double)Plugin.Instances[id].GetTempValue("__RMT_U2_LastValue", 0.0); } catch { return 0.0; } } private class Update2Thread { private Rainmeter.Settings.InstanceSettings Instance = null; public Update2Thread(Rainmeter.Settings.InstanceSettings _Instance) { this.Instance = _Instance; } public void Go() { this.Instance.SetTempValue("__RMT_U2_AlreadyRunning", true); try { this.Instance.SetTempValue("__RMT_U2_LastValue", new PluginCode().Update2(this.Instance)); } catch (Exception ex) { Rainmeter.Log(Rainmeter.LogLevel.Error, "C# plugin in GetString(), " + ex.GetType().ToString() + ": " + ex.Message); } this.Instance.SetTempValue("__RMT_U2_AlreadyRunning", false); } } public string GetString(Rainmeter.Settings Plugin, UInt32 id) { bool bAlreadyRunning = (bool)Plugin.Instances[id].GetTempValue("__RMT_GS_AlreadyRunning", false); if (!bAlreadyRunning) { GetStringThread thread_details = new GetStringThread(Plugin.Instances[id]); Thread thread = new Thread(new ThreadStart(thread_details.Go)); thread.Start(); } try { return (string)Plugin.Instances[id].GetTempValue("__RMT_GS_LastValue", string.Empty); } catch { return string.Empty; } } private class GetStringThread { private Rainmeter.Settings.InstanceSettings Instance = null; public GetStringThread(Rainmeter.Settings.InstanceSettings _Instance) { this.Instance = _Instance; } public void Go() { this.Instance.SetTempValue("__RMT_GS_AlreadyRunning", true); try { this.Instance.SetTempValue("__RMT_GS_LastValue", new PluginCode().GetString(this.Instance)); } catch (Exception ex) { Rainmeter.Log(Rainmeter.LogLevel.Error, "C# plugin in GetString(), " + ex.GetType().ToString() + ": " + ex.Message); } this.Instance.SetTempValue("__RMT_GS_AlreadyRunning", false); } } public void ExecuteBang(Rainmeter.Settings Plugin, UInt32 id, string sArguments) { bool bAlreadyRunning = (bool)Plugin.Instances[id].GetTempValue("__RMT_EB_AlreadyRunning", false); if (!bAlreadyRunning) { ExecuteBangThread thread_details = new ExecuteBangThread(Plugin.Instances[id], sArguments); Thread thread = new Thread(new ThreadStart(thread_details.Go)); thread.Start(); } return; } private class ExecuteBangThread { private Rainmeter.Settings.InstanceSettings Instance = null; private string Command = string.Empty; public ExecuteBangThread(Rainmeter.Settings.InstanceSettings _Instance, string _Command) { this.Instance = _Instance; this.Command = _Command; } public void Go() { this.Instance.SetTempValue("__RMT_EB_AlreadyRunning", true); try { new PluginCode().ExecuteBang(this.Instance, this.Command); } catch (Exception ex) { Rainmeter.Log(Rainmeter.LogLevel.Error, "C# plugin in GetString(), " + ex.GetType().ToString() + ": " + ex.Message); } this.Instance.SetTempValue("__RMT_EB_AlreadyRunning", false); } } #endregion } }