moel@202: /* moel@176: moel@176: Version: MPL 1.1/GPL 2.0/LGPL 2.1 moel@176: moel@176: The contents of this file are subject to the Mozilla Public License Version moel@176: 1.1 (the "License"); you may not use this file except in compliance with moel@176: the License. You may obtain a copy of the License at moel@176: moel@176: http://www.mozilla.org/MPL/ moel@176: moel@176: Software distributed under the License is distributed on an "AS IS" basis, moel@176: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License moel@176: for the specific language governing rights and limitations under the License. moel@176: moel@176: The Original Code is the Open Hardware Monitor code. moel@176: moel@176: The Initial Developer of the Original Code is moel@176: Michael Möller . moel@176: Portions created by the Initial Developer are Copyright (C) 2010 moel@176: the Initial Developer. All Rights Reserved. moel@176: moel@176: Contributor(s): moel@176: moel@176: Alternatively, the contents of this file may be used under the terms of moel@176: either the GNU General Public License Version 2 or later (the "GPL"), or moel@176: the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), moel@176: in which case the provisions of the GPL or the LGPL are applicable instead moel@176: of those above. If you wish to allow use of your version of this file only moel@176: under the terms of either the GPL or the LGPL, and not to allow others to moel@176: use your version of this file under the terms of the MPL, indicate your moel@176: decision by deleting the provisions above and replace them with the notice moel@176: and other provisions required by the GPL or the LGPL. If you do not delete moel@176: the provisions above, a recipient may use your version of this file under moel@176: the terms of any one of the MPL, the GPL or the LGPL. moel@176: moel@176: */ moel@176: moel@176: using System; moel@176: using System.Drawing; moel@176: using System.Reflection; moel@176: using System.Runtime.InteropServices; moel@176: using System.Windows.Forms; moel@176: moel@176: namespace OpenHardwareMonitor.GUI { moel@183: moel@176: public class GadgetWindow : NativeWindow { moel@176: moel@176: private bool visible = false; moel@183: private bool lockPositionAndSize = false; moel@176: private bool alwaysOnTop = false; moel@176: private byte opacity = 255; moel@176: private Point location = new Point(100, 100); moel@176: private Size size = new Size(130, 84); moel@176: private ContextMenu contextMenu = null; moel@176: private MethodInfo commandDispatch; moel@176: moel@176: public GadgetWindow() { moel@176: Type commandType = moel@176: typeof(Form).Assembly.GetType("System.Windows.Forms.Command"); moel@176: commandDispatch = commandType.GetMethod("DispatchID", moel@176: BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, moel@176: null, new Type[]{ typeof(int) }, null); moel@176: moel@176: this.CreateHandle(CreateParams); moel@176: moel@176: // move window to the bottom moel@176: MoveToBottom(Handle); moel@176: moel@176: // prevent window from fading to a glass sheet when peek is invoked moel@176: try { moel@176: bool value = true; moel@202: NativeMethods.DwmSetWindowAttribute(Handle, moel@176: WindowAttribute.DWMWA_EXCLUDED_FROM_PEEK, ref value, moel@176: Marshal.SizeOf(value)); moel@176: } catch (DllNotFoundException) { } catch (EntryPointNotFoundException) { } moel@176: } moel@176: moel@176: private void ShowDesktopChanged(bool showDesktop) { moel@176: if (showDesktop) { moel@176: MoveToTopMost(Handle); moel@176: } else { moel@176: MoveToBottom(Handle); moel@176: } moel@176: } moel@176: moel@176: private void MoveToBottom(IntPtr handle) { moel@176: NativeMethods.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0, moel@176: SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); moel@176: } moel@176: moel@176: private void MoveToTopMost(IntPtr handle) { moel@176: NativeMethods.SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0, moel@176: SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); moel@176: } moel@176: moel@176: private void ShowContextMenu(Point position) { moel@176: NativeMethods.TrackPopupMenuEx(contextMenu.Handle, moel@176: TPM_RIGHTBUTTON | TPM_VERTICAL, position.X, moel@176: position.Y, Handle, IntPtr.Zero); moel@176: } moel@176: moel@176: protected virtual CreateParams CreateParams { moel@176: get { moel@183: CreateParams cp = new CreateParams(); moel@183: cp.Width = 4096; moel@183: cp.Height = 4096; moel@176: cp.X = location.X; moel@176: cp.Y = location.Y; moel@176: cp.ExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW; moel@176: return cp; moel@176: } moel@176: } moel@176: moel@176: protected override void WndProc(ref Message message) { moel@176: switch (message.Msg) { moel@183: case WM_COMMAND: { moel@183: // need to dispatch the message for the context menu moel@183: if (message.LParam == IntPtr.Zero) moel@183: commandDispatch.Invoke(null, new object[] { moel@183: message.WParam.ToInt32() & 0xFFFF }); moel@183: } break; moel@183: case WM_NCHITTEST: { moel@183: message.Result = (IntPtr)HitResult.Caption; moel@183: if (HitTest != null) { moel@183: Point p = new Point( paulwerelds@208: Macros.GET_X_LPARAM(message.LParam) - location.X, paulwerelds@208: Macros.GET_Y_LPARAM(message.LParam) - location.Y paulwerelds@208: ); moel@183: HitTestEventArgs e = new HitTestEventArgs(p, HitResult.Caption); moel@183: HitTest(this, e); moel@183: message.Result = (IntPtr)e.HitResult; moel@183: } moel@183: } break; moel@183: case WM_NCLBUTTONDBLCLK: { moel@244: if (MouseDoubleClick != null) { moel@244: MouseDoubleClick(this, new MouseEventArgs(MouseButtons.Left, 2, moel@244: Macros.GET_X_LPARAM(message.LParam) - location.X, moel@244: Macros.GET_Y_LPARAM(message.LParam) - location.Y, 0)); moel@244: } moel@183: message.Result = IntPtr.Zero; moel@183: } break; moel@183: case WM_NCRBUTTONDOWN: { moel@183: message.Result = IntPtr.Zero; moel@183: } break; moel@183: case WM_NCRBUTTONUP: { moel@183: if (contextMenu != null) moel@183: ShowContextMenu(new Point( paulwerelds@209: Macros.GET_X_LPARAM(message.LParam), paulwerelds@209: Macros.GET_Y_LPARAM(message.LParam) paulwerelds@209: )); moel@183: message.Result = IntPtr.Zero; moel@183: } break; moel@183: case WM_WINDOWPOSCHANGING: { moel@183: WindowPos wp = (WindowPos)Marshal.PtrToStructure( moel@183: message.LParam, typeof(WindowPos)); moel@183: moel@183: if (!lockPositionAndSize) { moel@183: // prevent the window from leaving the screen moel@183: if ((wp.flags & SWP_NOMOVE) == 0) { moel@243: Rectangle rect = Screen.GetWorkingArea( moel@243: new Rectangle(wp.x, wp.y, wp.cx, wp.cy)); moel@183: const int margin = 16; moel@183: wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin); moel@183: wp.x = Math.Min(wp.x, rect.Right - margin); moel@183: wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin); moel@183: wp.y = Math.Min(wp.y, rect.Bottom - margin); moel@183: } moel@176: moel@183: // update location and fire event moel@183: if ((wp.flags & SWP_NOMOVE) == 0) { moel@183: if (location.X != wp.x || location.Y != wp.y) { moel@183: location = new Point(wp.x, wp.y); moel@183: if (LocationChanged != null) moel@183: LocationChanged(this, EventArgs.Empty); moel@183: } moel@183: } moel@176: moel@183: // update size and fire event moel@183: if ((wp.flags & SWP_NOSIZE) == 0) { moel@183: if (size.Width != wp.cx || size.Height != wp.cy) { moel@183: size = new Size(wp.cx, wp.cy); moel@183: if (SizeChanged != null) moel@183: SizeChanged(this, EventArgs.Empty); moel@183: } moel@183: } moel@176: moel@183: // update the size of the layered window moel@183: if ((wp.flags & SWP_NOSIZE) == 0) { moel@183: NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, moel@183: IntPtr.Zero, ref size, IntPtr.Zero, IntPtr.Zero, 0, moel@183: IntPtr.Zero, 0); moel@183: } moel@183: moel@183: // update the position of the layered window moel@183: if ((wp.flags & SWP_NOMOVE) == 0) { moel@183: NativeMethods.SetWindowPos(Handle, IntPtr.Zero, moel@183: location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | moel@183: SWP_NOZORDER | SWP_NOSENDCHANGING); moel@183: } moel@176: } moel@183: moel@183: // do not forward any move or size messages moel@243: wp.flags |= SWP_NOSIZE | SWP_NOMOVE; moel@243: moel@243: // suppress any frame changed events moel@243: wp.flags &= ~SWP_FRAMECHANGED; moel@243: moel@183: Marshal.StructureToPtr(wp, message.LParam, false); moel@183: message.Result = IntPtr.Zero; moel@183: } break; moel@183: default: { moel@183: base.WndProc(ref message); moel@183: } break; moel@176: } moel@176: } moel@176: moel@176: private BlendFunction CreateBlendFunction() { moel@176: BlendFunction blend = new BlendFunction(); moel@176: blend.BlendOp = AC_SRC_OVER; moel@176: blend.BlendFlags = 0; moel@176: blend.SourceConstantAlpha = opacity; moel@176: blend.AlphaFormat = AC_SRC_ALPHA; moel@176: return blend; moel@176: } moel@176: moel@176: public void Update(Bitmap bitmap) { moel@176: IntPtr screen = NativeMethods.GetDC(IntPtr.Zero); moel@176: IntPtr memory = NativeMethods.CreateCompatibleDC(screen); moel@176: IntPtr newHBitmap = IntPtr.Zero; moel@176: IntPtr oldHBitmap = IntPtr.Zero; moel@176: moel@176: try { moel@176: newHBitmap = bitmap.GetHbitmap(Color.Black); moel@176: oldHBitmap = NativeMethods.SelectObject(memory, newHBitmap); moel@176: moel@176: Point pointSource = Point.Empty; moel@183: BlendFunction blend = CreateBlendFunction(); moel@176: moel@183: NativeMethods.UpdateLayeredWindow(Handle, screen, IntPtr.Zero, moel@176: ref size, memory, ref pointSource, 0, ref blend, ULW_ALPHA); moel@183: moel@183: // make sure the window is at the right location moel@183: NativeMethods.SetWindowPos(Handle, IntPtr.Zero, moel@183: location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | moel@183: SWP_NOZORDER | SWP_NOSENDCHANGING); moel@183: moel@183: } finally { moel@176: if (newHBitmap != IntPtr.Zero) { moel@176: NativeMethods.SelectObject(memory, oldHBitmap); moel@176: NativeMethods.DeleteObject(newHBitmap); moel@176: } moel@176: NativeMethods.DeleteDC(memory); moel@183: NativeMethods.ReleaseDC(IntPtr.Zero, screen); moel@176: } moel@176: } moel@176: moel@176: public byte Opacity { moel@176: get { moel@176: return opacity; moel@176: } moel@176: set { moel@176: if (opacity != value) { moel@176: opacity = value; moel@176: BlendFunction blend = CreateBlendFunction(); moel@176: NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero, moel@176: IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA); moel@176: } moel@176: } moel@176: } moel@176: moel@176: public bool Visible { moel@176: get { moel@176: return visible; moel@176: } moel@176: set { moel@176: if (visible != value) { moel@176: visible = value; moel@176: NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0, moel@176: SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | moel@176: (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW)); moel@180: if (value) { moel@180: if (!alwaysOnTop) moel@180: ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged; moel@180: } else { moel@180: if (!alwaysOnTop) moel@180: ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged; moel@180: } moel@176: } moel@176: } moel@176: } moel@176: moel@183: // if locked, the window can not be moved or resized moel@183: public bool LockPositionAndSize { moel@176: get { moel@183: return lockPositionAndSize; moel@176: } moel@176: set { moel@183: lockPositionAndSize = value; moel@176: } moel@176: } moel@176: moel@176: public bool AlwaysOnTop { moel@176: get { moel@176: return alwaysOnTop; moel@176: } moel@176: set { moel@176: if (value != alwaysOnTop) { moel@176: alwaysOnTop = value; moel@176: if (alwaysOnTop) { moel@180: if (visible) moel@180: ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged; moel@176: MoveToTopMost(Handle); moel@176: } else { moel@176: MoveToBottom(Handle); moel@180: if (visible) moel@180: ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged; moel@176: } moel@176: } moel@176: } moel@176: } moel@176: moel@176: public Size Size { moel@176: get { moel@176: return size; moel@176: } moel@176: set { moel@176: if (size != value) { moel@176: size = value; moel@183: NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero, moel@183: ref size, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, 0); moel@183: if (SizeChanged != null) moel@183: SizeChanged(this, EventArgs.Empty); moel@176: } moel@176: } moel@176: } moel@176: moel@183: public event EventHandler SizeChanged; moel@183: moel@176: public Point Location { moel@176: get { moel@176: return location; moel@176: } moel@176: set { moel@183: if (location != value) { moel@183: location = value; moel@183: NativeMethods.SetWindowPos(Handle, IntPtr.Zero, moel@183: location.X, location.Y, 0, 0, moel@183: SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING); moel@183: if (LocationChanged != null) moel@183: LocationChanged(this, EventArgs.Empty); moel@183: } moel@176: } moel@176: } moel@176: moel@176: public event EventHandler LocationChanged; moel@176: moel@176: public ContextMenu ContextMenu { moel@176: get { moel@176: return contextMenu; moel@176: } moel@176: set { moel@176: this.contextMenu = value; moel@176: } moel@176: } moel@176: moel@183: public event HitTestEventHandler HitTest; moel@183: moel@244: public event MouseEventHandler MouseDoubleClick; moel@244: moel@176: [StructLayout(LayoutKind.Sequential, Pack = 1)] moel@176: private struct BlendFunction { moel@176: public byte BlendOp; moel@176: public byte BlendFlags; moel@176: public byte SourceConstantAlpha; moel@176: public byte AlphaFormat; moel@176: } moel@176: moel@176: [StructLayout(LayoutKind.Sequential, Pack = 1)] moel@176: private struct WindowPos { moel@176: public IntPtr hwnd; moel@176: public IntPtr hwndInsertAfter; moel@176: public int x; moel@176: public int y; moel@176: public int cx; moel@176: public int cy; moel@176: public uint flags; moel@176: } moel@176: moel@176: public static readonly IntPtr HWND_BOTTOM = (IntPtr)1; moel@176: public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1); moel@176: moel@176: public const int WS_EX_LAYERED = 0x00080000; moel@176: public const int WS_EX_TOOLWINDOW = 0x00000080; moel@176: moel@176: public const uint SWP_NOSIZE = 0x0001; moel@176: public const uint SWP_NOMOVE = 0x0002; moel@176: public const uint SWP_NOACTIVATE = 0x0010; moel@243: public const uint SWP_FRAMECHANGED = 0x0020; moel@176: public const uint SWP_HIDEWINDOW = 0x0080; moel@176: public const uint SWP_SHOWWINDOW = 0x0040; moel@176: public const uint SWP_NOZORDER = 0x0004; moel@176: public const uint SWP_NOSENDCHANGING = 0x0400; moel@176: moel@176: public const int ULW_COLORKEY = 0x00000001; moel@176: public const int ULW_ALPHA = 0x00000002; moel@176: public const int ULW_OPAQUE = 0x00000004; moel@176: moel@176: public const byte AC_SRC_OVER = 0x00; moel@176: public const byte AC_SRC_ALPHA = 0x01; moel@176: moel@176: public const int WM_NCHITTEST = 0x0084; moel@176: public const int WM_NCLBUTTONDBLCLK = 0x00A3; moel@176: public const int WM_NCLBUTTONDOWN = 0x00A1; moel@176: public const int WM_NCLBUTTONUP = 0x00A2; moel@176: public const int WM_NCRBUTTONDOWN = 0x00A4; moel@176: public const int WM_NCRBUTTONUP = 0x00A5; moel@176: public const int WM_WINDOWPOSCHANGING = 0x0046; moel@176: public const int WM_COMMAND = 0x0111; moel@176: moel@176: public const int TPM_RIGHTBUTTON = 0x0002; moel@176: public const int TPM_VERTICAL = 0x0040; moel@176: moel@176: private enum WindowAttribute : int { moel@176: DWMWA_NCRENDERING_ENABLED = 1, moel@176: DWMWA_NCRENDERING_POLICY, moel@176: DWMWA_TRANSITIONS_FORCEDISABLED, moel@176: DWMWA_ALLOW_NCPAINT, moel@176: DWMWA_CAPTION_BUTTON_BOUNDS, moel@176: DWMWA_NONCLIENT_RTL_LAYOUT, moel@176: DWMWA_FORCE_ICONIC_REPRESENTATION, moel@176: DWMWA_FLIP3D_POLICY, moel@176: DWMWA_EXTENDED_FRAME_BOUNDS, moel@176: DWMWA_HAS_ICONIC_BITMAP, moel@176: DWMWA_DISALLOW_PEEK, moel@176: DWMWA_EXCLUDED_FROM_PEEK, moel@176: DWMWA_LAST moel@176: } moel@176: paulwerelds@208: /// paulwerelds@208: /// Some macros imported and converted from the Windows SDK paulwerelds@208: /// paulwerelds@208: private static class Macros { paulwerelds@210: public static ushort LOWORD(IntPtr l) { paulwerelds@210: return (ushort) ((ulong)l & 0xFFFF); paulwerelds@208: } paulwerelds@208: paulwerelds@208: public static UInt16 HIWORD(IntPtr l) { paulwerelds@210: return (ushort) (((ulong)l >> 16) & 0xFFFF); paulwerelds@208: } paulwerelds@208: paulwerelds@208: public static int GET_X_LPARAM(IntPtr lp) { paulwerelds@210: return (short) LOWORD(lp); paulwerelds@208: } paulwerelds@208: paulwerelds@208: public static int GET_Y_LPARAM(IntPtr lp) { paulwerelds@210: return (short) HIWORD(lp); paulwerelds@208: } paulwerelds@208: } paulwerelds@208: paulwerelds@208: /// paulwerelds@208: /// Imported native methods paulwerelds@208: /// moel@176: private static class NativeMethods { moel@176: private const string USER = "user32.dll"; moel@176: private const string GDI = "gdi32.dll"; moel@176: public const string DWMAPI = "dwmapi.dll"; moel@176: moel@176: [DllImport(USER, CallingConvention = CallingConvention.Winapi)] moel@176: [return: MarshalAs(UnmanagedType.Bool)] moel@183: public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, moel@183: IntPtr pptDst, ref Size psize, IntPtr hdcSrc, IntPtr pprSrc, moel@183: int crKey, IntPtr pblend, int dwFlags); moel@183: moel@183: [DllImport(USER, CallingConvention = CallingConvention.Winapi)] moel@183: [return: MarshalAs(UnmanagedType.Bool)] moel@176: public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, moel@183: IntPtr pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, moel@176: int crKey, ref BlendFunction pblend, int dwFlags); moel@176: moel@176: [DllImport(USER, CallingConvention = CallingConvention.Winapi)] moel@176: [return: MarshalAs(UnmanagedType.Bool)] moel@176: public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, moel@176: IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc, moel@176: int crKey, ref BlendFunction pblend, int dwFlags); moel@176: moel@176: [DllImport(USER, CallingConvention = CallingConvention.Winapi)] moel@176: public static extern IntPtr GetDC(IntPtr hWnd); moel@176: moel@176: [DllImport(USER, CallingConvention = CallingConvention.Winapi)] moel@176: public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); moel@176: moel@176: [DllImport(USER, CallingConvention = CallingConvention.Winapi)] moel@176: public static extern bool SetWindowPos(IntPtr hWnd, moel@176: IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); moel@176: moel@176: [DllImport(USER, CallingConvention = CallingConvention.Winapi)] moel@176: public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, moel@176: int x, int y, IntPtr hWnd, IntPtr tpmParams); moel@176: moel@176: [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] moel@176: public static extern IntPtr CreateCompatibleDC(IntPtr hDC); moel@176: moel@176: [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] moel@176: [return: MarshalAs(UnmanagedType.Bool)] moel@176: public static extern bool DeleteDC(IntPtr hdc); moel@176: moel@176: [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] moel@176: public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); moel@176: moel@176: [DllImport(GDI, CallingConvention = CallingConvention.Winapi)] moel@176: [return: MarshalAs(UnmanagedType.Bool)] moel@176: public static extern bool DeleteObject(IntPtr hObject); moel@176: moel@176: [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)] moel@176: public static extern int DwmSetWindowAttribute(IntPtr hwnd, moel@176: WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute); moel@176: } moel@176: } moel@183: moel@183: public enum HitResult { moel@183: Transparent = -1, moel@183: Nowhere = 0, moel@183: Client = 1, moel@183: Caption = 2, moel@183: Left = 10, moel@183: Right = 11, moel@183: Top = 12, moel@183: TopLeft = 13, moel@183: TopRight = 14, moel@183: Bottom = 15, moel@183: BottomLeft = 16, moel@183: BottomRight = 17, moel@183: Border = 18 moel@183: } moel@183: moel@183: public delegate void HitTestEventHandler(object sender, HitTestEventArgs e); moel@183: moel@183: public class HitTestEventArgs : EventArgs { moel@183: public HitTestEventArgs(Point location, HitResult hitResult) { moel@183: Location = location; moel@183: HitResult = hitResult; moel@183: } moel@183: public Point Location { get; private set; } moel@183: public HitResult HitResult { get; set; } moel@183: } moel@176: }