├── .gitignore ├── SolutionIcon.Tests.Unit ├── Helpers │ ├── AssertImages.cs │ └── TestPathResolver.cs ├── IconConverterTests.cs ├── IconGeneratorTests.cs ├── IconSelectorTests.cs ├── Properties │ └── AssemblyInfo.cs ├── SolutionIcon.Tests.Unit.csproj ├── TestFiles │ ├── IconConverter │ │ ├── _generated.expected.ico │ │ ├── hat.ico │ │ ├── hat.ico.expected.ico │ │ ├── jabbr-apple-touch-icon.png │ │ ├── jabbr-apple-touch-icon.png.expected.ico │ │ ├── wikimedia-logo.png │ │ └── wikimedia-logo.png.expected.ico │ └── IconGenerator │ │ ├── Magic.expected.png │ │ ├── SolutionIcon.expected.png │ │ ├── o_O.expected.png │ │ ├── ♪.expected.png │ │ └── 术.expected.png ├── TinyIdGeneratorTests.cs └── packages.config ├── SolutionIcon.sln ├── SolutionIcon.sln.DotSettings ├── SolutionIcon ├── Guids.cs ├── Icon.png ├── Implementation │ ├── ExtensionLogger.cs │ ├── IDiagnosticLogger.cs │ ├── IconConverter.cs │ ├── IconFinder.cs │ ├── IconGenerator.cs │ ├── SolutionExtensions.cs │ ├── SolutionIconManager.cs │ └── TinyIdGenerator.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Screenshot.png ├── SolutionIcon.csproj ├── SolutionIcon.vsct ├── SolutionIconPackage.cs ├── VSPackage.Designer.cs ├── VSPackage.resx ├── packages.config └── source.extension.vsixmanifest ├── nuget.config └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | *.snk 4 | bin/ 5 | obj/ 6 | \#packages/ -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/Helpers/AssertImages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Xunit.Sdk; 6 | 7 | namespace SolutionIcon.Tests.Unit.Helpers { 8 | public static class AssertImages { 9 | public static void Equal(string expectedPath, string actualPath) { 10 | var expectedBytes = File.ReadAllBytes(expectedPath); 11 | var actualBytes = File.ReadAllBytes(actualPath); 12 | if (!expectedBytes.SequenceEqual(actualBytes)) 13 | throw new AssertException("Image '" + expectedPath + "' was not equal to '" + actualPath + "'."); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/Helpers/TestPathResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using AshMind.Extensions; 7 | 8 | namespace SolutionIcon.Tests.Unit.Helpers { 9 | public static class TestPathResolver { 10 | private static string BasePath = Assembly.GetExecutingAssembly().GetAssemblyFileFromCodeBase().DirectoryName; 11 | 12 | public static string Resolve(string subPath) { 13 | return Path.Combine(BasePath, subPath); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/IconConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Linq; 5 | using SolutionIcon.Tests.Unit.Helpers; 6 | using Xunit; 7 | using Xunit.Extensions; 8 | using IconConverter = SolutionIcon.Implementation.IconConverter; 9 | 10 | namespace SolutionIcon.Tests.Unit { 11 | public class IconConverterTests { 12 | [Theory] 13 | [InlineData("wikimedia-logo.png")] 14 | [InlineData("jabbr-apple-touch-icon.png")] 15 | [InlineData("hat.ico")] 16 | public void ConvertIcon_ProducesExpectedImage_FromStaticFile(string inputFileName) { 17 | var actualFileName = inputFileName + ".actual.ico"; 18 | using (var image = (Bitmap) Image.FromFile(ResolveTestPath(inputFileName))) { 19 | ConvertToIconAndSave(image, actualFileName); 20 | } 21 | 22 | AssertImages.Equal( 23 | ResolveTestPath(inputFileName + ".expected.ico"), 24 | ResolveTestPath(actualFileName) 25 | ); 26 | } 27 | 28 | [Fact] 29 | public void ConvertIcon_ProducesExpectedImage_FromGeneratedIcon() { 30 | var actualFileName = "_generated.actual.ico"; 31 | using (var image = new Bitmap(32, 32)) { 32 | using (var graphics = Graphics.FromImage(image)) { 33 | graphics.FillRectangle(Brushes.Gold, 0, 0, 32, 32); 34 | } 35 | 36 | ConvertToIconAndSave(image, actualFileName); 37 | } 38 | 39 | AssertImages.Equal( 40 | ResolveTestPath("_generated.expected.ico"), 41 | ResolveTestPath(actualFileName) 42 | ); 43 | } 44 | 45 | private static void ConvertToIconAndSave(Bitmap image, string fileName) { 46 | using (var icon = new IconConverter().ConvertToIcon(image, new Size(32, 32))) 47 | using (var output = File.OpenWrite(ResolveTestPath(fileName))) { 48 | icon.Save(output); 49 | } 50 | } 51 | 52 | private static string ResolveTestPath(string fileName) { 53 | return TestPathResolver.Resolve(Path.Combine("TestFiles", "IconConverter", fileName)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/IconGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | using System.IO; 6 | using System.Linq; 7 | using SolutionIcon.Implementation; 8 | using SolutionIcon.Tests.Unit.Helpers; 9 | using Xunit; 10 | using Xunit.Extensions; 11 | 12 | namespace SolutionIcon.Tests.Unit { 13 | public class IconGeneratorTests { 14 | [Theory] 15 | [InlineData("SolutionIcon", "SolutionIconPath")] 16 | [InlineData("Magic", "MagicPath")] 17 | [InlineData("o_O", "o_O")] 18 | [InlineData("术", "术")] 19 | [InlineData("♪", "♪")] 20 | public void GenerateIcon_ReturnsExpectedImage(string solutionName, string solutionPath) { 21 | var actualPath = ResolveTestPath(solutionName + ".actual.png"); 22 | var generator = new IconGenerator(new TinyIdGenerator()); 23 | using (var image = generator.GenerateIcon(solutionName, solutionPath, new Size(32, 32))) { 24 | image.Save(actualPath, ImageFormat.Png); 25 | } 26 | 27 | AssertImages.Equal(ResolveTestPath(solutionName + ".expected.png"), actualPath); 28 | } 29 | 30 | private static string ResolveTestPath(string fileName) { 31 | return TestPathResolver.Resolve(Path.Combine("TestFiles", "IconGenerator", fileName)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/IconSelectorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using AshMind.IO.Abstractions.Mocks; 4 | using EnvDTE; 5 | using Moq; 6 | using SolutionIcon.Implementation; 7 | using Xunit; 8 | using Xunit.Extensions; 9 | 10 | namespace SolutionIcon.Tests.Unit { 11 | public class IconFinderTests { 12 | [Theory] 13 | [InlineData("Should not match unknown names.", new[] { "xsomething.png" }, null)] 14 | [InlineData("Should not match parts of names.", new[] { "_favicon_.png" }, null)] 15 | [InlineData("Should not match unknown extensions.", new[] { "favicon.meh" }, null)] 16 | [InlineData("Should prefer editoricon.", new[] { "favicon.ico", ".editoricon.png" }, ".editoricon.png")] 17 | [InlineData("Should match favicon.", new[] { "favicon.ico" }, "favicon.ico")] 18 | [InlineData("Should match logo.", new[] { "logo.png" }, "logo.png")] 19 | public void FindIcon_ReturnsExpectedIconBasedOnName(string description, string[] itemNames, string expected) { 20 | var solution = new Mock(); 21 | solution.SetupGet(x => x.FileName).Returns(Guid.NewGuid().ToString()); 22 | 23 | var solutionDirectory = new DirectoryMock("", itemNames.Select(name => new FileMock(name)).ToArray()); 24 | 25 | var fileSystem = new FileSystemMock(new FileMock(".sln") { 26 | FullName = solution.Object.FileName, 27 | Directory = solutionDirectory 28 | }); 29 | 30 | var best = new IconFinder(fileSystem).FindIcon(solution.Object); 31 | Assert.Equal(expected, best != null ? best.FullName : null); 32 | } 33 | 34 | //private static ProjectItem MockFileItem(string name) { 35 | // var mock = new Mock(); 36 | // mock.Setup(x => x.FileCount).Returns(1); 37 | // mock.Setup(x => x.FileNames[1]).Returns(name); 38 | // return mock.Object; 39 | //} 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SolutionIcon.Tests.Unit")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SolutionIcon.Tests.Unit")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5bd475dc-872a-427c-bd28-ab1be916c1e5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/SolutionIcon.Tests.Unit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {323015B4-F83C-4285-B311-5F8BD4DF98C3} 7 | Library 8 | Properties 9 | SolutionIcon.Tests.Unit 10 | SolutionIcon.Tests.Unit 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 52d34d2a 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\#packages\AshMind.Extensions.1.6.0\lib\net45\AshMind.Extensions.dll 41 | 42 | 43 | ..\#packages\AshMind.IO.Abstractions.Mocks.0.9.1-pre7\lib\net35\AshMind.IO.Abstractions.dll 44 | 45 | 46 | ..\#packages\AshMind.IO.Abstractions.Mocks.0.9.1-pre7\lib\net35\AshMind.IO.Abstractions.Mocks.dll 47 | 48 | 49 | False 50 | ..\#packages\VSSDK.DTE.7.0.4\lib\net20\envdte.dll 51 | True 52 | 53 | 54 | ..\#packages\Moq.4.2.1409.1722\lib\net40\Moq.dll 55 | 56 | 57 | False 58 | ..\#packages\VSSDK.DTE.7.0.4\lib\net20\stdole.dll 59 | True 60 | 61 | 62 | 63 | 64 | ..\#packages\xunit.1.9.2\lib\net20\xunit.dll 65 | 66 | 67 | ..\#packages\xunit.extensions.1.9.2\lib\net20\xunit.extensions.dll 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | PreserveNewest 82 | 83 | 84 | PreserveNewest 85 | 86 | 87 | PreserveNewest 88 | 89 | 90 | 91 | PreserveNewest 92 | 93 | 94 | PreserveNewest 95 | 96 | 97 | PreserveNewest 98 | 99 | 100 | PreserveNewest 101 | 102 | 103 | PreserveNewest 104 | 105 | 106 | PreserveNewest 107 | 108 | 109 | PreserveNewest 110 | 111 | 112 | PreserveNewest 113 | 114 | 115 | PreserveNewest 116 | 117 | 118 | 119 | 120 | {7814387b-57f4-4723-9162-011bc484229e} 121 | SolutionIcon 122 | 123 | 124 | 125 | 126 | 133 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconConverter/_generated.expected.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconConverter/_generated.expected.ico -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconConverter/hat.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconConverter/hat.ico -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconConverter/hat.ico.expected.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconConverter/hat.ico.expected.ico -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconConverter/jabbr-apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconConverter/jabbr-apple-touch-icon.png -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconConverter/jabbr-apple-touch-icon.png.expected.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconConverter/jabbr-apple-touch-icon.png.expected.ico -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconConverter/wikimedia-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconConverter/wikimedia-logo.png -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconConverter/wikimedia-logo.png.expected.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconConverter/wikimedia-logo.png.expected.ico -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconGenerator/Magic.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconGenerator/Magic.expected.png -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconGenerator/SolutionIcon.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconGenerator/SolutionIcon.expected.png -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconGenerator/o_O.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconGenerator/o_O.expected.png -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconGenerator/♪.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconGenerator/♪.expected.png -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TestFiles/IconGenerator/术.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon.Tests.Unit/TestFiles/IconGenerator/术.expected.png -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/TinyIdGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using SolutionIcon.Implementation; 5 | using Xunit; 6 | using Xunit.Extensions; 7 | 8 | namespace SolutionIcon.Tests.Unit { 9 | public class TinyIdGeneratorTests { 10 | [Theory] 11 | [InlineData("SolutionIcon", "SI")] 12 | [InlineData("Company.Product.MyLibrary", "ML")] 13 | [InlineData("Something", "S")] 14 | [InlineData("o", "o")] 15 | [InlineData("oO", "oO")] 16 | [InlineData("o_O", "oO")] 17 | [InlineData("lowercase", "lo")] 18 | public void GetTinyId_ReturnsExpectedId(string name, string expectedId) { 19 | Assert.Equal(expectedId, new TinyIdGenerator().GetTinyId(name)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SolutionIcon.Tests.Unit/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SolutionIcon.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionIcon", "SolutionIcon\SolutionIcon.csproj", "{7814387B-57F4-4723-9162-011BC484229E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolutionIcon.Tests.Unit", "SolutionIcon.Tests.Unit\SolutionIcon.Tests.Unit.csproj", "{323015B4-F83C-4285-B311-5F8BD4DF98C3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x86 = Debug|x86 14 | Release|Any CPU = Release|Any CPU 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {7814387B-57F4-4723-9162-011BC484229E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7814387B-57F4-4723-9162-011BC484229E}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7814387B-57F4-4723-9162-011BC484229E}.Debug|x86.ActiveCfg = Debug|Any CPU 21 | {7814387B-57F4-4723-9162-011BC484229E}.Debug|x86.Build.0 = Debug|Any CPU 22 | {7814387B-57F4-4723-9162-011BC484229E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {7814387B-57F4-4723-9162-011BC484229E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {7814387B-57F4-4723-9162-011BC484229E}.Release|x86.ActiveCfg = Release|Any CPU 25 | {7814387B-57F4-4723-9162-011BC484229E}.Release|x86.Build.0 = Release|Any CPU 26 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Debug|x86.Build.0 = Debug|Any CPU 30 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Release|x86.ActiveCfg = Release|Any CPU 33 | {323015B4-F83C-4285-B311-5F8BD4DF98C3}.Release|x86.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /SolutionIcon.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW 3 | END_OF_LINE 4 | END_OF_LINE 5 | END_OF_LINE 6 | END_OF_LINE 7 | END_OF_LINE 8 | END_OF_LINE 9 | System 10 | System.Collections.Generic 11 | System.Linq 12 | ID 13 | True 14 | True 15 | True -------------------------------------------------------------------------------- /SolutionIcon/Guids.cs: -------------------------------------------------------------------------------- 1 | // Guids.cs 2 | // MUST match guids.h 3 | 4 | using System; 5 | 6 | namespace SolutionIcon { 7 | public static class Guids { 8 | public const string PackageString = "7696a097-76fc-4940-87d8-97d5304a5a9b"; 9 | public static readonly Guid OutputPane = new Guid("be8c36c1-44cb-4b61-81ca-25b432fd4362"); 10 | }; 11 | } -------------------------------------------------------------------------------- /SolutionIcon/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon/Icon.png -------------------------------------------------------------------------------- /SolutionIcon/Implementation/ExtensionLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.Shell.Interop; 6 | 7 | namespace SolutionIcon.Implementation { 8 | public class ExtensionLogger : IDiagnosticLogger { 9 | private readonly IVsOutputWindowPane _outputPane; 10 | private readonly string _traceCategory; 11 | 12 | public ExtensionLogger(string name, Func getOutputPane) { 13 | _outputPane = getOutputPane("Ext: " + name + " (Diagnostic)"); 14 | _traceCategory = name; 15 | } 16 | 17 | public void WriteLine(string message) { 18 | _outputPane.OutputString(message + Environment.NewLine); 19 | Trace.WriteLine(message, _traceCategory); 20 | } 21 | 22 | public void WriteLine(string format, params object[] args) { 23 | WriteLine(string.Format(format, args)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SolutionIcon/Implementation/IDiagnosticLogger.cs: -------------------------------------------------------------------------------- 1 | namespace SolutionIcon.Implementation { 2 | public interface IDiagnosticLogger { 3 | void WriteLine(string message); 4 | void WriteLine(string format, params object[] args); 5 | } 6 | } -------------------------------------------------------------------------------- /SolutionIcon/Implementation/IconConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Drawing2D; 5 | using System.Drawing.IconLib; 6 | using System.Drawing.Imaging; 7 | using System.IO; 8 | using System.Linq; 9 | using JetBrains.Annotations; 10 | 11 | namespace SolutionIcon.Implementation { 12 | public class IconConverter { 13 | [NotNull] 14 | public Icon ConvertToIcon([NotNull] Bitmap image, Size iconSize) { 15 | if (image.Width == iconSize.Width && image.Height == iconSize.Height) 16 | return ConvertToIcon(image); 17 | 18 | using (var resized = new Bitmap(iconSize.Width, iconSize.Height, PixelFormat.Format32bppArgb)) { 19 | using (var g = Graphics.FromImage(resized)) { 20 | g.InterpolationMode = InterpolationMode.HighQualityBicubic; 21 | g.DrawImage(image, 0, 0, iconSize.Width, iconSize.Height); 22 | } 23 | return ConvertToIcon(resized); 24 | } 25 | } 26 | 27 | private Icon ConvertToIcon(Bitmap image) { 28 | var converted = new MemoryStream(); 29 | var multi = new MultiIcon(); 30 | var icon = multi.Add("Main"); 31 | icon.CreateFrom(image, IconOutputFormat.Vista); 32 | multi.SelectedIndex = 0; 33 | multi.Save(converted, MultiIconFormat.ICO); 34 | 35 | converted.Seek(0, SeekOrigin.Begin); 36 | return new Icon(converted); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SolutionIcon/Implementation/IconFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using AshMind.IO.Abstractions; 7 | using EnvDTE; 8 | using JetBrains.Annotations; 9 | 10 | namespace SolutionIcon.Implementation { 11 | public class IconFinder { 12 | private readonly IFileSystem _fileSystem; 13 | 14 | private static readonly HashSet ImageFileExtensions = new HashSet(StringComparer.InvariantCultureIgnoreCase) { 15 | ".gif", 16 | ".jpg", 17 | ".jpeg", 18 | ".png", 19 | ".ico" 20 | }; 21 | 22 | private static readonly Regex NameRegex = new Regex(@"^(favicon|logo|icon)$|^apple-touch-icon", RegexOptions.IgnoreCase); 23 | 24 | public IconFinder(IFileSystem fileSystem) { 25 | _fileSystem = fileSystem; 26 | } 27 | 28 | [CanBeNull] 29 | public IFile FindIcon([NotNull] Solution solution) { 30 | // ReSharper disable once PossibleNullReferenceException 31 | var solutionDirectory = _fileSystem.GetFile(solution.FileName).Directory; 32 | 33 | // .editoricon, not part of any standard. But it should be! 34 | // ReSharper disable once PossibleNullReferenceException 35 | var editorIcon = solutionDirectory.EnumerateFiles(".editoricon.*").FirstOrDefault(); 36 | if (editorIcon != null && ImageFileExtensions.Contains(editorIcon.Extension)) 37 | return editorIcon; 38 | 39 | // I started using EnvDTE for this, but there wasn't much benefit. 40 | // This approach does not cover Links and other virtual project items, 41 | // but I don't see those as essential for solution image. 42 | var topLevelDirectories = solutionDirectory.EnumerateDirectories(); 43 | return new[] { solutionDirectory } 44 | .Concat(topLevelDirectories) 45 | .SelectMany(d => d.EnumerateFiles()) 46 | .FirstOrDefault(IsAcceptableAsIcon); 47 | } 48 | 49 | private static bool IsAcceptableAsIcon(IFile file) { 50 | return ImageFileExtensions.Contains(file.Extension) 51 | && NameRegex.IsMatch(Path.GetFileNameWithoutExtension(file.Name)); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /SolutionIcon/Implementation/IconGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Linq; 4 | using System.Text; 5 | using JetBrains.Annotations; 6 | 7 | namespace SolutionIcon.Implementation { 8 | public class IconGenerator { 9 | private static readonly Color[] Colors = (new[] { 10 | // http://flatuicolors.com/ 11 | "#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", 12 | "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50", 13 | "#f1c40f", "#e67e22", "#95a5a6", 14 | "#f39c12", "#7f8c8d" 15 | }).Select(ColorTranslator.FromHtml).ToArray(); 16 | 17 | private readonly TinyIdGenerator _idGenerator; 18 | 19 | public IconGenerator(TinyIdGenerator idGenerator) { 20 | _idGenerator = idGenerator; 21 | } 22 | 23 | [NotNull] 24 | public Bitmap GenerateIcon([NotNull] string name, [NotNull] string path, Size size) { 25 | var id = _idGenerator.GetTinyId(name); 26 | 27 | Bitmap image = null; 28 | try { 29 | image = new Bitmap(size.Width, size.Height); 30 | 31 | var color = Colors[GetStableHash(path)%Colors.Length]; 32 | var bounds = new RectangleF(0, 0, size.Width, size.Height); 33 | 34 | using (var graphics = Graphics.FromImage(image)) 35 | using (var brush = new SolidBrush(color)) 36 | using (var font = GetIconFont(graphics, id, bounds.Size)) { 37 | graphics.FillRectangle(brush, bounds); 38 | graphics.DrawString(id, font, Brushes.White, bounds, new StringFormat { 39 | Alignment = StringAlignment.Center, 40 | LineAlignment = StringAlignment.Center 41 | }); 42 | return image; 43 | } 44 | } 45 | catch (Exception) { 46 | if (image != null) 47 | image.Dispose(); 48 | 49 | throw; 50 | } 51 | } 52 | 53 | private Font GetIconFont(Graphics graphics, string text, SizeF bounds) { 54 | using (var initial = new Font("Segoe UI", 10)) { 55 | var size = CalculateMaxFitFontSize(graphics, text, initial, bounds); 56 | return new Font(initial.FontFamily, size); 57 | } 58 | } 59 | 60 | // based on 61 | // http://stackoverflow.com/questions/19674743/dynamically-resizing-font-to-fit-space-while-using-graphics-drawstring/#19674954 62 | private float CalculateMaxFitFontSize(Graphics graphics, string text, Font font, SizeF bounds) { 63 | var textSize = graphics.MeasureString(text, font); 64 | var scaleHeight = bounds.Height / textSize.Height; 65 | var scaleWidth = bounds.Width / textSize.Width; 66 | return font.Size * Math.Min(scaleHeight, scaleWidth); 67 | } 68 | 69 | // Jenkins' one-at-a-time 70 | // http://stackoverflow.com/questions/548158/fixed-length-numeric-hash-code-from-variable-length-string-in-c-sharp/#549352 71 | private uint GetStableHash(string value) { 72 | uint hash = 0; 73 | foreach (var @byte in Encoding.UTF8.GetBytes(value)) { 74 | hash += @byte; 75 | hash += (hash << 10); 76 | hash ^= (hash >> 6); 77 | } 78 | // final avalanche 79 | hash += (hash << 3); 80 | hash ^= (hash >> 11); 81 | hash += (hash << 15); 82 | 83 | return hash; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /SolutionIcon/Implementation/SolutionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using EnvDTE; 6 | 7 | namespace SolutionIcon.Implementation { 8 | public static class DteExtensions { 9 | public static string GetName(this Solution solution) { 10 | return Path.GetFileNameWithoutExtension(solution.FullName); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SolutionIcon/Implementation/SolutionIconManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using EnvDTE; 6 | using JetBrains.Annotations; 7 | using Microsoft.WindowsAPICodePack.Taskbar; 8 | 9 | namespace SolutionIcon.Implementation { 10 | public class SolutionIconManager { 11 | private static readonly Size IconSize = new Size(32, 32); 12 | 13 | private readonly DTE _dte; 14 | private readonly IconFinder _iconFinder; 15 | private readonly IconConverter _iconConverter; 16 | private readonly IconGenerator _iconGenerator; 17 | private readonly IDiagnosticLogger _logger; 18 | 19 | // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (GC fix) 20 | private readonly SolutionEvents _solutionEvents; 21 | 22 | public SolutionIconManager(DTE dte, IconFinder iconFinder, IconConverter iconConverter, IconGenerator iconGenerator, IDiagnosticLogger logger) { 23 | _dte = dte; 24 | _iconFinder = iconFinder; 25 | _iconConverter = iconConverter; 26 | _iconGenerator = iconGenerator; 27 | _logger = logger; 28 | 29 | if (!TaskbarManager.IsPlatformSupported) { 30 | _logger.WriteLine("Overlay icons are not supported on this platform, exiting."); 31 | return; 32 | } 33 | 34 | _solutionEvents = _dte.Events.SolutionEvents; 35 | _solutionEvents.Opened += SolutionEvents_Opened; 36 | _solutionEvents.AfterClosing += SolutionEvents_AfterClosing; 37 | } 38 | 39 | private void SolutionEvents_Opened() { 40 | var solution = _dte.Solution; 41 | var solutionName = solution.GetName(); 42 | _logger.WriteLine("Solution '{0}' opened.", solutionName); 43 | if (string.IsNullOrEmpty(solutionName)) { 44 | // https://github.com/ashmind/SolutionIcon/issues/4 45 | // I can't reproduce this for now, so jsut skipping. 46 | // However in the future I should improve it to work 47 | // with new "Open Folder". 48 | _logger.WriteLine("Solution name is empty, ignoring."); 49 | return; 50 | } 51 | 52 | try { 53 | using (var icon = GetIcon(solution)) { 54 | TaskbarManager.Instance.SetOverlayIcon(icon, ""); 55 | } 56 | } 57 | catch (Exception ex) { 58 | _logger.WriteLine(ex.ToString()); 59 | } 60 | } 61 | 62 | private void SolutionEvents_AfterClosing() { 63 | _logger.WriteLine("Solution closed."); 64 | TaskbarManager.Instance.SetOverlayIcon(null, ""); 65 | } 66 | 67 | [CanBeNull] 68 | private Icon GetIcon([NotNull] Solution solution) { 69 | var solutionName = solution.GetName(); 70 | var existing = FindAndConvertExistingIcon(solution); 71 | if (existing != null) 72 | return existing; 73 | 74 | _logger.WriteLine("Solution '{0}': icon not available, generating.", solutionName); 75 | using (var image = _iconGenerator.GenerateIcon(solutionName, solution.FileName, IconSize)) { 76 | return ConvertToIconFailSafe(image, "generated image"); 77 | } 78 | } 79 | 80 | [CanBeNull] 81 | private Icon FindAndConvertExistingIcon([NotNull] Solution solution) { 82 | var iconFile = _iconFinder.FindIcon(solution); 83 | if (iconFile == null) 84 | return null; 85 | 86 | _logger.WriteLine("Solution '{0}': found icon at '{1}'.", solution.GetName(), iconFile.FullName); 87 | using (var stream = iconFile.OpenRead()) 88 | using (var image = (Bitmap) Image.FromStream(stream)) { 89 | return ConvertToIconFailSafe(image, iconFile.FullName); 90 | } 91 | } 92 | 93 | [CanBeNull] 94 | private Icon ConvertToIconFailSafe([NotNull] Bitmap image, [NotNull] string imageDescriptionForLog) { 95 | try { 96 | return _iconConverter.ConvertToIcon(image, IconSize); 97 | } 98 | catch (Exception ex) { 99 | _logger.WriteLine("Failed to convert {0} to icon: {1}", imageDescriptionForLog, ex); 100 | return null; 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SolutionIcon/Implementation/TinyIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using AshMind.Extensions; 5 | 6 | namespace SolutionIcon.Implementation { 7 | public class TinyIdGenerator { 8 | public string GetTinyId(string name) { 9 | Argument.NotNullOrEmpty("name", name); 10 | 11 | // this can all be done with one regexp, but I find this easier to read 12 | var lastPart = name.SubstringAfterLast("."); // e.g. in Company.Whatever.MyThing -- MyThing is the most important 13 | var idChar1 = lastPart[0]; // MyThing => M, myThing => m 14 | var idChar2 = lastPart.Skip(1).Where(Char.IsUpper).Cast().FirstOrDefault(); // MyThing => T, myThing => T 15 | if (idChar2 == null && !idChar1.IsUpper() && lastPart.Length > 1) 16 | idChar2 = lastPart[1]; // mything => y 17 | 18 | if (idChar2 == null) 19 | return new string(idChar1, 1); 20 | 21 | return new string(new[] { idChar1, idChar2.Value }); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SolutionIcon/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Resources; 4 | using System.Runtime.InteropServices; 5 | using SolutionIcon.Properties; 6 | 7 | [assembly: AssemblyTitle("SolutionIcon")] 8 | [assembly: AssemblyCompany("Andrey Shchekin")] 9 | [assembly: ComVisible(false)] 10 | [assembly: CLSCompliant(false)] 11 | [assembly: NeutralResourcesLanguage("en-US")] 12 | 13 | [assembly: AssemblyVersion(AssemblyInfo.VersionString)] 14 | [assembly: AssemblyFileVersion(AssemblyInfo.VersionString)] 15 | [assembly: AssemblyInformationalVersion(AssemblyInfo.VersionString)] 16 | 17 | namespace SolutionIcon.Properties { 18 | internal static class AssemblyInfo { 19 | // Please keep in sync with vsixmanifest 20 | public const string VersionString = "1.1.1"; 21 | } 22 | } -------------------------------------------------------------------------------- /SolutionIcon/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SolutionIcon { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SolutionIcon.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SolutionIcon/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 11 | 12 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | text/microsoft-resx 119 | 120 | 121 | 2.0 122 | 123 | 124 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 125 | 126 | 127 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 128 | 129 | -------------------------------------------------------------------------------- /SolutionIcon/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/SolutionIcon/Screenshot.png -------------------------------------------------------------------------------- /SolutionIcon/SolutionIcon.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 15.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | true 8 | 9 | 10 | 11 | Debug 12 | AnyCPU 13 | 2.0 14 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | {7814387B-57F4-4723-9162-011BC484229E} 16 | Library 17 | Properties 18 | SolutionIcon 19 | SolutionIcon 20 | v4.5 21 | true 22 | true 23 | false 24 | false 25 | true 26 | true 27 | 28 | 29 | true 30 | full 31 | false 32 | bin\Debug\ 33 | DEBUG;TRACE 34 | prompt 35 | 4 36 | 37 | 38 | pdbonly 39 | true 40 | bin\Release\ 41 | TRACE 42 | prompt 43 | 4 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Designer 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | True 66 | True 67 | Resources.resx 68 | 69 | 70 | 71 | 72 | 73 | ResXFileCodeGenerator 74 | Resources.Designer.cs 75 | Designer 76 | 77 | 78 | true 79 | VSPackage 80 | Designer 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Menus.ctmenu 89 | Designer 90 | 91 | 92 | 93 | 94 | Always 95 | true 96 | 97 | 98 | Always 99 | true 100 | 101 | 102 | 103 | 104 | ..\#packages\Argument.1.0.3\lib\net45\Argument.dll 105 | 106 | 107 | ..\#packages\AshMind.Extensions.1.6.0\lib\net45\AshMind.Extensions.dll 108 | 109 | 110 | ..\#packages\AshMind.IO.Abstractions.0.9.1-pre\lib\net40\AshMind.IO.Abstractions.dll 111 | 112 | 113 | False 114 | ..\#packages\VSSDK.DTE.7.0.4\lib\net20\envdte.dll 115 | True 116 | 117 | 118 | ..\#packages\IconLib.Unofficial.0.73.0\lib\net20\IconLib.dll 119 | 120 | 121 | ..\#packages\JetBrains.Annotations.8.0.4.0\lib\net20\JetBrains.Annotations.dll 122 | 123 | 124 | ..\#packages\VSSDK.GraphModel.11.0.4\lib\net45\Microsoft.VisualStudio.GraphModel.dll 125 | False 126 | 127 | 128 | ..\#packages\VSSDK.OLE.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.OLE.Interop.dll 129 | True 130 | False 131 | 132 | 133 | ..\#packages\VSSDK.Shell.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.11.0.dll 134 | False 135 | 136 | 137 | ..\#packages\VSSDK.Shell.Immutable.10.10.0.4\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll 138 | True 139 | False 140 | 141 | 142 | ..\#packages\VSSDK.Shell.Immutable.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll 143 | True 144 | False 145 | 146 | 147 | ..\#packages\VSSDK.Shell.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.dll 148 | True 149 | False 150 | 151 | 152 | False 153 | ..\#packages\VSSDK.Shell.Interop.10.10.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.10.0.dll 154 | True 155 | 156 | 157 | False 158 | ..\#packages\VSSDK.Shell.Interop.11.11.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.11.0.dll 159 | True 160 | 161 | 162 | ..\#packages\VSSDK.Shell.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.8.0.dll 163 | True 164 | False 165 | 166 | 167 | ..\#packages\VSSDK.Shell.Interop.9.9.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.9.0.dll 168 | True 169 | False 170 | 171 | 172 | ..\#packages\VSSDK.TextManager.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.dll 173 | True 174 | False 175 | 176 | 177 | ..\#packages\VSSDK.TextManager.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.8.0.dll 178 | True 179 | False 180 | 181 | 182 | ..\#packages\WindowsAPICodePack-Core.1.1.1\lib\Microsoft.WindowsAPICodePack.dll 183 | 184 | 185 | ..\#packages\WindowsAPICodePack-Shell.1.1.1\lib\Microsoft.WindowsAPICodePack.Shell.dll 186 | 187 | 188 | 189 | 190 | False 191 | ..\#packages\VSSDK.DTE.7.0.4\lib\net20\stdole.dll 192 | True 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 213 | -------------------------------------------------------------------------------- /SolutionIcon/SolutionIcon.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SolutionIcon/SolutionIconPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using AshMind.IO.Abstractions.Adapters; 7 | using EnvDTE; 8 | using SolutionIcon.Implementation; 9 | using Microsoft.VisualStudio.Shell; 10 | using Microsoft.VisualStudio.Shell.Interop; 11 | 12 | namespace SolutionIcon { 13 | [PackageRegistration(UseManagedResourcesOnly = true)] 14 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] 15 | [ProvideAutoLoad(UIContextGuids.SolutionExists)] 16 | [Guid(Guids.PackageString)] 17 | public sealed class SolutionIconPackage : Package { 18 | private SolutionIconManager _manager; 19 | 20 | /// 21 | /// Default constructor of the package. 22 | /// Inside this method you can place any initialization code that does not require 23 | /// any Visual Studio service because at this point the package object is created but 24 | /// not sited yet inside Visual Studio environment. The place to do all the other 25 | /// initialization is the Initialize method. 26 | /// 27 | public SolutionIconPackage() { 28 | Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this)); 29 | } 30 | 31 | /// 32 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 33 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 34 | /// 35 | protected override void Initialize() { 36 | Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this)); 37 | base.Initialize(); 38 | 39 | var logger = new ExtensionLogger("SolutionIcon", title => GetOutputPane(Guids.OutputPane, title)); 40 | _manager = new SolutionIconManager( 41 | (DTE)GetService(typeof(DTE)), 42 | new IconFinder(new FileSystem()), 43 | new IconConverter(), 44 | new IconGenerator(new TinyIdGenerator()), 45 | logger 46 | ); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /SolutionIcon/VSPackage.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.17929 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace AttachToAnything { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class VSPackage { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal VSPackage() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AttachToAnything.VSPackage", typeof(VSPackage).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to AttachToAnything. 65 | /// 66 | internal static string _110 { 67 | get { 68 | return ResourceManager.GetString("110", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Provides a drop-down box to attach to a running process.. 74 | /// 75 | internal static string _112 { 76 | get { 77 | return ResourceManager.GetString("112", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 83 | /// 84 | internal static System.Drawing.Icon _400 { 85 | get { 86 | object obj = ResourceManager.GetObject("400", resourceCulture); 87 | return ((System.Drawing.Icon)(obj)); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /SolutionIcon/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | SolutionIcon 122 | 123 | 124 | Provides a drop-down box to attach to a running process. 125 | 126 | 127 | Solution Icon 128 | 129 | -------------------------------------------------------------------------------- /SolutionIcon/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SolutionIcon/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Solution Icon 6 | Shows an additional solution-related icon over the Visual Studio taskbar button. 7 | https://github.com/ashmind/SolutionIcon 8 | Icon.png 9 | Screenshot.png 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmind/SolutionIcon/889b13b03359271144b714d2d85b4b2bb45b74e9/readme.md --------------------------------------------------------------------------------