From 1ff009849ed3c478e3ce4fbda9344a287c831191 Mon Sep 17 00:00:00 2001 From: wj32 Date: Sat, 26 Sep 2009 03:35:55 +0000 Subject: [PATCH] added TaskbarLib (not compilable yet) git-svn-id: svn://svn.code.sf.net/p/processhacker/code@1947 21ef857c-d57f-4fe0-8362-d861dc6d29cd --- .../Components/TaskbarLib/Interop/COMTypes.cs | 197 +++++++ .../Components/TaskbarLib/Interop/Interop.cs | 290 ++++++++++ .../TaskbarLib/JumpLists/JumpListImpl.cs | 411 ++++++++++++++ .../TaskbarLib/JumpLists/JumpListManager.cs | 528 ++++++++++++++++++ .../ThumbnailButtons/ThumbButton.cs | 119 ++++ .../ThumbnailButtons/ThumbButtonManager.cs | 113 ++++ .../Components/TaskbarLib/Windows7Taskbar.cs | 316 +++++++++++ .../TaskbarLib/WindowsFormsExtensions.cs | 189 +++++++ trunk/ProcessHacker/ProcessHacker.csproj | 8 + 9 files changed, 2171 insertions(+) create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/Interop/COMTypes.cs create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/Interop/Interop.cs create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListImpl.cs create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListManager.cs create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButton.cs create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButtonManager.cs create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/Windows7Taskbar.cs create mode 100644 trunk/ProcessHacker/Components/TaskbarLib/WindowsFormsExtensions.cs diff --git a/trunk/ProcessHacker/Components/TaskbarLib/Interop/COMTypes.cs b/trunk/ProcessHacker/Components/TaskbarLib/Interop/COMTypes.cs new file mode 100644 index 000000000..347d0241f --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/Interop/COMTypes.cs @@ -0,0 +1,197 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Runtime.CompilerServices; + +namespace TaskbarLib.Interop +{ + #region "Interface Classes" + + [ComImportAttribute()] + [GuidAttribute("86C14003-4D6B-4EF3-A7B4-0506663B2E68")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + internal class CApplicationDestinations { } + + [ComImportAttribute()] + [GuidAttribute("86BEC222-30F2-47E0-9F25-60D11CD75C28")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + internal class CApplicationDocumentLists { } + + [ComImportAttribute()] + [GuidAttribute("56FDF344-FD6D-11d0-958A-006097C9A090")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + internal class CTaskbarList { } + + [ComImportAttribute()] + [GuidAttribute("00021401-0000-0000-C000-000000000046")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + internal class CShellLink { } + + [ComImportAttribute()] + [GuidAttribute("77F10CF0-3DB5-4966-B520-B7C54FD35ED6")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + internal class CDestinationList { } + + [ComImportAttribute()] + [GuidAttribute("2D3468C1-36A7-43B6-AC24-D3F02FD9607A")] + [ClassInterfaceAttribute(ClassInterfaceType.None)] + internal class CEnumerableObjectCollection { } + + #endregion + + [ComImportAttribute()] + [GuidAttribute("92CA9DCD-5622-4BBA-A805-5E9F541BD8C9")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IObjectArray + { + void GetCount(out uint cObjects); + void GetAt(uint iIndex, ref Guid riid, [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + } + + [ComImportAttribute()] + [GuidAttribute(SafeNativeMethods.IID_IObjectCollection)] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IObjectCollection + { + // IObjectArray + [PreserveSig] + void GetCount(out uint cObjects); + [PreserveSig] + void GetAt(uint iIndex, ref Guid riid, [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + + // IObjectCollection + void AddObject([MarshalAs(UnmanagedType.Interface)] object pvObject); + void AddFromArray([MarshalAs(UnmanagedType.Interface)] IObjectArray poaSource); + void RemoveObject(uint uiIndex); + void Clear(); + } + + [ComImportAttribute()] + [GuidAttribute("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyStore + { + void GetCount(out UInt32 cProps); + void GetAt(UInt32 iProp, [MarshalAs(UnmanagedType.Struct)] out PropertyKey pkey); + void GetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey pkey, [Out(), MarshalAs(UnmanagedType.Struct)] out PropVariant pv); + void SetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey pkey, [In, MarshalAs(UnmanagedType.Struct)] ref PropVariant pv); + void Commit(); + } + + [ComImportAttribute()] + [GuidAttribute("6332DEBF-87B5-4670-90C0-5E57B408A49E")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ICustomDestinationList + { + void SetAppID([MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + void BeginList(out uint cMaxSlots, ref Guid riid, [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + void AppendCategory([MarshalAs(UnmanagedType.LPWStr)] string pszCategory, [MarshalAs(UnmanagedType.Interface)] IObjectArray poa); + void AppendKnownCategory([MarshalAs(UnmanagedType.I4)] KNOWNDESTCATEGORY category); + void AddUserTasks([MarshalAs(UnmanagedType.Interface)] IObjectArray poa); + void CommitList(); + void GetRemovedDestinations(ref Guid riid, [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + void DeleteList([MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + void AbortList(); + } + + [ComImportAttribute()] + [GuidAttribute("12337D35-94C6-48A0-BCE7-6A9C69D4D600")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IApplicationDestinations + { + void SetAppID([MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + void RemoveDestination([MarshalAs(UnmanagedType.Interface)] object pvObject); + void RemoveAllDestinations(); + } + + [ComImportAttribute()] + [GuidAttribute("3C594F9F-9F30-47A1-979A-C9E83D3D0A06")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IApplicationDocumentLists + { + void SetAppID([MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + void GetList([MarshalAs(UnmanagedType.I4)] APPDOCLISTTYPE listtype, uint cItemsDesired, ref Guid riid, [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + } + + [ComImportAttribute()] + [GuidAttribute("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ITaskbarList3 + { + // ITaskbarList + [PreserveSig] + void HrInit(); + [PreserveSig] + void AddTab(IntPtr hwnd); + [PreserveSig] + void DeleteTab(IntPtr hwnd); + [PreserveSig] + void ActivateTab(IntPtr hwnd); + [PreserveSig] + void SetActiveAlt(IntPtr hwnd); + + // ITaskbarList2 + [PreserveSig] + void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen); + + // ITaskbarList3 + void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal); + void SetProgressState(IntPtr hwnd, TBPFLAG tbpFlags); + void RegisterTab(IntPtr hwndTab, IntPtr hwndMDI); + void UnregisterTab(IntPtr hwndTab); + void SetTabOrder(IntPtr hwndTab, IntPtr hwndInsertBefore); + void SetTabActive(IntPtr hwndTab, IntPtr hwndMDI, TBATFLAG tbatFlags); + void ThumbBarAddButtons(IntPtr hwnd, uint cButtons, [MarshalAs(UnmanagedType.LPArray)] THUMBBUTTON[] pButtons); + void ThumbBarUpdateButtons(IntPtr hwnd, uint cButtons, [MarshalAs(UnmanagedType.LPArray)] THUMBBUTTON[] pButtons); + void ThumbBarSetImageList(IntPtr hwnd, IntPtr himl); + void SetOverlayIcon(IntPtr hwnd, IntPtr hIcon, [MarshalAs(UnmanagedType.LPWStr)] string pszDescription); + void SetThumbnailTooltip(IntPtr hwnd, [MarshalAs(UnmanagedType.LPWStr)] string pszTip); + void SetThumbnailClip(IntPtr hwnd, /*[MarshalAs(UnmanagedType.LPStruct)]*/ ref RECT prcClip); + } + + [ComImportAttribute()] + [GuidAttribute("43826D1E-E718-42EE-BC55-A1E261C37BFE")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItem + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayName([In] SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } + + [ComImportAttribute()] + [GuidAttribute("000214F9-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellLinkW + { + void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, uint fFlags); + void GetIDList(out IntPtr ppidl); + void SetIDList(IntPtr pidl); + void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxName); + void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); + void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); + void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); + void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); + void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + void GetHotKey(out short wHotKey); + void SetHotKey(short wHotKey); + void GetShowCmd(out uint iShowCmd); + void SetShowCmd(uint iShowCmd); + void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int iIcon); + void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); + void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved); + void Resolve(IntPtr hwnd, uint fFlags); + void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); + } +} \ No newline at end of file diff --git a/trunk/ProcessHacker/Components/TaskbarLib/Interop/Interop.cs b/trunk/ProcessHacker/Components/TaskbarLib/Interop/Interop.cs new file mode 100644 index 000000000..f5519a790 --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/Interop/Interop.cs @@ -0,0 +1,290 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; + +namespace TaskbarLib.Interop +{ + [Flags] + internal enum KNOWNDESTCATEGORY + { + KDC_FREQUENT = 1, + KDC_RECENT + } + [Flags] + internal enum APPDOCLISTTYPE + { + ADLT_RECENT = 0, + ADLT_FREQUENT + } + [StructLayout(LayoutKind.Sequential)] + internal struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + + public RECT(int left, int top, int right, int bottom) + { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + } + [Flags] + internal enum TBPFLAG + { + TBPF_NOPROGRESS = 0, + TBPF_INDETERMINATE = 0x1, + TBPF_NORMAL = 0x2, + TBPF_ERROR = 0x4, + TBPF_PAUSED = 0x8 + } + [Flags] + internal enum TBATFLAG + { + TBATF_USEMDITHUMBNAIL = 0x1, + TBATF_USEMDILIVEPREVIEW = 0x2 + } + [Flags] + internal enum THBMASK + { + THB_BITMAP = 0x1, + THB_ICON = 0x2, + THB_TOOLTIP = 0x4, + THB_FLAGS = 0x8 + } + [Flags] + internal enum THBFLAGS + { + THBF_ENABLED = 0, + THBF_DISABLED = 0x1, + THBF_DISMISSONCLICK = 0x2, + THBF_NOBACKGROUND = 0x4, + THBF_HIDDEN = 0x8 + } + [Flags] + internal enum SIGDN : uint + { + SIGDN_NORMALDISPLAY = 0x00000000, // SHGDN_NORMAL + SIGDN_PARENTRELATIVEPARSING = 0x80018001, // SHGDN_INFOLDER | SHGDN_FORPARSING + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, // SHGDN_FORPARSING + SIGDN_PARENTRELATIVEEDITING = 0x80031001, // SHGDN_INFOLDER | SHGDN_FOREDITING + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, // SHGDN_FORPARSING | SHGDN_FORADDRESSBAR + SIGDN_FILESYSPATH = 0x80058000, // SHGDN_FORPARSING + SIGDN_URL = 0x80068000, // SHGDN_FORPARSING + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, // SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR + SIGDN_PARENTRELATIVE = 0x80080001 // SHGDN_INFOLDER + } + + [StructLayout(LayoutKind.Sequential)] + internal struct POINT + { + internal int X; + internal int Y; + + internal POINT(int x, int y) + { + this.X = x; + this.Y = y; + } + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct THUMBBUTTON + { + [MarshalAs(UnmanagedType.U4)] + public THBMASK dwMask; + public uint iId; + public uint iBitmap; + public IntPtr hIcon; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szTip; + [MarshalAs(UnmanagedType.U4)] + public THBFLAGS dwFlags; + } + [StructLayout(LayoutKind.Sequential, Pack = 4)] + internal struct PropertyKey + { + public Guid fmtid; + public uint pid; + + public PropertyKey(Guid fmtid, uint pid) + { + this.fmtid = fmtid; + this.pid = pid; + } + + public static PropertyKey PKEY_Title = new PropertyKey(new Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9"), 2); + public static PropertyKey PKEY_AppUserModel_ID = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 5); + public static PropertyKey PKEY_AppUserModel_IsDestListSeparator = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 6); + public static PropertyKey PKEY_AppUserModel_RelaunchCommand = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 2); + public static PropertyKey PKEY_AppUserModel_RelaunchDisplayNameResource = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 4); + public static PropertyKey PKEY_AppUserModel_RelaunchIconResource = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 3); + } + [StructLayout(LayoutKind.Explicit)] + internal struct CALPWSTR + { + [FieldOffset(0)] + internal uint cElems; + [FieldOffset(4)] + internal IntPtr pElems; + } + [StructLayout(LayoutKind.Explicit)] + internal struct PropVariant + { + [FieldOffset(0)] + private ushort vt; + [FieldOffset(8)] + private IntPtr pointerValue; + [FieldOffset(8)] + private byte byteValue; + [FieldOffset(8)] + private long longValue; + [FieldOffset(8)] + private short boolValue; + [MarshalAs(UnmanagedType.Struct)] + [FieldOffset(8)] + private CALPWSTR calpwstr; + + [DllImport("ole32.dll")] + private static extern int PropVariantClear(ref PropVariant pvar); + + public VarEnum VarType + { + get { return (VarEnum)vt; } + } + + public void SetValue(String val) + { + this.Clear(); + this.vt = (ushort)VarEnum.VT_LPWSTR; + this.pointerValue = Marshal.StringToCoTaskMemUni(val); + } + public void SetValue(bool val) + { + this.Clear(); + this.vt = (ushort)VarEnum.VT_BOOL; + this.boolValue = val ? (short)-1 : (short)0; + } + + public string GetValue() + { + return Marshal.PtrToStringUni(this.pointerValue); + } + + public void Clear() + { + PropVariantClear(ref this); + } + } + + [SuppressUnmanagedCodeSecurity] + internal static class SafeNativeMethods + { + //Obviously, these GUIDs shouldn't be modified. The reason they + //are not readonly is that they are passed with 'ref' to various + //native methods. + public static Guid IID_IObjectArray = new Guid("92CA9DCD-5622-4BBA-A805-5E9F541BD8C9"); + public const string IID_IObjectCollection = "5632B1A4-E38A-400A-928A-D4CD63230295"; + public static Guid IID_IPropertyStore = new Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"); + public static Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); + + public const int DWM_SIT_DISPLAYFRAME = 0x00000001; + + public const int DWMWA_FORCE_ICONIC_REPRESENTATION = 7; + public const int DWMWA_HAS_ICONIC_BITMAP = 10; + + public const int WM_COMMAND = 0x111; + public const int WM_SYSCOMMAND = 0x112; + public const int WM_DWMSENDICONICTHUMBNAIL = 0x0323; + public const int WM_DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326; + public const int WM_CLOSE = 0x0010; + public const int WM_ACTIVATE = 0x0006; + + public const int WA_ACTIVE = 1; + public const int WA_CLICKACTIVE = 2; + + public const int SC_CLOSE = 0xF060; + + public const int MSGFLT_ADD = 1; + public const int MSGFLT_REMOVE = 2; + + // Thumbbutton WM_COMMAND notification + public const uint THBN_CLICKED = 0x1800; + } + [SuppressUnmanagedCodeSecurity] + internal static class UnsafeNativeMethods + { + [DllImport("shell32.dll")] + public static extern int SHGetPropertyStoreForWindow(IntPtr hwnd, ref Guid iid /*IID_IPropertyStore*/, [Out(), MarshalAs(UnmanagedType.Interface)] out IPropertyStore propertyStore); + [DllImport("dwmapi.dll")] + public static extern int DwmSetIconicThumbnail(IntPtr hwnd, IntPtr hbitmap, uint flags); + [DllImport("dwmapi.dll")] + public static extern int DwmSetIconicLivePreviewBitmap(IntPtr hwnd, IntPtr hbitmap, ref POINT ptClient, uint flags); + [DllImport("dwmapi.dll")] + public static extern int DwmSetIconicLivePreviewBitmap(IntPtr hwnd, IntPtr hbitmap, IntPtr ptClient, uint flags); + [DllImport("dwmApi.dll")] + internal static extern int DwmSetWindowAttribute(IntPtr hwnd, uint dwAttributeToSet, IntPtr pvAttributeValue, uint cbAttribute); + [DllImport("dwmApi.dll")] + internal static extern int DwmSetWindowAttribute(IntPtr hwnd, uint dwAttributeToSet, ref int pvAttributeValue, uint cbAttribute); + [DllImport("dwmapi.dll")] + public static extern int DwmInvalidateIconicBitmaps(IntPtr hwnd); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ChangeWindowMessageFilter(uint message, uint flags); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern uint RegisterWindowMessage(string lpString); + + public static readonly uint WM_TaskbarButtonCreated = RegisterWindowMessage("TaskbarButtonCreated"); + + [DllImport("shell32.dll")] + public static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID); + [DllImport("shell32.dll")] + public static extern void GetCurrentProcessExplicitAppUserModelID([Out(), MarshalAs(UnmanagedType.LPWStr)] out string AppID); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetWindowRect(IntPtr hwnd, ref RECT rect); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetClientRect(IntPtr hwnd, ref RECT rect); + public static bool GetClientSize(IntPtr hwnd, out System.Drawing.Size size) + { + RECT rect = new RECT(); + if (!GetClientRect(hwnd, ref rect)) + { + size = new System.Drawing.Size(-1, -1); + return false; + } + size = new System.Drawing.Size(rect.right, rect.bottom); + return true; + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ShowWindow(IntPtr hwnd, int cmd); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int X, int Y, int cx, int cy, uint flags); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ClientToScreen(IntPtr hwnd, ref POINT point); + [DllImport("user32.dll")] + public static extern int GetWindowText(IntPtr hwnd, StringBuilder str, int maxCount); + [DllImport("gdi32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool BitBlt (IntPtr hDestDC, int destX, int destY, int width, int height, IntPtr hSrcDC, int srcX, int srcY, uint operation); + [DllImport("gdi32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool StretchBlt(IntPtr hDestDC, int destX, int destY, int destWidth, int destHeight, IntPtr hSrcDC, int srcX, int srcY, int srcWidth, int srcHeight, uint operation); + [DllImport("user32.dll")] + public static extern IntPtr GetWindowDC(IntPtr hwnd); + [DllImport("user32.dll")] + public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc); + [DllImport("Shell32", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern uint SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, /* The following parameter is not used - binding context. */ IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); + } +} \ No newline at end of file diff --git a/trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListImpl.cs b/trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListImpl.cs new file mode 100644 index 000000000..fb9678c6b --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListImpl.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TaskbarLib.Interop; + +namespace TaskbarLib.DesktopIntegration +{ + /// + /// A collection of categorized jump list destinations. + /// + internal sealed class JumpListDestinations + { + //TODO: This is highly inefficient, but we want to maintain + //insertion order when adding the categories because the bottom + //categories are first to be truncated if screen estate is low. + private SortedDictionary> _categorizedDestinations = + new SortedDictionary>(); + + public void AddDestination(IJumpListDestination destination) + { + List destinations = + _categorizedDestinations.Values.FirstOrDefault( + list => list.First().Category == destination.Category); + if (destinations == null) + { + destinations = new List(); + _categorizedDestinations.Add( + _categorizedDestinations.Keys.LastOrDefault() + 1, destinations); + } + + destinations.Add(destination); + } + + public void DeleteDestination(IJumpListDestination destination) + { + List destinations = + _categorizedDestinations.Values.First( + list => list.First().Category == destination.Category); + IJumpListDestination toDelete = destinations.Find( + d => d.Path == destination.Path && d.Category == destination.Category && d.Title == destination.Title); + if (toDelete != null) + destinations.Remove(toDelete); + } + + internal void RefreshDestinations(ICustomDestinationList destinationList) + { + if (_categorizedDestinations.Count == 0) + return; + + foreach (int key in _categorizedDestinations.Keys) + { + IObjectCollection categoryContents = + (IObjectCollection)new CEnumerableObjectCollection(); + var destinations = _categorizedDestinations[key]; + foreach (IJumpListDestination destination in destinations) + { + categoryContents.AddObject(destination.GetShellRepresentation()); + } + destinationList.AppendCategory( + destinations.First().Category, + (IObjectArray)categoryContents); + } + } + + public IEnumerable Categories + { + get + { + return + (from d in _categorizedDestinations.Keys + select _categorizedDestinations[d].First().Category); + } + } + public IEnumerable GetDestinationsByCategory( + string category) + { + return + (from k in _categorizedDestinations.Keys + let d = _categorizedDestinations[k] + where d.First().Category == category + select d).Single(); + } + + public void Clear() + { + _categorizedDestinations.Clear(); + } + } + + /// + /// A collection of jump list tasks. + /// + internal sealed class JumpListTasks + { + private List _tasks = new List(); + + public void AddTask(IJumpListTask task) + { + _tasks.Add(task); + } + public void DeleteTask(IJumpListTask task) + { + IJumpListTask toDelete = _tasks.Find(t => t.Path == task.Path && t.Arguments == task.Arguments); + if (toDelete != null) + _tasks.Remove(toDelete); + } + + internal void RefreshTasks(ICustomDestinationList destinationList) + { + if (_tasks.Count == 0) + return; + + IObjectCollection taskCollection = + (IObjectCollection)new CEnumerableObjectCollection(); + foreach (IJumpListTask task in _tasks) + { + taskCollection.AddObject(task.GetShellRepresentation()); + } + destinationList.AddUserTasks((IObjectArray)taskCollection); + } + + public IEnumerable Tasks + { + get { return _tasks; } + } + + public void Clear() + { + _tasks.Clear(); + } + } + + /// + /// Represents a shell object that can be inserted to an application's + /// jump list. + /// + public interface IJumpListShellObject + { + /// + /// Gets or sets the object's title. + /// + string Title { get; } + /// + /// Gets or sets the object's path. + /// + string Path { get; } + + /// + /// Gets the shell representation of an object, such as + /// IShellLink or IShellItem. + /// + /// + object GetShellRepresentation(); + } + + /// + /// Represents a jump list destination. + /// + public interface IJumpListDestination : IJumpListShellObject + { + /// + /// Gets or sets the destination's category. + /// + string Category { get; } + } + /// + /// Represents a jump list task. + /// + public interface IJumpListTask : IJumpListShellObject + { + /// + /// Gets or sets the task's command line arguments. + /// + string Arguments { get; } + } + + /// + /// Flags controlling the appearance of a window. + /// + public enum WindowShowCommand : uint + { + /// + /// Hides the window and activates another window. + /// + Hide = 0, + /// + /// Activates and displays the window (including restoring + /// it to its original size and position). + /// + Normal = 1, + /// + /// Minimizes the window. + /// + Minimized = 2, + /// + /// Maximizes the window. + /// + Maximized = 3, + /// + /// Similar to , except that the window + /// is not activated. + /// + ShowNoActivate = 4, + /// + /// Activates the window and displays it in its current size + /// and position. + /// + Show = 5, + /// + /// Minimizes the window and activates the next top-level window. + /// + Minimize = 6, + /// + /// Minimizes the window and does not activate it. + /// + ShowMinimizedNoActivate = 7, + /// + /// Similar to , except that the window is not + /// activated. + /// + ShowNA = 8, + /// + /// Activates and displays the window, restoring it to its original + /// size and position. + /// + Restore = 9, + /// + /// Sets the show state based on the initial value specified when + /// the process was created. + /// + Default = 10, + /// + /// Minimizes a window, even if the thread owning the window is not + /// responding. Use this only to minimize windows from a different + /// thread. + /// + ForceMinimize = 11 + } + + /// + /// Represents a separator in the task area of the jump list. + /// There is no need to set any properties on this class. + /// + public sealed class Separator : IJumpListTask + { + string IJumpListTask.Arguments + { + get { throw new NotImplementedException(); } + } + + string IJumpListShellObject.Title + { + get { throw new NotImplementedException(); } + } + + string IJumpListShellObject.Path + { + get { throw new NotImplementedException(); } + } + + object IJumpListShellObject.GetShellRepresentation() + { + ShellLink shellLink = new ShellLink + { + IsSeparator = true + }; + return shellLink.GetShellRepresentation(); + } + } + + /// + /// Represents a shell link (IShellLink) object. + /// + public sealed class ShellLink : IJumpListTask, IJumpListDestination + { + /// + /// Gets or sets the object's title. + /// + public string Title { get; set; } + /// + /// Gets or sets the object's category. + /// + public string Category { get; set; } + + /// + /// Gets or sets the object's path. + /// + public string Path { get; set; } + /// + /// Gets or sets the location of the object's icon. + /// + public string IconLocation { get; set; } + /// + /// Gets or sets the index of the object's icon in the specified + /// icon's location (). + /// + public int IconIndex { get; set; } + /// + /// Gets or sets the object's arguments (passed to the command + /// line). + /// + public string Arguments { get; set; } + /// + /// Gets or sets the object's working directory. + /// + public string WorkingDirectory { get; set; } + + /// + /// Gets or sets the show command of the launched application. + /// + public WindowShowCommand ShowCommand { get; set; } + + /// + /// Gets or sets a flag indicating that the shell link + /// is a menu separator. If this flag is set, all other + /// properties are ignored. + /// + internal bool IsSeparator { get; set; } + + /// + /// Gets the shell IShellLink representation + /// of the object. + /// + /// An IShellLink up-cast to object. + public object GetShellRepresentation() + { + IShellLinkW shellLink = (IShellLinkW)new CShellLink(); + + IPropertyStore propertyStore = (IPropertyStore)shellLink; + PropVariant propVariant = new PropVariant(); + + if (IsSeparator) + { + propVariant.SetValue(true); + propertyStore.SetValue(ref PropertyKey.PKEY_AppUserModel_IsDestListSeparator, ref propVariant); + propVariant.Clear(); + } + else + { + shellLink.SetPath(Path); + + if (!String.IsNullOrEmpty(IconLocation)) + shellLink.SetIconLocation(IconLocation, IconIndex); + if (!String.IsNullOrEmpty(Arguments)) + shellLink.SetArguments(Arguments); + if (!String.IsNullOrEmpty(WorkingDirectory)) + shellLink.SetWorkingDirectory(WorkingDirectory); + shellLink.SetShowCmd((uint)ShowCommand); + + propVariant.SetValue(Title); + propertyStore.SetValue(ref PropertyKey.PKEY_Title, ref propVariant); + propVariant.Clear(); + } + + propertyStore.Commit(); + + return shellLink; + } + } + + /// + /// Represents a IShellItem object. + /// + public sealed class ShellItem : IJumpListDestination + { + string IJumpListShellObject.Title { get { return null; } } + + /// + /// Gets or sets the object's category. + /// + public string Category { get; set; } + + /// + /// Gets or sets the object's path. + /// + public string Path { get; set; } + + /// + /// Gets the shell IShellItem representation + /// of the object. + /// + /// An IShellItem up-cast to object. + public object GetShellRepresentation() + { + return null;// GetShellItemFromPath(Path); + + //Note: this will only work if there is a file association + //for the extension we're trying to register to our program. + } + + internal static IShellItem GetShellItemFromPath(string path) + { + if (String.IsNullOrEmpty(path)) + throw new ArgumentNullException( + "path", "Shell item cannot be generated from null or empty path."); + + IShellItem resultItem = default(IShellItem); + //Guid shellItemGuid = new Guid(IIDGuid.IShellItem); + //uint result = SHCreateItemFromParsingName( + // path, + // IntPtr.Zero, + // ref shellItemGuid, + // out resultItem); + //// Throw if an error occurred. + //System.Runtime.InteropServices.Marshal.ThrowExceptionForHR((int)result); + return resultItem; + } + + + } +} \ No newline at end of file diff --git a/trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListManager.cs b/trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListManager.cs new file mode 100644 index 000000000..cf0d629c2 --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/JumpLists/JumpListManager.cs @@ -0,0 +1,528 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using Microsoft.Win32; +using TaskbarLib.Interop; +using System.Runtime.CompilerServices; +using TaskbarLib.DesktopIntegration; + +namespace TaskbarLib.DesktopIntegration +{ + /// + /// Provides services to manage taskbar jump lists, including + /// custom destinations and custom tasks. + /// + /// + /// This class mostly borrows the Windows Shell's concepts where + /// jump lists are concerned including: + /// Application destinations - Destinations added to the application's + /// recent and frequent categories by the shell or by the application. + /// Custom destinations - Destinations added to the application's + /// jump list in other categories by the application. + /// Tasks - Tasks added to the application's jump list. + /// The methods of this class are not thread-safe. + /// + public sealed class JumpListManager : IDisposable + { + #region Members + + string _appId; + JumpListDestinations _destinations; + JumpListTasks _tasks; + + ICustomDestinationList _customDestinationList; + uint _maxSlotsInList; + + ApplicationDestinationType _enabledAutoDestinationType = ApplicationDestinationType.Recent; + + EventHandler _displaySettingsChangeHandler; + + #endregion + + /// + /// Initializes a new instance of the jump list manager + /// with the specified application id. + /// + /// The application id. + public JumpListManager(string appId) + { + _appId = appId; + _destinations = new JumpListDestinations(); + _tasks = new JumpListTasks(); + + _customDestinationList = (ICustomDestinationList)new CDestinationList(); + if (String.IsNullOrEmpty(_appId)) + { + _appId = Windows7Taskbar.GetCurrentProcessAppId(); + } + if (!String.IsNullOrEmpty(_appId)) + { + _customDestinationList.SetAppID(_appId); + } + + _displaySettingsChangeHandler = delegate + { + RefreshMaxSlots(); + }; + SystemEvents.DisplaySettingsChanged += + _displaySettingsChangeHandler; + } + + /// + /// Initializes a new instance of the jump list manager + /// with the specified window handle. + /// + /// The window handle. + public JumpListManager(IntPtr hwnd) + : this(Windows7Taskbar.GetWindowAppId(hwnd)) + { + } + + /// + /// Adds a task to the application's jump list. + /// + /// An object implementing , + /// such as . + public void AddUserTask(IJumpListTask task) + { + _tasks.AddTask(task); + } + + /// + /// Retrieves the tasks currently present in the application's + /// jump list. If the tasks are modified through the use of this + /// property, the method must be called to + /// repopulate the application's jump list. + /// + public IEnumerable Tasks + { + get { return _tasks.Tasks; } + } + + /// + /// Deletes the specified task from the application's jump list. + /// + /// The task to delete. + public void DeleteTask(IJumpListTask task) + { + _tasks.DeleteTask(task); + } + + /// + /// Deletes all the tasks from the application's jump list. + /// + public void ClearTasks() + { + _tasks.Clear(); + } + + /// + /// Adds a custom destination to the application's jump list. + /// + /// An object implementing + /// such as + /// or . + public void AddCustomDestination(IJumpListDestination destination) + { + _destinations.AddDestination(destination); + } + + /// + /// Deletes the specified custom destination from the application's + /// jump list. + /// + /// The destination to delete. + public void DeleteCustomDestination(IJumpListDestination destination) + { + _destinations.DeleteDestination(destination); + } + + /// + /// The currently enabled automatic application destination type. + /// The supported values are the values of the + /// enumeration. + /// Only of the values can be set at any given time. + /// + public ApplicationDestinationType EnabledAutoDestinationType + { + get + { + return _enabledAutoDestinationType; + } + set + { + if (_enabledAutoDestinationType == value) + return; + + _enabledAutoDestinationType = value; + } + } + + /// + /// Removes all destinations from the application's jump list. + /// + public void ClearAllDestinations() + { + ClearApplicationDestinations(); + ClearCustomDestinations(); + } + + /// + /// Removes all application destinations (such as frequent and recent) + /// from the application's jump list. + /// + public void ClearApplicationDestinations() + { + IApplicationDestinations destinations = (IApplicationDestinations) + new CApplicationDestinations(); + if (!String.IsNullOrEmpty(_appId)) + { + destinations.SetAppID(_appId); + } + try + { + //This does not remove pinned items. + destinations.RemoveAllDestinations(); + } + catch (System.IO.FileNotFoundException) + {//There are no destinations. That's cool. + } + } + + /// + /// Retrieves all application destinations belonging to the specified + /// application destination type. + /// + /// The application destination type. + /// A copy of the application destinations belonging to + /// the specified type; modifying the returned objects has no effect + /// on the application's destination list. + public IEnumerable GetApplicationDestinations(ApplicationDestinationType type) + { + if (type == ApplicationDestinationType.NONE) + throw new ArgumentException("type can't be NONE"); + + IApplicationDocumentLists destinations = (IApplicationDocumentLists)new CApplicationDocumentLists(); + Guid iidObjectArray = typeof(IObjectArray).GUID; + object obj; + + destinations.GetList((APPDOCLISTTYPE)type, 100, ref iidObjectArray, out obj); + + List returnValue = new List(); + + Guid iidShellItem = typeof(IShellItem).GUID; + Guid iidShellLink = typeof(IShellLinkW).GUID; + IObjectArray array = (IObjectArray)obj; + uint count; + array.GetCount(out count); + for (uint i = 0; i < count; ++i) + { + try + { + array.GetAt(i, ref iidShellItem, out obj); + } + catch (Exception)//Wrong type + { + } + if (obj == null) + { + array.GetAt(i, ref iidShellLink, out obj); + //This shouldn't fail since if it's not IShellItem + //then it must be IShellLink. + + IShellLinkW link = (IShellLinkW)obj; + ShellLink wrapper = new ShellLink(); + + StringBuilder sb = new StringBuilder(256); + link.GetPath(sb, sb.Capacity, IntPtr.Zero, 2); + wrapper.Path = sb.ToString(); + + link.GetArguments(sb, sb.Capacity); + wrapper.Arguments = sb.ToString(); + + int iconId; + link.GetIconLocation(sb, sb.Capacity, out iconId); + wrapper.IconIndex = iconId; + wrapper.IconLocation = sb.ToString(); + + uint showCmd; + link.GetShowCmd(out showCmd); + wrapper.ShowCommand = (WindowShowCommand)showCmd; + + link.GetWorkingDirectory(sb, sb.Capacity); + wrapper.WorkingDirectory = sb.ToString(); + + returnValue.Add(wrapper); + } + else //It's an IShellItem. + { + IShellItem item = (IShellItem)obj; + ShellItem wrapper = new ShellItem(); + + string path; + item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out path); + wrapper.Path = path; + + //Title and Category are irrelevant here, because it's + //an IShellItem. The user might want to see them, but he's + //free to go to the IShellItem and look at its property store. + + returnValue.Add(wrapper); + } + } + + return returnValue; + } + + /// + /// Deletes the specified application destination from the application's + /// jump list. + /// + /// The application destination. + public void DeleteApplicationDestination(IJumpListDestination destination) + { + IApplicationDestinations destinations = (IApplicationDestinations) + new CApplicationDestinations(); + if (!String.IsNullOrEmpty(_appId)) + { + destinations.SetAppID(_appId); + } + + destinations.RemoveDestination(destination.GetShellRepresentation()); + } + + /// + /// Deletes all custom destinations from the application's jump list. + /// + public void ClearCustomDestinations() + { + try + { + _customDestinationList.DeleteList(_appId); + } + catch (FileNotFoundException) + {//Means the list is empty, that's cool. + } + + _destinations.Clear(); + } + + /// + /// Repopulates the application's jump list. + /// Use this method after all current changes to + /// the application's jump list have been introduced, + /// and you want the list to be refreshed. + /// + /// true if the list was refreshed; false + /// if the operation was cancelled. The operation might have + /// been cancelled if the event + /// handler instructed us to cancel the operation. + /// + /// If the user removed items from the jump list between the + /// last refresh operation and this one, then the + /// event will be invoked. + /// If the event handler for this event instructed us to cancel + /// the operation, then the current transaction is aborted, + /// no items are added, and this method returns false. + /// Check the return value to determine whether the jump list + /// needs to be changed and the operation attempted again. + /// + public bool Refresh() + { + if (!BeginList()) + return false; //Operation was cancelled + + _tasks.RefreshTasks(_customDestinationList); + _destinations.RefreshDestinations(_customDestinationList); + + switch (EnabledAutoDestinationType) + { + case ApplicationDestinationType.Frequent: + _customDestinationList.AppendKnownCategory(KNOWNDESTCATEGORY.KDC_FREQUENT); + break; + case ApplicationDestinationType.Recent: + _customDestinationList.AppendKnownCategory(KNOWNDESTCATEGORY.KDC_RECENT); + break; + } + + CommitList(); + return true; + } + + /// + /// Returns the maximum number of items to be placed + /// in the application's jump list. This number depends + /// on factors such as the display resolution or monitor + /// change - do not assume it is always constant. + /// + public uint MaximumSlotsInList + { + get + { + if (_maxSlotsInList == 0) + { + RefreshMaxSlots(); + } + return _maxSlotsInList; + } + } + + /// + /// Cleans the resources associated with this jump list. + /// + public void Dispose() + { + SystemEvents.DisplaySettingsChanged -= + _displaySettingsChangeHandler; + if (_customDestinationList != null) + Marshal.ReleaseComObject(_customDestinationList); + } + + /// + /// Register to this event to receive notifications when custom + /// destinations are being removed from your jump list by the user. + /// If you do not register to this event, you will not be able + /// to refresh the list. Additionally, if you attempt to add + /// items to the list which have been previously removed by the user, + /// the next refresh will fail to add your category. + /// + public event EventHandler UserRemovedItems; + + #region Implementation + + private void RefreshMaxSlots() + { + object obj; + _customDestinationList.BeginList(out _maxSlotsInList, ref SafeNativeMethods.IID_IObjectArray, out obj); + _customDestinationList.AbortList(); + } + + private bool BeginList() + { + if (UserRemovedItems == null) + { + throw new InvalidOperationException("You must register for the JumpListManager.UserRemovedItems event before adding any items"); + } + + object obj; + _customDestinationList.BeginList(out _maxSlotsInList, ref SafeNativeMethods.IID_IObjectArray, out obj); + + IObjectArray removedItems = (IObjectArray)obj; + uint count; + removedItems.GetCount(out count); + if (count == 0) + return true; + + string[] removedItemsArr = new string[count]; + for (uint i = 0; i < count; ++i) + { + object item; + removedItems.GetAt(i, ref SafeNativeMethods.IID_IUnknown, out item); + + try + { + IShellLinkW shellLink = (IShellLinkW)item; + if (shellLink != null) + { + StringBuilder sb = new StringBuilder(256); + shellLink.GetPath(sb, sb.Capacity, IntPtr.Zero, 2); + removedItemsArr[i] = sb.ToString(); + } + continue; + } + catch (InvalidCastException) //It's not a ShellLink + { } + + try + { + IShellItem shellItem = (IShellItem)item; + if (shellItem != null) + { + string path; + shellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out path); + removedItemsArr[i] = path; + } + } + catch (InvalidCastException) + { + //It's neither a shell link nor a shell item. + //This is impossible. + Debug.Assert(false, + "List of removed items contains something that is neither a shell item nor a shell link"); + } + } + + UserRemovedItemsEventArgs args = new UserRemovedItemsEventArgs(removedItemsArr); + UserRemovedItems(this, args); + if (args.CancelCurrentOperation) + { + _customDestinationList.AbortList(); + } + return !args.CancelCurrentOperation; + } + + private void CommitList() + { + _customDestinationList.CommitList(); + } + + #endregion + } + + /// + /// The application destination type. + /// + public enum ApplicationDestinationType + { + /// + /// No application destination type is selected. + /// + NONE = -1, + /// + /// Destinations used recently. + /// + Recent = 0, + /// + /// Destinations used frequently. + /// + Frequent + } + + /// + /// The event arguments for the event that occurs + /// when the user removes items from the application's + /// jump list. + /// + public class UserRemovedItemsEventArgs : EventArgs + { + readonly string[] _removedItems; + + internal UserRemovedItemsEventArgs(string[] removedItems) + { + _removedItems = removedItems; + } + + /// + /// The collection of removed items. Each item is the path. + /// + public string[] RemovedItems + { + get + { + return _removedItems; + } + } + + /// + /// Set to true if the current operation + /// should be cancelled. Should be used by the application + /// if because of the items the user has removed + /// there is no real work to do with the jump list. + /// + public bool CancelCurrentOperation { get; set; } + } +} \ No newline at end of file diff --git a/trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButton.cs b/trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButton.cs new file mode 100644 index 000000000..221420a7e --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButton.cs @@ -0,0 +1,119 @@ +using System; +using System.Drawing; +using TaskbarLib.Interop; + +namespace TaskbarLib.DesktopIntegration +{ + /// + /// Represents a taskbar thumbnail button in the thumbnail toolbar. + /// + public sealed class ThumbButton + { + private ThumbButtonManager _manager; + + internal ThumbButton(ThumbButtonManager manager, uint id, Icon icon, string tooltip) + { + _manager = manager; + + Id = id; + Icon = icon; + Tooltip = tooltip; + } + + /// + /// The event that occurs when the taskbar thumbnail button + /// is clicked. + /// + public event EventHandler Clicked; + + /// + /// Gets or sets thumbnail button's id. + /// + public uint Id { get; set; } + + /// + /// Gets or sets the thumbnail button's icon. + /// + public Icon Icon { get; set; } + + /// + /// Gets or sets the thumbnail button's tooltip. + /// + public string Tooltip { get; set; } + + internal THBFLAGS Flags { get; set; } + + internal THUMBBUTTON Win32ThumbButton + { + get + { + THUMBBUTTON win32ThumbButton = new THUMBBUTTON(); + win32ThumbButton.iId = Id; + win32ThumbButton.szTip = Tooltip; + win32ThumbButton.hIcon = Icon.Handle; + win32ThumbButton.dwFlags = Flags; + + win32ThumbButton.dwMask = THBMASK.THB_FLAGS; + if (Tooltip != null) + win32ThumbButton.dwMask |= THBMASK.THB_TOOLTIP; + if (Icon != null) + win32ThumbButton.dwMask |= THBMASK.THB_ICON; + + return win32ThumbButton; + } + } + + /// + /// Gets or sets the thumbnail button's visibility. + /// + public bool Visible + { + get + { + return (this.Flags & THBFLAGS.THBF_HIDDEN) == 0; + } + set + { + if (value) + { + this.Flags &= ~(THBFLAGS.THBF_HIDDEN); + } + else + { + this.Flags |= THBFLAGS.THBF_HIDDEN; + } + _manager.RefreshThumbButtons(); + } + } + + /// + /// Gets or sets the thumbnail button's enabled state. + /// + public bool Enabled + { + get + { + return (this.Flags & THBFLAGS.THBF_DISABLED) == 0; + } + set + { + if (value) + { + this.Flags &= ~(THBFLAGS.THBF_DISABLED); + } + else + { + this.Flags |= THBFLAGS.THBF_DISABLED; + } + _manager.RefreshThumbButtons(); + } + } + + internal void FireClick() + { + if (Clicked != null) + Clicked(this, EventArgs.Empty); + } + } + +} \ No newline at end of file diff --git a/trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButtonManager.cs b/trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButtonManager.cs new file mode 100644 index 000000000..9a71231bc --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/ThumbnailButtons/ThumbButtonManager.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using TaskbarLib.Interop; +using System.Collections; + +namespace TaskbarLib.DesktopIntegration +{ + /// + /// Manages a set of taskbar thumbnail buttons in an application. + /// + public sealed class ThumbButtonManager + { + /// + /// Initializes a new manager with the specified window handle. + /// + /// The window handle. + public ThumbButtonManager(IntPtr hwnd) + { + _hwnd = hwnd; + } + + /// + /// Creates a new taskbar thumbnail button. + /// + /// The button's id. + /// The button's icon. + /// The button's tooltip. + /// An object of type + /// representing the newly created thumbnail button. + public ThumbButton CreateThumbButton(uint id, Icon icon, string tooltip) + { + return new ThumbButton(this, id, icon, tooltip); + } + + /// + /// Dispatches a Windows message to the thumbnail button event + /// handlers if appropriate. + /// + /// + /// This method is intended for infrastructure use only. + /// + /// The message. + public void DispatchMessage(ref Message message) + { + UInt64 wparam = (UInt64)message.WParam.ToInt64(); + UInt32 wparam32 = (UInt32) (wparam & 0xffffffff); //Clear top 32 bits + + if (message.Msg == SafeNativeMethods.WM_COMMAND && (wparam32 >> 16 == SafeNativeMethods.THBN_CLICKED)) + { + uint id = wparam32 & 0xffff; //Bottom 16 bits + _thumbButtons[id].FireClick(); + } + } + + /// + /// Adds the specified taskbar thumbnail buttons to the application's + /// thumbnail toolbar. + /// + /// + /// Thumbnail buttons can only be added once - after being added, + /// they cannot be removed or deleted. However, they can be shown, + /// hidden, enabled and disabled. + /// + /// The buttons to add. + public void AddThumbButtons(params ThumbButton[] buttons) + { + Array.ForEach(buttons, b => _thumbButtons.Add(b.Id, b)); + + RefreshThumbButtons(); + } + + /// + /// Gets a specific thumbnail button by its id. + /// + /// The thumbnail button's id. + /// An object of type + /// with the specified id. + public ThumbButton this[uint id] + { + get + { + return _thumbButtons[id]; + } + } + + #region Implementation + + private bool _buttonsLoaded; + internal void RefreshThumbButtons() + { + THUMBBUTTON[] win32Buttons = (from thumbButton in _thumbButtons.Values select thumbButton.Win32ThumbButton).ToArray(); + + if (_buttonsLoaded) + { + Windows7Taskbar.TaskbarList.ThumbBarUpdateButtons(_hwnd, (uint)win32Buttons.Length, win32Buttons); + } + else //First time + { + Windows7Taskbar.TaskbarList.ThumbBarAddButtons(_hwnd, (uint)win32Buttons.Length, win32Buttons); + _buttonsLoaded = true; + } + } + + private Dictionary _thumbButtons = new Dictionary(); + private IntPtr _hwnd; + + #endregion + } + +} \ No newline at end of file diff --git a/trunk/ProcessHacker/Components/TaskbarLib/Windows7Taskbar.cs b/trunk/ProcessHacker/Components/TaskbarLib/Windows7Taskbar.cs new file mode 100644 index 000000000..b9dd2ee4f --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/Windows7Taskbar.cs @@ -0,0 +1,316 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using TaskbarLib.Interop; + +namespace TaskbarLib.DesktopIntegration +{ + /// + /// The primary coordinator of the Windows 7 taskbar-related activities. + /// For additional functionality, see the JumpListManager + /// and ThumbButtonManager classes. + /// + public static class Windows7Taskbar + { + #region Infrastructure + + /// + /// The WM_TaskbarButtonCreated message number. + /// + public static uint TaskbarButtonCreatedMessage + { + get { return UnsafeNativeMethods.WM_TaskbarButtonCreated; } + } + + // Use thread static to work around COM threading issues. + [ThreadStatic] + private static ITaskbarList3 _taskbarList; + internal static ITaskbarList3 TaskbarList + { + get + { + if (_taskbarList == null) + { + _taskbarList = (ITaskbarList3)new CTaskbarList(); + _taskbarList.HrInit(); + } + return _taskbarList; + } + } + + private static IPropertyStore InternalGetWindowPropertyStore(IntPtr hwnd) + { + IPropertyStore propStore; + int rc = UnsafeNativeMethods.SHGetPropertyStoreForWindow(hwnd, ref SafeNativeMethods.IID_IPropertyStore, out propStore); + if (rc != 0) + throw Marshal.GetExceptionForHR(rc); + + return propStore; + } + + private static void InternalEnableCustomWindowPreview(IntPtr hwnd, bool enable) + { + int t = enable ? 1 : 0; + + int rc; + rc = UnsafeNativeMethods.DwmSetWindowAttribute(hwnd, SafeNativeMethods.DWMWA_HAS_ICONIC_BITMAP, ref t, 4); + if (rc != 0) + throw Marshal.GetExceptionForHR(rc); + + rc = UnsafeNativeMethods.DwmSetWindowAttribute(hwnd, SafeNativeMethods.DWMWA_FORCE_ICONIC_REPRESENTATION, ref t, 4); + if (rc != 0) + throw Marshal.GetExceptionForHR(rc); + } + + #endregion + + #region Application Id + + /// + /// Gets a window's application id by its window handle. + /// + /// The window handle. + /// The application id of that window. + public static string GetWindowAppId(IntPtr hwnd) + { + IPropertyStore propStore = InternalGetWindowPropertyStore(hwnd); + + PropVariant pv; + propStore.GetValue(ref PropertyKey.PKEY_AppUserModel_ID, out pv); + + Marshal.ReleaseComObject(propStore); + + return pv.GetValue(); + } + + /// + /// Sets the window's application id by its window handle. + /// + /// The window handle. + /// The application id. + public static void SetWindowAppId(IntPtr hwnd, string appId) + { + IPropertyStore propStore = InternalGetWindowPropertyStore(hwnd); + + PropVariant pv = new PropVariant(); + pv.SetValue(appId); + propStore.SetValue(ref PropertyKey.PKEY_AppUserModel_ID, ref pv); + + Marshal.ReleaseComObject(propStore); + } + + /// + /// Sets the current process' explicit application user model id. + /// + /// The application id. + public static void SetCurrentProcessAppId(string appId) + { + UnsafeNativeMethods.SetCurrentProcessExplicitAppUserModelID(appId); + } + + /// + /// Gets the current process' explicit application user model id. + /// + /// The application id. + public static string GetCurrentProcessAppId() + { + string appId; + UnsafeNativeMethods.GetCurrentProcessExplicitAppUserModelID(out appId); + return appId; + } + + #endregion + + #region DWM Iconic Thumbnail and Peek Bitmap + + /// + /// Indicates that the specified window requests the DWM + /// to demand live preview (thumbnail and peek) mode when necessary + /// instead of relying on default preview. + /// + /// The window handle. + public static void EnableCustomWindowPreview(IntPtr hwnd) + { + InternalEnableCustomWindowPreview(hwnd, true); + } + + /// + /// Indicates that the specified window does not require the DWM + /// to demand live preview (thumbnail and peek) mode when necessary, + /// i.e. this window relies on default preview. + /// + /// The window handle. + public static void DisableCustomWindowPreview(IntPtr hwnd) + { + InternalEnableCustomWindowPreview(hwnd, false); + } + + /// + /// Sets the specified iconic thumbnail for the specified window. + /// This is typically done in response to a DWM message. + /// + /// The window handle. + /// The thumbnail bitmap. + public static void SetIconicThumbnail(IntPtr hwnd, Bitmap bitmap) + { + int rc = UnsafeNativeMethods.DwmSetIconicThumbnail(hwnd, bitmap.GetHbitmap(), SafeNativeMethods.DWM_SIT_DISPLAYFRAME); + if (rc != 0) + throw Marshal.GetExceptionForHR(rc); + } + + /// + /// Sets the specified peek (live preview) bitmap for the specified + /// window. This is typically done in response to a DWM message. + /// + /// The window handle. + /// The thumbnail bitmap. + /// Whether to display a standard window + /// frame around the bitmap. + public static void SetPeekBitmap(IntPtr hwnd, Bitmap bitmap, bool displayFrame) + { + int rc = UnsafeNativeMethods.DwmSetIconicLivePreviewBitmap(hwnd, bitmap.GetHbitmap(), IntPtr.Zero, displayFrame ? SafeNativeMethods.DWM_SIT_DISPLAYFRAME : (uint)0); + + if (rc != 0) + throw Marshal.GetExceptionForHR(rc); + } + + /// + /// Sets the specified peek (live preview) bitmap for the specified + /// window. This is typically done in response to a DWM message. + /// + /// The window handle. + /// The thumbnail bitmap. + /// The client area offset at which to display + /// the specified bitmap. The rest of the parent window will be + /// displayed as "remembered" by the DWM. + /// Whether to display a standard window + /// frame around the bitmap. + public static void SetPeekBitmap(IntPtr hwnd, Bitmap bitmap, Point offset, bool displayFrame) + { + var nativePoint = new POINT(offset.X, offset.Y); + int rc = UnsafeNativeMethods.DwmSetIconicLivePreviewBitmap(hwnd, bitmap.GetHbitmap(), ref nativePoint, displayFrame ? SafeNativeMethods.DWM_SIT_DISPLAYFRAME : (uint)0); + if (rc != 0) + throw Marshal.GetExceptionForHR(rc); + } + + #endregion + + #region Taskbar Overlay Icon + + /// + /// Draws the specified overlay icon on the specified window's + /// taskbar button. + /// + /// The window handle. + /// The overlay icon. + /// The overlay icon description. + public static void SetTaskbarOverlayIcon(IntPtr hwnd, Icon icon, string description) + { + TaskbarList.SetOverlayIcon( + hwnd, + icon == null ? IntPtr.Zero : icon.Handle, + description); + } + + #endregion + + #region Taskbar Progress Bar + + /// + /// Represents the thumbnail progress bar state. + /// + public enum ThumbnailProgressState + { + /// + /// No progress is displayed. + /// + NoProgress = 0, + /// + /// The progress is indeterminate (marquee). + /// + Indeterminate = 0x1, + /// + /// Normal progress is displayed. + /// + Normal = 0x2, + /// + /// An error occurred (red). + /// + Error = 0x4, + /// + /// The operation is paused (yellow). + /// + Paused = 0x8 + } + + /// + /// Sets the progress state of the specified window's + /// taskbar button. + /// + /// The window handle. + /// The progress state. + public static void SetProgressState(IntPtr hwnd, ThumbnailProgressState state) + { + TaskbarList.SetProgressState(hwnd, (TBPFLAG)state); + } + /// + /// Sets the progress value of the specified window's + /// taskbar button. + /// + /// The window handle. + /// The current value. + /// The maximum value. + public static void SetProgressValue(IntPtr hwnd, ulong current, ulong maximum) + { + TaskbarList.SetProgressValue(hwnd, current, maximum); + } + + #endregion + + #region Taskbar Thumbnails + + /// + /// Specifies that only a portion of the window's client area + /// should be used in the window's thumbnail. + /// + /// The window. + /// The rectangle that specifies the clipped region. + public static void SetThumbnailClip(IntPtr hwnd, Rectangle clipRect) + { + RECT rect = new RECT(clipRect.Left, clipRect.Top, clipRect.Right, clipRect.Bottom); + TaskbarList.SetThumbnailClip(hwnd, ref rect); + } + + /// + /// Sets the specified window's thumbnail tooltip. + /// + /// The window. + /// The tooltip text. + public static void SetThumbnailTooltip(IntPtr hwnd, string tooltip) + { + TaskbarList.SetThumbnailTooltip(hwnd, tooltip); + } + + #endregion + + #region Miscellaneous + + /// + /// Specifies that the taskbar- and DWM-related windows messages should + /// pass through the Windows UIPI mechanism even if the process is + /// running elevated. Calling this method is not required unless the + /// process is running elevated. + /// + public static void AllowTaskbarWindowMessagesThroughUipi() + { + UnsafeNativeMethods.ChangeWindowMessageFilter(UnsafeNativeMethods.WM_TaskbarButtonCreated, SafeNativeMethods.MSGFLT_ADD); + UnsafeNativeMethods.ChangeWindowMessageFilter(SafeNativeMethods.WM_DWMSENDICONICTHUMBNAIL, SafeNativeMethods.MSGFLT_ADD); + UnsafeNativeMethods.ChangeWindowMessageFilter(SafeNativeMethods.WM_DWMSENDICONICLIVEPREVIEWBITMAP, SafeNativeMethods.MSGFLT_ADD); + UnsafeNativeMethods.ChangeWindowMessageFilter(SafeNativeMethods.WM_COMMAND, SafeNativeMethods.MSGFLT_ADD); + UnsafeNativeMethods.ChangeWindowMessageFilter(SafeNativeMethods.WM_SYSCOMMAND, SafeNativeMethods.MSGFLT_ADD); + UnsafeNativeMethods.ChangeWindowMessageFilter(SafeNativeMethods.WM_ACTIVATE, SafeNativeMethods.MSGFLT_ADD); + } + + #endregion + } +} \ No newline at end of file diff --git a/trunk/ProcessHacker/Components/TaskbarLib/WindowsFormsExtensions.cs b/trunk/ProcessHacker/Components/TaskbarLib/WindowsFormsExtensions.cs new file mode 100644 index 000000000..e53b30f90 --- /dev/null +++ b/trunk/ProcessHacker/Components/TaskbarLib/WindowsFormsExtensions.cs @@ -0,0 +1,189 @@ +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace TaskbarLib.DesktopIntegration.WindowsForms +{ + /// + /// Contains extension methods for easier Windows Forms interop. + /// + public static class WindowsFormsExtensions + { + /// + /// Sets this form's application id. + /// + /// The form. + /// The application id. + public static void SetAppId(this Form form, string appId) + { + Windows7Taskbar.SetWindowAppId(form.Handle, appId); + } + + /// + /// Gets this form's application id. + /// + /// The form. + /// The form's application id. + public static string GetAppId(this Form form) + { + return Windows7Taskbar.GetWindowAppId(form.Handle); + } + + /// + /// Enables custom window preview on this form, meaning that + /// the DWM will send messages when a live thumbnail preview + /// or a live peek will be necessary. + /// + /// The form. + public static void EnableCustomWindowPreview(this Form form) + { + Windows7Taskbar.EnableCustomWindowPreview(form.Handle); + } + + /// + /// Disables custom window preview on this form, meaning that + /// the DWM will not send messages when a live thumbnail preview + /// or a live peek will be necessary, but instead rely on its + /// default rendering. + /// + /// The form. + public static void DisableCustomWindowPreview(this Form form) + { + Windows7Taskbar.DisableCustomWindowPreview(form.Handle); + } + + /// + /// Sets this form's iconic thumbnail to the specified bitmap. + /// + /// The form. + /// The bitmap. + public static void SetIconicThumbnail(this Form form, Bitmap bitmap) + { + Windows7Taskbar.SetIconicThumbnail(form.Handle, bitmap); + } + + /// + /// Sets this form's peek (live preview) bitmap. + /// + /// The form. + /// The bitmap. + /// Whether to display a standard window + /// frame around the bitmap. + public static void SetPeekBitmap(this Form form, Bitmap bitmap, bool displayFrame) + { + Windows7Taskbar.SetPeekBitmap(form.Handle, bitmap, displayFrame); + } + + /// + /// Draws the specified overlay icon over this form's taskbar button. + /// + /// The form. + /// The overlay icon. + /// The overlay icon's description. + public static void SetTaskbarOverlayIcon(this Form form, Icon icon, string description) + { + Windows7Taskbar.SetTaskbarOverlayIcon(form.Handle, icon, description); + } + + /// + /// Sets the progress bar in the containing form's taskbar button + /// to this progress bar's progress. + /// + /// The progress bar. + public static void SetTaskbarProgress(ProgressBar progressBar) + { + Form form = (Form)progressBar.TopLevelControl; + + //Approximation: + ulong maximum = (ulong)(progressBar.Maximum - progressBar.Minimum); + ulong progress = (ulong)(progressBar.Value - progressBar.Minimum); + + Windows7Taskbar.SetProgressState(form.Handle, Windows7Taskbar.ThumbnailProgressState.Normal); + Windows7Taskbar.SetProgressValue(form.Handle, progress, maximum); + } + + /// + /// Sets the progress bar in the containing form's taskbar button + /// to this toolstrip progress bar's progress. + /// + /// The progress bar. + public static void SetTaskbarProgress(ToolStripProgressBar progressBar) + { + Form form = (Form)progressBar.Control.TopLevelControl; + + //Approximation: + ulong maximum = (ulong)(progressBar.Maximum - progressBar.Minimum); + ulong progress = (ulong)(progressBar.Value - progressBar.Minimum); + + Windows7Taskbar.SetProgressState(form.Handle, Windows7Taskbar.ThumbnailProgressState.Normal); + Windows7Taskbar.SetProgressValue(form.Handle, progress, maximum); + } + + /// + /// Sets the progress bar of this form's taskbar button to the + /// specified percentage. + /// + /// The form. + /// The progress percentage. + public static void SetTaskbarProgress(this Form form, float percent) + { + Windows7Taskbar.SetProgressState(form.Handle, Windows7Taskbar.ThumbnailProgressState.Normal); + Windows7Taskbar.SetProgressValue(form.Handle, (ulong)percent, 100); + } + + /// + /// Sets the progress bar of this form's taskbar button to the + /// specified state. + /// + /// The form. + /// The taskbar progress state. + public static void SetTaskbarProgressState(this Form form, Windows7Taskbar.ThumbnailProgressState state) + { + Windows7Taskbar.SetProgressState(form.Handle, state); + } + + /// + /// Creates a taskbar thumbnail button manager for this form. + /// + /// The form. + /// An object of type + /// that can be used to add and manage thumbnail toolbar buttons. + public static ThumbButtonManager CreateThumbButtonManager(this Form form) + { + return new ThumbButtonManager(form.Handle); + } + + /// + /// Creates a jump list manager for this form. + /// + /// The form. + /// An object of type + /// that can be used to manage the application's jump list. + public static JumpListManager CreateJumpListManager(this Form form) + { + return new JumpListManager(form.Handle); + } + + /// + /// Specifies that only a portion of the form's client area + /// should be used in the form's thumbnail. + /// + /// The form. + /// The rectangle that specifies the clipped region. + public static void SetThumbnailClip(this Form form, Rectangle clipRect) + { + Windows7Taskbar.SetThumbnailClip(form.Handle, clipRect); + } + + /// + /// Sets the specified form's thumbnail tooltip. + /// + /// The form. + /// The tooltip text. + public static void SetThumbnailTooltip(this Form form, string tooltip) + { + Windows7Taskbar.SetThumbnailTooltip(form.Handle, tooltip); + } + + } +} \ No newline at end of file diff --git a/trunk/ProcessHacker/ProcessHacker.csproj b/trunk/ProcessHacker/ProcessHacker.csproj index 3819feca8..e2b133ae1 100644 --- a/trunk/ProcessHacker/ProcessHacker.csproj +++ b/trunk/ProcessHacker/ProcessHacker.csproj @@ -416,6 +416,14 @@ Component + + + + + + + + UserControl