GUI/GadgetWindow.cs
author moel.mich
Fri, 24 Sep 2010 21:33:55 +0000
changeset 199 7026bb2e1f7d
parent 180 d40f49d45614
child 202 551243a66b32
permissions -rw-r--r--
Added support for more than one Heatmaster fan controller.
     1 /*
     2   
     3   Version: MPL 1.1/GPL 2.0/LGPL 2.1
     4 
     5   The contents of this file are subject to the Mozilla Public License Version
     6   1.1 (the "License"); you may not use this file except in compliance with
     7   the License. You may obtain a copy of the License at
     8  
     9   http://www.mozilla.org/MPL/
    10 
    11   Software distributed under the License is distributed on an "AS IS" basis,
    12   WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
    13   for the specific language governing rights and limitations under the License.
    14 
    15   The Original Code is the Open Hardware Monitor code.
    16 
    17   The Initial Developer of the Original Code is 
    18   Michael Möller <m.moeller@gmx.ch>.
    19   Portions created by the Initial Developer are Copyright (C) 2010
    20   the Initial Developer. All Rights Reserved.
    21 
    22   Contributor(s):
    23 
    24   Alternatively, the contents of this file may be used under the terms of
    25   either the GNU General Public License Version 2 or later (the "GPL"), or
    26   the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    27   in which case the provisions of the GPL or the LGPL are applicable instead
    28   of those above. If you wish to allow use of your version of this file only
    29   under the terms of either the GPL or the LGPL, and not to allow others to
    30   use your version of this file under the terms of the MPL, indicate your
    31   decision by deleting the provisions above and replace them with the notice
    32   and other provisions required by the GPL or the LGPL. If you do not delete
    33   the provisions above, a recipient may use your version of this file under
    34   the terms of any one of the MPL, the GPL or the LGPL.
    35  
    36 */
    37 
    38 using System;
    39 using System.Drawing;
    40 using System.Reflection;
    41 using System.Runtime.InteropServices;
    42 using System.Windows.Forms;
    43 
    44 namespace OpenHardwareMonitor.GUI {
    45 
    46   public class GadgetWindow : NativeWindow {
    47 
    48     private bool visible = false;
    49     private bool lockPositionAndSize = false;
    50     private bool alwaysOnTop = false;
    51     private byte opacity = 255;
    52     private Point location = new Point(100, 100);
    53     private Size size = new Size(130, 84);
    54     private ContextMenu contextMenu = null;
    55     private MethodInfo commandDispatch;
    56 
    57     public GadgetWindow() {
    58       Type commandType = 
    59         typeof(Form).Assembly.GetType("System.Windows.Forms.Command");
    60       commandDispatch = commandType.GetMethod("DispatchID", 
    61         BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, 
    62         null, new Type[]{ typeof(int) }, null);
    63 
    64       this.CreateHandle(CreateParams);
    65 
    66       // move window to the bottom
    67       MoveToBottom(Handle);
    68 
    69       // prevent window from fading to a glass sheet when peek is invoked
    70       try {
    71         bool value = true;
    72         int r = NativeMethods.DwmSetWindowAttribute(Handle,
    73           WindowAttribute.DWMWA_EXCLUDED_FROM_PEEK, ref value,
    74           Marshal.SizeOf(value));
    75       } catch (DllNotFoundException) { } catch (EntryPointNotFoundException) { }
    76     }
    77 
    78     private void ShowDesktopChanged(bool showDesktop) {
    79       if (showDesktop) {
    80         MoveToTopMost(Handle);
    81       } else {
    82         MoveToBottom(Handle);
    83       }
    84     }
    85 
    86     private void MoveToBottom(IntPtr handle) {
    87       NativeMethods.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0,
    88         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
    89     }
    90 
    91     private void MoveToTopMost(IntPtr handle) {
    92       NativeMethods.SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0,
    93         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
    94     }
    95 
    96     private void ShowContextMenu(Point position) {
    97       NativeMethods.TrackPopupMenuEx(contextMenu.Handle, 
    98         TPM_RIGHTBUTTON | TPM_VERTICAL, position.X,
    99         position.Y, Handle, IntPtr.Zero);
   100     }
   101 
   102     protected virtual CreateParams CreateParams {
   103       get {
   104         CreateParams cp = new CreateParams();        
   105         cp.Width = 4096;
   106         cp.Height = 4096;
   107         cp.X = location.X;
   108         cp.Y = location.Y;
   109         cp.ExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW;
   110         return cp;
   111       }
   112     }
   113 
   114     protected override void WndProc(ref Message message) {
   115       switch (message.Msg) {
   116         case WM_COMMAND: {
   117             // need to dispatch the message for the context menu
   118             if (message.LParam == IntPtr.Zero)
   119               commandDispatch.Invoke(null, new object[] { 
   120               message.WParam.ToInt32() & 0xFFFF });
   121           } break;
   122         case WM_NCHITTEST: {
   123             message.Result = (IntPtr)HitResult.Caption;
   124             if (HitTest != null) {
   125               Point p = new Point(
   126                 (int)((uint)message.LParam & 0xFFFF) - location.X,
   127                 (int)(((uint)message.LParam >> 16) & 0xFFFF) - location.Y);
   128               HitTestEventArgs e = new HitTestEventArgs(p, HitResult.Caption);
   129               HitTest(this, e);
   130               message.Result = (IntPtr)e.HitResult;
   131             }
   132           } break;
   133         case WM_NCLBUTTONDBLCLK: {
   134             message.Result = IntPtr.Zero;
   135           } break;
   136         case WM_NCRBUTTONDOWN: {
   137             message.Result = IntPtr.Zero;
   138           } break;
   139         case WM_NCRBUTTONUP: {
   140             if (contextMenu != null)
   141               ShowContextMenu(new Point(
   142                 (int)((uint)message.LParam & 0xFFFF),
   143                 (int)(((uint)message.LParam >> 16) & 0xFFFF)));
   144             message.Result = IntPtr.Zero;
   145           } break;
   146         case WM_WINDOWPOSCHANGING: {
   147             WindowPos wp = (WindowPos)Marshal.PtrToStructure(
   148               message.LParam, typeof(WindowPos));
   149             
   150             if (!lockPositionAndSize) {
   151               // prevent the window from leaving the screen
   152               if ((wp.flags & SWP_NOMOVE) == 0) {
   153                 Rectangle rect = Screen.GetWorkingArea(new Point(wp.x, wp.y));
   154                 const int margin = 16;
   155                 wp.x = Math.Max(wp.x, rect.Left - wp.cx + margin);
   156                 wp.x = Math.Min(wp.x, rect.Right - margin);
   157                 wp.y = Math.Max(wp.y, rect.Top - wp.cy + margin);
   158                 wp.y = Math.Min(wp.y, rect.Bottom - margin);
   159               }
   160 
   161               // update location and fire event
   162               if ((wp.flags & SWP_NOMOVE) == 0) {
   163                 if (location.X != wp.x || location.Y != wp.y) {
   164                   location = new Point(wp.x, wp.y);
   165                   if (LocationChanged != null)
   166                     LocationChanged(this, EventArgs.Empty);
   167                 }
   168               }
   169 
   170               // update size and fire event
   171               if ((wp.flags & SWP_NOSIZE) == 0) {
   172                 if (size.Width != wp.cx || size.Height != wp.cy) {
   173                   size = new Size(wp.cx, wp.cy);
   174                   if (SizeChanged != null)
   175                     SizeChanged(this, EventArgs.Empty);
   176                 }
   177               } 
   178 
   179               // update the size of the layered window
   180               if ((wp.flags & SWP_NOSIZE) == 0) {
   181                 NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero,
   182                   IntPtr.Zero, ref size, IntPtr.Zero, IntPtr.Zero, 0,
   183                   IntPtr.Zero, 0);                
   184               }
   185 
   186               // update the position of the layered window
   187               if ((wp.flags & SWP_NOMOVE) == 0) {
   188                 NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   189                   location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | 
   190                   SWP_NOZORDER | SWP_NOSENDCHANGING);
   191               }
   192             }
   193             
   194             // do not forward any move or size messages
   195             wp.flags |= SWP_NOSIZE | SWP_NOMOVE;            
   196             Marshal.StructureToPtr(wp, message.LParam, false);                      
   197             message.Result = IntPtr.Zero;
   198           } break;
   199         default: {
   200             base.WndProc(ref message);
   201           } break;
   202       }      
   203     }
   204 
   205     private BlendFunction CreateBlendFunction() {
   206       BlendFunction blend = new BlendFunction();
   207       blend.BlendOp = AC_SRC_OVER;
   208       blend.BlendFlags = 0;
   209       blend.SourceConstantAlpha = opacity;
   210       blend.AlphaFormat = AC_SRC_ALPHA;
   211       return blend;
   212     }
   213 
   214     public void Update(Bitmap bitmap) {
   215       IntPtr screen = NativeMethods.GetDC(IntPtr.Zero);
   216       IntPtr memory = NativeMethods.CreateCompatibleDC(screen);
   217       IntPtr newHBitmap = IntPtr.Zero;
   218       IntPtr oldHBitmap = IntPtr.Zero;
   219 
   220       try {
   221         newHBitmap = bitmap.GetHbitmap(Color.Black);
   222         oldHBitmap = NativeMethods.SelectObject(memory, newHBitmap);
   223 
   224         Point pointSource = Point.Empty;
   225         BlendFunction blend = CreateBlendFunction();
   226 
   227         NativeMethods.UpdateLayeredWindow(Handle, screen, IntPtr.Zero,
   228           ref size, memory, ref pointSource, 0, ref blend, ULW_ALPHA);
   229 
   230         // make sure the window is at the right location
   231         NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   232           location.X, location.Y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | 
   233           SWP_NOZORDER | SWP_NOSENDCHANGING);
   234 
   235       } finally {        
   236         if (newHBitmap != IntPtr.Zero) {
   237           NativeMethods.SelectObject(memory, oldHBitmap);
   238           NativeMethods.DeleteObject(newHBitmap);
   239         }
   240         NativeMethods.DeleteDC(memory);
   241         NativeMethods.ReleaseDC(IntPtr.Zero, screen);
   242       }
   243     }
   244 
   245     public byte Opacity {
   246       get {
   247         return opacity;
   248       }
   249       set {
   250         if (opacity != value) {
   251           opacity = value;
   252           BlendFunction blend = CreateBlendFunction();
   253           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   254             IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, ref blend, ULW_ALPHA);
   255         }
   256       }
   257     }
   258 
   259     public bool Visible {
   260       get {
   261         return visible;
   262       }
   263       set {
   264         if (visible != value) {
   265           visible = value;
   266           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0,
   267             SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER |
   268             (value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
   269           if (value) {
   270             if (!alwaysOnTop)
   271               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   272           } else {
   273             if (!alwaysOnTop)
   274               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   275           }
   276         }
   277       }
   278     }
   279 
   280     // if locked, the window can not be moved or resized
   281     public bool LockPositionAndSize {
   282       get {
   283         return lockPositionAndSize;
   284       }
   285       set {
   286         lockPositionAndSize = value;
   287       }
   288     }
   289 
   290     public bool AlwaysOnTop {
   291       get {
   292         return alwaysOnTop;
   293       }
   294       set {
   295         if (value != alwaysOnTop) {
   296           alwaysOnTop = value;
   297           if (alwaysOnTop) {
   298             if (visible)
   299               ShowDesktop.Instance.ShowDesktopChanged -= ShowDesktopChanged;
   300             MoveToTopMost(Handle);            
   301           } else {
   302             MoveToBottom(Handle);
   303             if (visible)
   304               ShowDesktop.Instance.ShowDesktopChanged += ShowDesktopChanged;
   305           }
   306         }
   307       }
   308     }
   309 
   310     public Size Size {
   311       get {
   312         return size; 
   313       }
   314       set {
   315         if (size != value) {
   316           size = value;
   317           NativeMethods.UpdateLayeredWindow(Handle, IntPtr.Zero, IntPtr.Zero,
   318             ref size, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, 0);                    
   319           if (SizeChanged != null)
   320             SizeChanged(this, EventArgs.Empty);
   321         }
   322       }
   323     }
   324 
   325     public event EventHandler SizeChanged;
   326 
   327     public Point Location {
   328       get {
   329         return location;
   330       }
   331       set {
   332         if (location != value) {
   333           location = value;
   334           NativeMethods.SetWindowPos(Handle, IntPtr.Zero, 
   335             location.X, location.Y, 0, 0, 
   336             SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING);          
   337           if (LocationChanged != null)
   338             LocationChanged(this, EventArgs.Empty);
   339         }
   340       }
   341     }
   342 
   343     public event EventHandler LocationChanged;
   344 
   345     public ContextMenu ContextMenu {
   346       get {
   347         return contextMenu;
   348       }
   349       set {
   350         this.contextMenu = value;
   351       }
   352     }
   353 
   354     public event HitTestEventHandler HitTest;
   355 
   356     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   357     private struct BlendFunction {
   358       public byte BlendOp;
   359       public byte BlendFlags;
   360       public byte SourceConstantAlpha;
   361       public byte AlphaFormat;
   362     }
   363 
   364     [StructLayout(LayoutKind.Sequential, Pack = 1)]
   365     private struct WindowPos {
   366       public IntPtr hwnd;
   367       public IntPtr hwndInsertAfter;
   368       public int x;
   369       public int y;
   370       public int cx;
   371       public int cy;
   372       public uint flags;
   373     }
   374 
   375     public static readonly IntPtr HWND_BOTTOM = (IntPtr)1;
   376     public static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
   377 
   378     public const int WS_EX_LAYERED = 0x00080000;
   379     public const int WS_EX_TOOLWINDOW = 0x00000080;
   380 
   381     public const uint SWP_NOSIZE = 0x0001;
   382     public const uint SWP_NOMOVE = 0x0002;
   383     public const uint SWP_NOACTIVATE = 0x0010;
   384     public const uint SWP_HIDEWINDOW = 0x0080;
   385     public const uint SWP_SHOWWINDOW = 0x0040;
   386     public const uint SWP_NOZORDER = 0x0004;
   387     public const uint SWP_NOSENDCHANGING = 0x0400;
   388 
   389     public const int ULW_COLORKEY = 0x00000001;
   390     public const int ULW_ALPHA = 0x00000002;
   391     public const int ULW_OPAQUE = 0x00000004;
   392 
   393     public const byte AC_SRC_OVER = 0x00;
   394     public const byte AC_SRC_ALPHA = 0x01;
   395 
   396     public const int WM_NCHITTEST = 0x0084;
   397     public const int WM_NCLBUTTONDBLCLK = 0x00A3;
   398     public const int WM_NCLBUTTONDOWN = 0x00A1;
   399     public const int WM_NCLBUTTONUP = 0x00A2;
   400     public const int WM_NCRBUTTONDOWN = 0x00A4;
   401     public const int WM_NCRBUTTONUP = 0x00A5;
   402     public const int WM_WINDOWPOSCHANGING = 0x0046;
   403     public const int WM_COMMAND = 0x0111;
   404 
   405     public const int TPM_RIGHTBUTTON = 0x0002;
   406     public const int TPM_VERTICAL = 0x0040;
   407 
   408     private enum WindowAttribute : int {
   409       DWMWA_NCRENDERING_ENABLED = 1,
   410       DWMWA_NCRENDERING_POLICY,
   411       DWMWA_TRANSITIONS_FORCEDISABLED,
   412       DWMWA_ALLOW_NCPAINT,
   413       DWMWA_CAPTION_BUTTON_BOUNDS,
   414       DWMWA_NONCLIENT_RTL_LAYOUT,
   415       DWMWA_FORCE_ICONIC_REPRESENTATION,
   416       DWMWA_FLIP3D_POLICY,
   417       DWMWA_EXTENDED_FRAME_BOUNDS,
   418       DWMWA_HAS_ICONIC_BITMAP,
   419       DWMWA_DISALLOW_PEEK,
   420       DWMWA_EXCLUDED_FROM_PEEK,
   421       DWMWA_LAST
   422     }
   423 
   424     private static class NativeMethods {
   425       private const string USER = "user32.dll";
   426       private const string GDI = "gdi32.dll";
   427       public const string DWMAPI = "dwmapi.dll";
   428 
   429       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   430       [return: MarshalAs(UnmanagedType.Bool)]
   431       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   432         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, IntPtr pprSrc,
   433         int crKey, IntPtr pblend, int dwFlags);
   434 
   435       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   436       [return: MarshalAs(UnmanagedType.Bool)]
   437       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, 
   438         IntPtr pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, 
   439         int crKey, ref BlendFunction pblend, int dwFlags);
   440 
   441       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   442       [return: MarshalAs(UnmanagedType.Bool)]
   443       public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
   444         IntPtr pptDst, IntPtr psize, IntPtr hdcSrc, IntPtr pprSrc,
   445         int crKey, ref BlendFunction pblend, int dwFlags);  
   446 
   447       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   448       public static extern IntPtr GetDC(IntPtr hWnd);
   449 
   450       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   451       public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
   452 
   453       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   454       public static extern bool SetWindowPos(IntPtr hWnd,
   455         IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
   456 
   457       [DllImport(USER, CallingConvention = CallingConvention.Winapi)]
   458       public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, 
   459         int x, int y, IntPtr hWnd, IntPtr tpmParams);
   460 
   461       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   462       public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
   463 
   464       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   465       [return: MarshalAs(UnmanagedType.Bool)]
   466       public static extern bool DeleteDC(IntPtr hdc);
   467       
   468       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   469       public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
   470 
   471       [DllImport(GDI, CallingConvention = CallingConvention.Winapi)]
   472       [return: MarshalAs(UnmanagedType.Bool)]
   473       public static extern bool DeleteObject(IntPtr hObject);
   474 
   475       [DllImport(DWMAPI, CallingConvention = CallingConvention.Winapi)]
   476       public static extern int DwmSetWindowAttribute(IntPtr hwnd,
   477         WindowAttribute dwAttribute, ref bool pvAttribute, int cbAttribute);
   478     }    
   479   }
   480 
   481   public enum HitResult {
   482     Transparent = -1,
   483     Nowhere = 0,
   484     Client = 1,
   485     Caption = 2,
   486     Left = 10,
   487     Right = 11,
   488     Top = 12,
   489     TopLeft = 13,
   490     TopRight = 14,
   491     Bottom = 15,
   492     BottomLeft = 16,
   493     BottomRight = 17,
   494     Border = 18
   495   }
   496 
   497   public delegate void HitTestEventHandler(object sender, HitTestEventArgs e);
   498 
   499   public class HitTestEventArgs : EventArgs {
   500     public HitTestEventArgs(Point location, HitResult hitResult) {
   501       Location = location;
   502       HitResult = hitResult;
   503     }
   504     public Point Location { get; private set; }
   505     public HitResult HitResult { get; set; }
   506   }
   507 }