From 39600d1cf304c85388fc90b06eca00d2c7e33c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=B6ller?= Date: Mon, 6 Sep 2010 19:53:13 +0000 Subject: [PATCH] Added a desktop gadget implementation. --- GUI/Gadget.cs | 172 +++++++++++ GUI/GadgetWindow.cs | 423 ++++++++++++++++++++++++++ GUI/HardwareNode.cs | 27 +- GUI/HardwareTypeImage.cs | 95 ++++++ GUI/MainForm.Designer.cs | 12 +- GUI/MainForm.cs | 52 +++- GUI/SensorGadget.cs | 361 ++++++++++++++++++++++ GUI/SensorNotifyIcon.cs | 4 +- GUI/ShowDesktop.cs | 172 +++++++++++ Hardware/ATI/ATIGPU.cs | 2 +- Hardware/HDD/HDD.cs | 4 + Hardware/Hardware.cs | 4 + Hardware/IHardware.cs | 15 +- Hardware/Identifier.cs | 9 +- Hardware/Mainboard/Mainboard.cs | 6 +- Hardware/Mainboard/SuperIOHardware.cs | 10 +- Hardware/Nvidia/NvidiaGPU.cs | 2 +- Hardware/TBalancer/TBalancer.cs | 4 + OpenHardwareMonitor.csproj | 10 + Properties/AssemblyVersion.cs | 4 +- Resources/barback.png | Bin 0 -> 440 bytes Resources/barblue.png | Bin 0 -> 571 bytes Resources/gadget.png | Bin 0 -> 5969 bytes Resources/gadget.xcf | Bin 0 -> 91414 bytes 24 files changed, 1327 insertions(+), 61 deletions(-) create mode 100644 GUI/Gadget.cs create mode 100644 GUI/GadgetWindow.cs create mode 100644 GUI/HardwareTypeImage.cs create mode 100644 GUI/SensorGadget.cs create mode 100644 GUI/ShowDesktop.cs create mode 100644 Resources/barback.png create mode 100644 Resources/barblue.png create mode 100644 Resources/gadget.png create mode 100644 Resources/gadget.xcf diff --git a/GUI/Gadget.cs b/GUI/Gadget.cs new file mode 100644 index 0000000..37ed8e4 --- /dev/null +++ b/GUI/Gadget.cs @@ -0,0 +1,172 @@ +/* + + Version: MPL 1.1/GPL 2.0/LGPL 2.1 + + The contents of this file are subject to the Mozilla Public License Version + 1.1 (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + for the specific language governing rights and limitations under the License. + + The Original Code is the Open Hardware Monitor code. + + The Initial Developer of the Original Code is + Michael Möller . + Portions created by the Initial Developer are Copyright (C) 2010 + the Initial Developer. All Rights Reserved. + + Contributor(s): + + Alternatively, the contents of this file may be used under the terms of + either the GNU General Public License Version 2 or later (the "GPL"), or + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the GPL or the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of either the GPL or the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the GPL or the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the MPL, the GPL or the LGPL. + +*/ + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Drawing.Text; +using System.Windows.Forms; + +namespace OpenHardwareMonitor.GUI { + public abstract class Gadget : IDisposable { + + private GadgetWindow window; + private Bitmap buffer; + private Graphics graphics; + + public Gadget() { + this.window = new GadgetWindow(); + CreateBuffer(); + } + + public void Dispose() { + this.graphics.Dispose(); + this.buffer.Dispose(); + } + + public Point Location { + get { + return window.Location; + } + set { + window.Location = value; + } + } + + public event EventHandler LocationChanged { + add { + window.LocationChanged += value; + } + remove { + window.LocationChanged -= value; + } + } + + protected virtual Size Size { + get { + return window.Size; + } + set { + if (window.Size != value) { + DisposeBuffer(); + this.window.Size = value; + CreateBuffer(); + } + } + } + + public byte Opacity { + get { + return window.Opacity; + } + set { + window.Opacity = value; + } + } + + public bool LockPosition { + get { + return window.LockPosition; + } + set { + window.LockPosition = value; + } + } + + public bool AlwaysOnTop { + get { + return window.AlwaysOnTop; + } + set { + window.AlwaysOnTop = value; + } + } + + public ContextMenu ContextMenu { + get { + return window.ContextMenu; + } + set { + window.ContextMenu = value; + } + } + + private void CreateBuffer() { + this.buffer = new Bitmap(window.Size.Width, window.Size.Height, + PixelFormat.Format32bppArgb); + this.graphics = Graphics.FromImage(this.buffer); + if (Environment.OSVersion.Version.Major > 5) { + this.graphics.TextRenderingHint = TextRenderingHint.SystemDefault; + this.graphics.SmoothingMode = SmoothingMode.HighQuality; + } + } + + private void DisposeBuffer() { + if (buffer != null) { + this.buffer.Dispose(); + this.buffer = null; + } + if (graphics != null) { + this.graphics.Dispose(); + this.graphics = null; + } + } + + public bool Visible { + get { + return window.Visible; + } + set { + if (value != window.Visible) { + if (value) + Redraw(); + window.Visible = value; + } + } + } + + public void Redraw() { + OnPaint(new PaintEventArgs(graphics, + new Rectangle(Point.Empty, window.Size))); + window.Update(buffer); + } + + protected abstract void OnPaint(PaintEventArgs e); + + } +} diff --git a/GUI/GadgetWindow.cs b/GUI/GadgetWindow.cs new file mode 100644 index 0000000..677379c --- /dev/null +++ b/GUI/GadgetWindow.cs @@ -0,0 +1,423 @@ +/* + + Version: MPL 1.1/GPL 2.0/LGPL 2.1 + + The contents of this file are subject to the Mozilla Public License Version + 1.1 (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + for the specific language governing rights and limitations under the License. + + The Original Code is the Open Hardware Monitor code. + + The Initial Developer of the Original Code is + Michael Möller . + Portions created by the Initial Developer are Copyright (C) 2010 + the Initial Developer. All Rights Reserved. + + Contributor(s): + + Alternatively, the contents of this file may be used under the terms of + either the GNU General Public License Version 2 or later (the "GPL"), or + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the GPL or the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of either the GPL or the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the GPL or the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the MPL, the GPL or the LGPL. + +*/ + +using System; +using System.Drawing; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace OpenHardwareMonitor.GUI { + public class GadgetWindow : NativeWindow { + + private bool visible = false; + private bool lockPosition = false; + private bool alwaysOnTop = false; + private byte opacity = 255; + private Point location = new Point(100, 100); + private Size size = new Size(130, 84); + private ContextMenu contextMenu = null; + private MethodInfo commandDispatch; + + public GadgetWindow() { + Type commandType = + typeof(Form).Assembly.GetType("System.Windows.Forms.Command"); + commandDispatch = commandType.GetMethod("DispatchID", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, + null, new Type[]{ typeof(int) }, null); + + this.CreateHandle(CreateParams); + + // move window to the bottom + MoveToBottom(Handle); + + // prevent window from fading to a glass sheet when peek is invoked + try { + bool value = true; + int r = NativeMethods.DwmSetWindowAttribute(Handle, + WindowAttribute.DWMWA_EXCLUDED_FROM_PEEK, ref value, + Marshal.SizeOf(value)); + } catch (DllNotFoundException) { } catch (EntryPointNotFoundException) { } + } + + private void ShowDesktopChanged(bool showDesktop) { + if (showDesktop) { + MoveToTopMost(Handle); + } else { + MoveToBottom(Handle); + } + } + + private void MoveToBottom(IntPtr handle) { + NativeMethods.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); + } + + private void MoveToTopMost(IntPtr handle) { + NativeMethods.SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); + } + + private void ShowContextMenu(Point position) { + NativeMethods.TrackPopupMenuEx(contextMenu.Handle, + TPM_RIGHTBUTTON | TPM_VERTICAL, position.X, + position.Y, Handle, IntPtr.Zero); + } + + protected virtual CreateParams CreateParams { + get { + CreateParams cp = new CreateParams(); + cp.Width = size.Width; + cp.Height = size.Height; + cp.X = location.X; + cp.Y = location.Y; + cp.ExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW; + return cp; + } + } + + protected override void WndProc(ref Message message) { + switch (message.Msg) { + case WM_COMMAND: + // need to dispatch the message for the context menu + if (message.LParam == IntPtr.Zero) + commandDispatch.Invoke(null, new object[] { + message.WParam.ToInt32() & 0xFFFF }); + break; + case WM_NCHITTEST: + // all pixels of the form belong to the caption + message.Result = HTCAPTION; + break; + case WM_NCLBUTTONDBLCLK: + message.Result = IntPtr.Zero; break; + case WM_NCRBUTTONDOWN: + message.Result = IntPtr.Zero; break; + case WM_NCRBUTTONUP: + if (contextMenu != null) + ShowContextMenu(new Point( + (int)((uint)message.LParam & 0xFFFF), + (int)(((uint)message.LParam >>16) & 0xFFFF))); + message.Result = IntPtr.Zero; + break; + case WM_WINDOWPOSCHANGING: + WindowPos wp = (WindowPos)Marshal.PtrToStructure( + message.LParam, typeof(WindowPos)); + + // add the nomove flag if position is locked + if (lockPosition) + wp.flags |= SWP_NOMOVE; + + // prevent the window from leaving the screen + if ((wp.flags & SWP_NOMOVE) == 0) { + Rectangle rect = Screen.GetWorkingArea(new Point(wp.x, wp.y)); + const int margin = 20; + wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin); + wp.x = Math.Min(wp.x, rect.Right - margin); + wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin); + wp.y = Math.Min(wp.y, rect.Bottom - margin); + + // raise the event if location changed + if (location.X != wp.x || location.Y != wp.y) { + location = new Point(wp.x, wp.y); + if (LocationChanged != null) + LocationChanged(this, EventArgs.Empty); + } + } + + Marshal.StructureToPtr(wp, message.LParam, false); + message.Result = IntPtr.Zero; + break; + default: + base.WndProc(ref message); break; + } + } + + private BlendFunction CreateBlendFunction() { + BlendFunction blend = new BlendFunction(); + blend.BlendOp = AC_SRC_OVER; + blend.BlendFlags = 0; + blend.SourceConstantAlpha = opacity; + blend.AlphaFormat = AC_SRC_ALPHA; + return blend; + } + + public void Update(Bitmap bitmap) { + IntPtr screen = NativeMethods.GetDC(IntPtr.Zero); + IntPtr memory = NativeMethods.CreateCompatibleDC(screen); + IntPtr newHBitmap = IntPtr.Zero; + IntPtr oldHBitmap = IntPtr.Zero; + + try { + newHBitmap = bitmap.GetHbitmap(Color.Black); + oldHBitmap = NativeMethods.SelectObject(memory, newHBitmap); + + Size size = bitmap.Size; + Point pointSource = Point.Empty; + Point topPos = Location; + + BlendFunction blend = CreateBlendFunction(); + NativeMethods.UpdateLayeredWindow(Handle, screen, ref topPos, + ref size, memory, ref pointSource, 0, ref blend, ULW_ALPHA); + } finally { + NativeMethods.ReleaseDC(IntPtr.Zero, screen); + if (newHBitmap != IntPtr.Zero) { + NativeMethods.SelectObject(memory, oldHBitmap); + NativeMethods.DeleteObject(newHBitmap); + } + NativeMethods.DeleteDC(memory); + } + } + + public byte Opacity { + get { + return opacity; + } + set { + if (opacity != value) { + opacity = value; + BlendFunction blend = CreateBlendFunction(); + NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero, + IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA); + } + } + } + + public bool Visible { + get { + return visible; + } + set { + if (visible != value) { + visible = value; + NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | + (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW)); + if (value) + ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged; + else + ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged; + } + } + } + + // if locked, the window can not be moved + public bool LockPosition { + get { + return lockPosition; + } + set { + lockPosition = value; + } + } + + public bool AlwaysOnTop { + get { + return alwaysOnTop; + } + set { + if (value != alwaysOnTop) { + alwaysOnTop = value; + if (alwaysOnTop) { + ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged; + MoveToTopMost(Handle); + } else { + MoveToBottom(Handle); + ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged; + } + } + } + } + + public Size Size { + get { + return size; + } + set { + if (size != value) { + size = value; + NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, size.Width, + size.Height, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | + SWP_NOSENDCHANGING); + } + } + } + + public Point Location { + get { + return location; + } + set { + NativeMethods.SetWindowPos(Handle, IntPtr.Zero, value.X, value.Y, 0, + 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING); + location = value; + if (LocationChanged != null) + LocationChanged(this, EventArgs.Empty); + } + } + + public event EventHandler LocationChanged; + + public ContextMenu ContextMenu { + get { + return contextMenu; + } + set { + this.contextMenu = value; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct BlendFunction { + public byte BlendOp; + public byte BlendFlags; + public byte SourceConstantAlpha; + public byte AlphaFormat; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct WindowPos { + public IntPtr hwnd; + public IntPtr hwndInsertAfter; + public int x; + public int y; + public int cx; + public int cy; + public uint flags; + } + + public static readonly IntPtr HWND_BOTTOM = (IntPtr)1; + public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1); + + public const int WS_EX_LAYERED = 0x00080000; + public const int WS_EX_TOOLWINDOW = 0x00000080; + + public const uint SWP_NOSIZE = 0x0001; + public const uint SWP_NOMOVE = 0x0002; + public const uint SWP_NOACTIVATE = 0x0010; + public const uint SWP_HIDEWINDOW = 0x0080; + public const uint SWP_SHOWWINDOW = 0x0040; + public const uint SWP_NOZORDER = 0x0004; + public const uint SWP_NOSENDCHANGING = 0x0400; + + public const int ULW_COLORKEY = 0x00000001; + public const int ULW_ALPHA = 0x00000002; + public const int ULW_OPAQUE = 0x00000004; + + public const byte AC_SRC_OVER = 0x00; + public const byte AC_SRC_ALPHA = 0x01; + + public const int WM_NCHITTEST = 0x0084; + public const int WM_NCLBUTTONDBLCLK = 0x00A3; + public const int WM_NCLBUTTONDOWN = 0x00A1; + public const int WM_NCLBUTTONUP = 0x00A2; + public const int WM_NCRBUTTONDOWN = 0x00A4; + public const int WM_NCRBUTTONUP = 0x00A5; + public const int WM_WINDOWPOSCHANGING = 0x0046; + public const int WM_COMMAND = 0x0111; + + public const int TPM_RIGHTBUTTON = 0x0002; + public const int TPM_VERTICAL = 0x0040; + + public readonly IntPtr HTCAPTION = (IntPtr)2; + + private enum WindowAttribute : int { + DWMWA_NCRENDERING_ENABLED = 1, + DWMWA_NCRENDERING_POLICY, + DWMWA_TRANSITIONS_FORCEDISABLED, + DWMWA_ALLOW_NCPAINT, + DWMWA_CAPTION_BUTTON_BOUNDS, + DWMWA_NONCLIENT_RTL_LAYOUT, + DWMWA_FORCE_ICONIC_REPRESENTATION, + DWMWA_FLIP3D_POLICY, + DWMWA_EXTENDED_FRAME_BOUNDS, + DWMWA_HAS_ICONIC_BITMAP, + DWMWA_DISALLOW_PEEK, + DWMWA_EXCLUDED_FROM_PEEK, + DWMWA_LAST + } + + private static class NativeMethods { + private const string USER = "user32.dll"; + private const string GDI = "gdi32.dll"; + public const string DWMAPI = "dwmapi.dll"; + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, + ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, + int crKey, ref BlendFunction pblend, int dwFlags); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, + IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc, + int crKey, ref BlendFunction pblend, int dwFlags); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern bool SetWindowPos(IntPtr hWnd, + IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, + int x, int y, IntPtr hWnd, IntPtr tpmParams); + + [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] + public static extern IntPtr CreateCompatibleDC(IntPtr hDC); + + [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteDC(IntPtr hdc); + + [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] + public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); + + [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteObject(IntPtr hObject); + + [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)] + public static extern int DwmSetWindowAttribute(IntPtr hwnd, + WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute); + } + } +} diff --git a/GUI/HardwareNode.cs b/GUI/HardwareNode.cs index ff1cf62..3c90d34 100644 --- a/GUI/HardwareNode.cs +++ b/GUI/HardwareNode.cs @@ -55,32 +55,7 @@ namespace OpenHardwareMonitor.GUI { this.settings = settings; this.unitManager = unitManager; this.hardware = hardware; - switch (hardware.HardwareType) { - case HardwareType.CPU: - this.Image = Utilities.EmbeddedResources.GetImage("cpu.png"); - break; - case HardwareType.GPU: - if (hardware.Identifier.ToString().Contains("nvidia")) - this.Image = Utilities.EmbeddedResources.GetImage("nvidia.png"); - else - this.Image = Utilities.EmbeddedResources.GetImage("ati.png"); - break; - case HardwareType.HDD: - this.Image = Utilities.EmbeddedResources.GetImage("hdd.png"); - break; - case HardwareType.Heatmaster: - this.Image = Utilities.EmbeddedResources.GetImage("bigng.png"); - break; - case HardwareType.Mainboard: - this.Image = Utilities.EmbeddedResources.GetImage("mainboard.png"); - break; - case HardwareType.SuperIO: - this.Image = Utilities.EmbeddedResources.GetImage("chip.png"); - break; - case HardwareType.TBalancer: - this.Image = Utilities.EmbeddedResources.GetImage("bigng.png"); - break; - } + this.Image = HardwareTypeImage.Instance.GetImage(hardware.HardwareType); typeNodes.Add(new TypeNode(SensorType.Voltage)); typeNodes.Add(new TypeNode(SensorType.Clock)); diff --git a/GUI/HardwareTypeImage.cs b/GUI/HardwareTypeImage.cs new file mode 100644 index 0000000..0635b46 --- /dev/null +++ b/GUI/HardwareTypeImage.cs @@ -0,0 +1,95 @@ +/* + + Version: MPL 1.1/GPL 2.0/LGPL 2.1 + + The contents of this file are subject to the Mozilla Public License Version + 1.1 (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + for the specific language governing rights and limitations under the License. + + The Original Code is the Open Hardware Monitor code. + + The Initial Developer of the Original Code is + Michael Möller . + Portions created by the Initial Developer are Copyright (C) 2010 + the Initial Developer. All Rights Reserved. + + Contributor(s): + + Alternatively, the contents of this file may be used under the terms of + either the GNU General Public License Version 2 or later (the "GPL"), or + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the GPL or the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of either the GPL or the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the GPL or the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the MPL, the GPL or the LGPL. + +*/ + +using System; +using System.Drawing; +using System.Collections.Generic; +using OpenHardwareMonitor.Hardware; + +namespace OpenHardwareMonitor.GUI { + public class HardwareTypeImage { + private static HardwareTypeImage instance = new HardwareTypeImage(); + + private IDictionary images = + new Dictionary(); + + private HardwareTypeImage() { } + + public static HardwareTypeImage Instance { + get { return instance; } + } + + public Image GetImage(HardwareType hardwareType) { + Image image; + if (images.TryGetValue(hardwareType, out image)) { + return image; + } else { + switch (hardwareType) { + case HardwareType.CPU: + image = Utilities.EmbeddedResources.GetImage("cpu.png"); + break; + case HardwareType.GpuNvidia: + image = Utilities.EmbeddedResources.GetImage("nvidia.png"); + break; + case HardwareType.GpuAti: + image = Utilities.EmbeddedResources.GetImage("ati.png"); + break; + case HardwareType.HDD: + image = Utilities.EmbeddedResources.GetImage("hdd.png"); + break; + case HardwareType.Heatmaster: + image = Utilities.EmbeddedResources.GetImage("bigng.png"); + break; + case HardwareType.Mainboard: + image = Utilities.EmbeddedResources.GetImage("mainboard.png"); + break; + case HardwareType.SuperIO: + image = Utilities.EmbeddedResources.GetImage("chip.png"); + break; + case HardwareType.TBalancer: + image = Utilities.EmbeddedResources.GetImage("bigng.png"); + break; + default: + image = new Bitmap(1, 1); + break; + } + images.Add(hardwareType, image); + return image; + } + } + } +} diff --git a/GUI/MainForm.Designer.cs b/GUI/MainForm.Designer.cs index d7dda3e..21b36fc 100644 --- a/GUI/MainForm.Designer.cs +++ b/GUI/MainForm.Designer.cs @@ -88,6 +88,7 @@ namespace OpenHardwareMonitor.GUI { this.valueMenuItem = new System.Windows.Forms.MenuItem(); this.minMenuItem = new System.Windows.Forms.MenuItem(); this.maxMenuItem = new System.Windows.Forms.MenuItem(); + this.gadgetMenuItem = new System.Windows.Forms.MenuItem(); this.optionsMenuItem = new System.Windows.Forms.MenuItem(); this.startMinMenuItem = new System.Windows.Forms.MenuItem(); this.minTrayMenuItem = new System.Windows.Forms.MenuItem(); @@ -231,6 +232,7 @@ namespace OpenHardwareMonitor.GUI { this.MenuItem3, this.hiddenMenuItem, this.plotMenuItem, + this.gadgetMenuItem, this.MenuItem1, this.columnsMenuItem}); this.viewMenuItem.Text = "View"; @@ -258,12 +260,12 @@ namespace OpenHardwareMonitor.GUI { // // MenuItem1 // - this.MenuItem1.Index = 4; + this.MenuItem1.Index = 5; this.MenuItem1.Text = "-"; // // columnsMenuItem // - this.columnsMenuItem.Index = 5; + this.columnsMenuItem.Index = 6; this.columnsMenuItem.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.valueMenuItem, this.minMenuItem, @@ -285,6 +287,11 @@ namespace OpenHardwareMonitor.GUI { this.maxMenuItem.Index = 2; this.maxMenuItem.Text = "Max"; // + // gadgetMenuItem + // + this.gadgetMenuItem.Index = 4; + this.gadgetMenuItem.Text = "Show Gadget"; + // // optionsMenuItem // this.optionsMenuItem.Index = 2; @@ -501,6 +508,7 @@ namespace OpenHardwareMonitor.GUI { private System.Windows.Forms.MenuItem MenuItem2; private System.Windows.Forms.MenuItem resetMinMaxMenuItem; private System.Windows.Forms.MenuItem MenuItem3; + private System.Windows.Forms.MenuItem gadgetMenuItem; } } diff --git a/GUI/MainForm.cs b/GUI/MainForm.cs index 0d653d6..cf9d7bb 100644 --- a/GUI/MainForm.cs +++ b/GUI/MainForm.cs @@ -62,6 +62,7 @@ namespace OpenHardwareMonitor.GUI { private SystemTray systemTray; private StartupManager startupManager = new StartupManager(); private UpdateVisitor updateVisitor = new UpdateVisitor(); + private SensorGadget gadget; private UserOption showHiddenSensors; private UserOption showPlot; @@ -72,6 +73,7 @@ namespace OpenHardwareMonitor.GUI { private UserOption minimizeToTray; private UserOption autoStart; private UserOption readHddSensors; + private UserOption showGadget; public MainForm() { InitializeComponent(); @@ -138,6 +140,8 @@ namespace OpenHardwareMonitor.GUI { systemTray.HideShowCommand += hideShowClick; systemTray.ExitCommand += exitClick; + gadget = new SensorGadget(computer, settings, unitManager); + computer.HardwareAdded += new HardwareEventHandler(HardwareAdded); computer.HardwareRemoved += new HardwareEventHandler(HardwareRemoved); computer.Open(); @@ -194,7 +198,7 @@ namespace OpenHardwareMonitor.GUI { autoStart = new UserOption(null, startupManager.Startup, startupMenuItem, settings); autoStart.Changed += delegate(object sender, EventArgs e) { - startupManager.Startup = autoStart.Value; ; + startupManager.Startup = autoStart.Value; }; readHddSensors = new UserOption("hddMenuItem", true, hddMenuItem, settings); @@ -203,6 +207,11 @@ namespace OpenHardwareMonitor.GUI { UpdatePlotSelection(null, null); }; + showGadget = new UserOption("gadgetMenuItem", false, gadgetMenuItem, settings); + showGadget.Changed += delegate(object sender, EventArgs e) { + gadget.Visible = showGadget.Value; + }; + celciusMenuItem.Checked = unitManager.TemperatureUnit == TemperatureUnit.Celcius; fahrenheitMenuItem.Checked = !celciusMenuItem.Checked; @@ -225,7 +234,7 @@ namespace OpenHardwareMonitor.GUI { Microsoft.Win32.SystemEvents.SessionEnded += delegate(object sender, Microsoft.Win32.SessionEndedEventArgs e) { SaveConfiguration(); - }; + }; } private void SubHardwareAdded(IHardware hardware, Node node) { @@ -313,6 +322,7 @@ namespace OpenHardwareMonitor.GUI { treeView.Invalidate(); plotPanel.Invalidate(); systemTray.Redraw(); + gadget.Redraw(); } private void SaveConfiguration() { @@ -369,7 +379,7 @@ namespace OpenHardwareMonitor.GUI { nodeTextBoxText.BeginEdit(); }; sensorContextMenu.MenuItems.Add(item); - } + } if (node.IsVisible) { MenuItem item = new MenuItem("Hide"); item.Click += delegate(object obj, EventArgs args) { @@ -382,20 +392,30 @@ namespace OpenHardwareMonitor.GUI { node.IsVisible = true; }; sensorContextMenu.MenuItems.Add(item); - } - if (systemTray.Contains(node.Sensor)) { - MenuItem item = new MenuItem("Remove From Tray"); - item.Click += delegate(object obj, EventArgs args) { - systemTray.Remove(node.Sensor); - }; - sensorContextMenu.MenuItems.Add(item); - } else { - MenuItem item = new MenuItem("Add To Tray"); - item.Click += delegate(object obj, EventArgs args) { - systemTray.Add(node.Sensor, true); - }; - sensorContextMenu.MenuItems.Add(item); } + sensorContextMenu.MenuItems.Add(new MenuItem("-")); + + MenuItem menuItem = new MenuItem("Show in Tray"); + menuItem.Checked = systemTray.Contains(node.Sensor); + menuItem.Click += delegate(object obj, EventArgs args) { + if (menuItem.Checked) + systemTray.Remove(node.Sensor); + else + systemTray.Add(node.Sensor, true); + }; + sensorContextMenu.MenuItems.Add(menuItem); + + menuItem = new MenuItem("Show in Gadget"); + menuItem.Checked = gadget.Contains(node.Sensor); + menuItem.Click += delegate(object obj, EventArgs args) { + if (menuItem.Checked) { + gadget.Remove(node.Sensor); + } else { + gadget.Add(node.Sensor); + } + }; + sensorContextMenu.MenuItems.Add(menuItem); + sensorContextMenu.Show(treeView, new Point(m.X, m.Y)); } } diff --git a/GUI/SensorGadget.cs b/GUI/SensorGadget.cs new file mode 100644 index 0000000..dc535e7 --- /dev/null +++ b/GUI/SensorGadget.cs @@ -0,0 +1,361 @@ +/* + + Version: MPL 1.1/GPL 2.0/LGPL 2.1 + + The contents of this file are subject to the Mozilla Public License Version + 1.1 (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + for the specific language governing rights and limitations under the License. + + The Original Code is the Open Hardware Monitor code. + + The Initial Developer of the Original Code is + Michael Möller . + Portions created by the Initial Developer are Copyright (C) 2010 + the Initial Developer. All Rights Reserved. + + Contributor(s): + + Alternatively, the contents of this file may be used under the terms of + either the GNU General Public License Version 2 or later (the "GPL"), or + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the GPL or the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of either the GPL or the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the GPL or the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the MPL, the GPL or the LGPL. + +*/ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; +using OpenHardwareMonitor.Hardware; + +namespace OpenHardwareMonitor.GUI { + public class SensorGadget : Gadget { + + private UnitManager unitManager; + + private Image back = Utilities.EmbeddedResources.GetImage("gadget.png"); + private Image barBack = Utilities.EmbeddedResources.GetImage("barback.png"); + private Image barblue = Utilities.EmbeddedResources.GetImage("barblue.png"); + private const int topBorder = 4; + private const int bottomBorder = 6; + private const int leftBorder = 6; + private const int rightBorder = 6; + private const int iconSize = 11; + private const int hardwareLineHeight = 13; + private const int sensorLineHeight = 11; + + private IDictionary> sensors = + new SortedDictionary>(new HardwareComparer()); + + private PersistentSettings settings; + private UserOption alwaysOnTop; + private UserOption lockPosition; + + private Font largeFont; + private Font smallFont; + private Brush darkWhite = new SolidBrush(Color.FromArgb(0xF0, 0xF0, 0xF0)); + + public SensorGadget(IComputer computer, PersistentSettings settings, + UnitManager unitManager) + { + this.unitManager = unitManager; + this.settings = settings; + computer.HardwareAdded += new HardwareEventHandler(HardwareAdded); + computer.HardwareRemoved += new HardwareEventHandler(HardwareRemoved); + + this.largeFont = new Font(SystemFonts.MessageBoxFont.FontFamily, 7.5f, + FontStyle.Bold); + this.smallFont = new Font(SystemFonts.MessageBoxFont.FontFamily, 6.5f); + + this.Location = new Point( + settings.GetValue("sensorGadget.Location.X", 100), + settings.GetValue("sensorGadget.Location.Y", 100)); + LocationChanged += delegate(object sender, EventArgs e) { + settings.SetValue("sensorGadget.Location.X", Location.X); + settings.SetValue("sensorGadget.Location.Y", Location.Y); + }; + + ContextMenu contextMenu = new ContextMenu(); + MenuItem lockItem = new MenuItem("Lock Position"); + contextMenu.MenuItems.Add(lockItem); + contextMenu.MenuItems.Add(new MenuItem("-")); + MenuItem alwaysOnTopItem = new MenuItem("Always on Top"); + contextMenu.MenuItems.Add(alwaysOnTopItem); + MenuItem opacityMenu = new MenuItem("Opacity"); + contextMenu.MenuItems.Add(opacityMenu); + Opacity = (byte)settings.GetValue("sensorGadget.Opacity", 255); + for (int i = 0; i < 5; i++) { + MenuItem item = new MenuItem((20 * (i + 1)).ToString() + " %"); + byte o = (byte)(51 * (i + 1)); + item.Tag = o; + item.Checked = Opacity == o; + item.Click += delegate(object sender, EventArgs e) { + Opacity = (byte)item.Tag; + settings.SetValue("sensorGadget.Opacity", Opacity); + foreach (MenuItem mi in opacityMenu.MenuItems) + mi.Checked = (byte)mi.Tag == Opacity; + }; + opacityMenu.MenuItems.Add(item); + } + this.ContextMenu = contextMenu; + + alwaysOnTop = new UserOption("sensorGadget.AlwaysOnTop", false, + alwaysOnTopItem, settings); + alwaysOnTop.Changed += delegate(object sender, EventArgs e) { + this.AlwaysOnTop = alwaysOnTop.Value; + }; + lockPosition = new UserOption("sensorGadget.LockPosition", false, + lockItem, settings); + lockPosition.Changed += delegate(object sender, EventArgs e) { + this.LockPosition = lockPosition.Value; + }; + + Resize(); + } + + private void HardwareRemoved(IHardware hardware) { + hardware.SensorAdded -= new SensorEventHandler(SensorAdded); + hardware.SensorRemoved -= new SensorEventHandler(SensorRemoved); + foreach (ISensor sensor in hardware.Sensors) + SensorRemoved(sensor); + foreach (IHardware subHardware in hardware.SubHardware) + HardwareRemoved(subHardware); + } + + private void HardwareAdded(IHardware hardware) { + foreach (ISensor sensor in hardware.Sensors) + SensorAdded(sensor); + hardware.SensorAdded += new SensorEventHandler(SensorAdded); + hardware.SensorRemoved += new SensorEventHandler(SensorRemoved); + foreach (IHardware subHardware in hardware.SubHardware) + HardwareAdded(subHardware); + } + + private void SensorAdded(ISensor sensor) { + if (settings.GetValue(new Identifier(sensor.Identifier, + "gadget").ToString(), false)) + Add(sensor); + } + + private void SensorRemoved(ISensor sensor) { + if (Contains(sensor)) + Remove(sensor, false); + } + + public bool Contains(ISensor sensor) { + foreach (IList list in sensors.Values) + if (list.Contains(sensor)) + return true; + return false; + } + + public void Add(ISensor sensor) { + if (Contains(sensor)) { + return; + } else { + // get the right hardware + IHardware hardware = sensor.Hardware; + while (hardware.Parent != null) + hardware = hardware.Parent; + + // get the sensor list associated with the hardware + IList list; + if (!sensors.TryGetValue(hardware, out list)) { + list = new List(); + sensors.Add(hardware, list); + } + + // insert the sensor at the right position + int i = 0; + while (i < list.Count && (list[i].SensorType < sensor.SensorType || + (list[i].SensorType == sensor.SensorType && + list[i].Index < sensor.Index))) i++; + list.Insert(i, sensor); + + settings.SetValue( + new Identifier(sensor.Identifier, "gadget").ToString(), true); + + Resize(); + Redraw(); + } + } + + public void Remove(ISensor sensor) { + Remove(sensor, true); + } + + private void Remove(ISensor sensor, bool deleteConfig) { + if (deleteConfig) + settings.Remove(new Identifier(sensor.Identifier, "gadget").ToString()); + + foreach (KeyValuePair> keyValue in sensors) + if (keyValue.Value.Contains(sensor)) { + keyValue.Value.Remove(sensor); + if (keyValue.Value.Count == 0) { + sensors.Remove(keyValue.Key); + break; + } + } + Resize(); + Redraw(); + } + + private void Resize() { + int y = topBorder + 1; + foreach (KeyValuePair> pair in sensors) { + y += hardwareLineHeight; + y += pair.Value.Count * sensorLineHeight; + } + y += bottomBorder + 2; + y = Math.Max(y, topBorder + bottomBorder + 10); + this.Size = new Size(130, y); + } + + private void DrawBackground(Graphics g) { + int w = Size.Width; + int h = Size.Height; + int t = topBorder; + int b = bottomBorder; + int l = leftBorder; + int r = rightBorder; + GraphicsUnit u = GraphicsUnit.Pixel; + + g.DrawImage(back, new Rectangle(0, 0, l, t), + new Rectangle(0, 0, l, t), u); + g.DrawImage(back, new Rectangle(l, 0, w - l - r, t), + new Rectangle(l, 0, back.Width - l - r, t), u); + g.DrawImage(back, new Rectangle(w - r, 0, r, t), + new Rectangle(back.Width - r, 0, r, t), u); + + g.DrawImage(back, new Rectangle(0, t, l, h - t - b), + new Rectangle(0, t, l, back.Height - t - b), u); + g.DrawImage(back, new Rectangle(l, t, w - l - r, h - t - b), + new Rectangle(l, t, back.Width - l - r, back.Height - t - b), u); + g.DrawImage(back, new Rectangle(w - r, t, r, h - t - b), + new Rectangle(back.Width - r, t, r, back.Height - t - b), u); + + g.DrawImage(back, new Rectangle(0, h - b, l, b), + new Rectangle(0, back.Height - b, l, b), u); + g.DrawImage(back, new Rectangle(l, h - b, w - l - r, b), + new Rectangle(l, back.Height - b, back.Width - l - r, b), u); + g.DrawImage(back, new Rectangle(w - r, h - b, r, b), + new Rectangle(back.Width - r, back.Height - b, r, b), u); + } + + private void DrawProgress(Graphics g, int x, int y, int width, int height, + float progress) + { + g.DrawImage(barBack, + new RectangleF(x + width * progress, y, width * (1 - progress), height), + new RectangleF(barBack.Width * progress, 0, + (1 - progress) * barBack.Width, barBack.Height), + GraphicsUnit.Pixel); + g.DrawImage(barblue, + new RectangleF(x, y, width * progress, height), + new RectangleF(0, 0, progress * barblue.Width, barblue.Height), + GraphicsUnit.Pixel); + } + + protected override void OnPaint(PaintEventArgs e) { + Graphics g = e.Graphics; + int w = Size.Width; + int h = Size.Height; + + g.Clear(Color.Transparent); + + DrawBackground(g); + + StringFormat stringFormat = new StringFormat(); + stringFormat.Alignment = StringAlignment.Far; + + int x; + int y = topBorder + 1; + foreach (KeyValuePair> pair in sensors) { + x = leftBorder + 1; + g.DrawImage(HardwareTypeImage.Instance.GetImage(pair.Key.HardwareType), + new Rectangle(x, y + 2, iconSize, iconSize)); + x += iconSize + 1; + g.DrawString(pair.Key.Name, largeFont, Brushes.White, + new Rectangle(x, y, w - rightBorder - x, 15)); + y += hardwareLineHeight; + + foreach (ISensor sensor in pair.Value) { + + g.DrawString(sensor.Name + ":", smallFont, darkWhite, + new Rectangle(9, y, 64, 15)); + + if (sensor.SensorType != SensorType.Load && + sensor.SensorType != SensorType.Control) + { + string format = ""; + switch (sensor.SensorType) { + case SensorType.Voltage: + format = "{0:F2} V"; + break; + case SensorType.Clock: + format = "{0:F0} MHz"; + break; + case SensorType.Temperature: + format = "{0:F1} °C"; + break; + case SensorType.Fan: + format = "{0:F0} RPM"; + break; + case SensorType.Flow: + format = "{0:F0} L/h"; + break; + } + + string formattedValue; + if (sensor.SensorType == SensorType.Temperature && + unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit) { + formattedValue = string.Format("{0:F1} °F", + sensor.Value * 1.8 + 32); + } else { + formattedValue = string.Format(format, sensor.Value); + } + + x = 75; + g.DrawString(formattedValue, smallFont, darkWhite, + new RectangleF(x, y, w - x - 9, 15), stringFormat); + } else { + x = 80; + DrawProgress(g, x, y + 4, w - x - 9, 6, 0.01f * sensor.Value.Value); + } + + y += sensorLineHeight; + } + } + } + + private class HardwareComparer : IComparer { + public int Compare(IHardware x, IHardware y) { + if (x == null && y == null) + return 0; + if (x == null) + return -1; + if (y == null) + return 1; + + if (x.HardwareType != y.HardwareType) + return x.HardwareType.CompareTo(y.HardwareType); + + return x.Name.CompareTo(y.Name); + } + } + } +} + diff --git a/GUI/SensorNotifyIcon.cs b/GUI/SensorNotifyIcon.cs index bc2665e..19ecddf 100644 --- a/GUI/SensorNotifyIcon.cs +++ b/GUI/SensorNotifyIcon.cs @@ -36,14 +36,12 @@ */ using System; -using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; -using System.Text; -using System.Windows.Forms; using System.Runtime.InteropServices; +using System.Windows.Forms; using OpenHardwareMonitor.Hardware; using OpenHardwareMonitor.Utilities; diff --git a/GUI/ShowDesktop.cs b/GUI/ShowDesktop.cs new file mode 100644 index 0000000..d44f0b7 --- /dev/null +++ b/GUI/ShowDesktop.cs @@ -0,0 +1,172 @@ +/* + + Version: MPL 1.1/GPL 2.0/LGPL 2.1 + + The contents of this file are subject to the Mozilla Public License Version + 1.1 (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + for the specific language governing rights and limitations under the License. + + The Original Code is the Open Hardware Monitor code. + + The Initial Developer of the Original Code is + Michael Möller . + Portions created by the Initial Developer are Copyright (C) 2010 + the Initial Developer. All Rights Reserved. + + Contributor(s): + + Alternatively, the contents of this file may be used under the terms of + either the GNU General Public License Version 2 or later (the "GPL"), or + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the GPL or the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of either the GPL or the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the GPL or the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the MPL, the GPL or the LGPL. + +*/ + +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace OpenHardwareMonitor.GUI { + public class ShowDesktop { + private static ShowDesktop instance = new ShowDesktop(); + + public delegate void ShowDesktopChangedEventHandler(bool showDesktop); + + private event ShowDesktopChangedEventHandler ShowDesktopChangedEvent; + + private System.Threading.Timer timer; + private bool showDesktop = false; + private NativeWindow referenceWindow; + private string referenceWindowCaption = + "OpenHardwareMonitorShowDesktopReferenceWindow"; + + private ShowDesktop() { + // create a reference window to detect show desktop + referenceWindow = new NativeWindow(); + CreateParams cp = new CreateParams(); + cp.ExStyle = GadgetWindow.WS_EX_TOOLWINDOW; + cp.Caption = referenceWindowCaption; + referenceWindow.CreateHandle(cp); + NativeMethods.SetWindowPos(referenceWindow.Handle, + GadgetWindow.HWND_BOTTOM, 0, 0, 0, 0, GadgetWindow.SWP_NOMOVE | + GadgetWindow.SWP_NOSIZE | GadgetWindow.SWP_NOACTIVATE | + GadgetWindow.SWP_NOSENDCHANGING); + + // start a repeated timer to detect "Show Desktop" events + timer = new System.Threading.Timer(OnTimer, null, + System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + } + + private void StartTimer() { + timer.Change(0, 200); + } + + private void StopTimer() { + timer.Change(System.Threading.Timeout.Infinite, + System.Threading.Timeout.Infinite); + } + + // the desktop worker window (if available) can hide the reference window + private IntPtr GetDesktopWorkerWindow() { + IntPtr shellWindow = NativeMethods.GetShellWindow(); + if (shellWindow == IntPtr.Zero) + return IntPtr.Zero; + + int shellId; + NativeMethods.GetWindowThreadProcessId(shellWindow, out shellId); + + IntPtr workerWindow = IntPtr.Zero; + while ((workerWindow = NativeMethods.FindWindowEx( + IntPtr.Zero, workerWindow, "WorkerW", null)) != IntPtr.Zero) { + + int workerId; + NativeMethods.GetWindowThreadProcessId(workerWindow, out workerId); + if (workerId == shellId) { + IntPtr window = NativeMethods.FindWindowEx( + workerWindow, IntPtr.Zero, "SHELLDLL_DefView", null); + if (window != IntPtr.Zero) { + IntPtr desktopWindow = NativeMethods.FindWindowEx( + window, IntPtr.Zero, "SysListView32", null); + if (desktopWindow != IntPtr.Zero) + return workerWindow; + } + } + } + return IntPtr.Zero; + } + + private void OnTimer(Object state) { + bool showDesktopDetected; + + IntPtr workerWindow = GetDesktopWorkerWindow(); + if (workerWindow != IntPtr.Zero) { + // search if the reference window is behind the worker window + IntPtr reference = NativeMethods.FindWindowEx( + IntPtr.Zero, workerWindow, null, referenceWindowCaption); + showDesktopDetected = reference == referenceWindow.Handle; + } else { + // if there is no worker window, then nothing can hide the reference + showDesktopDetected = false; + } + + if (showDesktop != showDesktopDetected) { + showDesktop = showDesktopDetected; + if (ShowDesktopChangedEvent != null) { + ShowDesktopChangedEvent(showDesktop); + } + } + } + + public static ShowDesktop Instance { + get { return instance; } + } + + // notify when the "show desktop" mode is changed + public event ShowDesktopChangedEventHandler ShowDesktopChanged { + add { + // start the monitor timer when someone is listening + if (ShowDesktopChangedEvent == null) + StartTimer(); + ShowDesktopChangedEvent += value; + } + remove { + ShowDesktopChangedEvent -= value; + // stop the monitor timer if nobody is interested + if (ShowDesktopChangedEvent == null) + StopTimer(); + } + } + + private static class NativeMethods { + private const string USER = "user32.dll"; + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern bool SetWindowPos(IntPtr hWnd, + IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern IntPtr FindWindowEx(IntPtr hwndParent, + IntPtr hwndChildAfter, string lpszClass, string lpszWindow); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern IntPtr GetShellWindow(); + + [DllImport(USER, CallingConvention = CallingConvention.Winapi)] + public static extern int GetWindowThreadProcessId(IntPtr hWnd, + out int processId); + } + } +} diff --git a/Hardware/ATI/ATIGPU.cs b/Hardware/ATI/ATIGPU.cs index 1db7324..e87b60e 100644 --- a/Hardware/ATI/ATIGPU.cs +++ b/Hardware/ATI/ATIGPU.cs @@ -88,7 +88,7 @@ namespace OpenHardwareMonitor.Hardware.ATI { } public override HardwareType HardwareType { - get { return HardwareType.GPU; } + get { return HardwareType.GpuAti; } } public override void Update() { diff --git a/Hardware/HDD/HDD.cs b/Hardware/HDD/HDD.cs index 5384223..3c8995c 100644 --- a/Hardware/HDD/HDD.cs +++ b/Hardware/HDD/HDD.cs @@ -85,6 +85,10 @@ namespace OpenHardwareMonitor.Hardware.HDD { get { return new IHardware[0]; } } + public virtual IHardware Parent { + get { return null; } + } + public ISensor[] Sensors { get { return new ISensor[] { temperature }; diff --git a/Hardware/Hardware.cs b/Hardware/Hardware.cs index b987663..7983742 100644 --- a/Hardware/Hardware.cs +++ b/Hardware/Hardware.cs @@ -48,6 +48,10 @@ namespace OpenHardwareMonitor.Hardware { get { return new IHardware[0]; } } + public virtual IHardware Parent { + get { return null; } + } + public ISensor[] Sensors { get { return active.ToArray(); } } diff --git a/Hardware/IHardware.cs b/Hardware/IHardware.cs index 6961367..0dd88c4 100644 --- a/Hardware/IHardware.cs +++ b/Hardware/IHardware.cs @@ -42,14 +42,15 @@ namespace OpenHardwareMonitor.Hardware { public delegate void SensorEventHandler(ISensor sensor); - public enum HardwareType { - CPU, - GPU, - HDD, - Heatmaster, + public enum HardwareType { Mainboard, SuperIO, - TBalancer + CPU, + GpuNvidia, + GpuAti, + TBalancer, + Heatmaster, + HDD, } public interface IHardware : IElement { @@ -65,6 +66,8 @@ namespace OpenHardwareMonitor.Hardware { IHardware[] SubHardware { get; } + IHardware Parent { get; } + ISensor[] Sensors { get; } event SensorEventHandler SensorAdded; diff --git a/Hardware/Identifier.cs b/Hardware/Identifier.cs index bccfa00..58cb99d 100644 --- a/Hardware/Identifier.cs +++ b/Hardware/Identifier.cs @@ -40,7 +40,7 @@ using System.Collections.Generic; using System.Text; namespace OpenHardwareMonitor.Hardware { - public class Identifier { + public class Identifier : IComparable { private string identifier; private static char SEPARATOR = '/'; @@ -92,5 +92,12 @@ namespace OpenHardwareMonitor.Hardware { public override int GetHashCode() { return identifier.GetHashCode(); } + + public int CompareTo(Identifier other) { + if (other == null) + return 1; + else + return this.identifier.CompareTo(other.identifier); + } } } diff --git a/Hardware/Mainboard/Mainboard.cs b/Hardware/Mainboard/Mainboard.cs index e31fddf..97b9978 100644 --- a/Hardware/Mainboard/Mainboard.cs +++ b/Hardware/Mainboard/Mainboard.cs @@ -78,7 +78,7 @@ namespace OpenHardwareMonitor.Hardware.Mainboard { superIOHardware = new IHardware[superIO.Length]; for (int i = 0; i < superIO.Length; i++) - superIOHardware[i] = new SuperIOHardware(superIO[i], + superIOHardware[i] = new SuperIOHardware(this, superIO[i], smbios.Board != null ? smbios.Board.Manufacturer : Manufacturer.Unknown, smbios.Board != null ? smbios.Board.Model : Model.Unknown, settings); @@ -96,6 +96,10 @@ namespace OpenHardwareMonitor.Hardware.Mainboard { get { return HardwareType.Mainboard; } } + public virtual IHardware Parent { + get { return null; } + } + public string GetReport() { StringBuilder r = new StringBuilder(); diff --git a/Hardware/Mainboard/SuperIOHardware.cs b/Hardware/Mainboard/SuperIOHardware.cs index 1ac635a..a42717e 100644 --- a/Hardware/Mainboard/SuperIOHardware.cs +++ b/Hardware/Mainboard/SuperIOHardware.cs @@ -43,6 +43,7 @@ using OpenHardwareMonitor.Hardware.LPC; namespace OpenHardwareMonitor.Hardware.Mainboard { internal class SuperIOHardware : Hardware { + private Mainboard mainboard; private ISuperIO superIO; private string name; @@ -51,9 +52,10 @@ namespace OpenHardwareMonitor.Hardware.Mainboard { private List fans = new List(); - public SuperIOHardware(ISuperIO superIO, Manufacturer manufacturer, - Model model, ISettings settings) + public SuperIOHardware(Mainboard mainboard, ISuperIO superIO, + Manufacturer manufacturer, Model model, ISettings settings) { + this.mainboard = mainboard; this.superIO = superIO; this.name = ChipName.GetName(superIO.Chip); @@ -614,6 +616,10 @@ namespace OpenHardwareMonitor.Hardware.Mainboard { get { return HardwareType.SuperIO; } } + public override IHardware Parent { + get { return mainboard; } + } + public override string Name { get { return name; } } diff --git a/Hardware/Nvidia/NvidiaGPU.cs b/Hardware/Nvidia/NvidiaGPU.cs index 10e5a3c..bc29e19 100644 --- a/Hardware/Nvidia/NvidiaGPU.cs +++ b/Hardware/Nvidia/NvidiaGPU.cs @@ -123,7 +123,7 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { } public override HardwareType HardwareType { - get { return HardwareType.GPU; } + get { return HardwareType.GpuNvidia; } } private NvGPUThermalSettings GetThermalSettings() { diff --git a/Hardware/TBalancer/TBalancer.cs b/Hardware/TBalancer/TBalancer.cs index 9cd0ccb..9e86f39 100644 --- a/Hardware/TBalancer/TBalancer.cs +++ b/Hardware/TBalancer/TBalancer.cs @@ -285,6 +285,10 @@ namespace OpenHardwareMonitor.Hardware.TBalancer { get { return new IHardware[0]; } } + public virtual IHardware Parent { + get { return null; } + } + public ISensor[] Sensors { get { return active.ToArray(); } } diff --git a/OpenHardwareMonitor.csproj b/OpenHardwareMonitor.csproj index 3f90ba4..6c1f386 100644 --- a/OpenHardwareMonitor.csproj +++ b/OpenHardwareMonitor.csproj @@ -70,6 +70,9 @@ + + + UserControl @@ -92,7 +95,9 @@ ParameterForm.cs + + Component @@ -197,6 +202,11 @@ true + + + + + diff --git a/Properties/AssemblyVersion.cs b/Properties/AssemblyVersion.cs index 0b2d2d7..7501eb9 100644 --- a/Properties/AssemblyVersion.cs +++ b/Properties/AssemblyVersion.cs @@ -38,5 +38,5 @@ using System; using System.Reflection; -[assembly: AssemblyVersion("0.1.37.9")] -[assembly: AssemblyFileVersion("0.1.37.9")] +[assembly: AssemblyVersion("0.1.37.10")] +[assembly: AssemblyFileVersion("0.1.37.10")] diff --git a/Resources/barback.png b/Resources/barback.png new file mode 100644 index 0000000000000000000000000000000000000000..695de2ded45dca3546766935388ffaa284faeb49 GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0vp^0YJ>b!3HGr&QzEKDaPU;cPEB*=VV?2Ic!PZ?k)`f zL2$v|<&%LToCO|{#S9GG!XV7ZFl&wkP>{XE)7O>#7AKn!n{4eD5mum(WQl7;iF1B# zZfaf$gL6@8Vo7R>LV0FMhJw4NZ$Nk>pE%GeVNVyw5Rc<;uLiDaRS;l%aKG@(ihI#F zV{@uAlx^Jup1r6IIuk7YVh-a>5#C=vR87x+j=%ps!QaC8)iDXT!?bk z{Ms0C{(e`O-Tkxn49PtliV9za+B@_TO6#=~?mBZ+`e%wJv-n?bNsWB4_xwg*PnAWd zJv`SW-e;N~AQWI3^l6fyak7m2hOJ_;*AH4wH@{NB`Sv!0x5wJjfA#``vz9e(70c-X z5-w8~|7r<8wD+mi^y^#O)(CpbC-d|eK1h^MP%1e0bh^#bw|bTNjC;Hv#cECVzHB1V b#`wP^hSxORo8db!3>iFK{an^LB{Ts5w*{sx literal 0 HcmV?d00001 diff --git a/Resources/barblue.png b/Resources/barblue.png new file mode 100644 index 0000000000000000000000000000000000000000..7f3683f89945711da5652b7e112b17fddf04f322 GIT binary patch literal 571 zcmV-B0>u4^P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ige+ z5(zIdyID#A000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0004&Nklu>{#*^u&4y;sGY|5uy7`mBJluIaW}CnFPu;3 zB>MF2yXV7!_fw$~lp_Amzo&Pzko-P%o*o{kM+NiKBR&NwQ6!`bq7lNFbuZ#|3uOXq z?<{<=ut^)+;N+;F9`Aoqmd*(%5_Ls$?7$Qlai&XM5ck;EIQ9i7Jn(V_#CZWtBg_JH zn@8YS3st@2-Zy6>2v7!&0+|?NiN3;_d;z$>#5K*|clTMr3HFvgWe1YsJ5Cvppnm!M zm7hQE$>-CSI+a31K&hw{6a|GqF%q|?avZ+$O0TIqr|n!%r*V#O(HAmvtk*O~oA~hY zjyku-+cy)>Au^Lp5Cy3q3Q&=SQUer$cYwb3k6zh9j<%5twlOivED`ka42TxAxiPm! zEpubKxk052k2jJ>br^@YN;txYE=nAJaY6$=U002ov JPDHLkV1htN?c4wW literal 0 HcmV?d00001 diff --git a/Resources/gadget.png b/Resources/gadget.png new file mode 100644 index 0000000000000000000000000000000000000000..94c3d9943629faf074be6f6a61f0bc2589827b0f GIT binary patch literal 5969 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 ~;2_Ia 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 PmE+Ljw9wy>vnc-y+qMaU literal 0 HcmV?d00001