/* * Process Hacker - * ProcessHacker Taskbar Extensions * * Copyright (C) 2009 dmex * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . * */ using System; using System.Collections.Generic; using System.Linq; using TaskbarLib.Interop; using System.Runtime.InteropServices; using ProcessHacker.Native.Api; namespace TaskbarLib { /// /// 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) { HResult addObjectResult = categoryContents.AddObject(destination.GetShellRepresentation()); addObjectResult.ThrowIf(); } HResult appendCategoryResult = destinationList.AppendCategory( destinations.First().Category, (IObjectArray)categoryContents); appendCategoryResult.ThrowIf(); } } 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) { HResult addObjectResult = taskCollection.AddObject(task.GetShellRepresentation()); addObjectResult.ThrowIf(); } HResult addUserTasksResult = destinationList.AddUserTasks((IObjectArray)taskCollection); addUserTasksResult.ThrowIf(); } 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); HResult setValueResult = propertyStore.SetValue(ref PropertyKey.PKEY_AppUserModel_IsDestListSeparator, ref propVariant); setValueResult.ThrowIf(); propVariant.Clear(); } else { HResult setPathResult = shellLink.SetPath(Path); setPathResult.ThrowIf(); if (!String.IsNullOrEmpty(IconLocation)) { HResult setIconLocationResult = shellLink.SetIconLocation(IconLocation, IconIndex); setIconLocationResult.ThrowIf(); } if (!String.IsNullOrEmpty(Arguments)) { HResult setArgumentsResult = shellLink.SetArguments(Arguments); setArgumentsResult.ThrowIf(); } if (!String.IsNullOrEmpty(WorkingDirectory)) { HResult setWorkingDirectoryResult = shellLink.SetWorkingDirectory(WorkingDirectory); setWorkingDirectoryResult.ThrowIf(); } HResult setShowCmdResult = shellLink.SetShowCmd((uint)ShowCommand); setShowCmdResult.ThrowIf(); propVariant.SetValue(Title); HResult setValueResult = propertyStore.SetValue(ref PropertyKey.PKEY_Title, ref propVariant); setValueResult.ThrowIf(); propVariant.Clear(); } HResult commitResult = propertyStore.Commit(); commitResult.ThrowIf(); 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 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("43826D1E-E718-42EE-BC55-A1E261C37BFE"); uint result = UnsafeNativeMethods.SHCreateItemFromParsingName( path, IntPtr.Zero, ref shellItemGuid, out resultItem); //// Throw if an error occurred. Marshal.ThrowExceptionForHR((int)result); return resultItem; } } }