├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ ├── FEATURE-REQUEST.yml │ ├── BUG-REPORT.yml │ └── BACKGROUND-REQUEST.yml ├── MonoMod └── MonoMod.csproj ├── .gitmodules ├── MicaVisualStudio ├── Resources │ ├── logo.png │ ├── license.txt │ └── third-party-notices.txt ├── Options │ ├── Controls │ │ ├── ToolPage.xaml.cs │ │ ├── DialogPage.xaml.cs │ │ ├── GeneralPage.xaml.cs │ │ ├── ToolPage.xaml │ │ ├── DialogPage.xaml │ │ ├── GeneralPage.xaml │ │ ├── WindowOptions.xaml │ │ └── WindowOptions.xaml.cs │ ├── Theme.cs │ ├── General.cs │ └── BackdropCommands.cs ├── VisualStudio │ ├── EnumFramesExtensions.cs │ ├── WindowObserver.cs │ ├── VsColorManager.cs │ └── VsWindowStyler.cs ├── GlobalUsings.cs ├── Properties │ └── AssemblyInfo.cs ├── source.extension.vsixmanifest ├── Extensions │ ├── TreeExtensions.cs │ ├── ExpressionExtensions.cs │ └── WeakEventExtensions.cs ├── Interop │ ├── ThemeHelper.cs │ ├── WinEventHook.cs │ └── WindowHelper.cs ├── MicaVisualStudioPackage.vsct ├── MicaVisualStudio.csproj └── MicaVisualStudioPackage.cs ├── LICENSE ├── README.md ├── THIRD-PARTY-NOTICES ├── .gitattributes ├── MicaVisualStudio.sln └── .gitignore /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /MonoMod/MonoMod.csproj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tech5G5G/Mica-Visual-Studio/HEAD/MonoMod/MonoMod.csproj -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MonoMod/MonoMod"] 2 | path = MonoMod/MonoMod 3 | url = https://github.com/Tech5G5G/MonoMod.git 4 | -------------------------------------------------------------------------------- /MicaVisualStudio/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tech5G5G/Mica-Visual-Studio/HEAD/MicaVisualStudio/Resources/logo.png -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/ToolPage.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.Options.Controls 2 | { 3 | /// 4 | /// Interaction logic for ToolPage.xaml 5 | /// 6 | public partial class ToolPage : UserControl 7 | { 8 | public ToolPage() 9 | { 10 | InitializeComponent(); 11 | windowOptions.SetOptionModel(General.Instance); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/DialogPage.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.Options.Controls 2 | { 3 | /// 4 | /// Interaction logic for DialogPage.xaml 5 | /// 6 | public partial class DialogPage : UserControl 7 | { 8 | public DialogPage() 9 | { 10 | InitializeComponent(); 11 | windowOptions.SetOptionModel(General.Instance); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Theme.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.Options; 2 | 3 | /// 4 | /// Specifies the theme to use. 5 | /// 6 | public enum Theme 7 | { 8 | /// 9 | /// Specifies to use the same theme as Visual Studio. 10 | /// 11 | VisualStudio, 12 | 13 | /// 14 | /// Specifies to use the same theme as the system. 15 | /// 16 | System, 17 | 18 | /// 19 | /// Specifies to use a light theme. 20 | /// 21 | Light, 22 | 23 | /// 24 | /// Specifies to use a dark theme. 25 | /// 26 | Dark 27 | } 28 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/GeneralPage.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.Options.Controls 2 | { 3 | /// 4 | /// Interaction logic for GeneralPage.xaml 5 | /// 6 | public partial class GeneralPage : UserControl 7 | { 8 | public GeneralPage() 9 | { 10 | InitializeComponent(); 11 | 12 | General general = General.Instance; 13 | windowOptions.SetOptionModel(general); 14 | 15 | WindowOptions.InitializeComboBox(theme, nameof(General.AppTheme), general); 16 | WindowOptions.InitializeCheckBox(force, nameof(General.ForceTransparency), general); 17 | WindowOptions.InitializeCheckBox(layered, nameof(General.LayeredWindows), general); 18 | 19 | Force_Click(this, new()); 20 | } 21 | 22 | private void Force_Click(object sender, RoutedEventArgs args) => layered.IsEnabled = force.IsChecked == true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/ToolPage.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/DialogPage.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /MicaVisualStudio/VisualStudio/EnumFramesExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.VisualStudio; 2 | 3 | /// 4 | /// Contains extensions for dealing with . 5 | /// 6 | public static class EnumFramesExtensions 7 | { 8 | /// 9 | /// Enumerates the specified into an of . 10 | /// 11 | /// An to enumerate. 12 | /// An of of the frames from the specified . 13 | public static IEnumerable ToEnumerable(this IEnumWindowFrames @enum) 14 | { 15 | ThreadHelper.ThrowIfNotOnUIThread(); 16 | 17 | const uint Amount = 1; 18 | var frames = new IVsWindowFrame[Amount]; 19 | 20 | while (ErrorHandler.Succeeded(@enum.Next(Amount, frames, out uint fetched)) && fetched == Amount) 21 | yield return frames[0]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml: -------------------------------------------------------------------------------- 1 | name: ⭐ Feature request 2 | description: Suggest an idea 3 | title: "[FEATURE] " 4 | labels: ["enhancement"] 5 | assignees: 6 | - tech5g5g 7 | body: 8 | - type: checkboxes 9 | attributes: 10 | label: "I have checked:" 11 | options: 12 | - label: This is not a duplicate of another request 13 | required: true 14 | - label: This is not an bug/background removal request 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: "Describe the feature you'd like" 19 | description: A clear and concise description of what you want to happen. 20 | validations: 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: Is your feature request related to a problem? 25 | description: A clear and concise description of what the problem is. 26 | validations: 27 | required: false 28 | - type: textarea 29 | attributes: 30 | label: Addtional context 31 | description: Any other context about the feature request. 32 | validations: 33 | required: false 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ignacy Tomczyk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MicaVisualStudio/Resources/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ignacy Tomczyk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MicaVisualStudio/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Linq; 3 | global using System.Threading; 4 | global using System.Diagnostics; 5 | global using System.Collections.Generic; 6 | global using System.Collections.Specialized; 7 | global using System.Collections.ObjectModel; 8 | global using System.Runtime.InteropServices; 9 | global using System.Windows; 10 | global using System.Windows.Media; 11 | global using System.Windows.Interop; 12 | global using System.Windows.Controls; 13 | global using System.Windows.Controls.Primitives; 14 | global using Task = System.Threading.Tasks.Task; 15 | global using Microsoft.VisualStudio; 16 | global using Microsoft.VisualStudio.Threading; 17 | global using Microsoft.VisualStudio.PlatformUI; 18 | global using Microsoft.VisualStudio.Shell; 19 | global using Microsoft.VisualStudio.Shell.Interop; 20 | global using Microsoft.VisualStudio.Imaging; 21 | global using Microsoft.VisualStudio.Imaging.Interop; 22 | global using Community.VisualStudio.Toolkit; 23 | global using MicaVisualStudio; 24 | global using MicaVisualStudio.Interop; 25 | global using MicaVisualStudio.Extensions; 26 | global using MicaVisualStudio.VisualStudio; 27 | global using MicaVisualStudio.Options; 28 | global using MicaVisualStudio.Options.Controls; 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Mica Visual Studio

2 |

Enhance your Visual Studio experience with Windows materials

3 | 4 | ![Mica Visual Studio showcase image](https://github.com/user-attachments/assets/84393113-b591-46fd-9472-dd197f869326) 5 | 6 | ## Overview 7 | 8 | With Mica Visual Studio, you can choose between four materials to apply as your window backdrop: 9 | 10 | 1. Mica 11 | 2. Tabbed 12 | 3. Acrylic 13 | 4. (Solid) Glass 14 | 15 | Official documentation of these materials can be found here (excluding Glass): [Materials in Windows (learn.microsoft.com)](https://learn.microsoft.com/en-us/windows/apps/design/signature-experiences/materials) 16 | 17 | To switch which material you use as your backdrop, use the **Tools > Backdrop** submenu. 18 | 19 | ## Compatibility 20 | 21 | Mica Visual Studio is meant to be used with: 22 | * Visual Studio 2026 or later 23 | * Windows 11 or later 24 | 25 | ## Additional information 26 | 27 | Additional information and documentation about Mica Visual Studio and its settings are available on its [wiki](https://github.com/Tech5G5G/Mica-Visual-Studio/wiki). 28 | 29 | If you find a bug with Mica Visual Studio, or have an idea to improve it, please [file an issue](https://github.com/Tech5G5G/Mica-Visual-Studio/issues) using the appropriate template. 30 | -------------------------------------------------------------------------------- /MicaVisualStudio/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("MicaVisualStudio")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Mica Visual Studio")] 13 | [assembly: AssemblyCopyright("© 2025 Ignacy Tomczyk")] 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 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("2.1.0.0")] 33 | [assembly: AssemblyFileVersion("2.1.0.0")] 34 | -------------------------------------------------------------------------------- /THIRD-PARTY-NOTICES: -------------------------------------------------------------------------------- 1 | This project incorporates material from the projects listed below. The original copyright notice and the license are set forth below. Such licenses and notices are provided for informational purposes only. 2 | 3 | ---------------------------------------------------------------------------- 4 | 5 | MonoMod by 0x0ade - https://github.com/MonoMod/MonoMod 6 | 7 | The MIT License (MIT) 8 | 9 | Copyright (c) 2015 - 2020 0x0ade 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG-REPORT.yml: -------------------------------------------------------------------------------- 1 | name: 🪲 Bug report 2 | description: Report bugs and errors 3 | title: "[BUG] " 4 | labels: ["bug"] 5 | assignees: 6 | - tech5g5g 7 | body: 8 | - type: checkboxes 9 | attributes: 10 | label: "I have checked:" 11 | options: 12 | - label: This is not a duplicate of another issue 13 | required: true 14 | - label: This is not an feature/background removal request 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: What happened? 19 | description: A clear and concise description of what the bug is. 20 | validations: 21 | required: true 22 | - type: textarea 23 | attributes: 24 | label: To reproduce 25 | description: Steps to reproduce the bug. 26 | validations: 27 | required: true 28 | - type: textarea 29 | attributes: 30 | label: Expected behavior 31 | description: A clear and concise description of what you expected to happen. 32 | validations: 33 | required: true 34 | - type: dropdown 35 | attributes: 36 | label: Edition 37 | description: What edition of Visual Studio are you using? 38 | options: 39 | - Community 40 | - Professional 41 | - Enterprise 42 | - Other 43 | validations: 44 | required: true 45 | - type: textarea 46 | attributes: 47 | label: Addtional context 48 | description: Any other context about the problem. 49 | validations: 50 | required: false 51 | 52 | -------------------------------------------------------------------------------- /MicaVisualStudio/Resources/third-party-notices.txt: -------------------------------------------------------------------------------- 1 | This project incorporates material from the projects listed below. The original copyright notice and the license are set forth below. Such licenses and notices are provided for informational purposes only. 2 | 3 | ---------------------------------------------------------------------------- 4 | 5 | MonoMod by 0x0ade - https://github.com/MonoMod/MonoMod 6 | 7 | The MIT License (MIT) 8 | 9 | Copyright (c) 2015 - 2020 0x0ade 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BACKGROUND-REQUEST.yml: -------------------------------------------------------------------------------- 1 | name: 🖼️ Background removal request 2 | description: Report background(s) that need to be removed 3 | title: "[BACKGROUND] " 4 | labels: ["background"] 5 | assignees: 6 | - tech5g5g 7 | body: 8 | - type: checkboxes 9 | attributes: 10 | label: "I have checked:" 11 | options: 12 | - label: This is not a duplicate of another issue 13 | required: true 14 | - label: This is not an bug/feature request 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: What background? 19 | description: A clear and concise description of which background and where to find it. Be sure to include an image! 20 | validations: 21 | required: true 22 | - type: dropdown 23 | attributes: 24 | label: Expected background 25 | description: What background is expected here. 26 | options: 27 | - Transparent 28 | - Layered (half translucent) 29 | validations: 30 | required: true 31 | - type: dropdown 32 | attributes: 33 | label: Origin 34 | description: Where does the background originate from? 35 | options: 36 | - First-party (Visual Studio) 37 | - Third-party (Extension) 38 | validations: 39 | required: true 40 | - type: input 41 | attributes: 42 | label: Extension name 43 | description: If third-party, name of the extension. 44 | placeholder: "Tech5G5G.MicaVisualStudio" 45 | validations: 46 | required: false 47 | - type: textarea 48 | attributes: 49 | label: Addtional context 50 | description: Any other context about the request or background. 51 | validations: 52 | required: false 53 | 54 | -------------------------------------------------------------------------------- /MicaVisualStudio/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mica Visual Studio 6 | Enhance your Visual Studio experience with Windows materials 7 | https://github.com/Tech5G5G/Mica-Visual-Studio 8 | Resources\license.txt 9 | Resources\logo.png 10 | Resources\logo.png 11 | Design, Modern, Theme, UI, Windows, Mica, Tabbed, Acrylic, Glass 12 | 13 | 14 | 15 | amd64 16 | 17 | 18 | arm64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /MicaVisualStudio/Extensions/TreeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.Extensions; 2 | 3 | /// 4 | /// Contains extensions for traversing the WPF element tree. 5 | /// 6 | public static class TreeExtensions 7 | { 8 | #region Logical 9 | 10 | /// 11 | /// Gets the logical descendants of using . 12 | /// 13 | /// The parent of the children to get. 14 | /// An of s containing the logical descendants of . 15 | public static IEnumerable LogicalDescendants(this DependencyObject parent) 16 | { 17 | foreach (var child in LogicalTreeHelper.GetChildren(parent).OfType()) 18 | { 19 | yield return child; 20 | 21 | foreach (var obj in LogicalDescendants(child)) 22 | yield return obj; 23 | } 24 | } 25 | 26 | /// 27 | /// Gets the logical descendants of using . 28 | /// 29 | /// The type of s to filter. 30 | /// The parent of the children to get. 31 | /// An of containing the logical descendants of . 32 | public static IEnumerable LogicalDescendants(this DependencyObject parent) where T : DependencyObject => 33 | LogicalDescendants(parent).OfType(); 34 | 35 | #endregion 36 | 37 | #region Utilities 38 | 39 | /// 40 | /// Finds the first in whose is . 41 | /// 42 | /// The -derived type of the element to find. 43 | /// An of s to filter through. 44 | /// The to check for. 45 | /// The first whose is . If none is found, . 46 | public static T FindElement(this IEnumerable source, string name) where T : FrameworkElement => 47 | source.FirstOrDefault(i => i is T element && element.Name == name) as T; 48 | 49 | #endregion 50 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /MicaVisualStudio/Interop/ThemeHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | 3 | namespace MicaVisualStudio.Interop; 4 | 5 | /// 6 | /// Represents a helper for getting and receiving updates about the system theme. 7 | /// 8 | public sealed class ThemeHelper : IDisposable 9 | { 10 | /// 11 | /// Gets the singleton instance of . 12 | /// 13 | public static ThemeHelper Instance => field ??= new(); 14 | 15 | #region PInvoke 16 | 17 | [DllImport("uxtheme.dll", EntryPoint = "#135")] 18 | private static extern int SetPreferredAppMode(PreferredAppMode preferredAppMode); 19 | 20 | [DllImport("uxtheme.dll", EntryPoint = "#136")] 21 | private static extern void FlushMenuThemes(); 22 | 23 | private enum PreferredAppMode 24 | { 25 | ForceDark = 2, 26 | ForceLight = 3 27 | } 28 | 29 | #endregion 30 | 31 | #region System Theme 32 | 33 | private const string PersonalizeSettings = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; 34 | 35 | /// 36 | /// Gets the current used by the system. 37 | /// 38 | public Theme SystemTheme => sysTheme; 39 | private Theme sysTheme; 40 | 41 | /// 42 | /// Occurs when has changed. 43 | /// 44 | public event EventHandler SystemThemeChanged; 45 | 46 | private Theme GetSystemTheme() => 47 | (int)Registry.GetValue(PersonalizeSettings, valueName: "AppsUseLightTheme", defaultValue: 0) == 1 ? //TRUE 48 | Theme.Light : Theme.Dark; 49 | 50 | #endregion 51 | 52 | private ThemeHelper() 53 | { 54 | sysTheme = GetSystemTheme(); 55 | SystemEvents.InvokeOnEventsThread(new Action(() => SystemEvents.UserPreferenceChanging += PreferenceChanging)); 56 | } 57 | 58 | private void PreferenceChanging(object sender, UserPreferenceChangingEventArgs args) => 59 | SystemThemeChanged?.Invoke(this, sysTheme = GetSystemTheme()); 60 | 61 | /// 62 | /// Sets the theme of the current app. 63 | /// 64 | /// Setting the app theme affects the theme of the system menu. 65 | /// The to set the app theme to. 66 | public void SetAppTheme(Theme theme) 67 | { 68 | SetPreferredAppMode(theme switch 69 | { 70 | Theme.Dark => PreferredAppMode.ForceDark, 71 | Theme.Light or _ => PreferredAppMode.ForceLight 72 | }); 73 | 74 | FlushMenuThemes(); 75 | } 76 | 77 | #region Dispose 78 | 79 | private bool disposed; 80 | 81 | /// 82 | /// Disposes the singleton instance of . 83 | /// 84 | /// Calling this method will stop the event from occuring. 85 | public void Dispose() 86 | { 87 | if (!disposed) 88 | { 89 | SystemEvents.InvokeOnEventsThread(new Action(() => SystemEvents.UserPreferenceChanging -= PreferenceChanging)); 90 | disposed = true; 91 | } 92 | } 93 | 94 | #endregion 95 | } 96 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/GeneralPage.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | 51 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /MicaVisualStudio.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 18 4 | VisualStudioVersion = 18.0.11217.181 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicaVisualStudio", "MicaVisualStudio\MicaVisualStudio.csproj", "{D4F84E24-0BE1-41E1-A510-68535792520D}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoMod", "MonoMod\MonoMod.csproj", "{4B63AF01-E456-B4C1-2D2D-EE5866DAA548}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|arm64 = Debug|arm64 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|arm64 = Release|arm64 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|arm64.ActiveCfg = Debug|arm64 25 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|arm64.Build.0 = Debug|arm64 26 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x64.Build.0 = Debug|Any CPU 28 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x86.ActiveCfg = Debug|x86 29 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x86.Build.0 = Debug|x86 30 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|arm64.ActiveCfg = Release|arm64 33 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|arm64.Build.0 = Release|arm64 34 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x64.ActiveCfg = Release|Any CPU 35 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x64.Build.0 = Release|Any CPU 36 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x86.ActiveCfg = Release|x86 37 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x86.Build.0 = Release|x86 38 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|arm64.ActiveCfg = Debug|Any CPU 41 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|arm64.Build.0 = Debug|Any CPU 42 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x64.Build.0 = Debug|Any CPU 44 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x86.Build.0 = Debug|Any CPU 46 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|arm64.ActiveCfg = Release|Any CPU 49 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|arm64.Build.0 = Release|Any CPU 50 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x64.ActiveCfg = Release|Any CPU 51 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x64.Build.0 = Release|Any CPU 52 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x86.ActiveCfg = Release|Any CPU 53 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x86.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {2289B7CA-7F7F-4A48-9701-F3A803173108} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/WindowOptions.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 17 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /MicaVisualStudio/MicaVisualStudioPackage.vsct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | Backdrop 18 | Backdrop 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 47 | 48 | 54 | 55 | 61 | 62 | 68 | 69 | 75 | 76 | 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 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/General.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio 2 | { 3 | internal partial class OptionsProvider 4 | { 5 | [ComVisible(true)] 6 | public class GeneralOptions : UIElementDialogPage 7 | { 8 | protected override UIElement Child => new GeneralPage(); 9 | } 10 | 11 | [ComVisible(true)] 12 | public class ToolOptions : UIElementDialogPage 13 | { 14 | protected override UIElement Child => new ToolPage(); 15 | } 16 | 17 | [ComVisible(true)] 18 | public class DialogOptions : UIElementDialogPage 19 | { 20 | protected override UIElement Child => new Options.Controls.DialogPage(); 21 | } 22 | } 23 | 24 | /// 25 | /// Represents the general options used by Mica Visual Studio. 26 | /// 27 | public class General : BaseOptionModel 28 | { 29 | /// 30 | /// Gets or sets the used for all windows. 31 | /// 32 | /// Used in . 33 | public int Theme { get; set; } = 0; //Theme.VisualStudio 34 | /// 35 | /// Gets or sets the used for all windows. 36 | /// 37 | /// Used in . 38 | public int Backdrop { get; set; } = 2; //BackdropType.Mica 39 | /// 40 | /// Gets or sets the used for all windows. 41 | /// 42 | /// Used in . 43 | public int CornerPreference { get; set; } = 0; //CornerPreference.Default 44 | 45 | /// 46 | /// Gets or sets whether to use separate options for windows. 47 | /// 48 | public bool ToolWindows { get; set; } = false; 49 | /// 50 | /// Gets or sets the used for windows. 51 | /// 52 | /// Used in . 53 | public int ToolTheme { get; set; } = 0; //Theme.VisualStudio 54 | /// 55 | /// Gets or sets the used for windows. 56 | /// 57 | /// Used in . 58 | public int ToolBackdrop { get; set; } = 2; //BackdropType.Mica 59 | /// 60 | /// Gets or sets the used for windows. 61 | /// 62 | /// Used in . 63 | public int ToolCornerPreference { get; set; } = 0; //CornerPreference.Default 64 | 65 | /// 66 | /// Gets or sets whether to use separate options for windows. 67 | /// 68 | public bool DialogWindows { get; set; } = false; 69 | /// 70 | /// Gets or sets the used for windows. 71 | /// 72 | /// Used in . 73 | public int DialogTheme { get; set; } = 0; //Theme.VisualStudio 74 | /// 75 | /// Gets or sets the used for windows. 76 | /// 77 | /// Used in . 78 | public int DialogBackdrop { get; set; } = 2; //BackdropType.Mica 79 | /// 80 | /// Gets or sets the used for windows. 81 | /// 82 | /// Used in . 83 | public int DialogCornerPreference { get; set; } = 0; //CornerPreference.Default 84 | 85 | /// 86 | /// Gets or sets the used by the app. 87 | /// 88 | /// Used in . 89 | public int AppTheme { get; set; } = 0; //Theme.VisualStudio 90 | 91 | /// 92 | /// Gets or sets whether or not to invoke . 93 | /// 94 | public bool ForceTransparency { get; set; } = true; 95 | /// 96 | /// Gets or sets whether child windows recieve the style via . 97 | /// 98 | /// Used by . 99 | public bool LayeredWindows { get; set; } = true; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /MicaVisualStudio/Extensions/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Linq.Expressions; 3 | using Expression = System.Linq.Expressions.Expression; 4 | 5 | namespace MicaVisualStudio.Extensions 6 | { 7 | /// 8 | /// Contains extensions for dealing with s. 9 | /// 10 | public static class ExpressionExtensions 11 | { 12 | /// 13 | /// Creates a that represents a type conversion operation to . 14 | /// 15 | /// Equivalent to . 16 | /// An to set the property equal to. 17 | /// A to set the property equal to. 18 | /// 19 | /// A that has the property equal to . 20 | /// 21 | public static UnaryExpression Convert(this Expression expression, Type type) => 22 | Expression.Convert(expression, type); 23 | 24 | /// 25 | /// Creates a that represents a type conversion operation to . 26 | /// 27 | /// Equivalent to . 28 | /// An to set the property equal to. 29 | /// The type to set the property equal to. 30 | /// 31 | /// A that has the property equal to . 32 | /// 33 | public static UnaryExpression Convert(this Expression expression) => 34 | expression.Convert(typeof(T)); 35 | 36 | /// 37 | /// Creates a that represents a type check of . 38 | /// 39 | /// Equivalent to . 40 | /// An to set the property equal to. 41 | /// A to set the property equal to. 42 | /// 43 | /// A for which the property is equal to . 44 | /// 45 | public static TypeBinaryExpression TypeIs(this Expression expression, Type type) => 46 | Expression.TypeIs(expression, type); 47 | 48 | /// 49 | /// Creates a that represents a type check of . 50 | /// 51 | /// Equivalent to . 52 | /// The type to set the property equal to. 53 | /// An to set the property equal to. 54 | /// 55 | /// A for which the property is equal to . 56 | /// 57 | public static TypeBinaryExpression TypeIs(this Expression expression) => 58 | expression.TypeIs(typeof(T)); 59 | 60 | /// 61 | /// Creates a that represents accessing a property. 62 | /// 63 | /// Equivalent to . 64 | /// An to set the property equal to. 65 | /// The to set the property equal to. 66 | /// 67 | /// A that has the property equal to . 68 | /// 69 | public static MemberExpression Property(this Expression expression, PropertyInfo property) => 70 | Expression.Property(expression, property); 71 | 72 | /// 73 | /// Compiles into executable code and produces a that represents it. 74 | /// 75 | /// Equivalent to followed with . 76 | /// The type of the parameter used by . 77 | /// The type of the value returned by . 78 | /// The to compile into executable code. 79 | /// The used by as a parameter. 80 | /// A that represents the compiled . 81 | public static Func Compile(this Expression body, ParameterExpression parameter) => 82 | Expression.Lambda>(body, parameter).Compile(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/BackdropCommands.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Design; 2 | 3 | namespace MicaVisualStudio 4 | { 5 | /// 6 | /// Command handler 7 | /// 8 | internal sealed class BackdropCommands 9 | { 10 | /// 11 | /// Command ID. 12 | /// 13 | public const int NoneCommandId = 0x0100, 14 | MicaCommandId = 0x0105, 15 | TabbedCommandId = 0x0110, 16 | AcrylicCommandId = 0x0115, 17 | GlassCommandId = 0x0120, 18 | MoreCommandId = 0x0125; 19 | 20 | /// 21 | /// Gets the instance of . 22 | /// 23 | public static BackdropCommands Instance { get; private set; } 24 | 25 | /// 26 | /// Command menu group (command set GUID). 27 | /// 28 | public static readonly Guid CommandSet = new("89c88597-b045-43a5-bc13-8d34202031e8"); 29 | 30 | /// 31 | /// that commands are added to. 32 | /// 33 | private readonly OleMenuCommandService commandService = 34 | VS.GetRequiredService(); 35 | 36 | /// 37 | /// Gets the service provider from the owner package. 38 | /// 39 | private IAsyncServiceProvider ServiceProvider => package; 40 | 41 | /// 42 | /// that provides this command, not null. 43 | /// 44 | private readonly AsyncPackage package; 45 | 46 | private int selection = MicaCommandId; //Default to Mica 47 | 48 | /// 49 | /// Initializes a new instance of the class. 50 | /// Adds our command handlers for menu (commands must exist in the command table file) 51 | /// 52 | /// Owner package, not null. 53 | /// Command service to add command to, not null. 54 | private BackdropCommands(AsyncPackage package, OleMenuCommandService commandService) 55 | { 56 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 57 | this.commandService = commandService; 58 | 59 | UpdateSelection(General.Instance); 60 | General.Saved += UpdateSelection; 61 | 62 | foreach (var id in new int[] { NoneCommandId, MicaCommandId, TabbedCommandId, AcrylicCommandId, GlassCommandId }) 63 | RegisterCommand(id); 64 | 65 | //Add additional "More options..." command 66 | commandService.AddCommand(new( 67 | (s, e) => VS.Settings.OpenAsync().Forget(), 68 | new(CommandSet, MoreCommandId))); 69 | 70 | void UpdateSelection(General general) => 71 | selection = (BackdropType)general.Backdrop switch 72 | { 73 | BackdropType.None => NoneCommandId, 74 | BackdropType.Tabbed => TabbedCommandId, 75 | BackdropType.Acrylic => AcrylicCommandId, 76 | BackdropType.Glass => GlassCommandId, 77 | _ => MicaCommandId 78 | }; 79 | } 80 | 81 | /// 82 | /// Initializes the singleton instance of the command. 83 | /// 84 | /// Owner package, not null. 85 | public static async Task InitializeAsync(AsyncPackage package) 86 | { 87 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 88 | 89 | if (await package.GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService commandService) 90 | Instance = new BackdropCommands( 91 | package, 92 | commandService); 93 | } 94 | 95 | private void RegisterCommand(int commandId) 96 | { 97 | CommandID menuCommandID = new(CommandSet, commandId); 98 | 99 | OleMenuCommand menuItem = new(Execute, menuCommandID); 100 | menuItem.BeforeQueryStatus += BeforeQueryStatus; 101 | 102 | commandService.AddCommand(menuItem); 103 | } 104 | 105 | private void BeforeQueryStatus(object sender, EventArgs args) 106 | { 107 | ThreadHelper.ThrowIfNotOnUIThread(); 108 | 109 | if (sender is OleMenuCommand command) 110 | command.Checked = selection == command.CommandID.ID; 111 | } 112 | 113 | /// 114 | /// This function is the callback used to execute the command when the menu item is clicked. 115 | /// See the constructor to see how the menu item is associated with this function using 116 | /// OleMenuCommandService service and MenuCommand class. 117 | /// 118 | /// Event sender. 119 | /// Event args. 120 | private void Execute(object sender, EventArgs args) 121 | { 122 | ThreadHelper.ThrowIfNotOnUIThread(); 123 | 124 | if ((sender as OleMenuCommand)?.CommandID.ID is not int commandId) 125 | return; 126 | 127 | selection = commandId; 128 | 129 | var general = General.Instance; 130 | general.Backdrop = (int)(commandId switch 131 | { 132 | NoneCommandId => BackdropType.None, 133 | TabbedCommandId => BackdropType.Tabbed, 134 | AcrylicCommandId => BackdropType.Acrylic, 135 | GlassCommandId => BackdropType.Glass, 136 | _ => BackdropType.Mica 137 | }); 138 | general.Save(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /MicaVisualStudio/VisualStudio/WindowObserver.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.VisualStudio; 2 | 3 | /// 4 | /// Represents an observer that listens for opened and closed s. 5 | /// 6 | public sealed class WindowObserver : IDisposable 7 | { 8 | /// 9 | /// Gets the singleton instance of . 10 | /// 11 | public static WindowObserver Instance => field ??= new(); 12 | 13 | #region Static Properties 14 | 15 | /// 16 | /// Gets the main window of the current . 17 | /// 18 | public static Window MainWindow => Application.Current.MainWindow; 19 | 20 | /// 21 | /// Gets the current, focused window of the current . 22 | /// 23 | public static Window CurrentWindow 24 | { 25 | get 26 | { 27 | var windows = Application.Current.Windows.OfType(); 28 | return windows.FirstOrDefault(i => i.IsActive) ?? windows.FirstOrDefault(i => i.Visibility == Visibility.Visible); 29 | } 30 | } 31 | 32 | /// 33 | /// Gets all of the windows of the current . 34 | /// 35 | public static List AllWindows => [.. Application.Current.Windows.OfType()]; 36 | 37 | #endregion 38 | 39 | /// 40 | /// Gets a containing the s found that are still alive. 41 | /// 42 | public ReadOnlyDictionary Windows 43 | { 44 | get 45 | { 46 | CleanHandles(); 47 | var sources = PresentationSource.CurrentSources.OfType().ToArray(); 48 | 49 | return new(handles.ToDictionary(i => i, i => new WindowInfo(sources.FirstOrDefault(x => x.Handle == i)?.RootVisual as Window))); 50 | } 51 | } 52 | 53 | /// 54 | /// Occurs when a new is opened. 55 | /// 56 | public event WindowChangedEventHandler WindowOpened; 57 | /// 58 | /// Occurs when a is closed. 59 | /// 60 | public event WindowChangedEventHandler WindowClosed; 61 | 62 | private readonly int procId; 63 | private readonly WinEventHook hook; 64 | 65 | private readonly HashSet handles = []; 66 | 67 | private WindowObserver() 68 | { 69 | hook = new(Event.Foreground, EventFlags.OutOfContext, procId = Process.GetCurrentProcess().Id); 70 | hook.EventOccurred += EventOccurred; 71 | 72 | EventManager.RegisterClassHandler( 73 | typeof(Window), 74 | FrameworkElement.LoadedEvent, 75 | new RoutedEventHandler(WindowLoaded)); 76 | 77 | EventManager.RegisterClassHandler( 78 | typeof(Window), 79 | FrameworkElement.UnloadedEvent, 80 | new RoutedEventHandler(WindowUnloaded)); 81 | } 82 | 83 | private void WindowLoaded(object sender, RoutedEventArgs args) 84 | { 85 | if (sender is not Window window) 86 | return; 87 | 88 | var handle = window.GetHandle(); 89 | 90 | handles.Add(handle); 91 | WindowOpened?.Invoke(window, new(handle, window)); 92 | } 93 | 94 | private void WindowUnloaded(object sender, RoutedEventArgs args) 95 | { 96 | if (sender is not Window window) 97 | return; 98 | 99 | CleanHandles(); 100 | WindowClosed?.Invoke(window, new(IntPtr.Zero, window)); 101 | } 102 | 103 | private void EventOccurred(WinEventHook sender, EventOccuredEventArgs args) 104 | { 105 | if (!WindowHelper.GetWindowStyles(args.WindowHandle).HasFlag(WindowStyles.Caption) || //Check window for title bar 106 | handles.Contains(args.WindowHandle)) //Prefer WPF over WinEventHook and avoid duplicates 107 | return; 108 | 109 | handles.Add(args.WindowHandle); 110 | 111 | var window = HwndSource.FromHwnd(args.WindowHandle) is HwndSource source ? source.RootVisual as Window : null; 112 | WindowOpened?.Invoke(window, new(args.WindowHandle, window)); 113 | } 114 | 115 | /// 116 | /// Appends to the end of . 117 | /// 118 | /// A to append. 119 | public void AppendWindow(Window window) 120 | { 121 | if (window.IsLoaded) 122 | AppendWindow(window.GetHandle()); 123 | } 124 | 125 | /// 126 | /// Appends a to a window to the end of . 127 | /// 128 | /// A handle to a window. 129 | public void AppendWindow(IntPtr handle) => 130 | handles.Add(handle); 131 | 132 | private void CleanHandles() => 133 | handles.RemoveWhere(i => 134 | !WindowHelper.IsAlive(i) || //Check if alive 135 | WindowHelper.GetProcessId(i) != procId); //and belongs to current process 136 | 137 | #region Dispose 138 | 139 | private bool disposed; 140 | 141 | /// 142 | /// Disposes the singleton instance of . 143 | /// 144 | /// Calling this method will stop the and events from occuring. 145 | public void Dispose() 146 | { 147 | if (!disposed) 148 | { 149 | hook.Dispose(); 150 | disposed = true; 151 | } 152 | } 153 | 154 | #endregion 155 | } 156 | 157 | /// 158 | /// Represents the handler for the and events. 159 | /// 160 | /// The that generated the event. 161 | /// The to go along with the event. 162 | public delegate void WindowChangedEventHandler(Window sender, WindowActionEventArgs args); 163 | 164 | /// 165 | /// Represents the for the and events. 166 | /// 167 | /// A handle to a window. 168 | /// The that generated the event. 169 | public class WindowActionEventArgs(IntPtr handle, Window window) : EventArgs 170 | { 171 | public IntPtr WindowHandle { get; } = handle; 172 | 173 | public WindowType WindowType { get; } = WindowHelper.GetWindowType(window); 174 | } 175 | 176 | /// 177 | /// Represents information about a specified . 178 | /// 179 | /// A to get information from. 180 | public class WindowInfo(Window window) 181 | { 182 | public Window Window { get; } = window; 183 | 184 | public WindowType Type { get; } = WindowHelper.GetWindowType(window); 185 | } 186 | -------------------------------------------------------------------------------- /MicaVisualStudio/Options/Controls/WindowOptions.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace MicaVisualStudio.Options.Controls 4 | { 5 | /// 6 | /// Interaction logic for WindowOptions.xaml 7 | /// 8 | public partial class WindowOptions : GroupBox 9 | { 10 | /// 11 | /// Gets or sets the name of the type of window represented by this . 12 | /// 13 | public string WindowName 14 | { 15 | get => (string)GetValue(WindowNameProperty); 16 | set => SetValue(WindowNameProperty, value); 17 | } 18 | /// 19 | /// Identifies the dependency property. 20 | /// 21 | public static DependencyProperty WindowNameProperty { get; } = 22 | DependencyProperty.Register(nameof(WindowName), typeof(string), typeof(WindowOptions), new(defaultValue: "Windows")); 23 | 24 | /// 25 | /// Gets or sets whether to show a at the top of this for toggling separate options. 26 | /// 27 | public bool ShowCheck 28 | { 29 | get => (bool)GetValue(ShowCheckProperty); 30 | set => SetValue(ShowCheckProperty, value); 31 | } 32 | /// 33 | /// Identifies the dependency property. 34 | /// 35 | public static DependencyProperty ShowCheckProperty { get; } = 36 | DependencyProperty.Register(nameof(ShowCheck), typeof(bool), typeof(WindowOptions), new(defaultValue: true)); 37 | 38 | /// 39 | /// Gets or sets the name of the property to modify in the specified model when setting the backdrop type. 40 | /// 41 | public string BackdropSource { get; set; } 42 | 43 | /// 44 | /// Gets or sets the name of the property to modify in the specified model when setting the corner preference. 45 | /// 46 | public string CornerPreferenceSource { get; set; } 47 | 48 | /// 49 | /// Gets or sets the name of the property to modify in the specified model when setting the theme. 50 | /// 51 | public string ThemeSource { get; set; } 52 | 53 | /// 54 | /// Gets or sets the name of the property to modify in the specified model when enabling or disabling separate options. 55 | /// 56 | public string EnabledSource { get; set; } 57 | 58 | private bool optionModelSet = false; 59 | 60 | /// 61 | /// Initializes a new instance of . 62 | /// 63 | public WindowOptions() 64 | { 65 | InitializeComponent(); 66 | } 67 | 68 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 69 | { 70 | base.OnPropertyChanged(e); 71 | 72 | if (e.Property == WindowNameProperty) 73 | enabled.Content = $"Enable separate options for {WindowName.ToLower()}"; 74 | else if (e.Property == ShowCheckProperty && !ShowCheck) 75 | enabled.IsChecked = true; 76 | } 77 | 78 | /// 79 | /// Specifies the to modify when changing settings. 80 | /// 81 | /// The type of . 82 | /// A whose properties are to be modified. 83 | public void SetOptionModel(T model) 84 | where T : BaseOptionModel, new() 85 | { 86 | if (optionModelSet) 87 | return; 88 | 89 | optionModelSet = true; 90 | 91 | InitializeComboBox(backdrop, BackdropSource, model); 92 | InitializeComboBox(cornerPreference, CornerPreferenceSource, model); 93 | InitializeComboBox(theme, ThemeSource, model); 94 | 95 | InitializeCheckBox(enabled, EnabledSource, model); 96 | } 97 | 98 | /// 99 | /// Binds 's to the specified on . 100 | /// 101 | /// The type of . 102 | /// A to bind. 103 | /// The name of the property to bind to. 104 | /// A to use as the binding source. 105 | public static void InitializeComboBox(ComboBox box, string propertyName, T model) 106 | where T : BaseOptionModel, new() 107 | { 108 | if (propertyName is null) 109 | return; 110 | 111 | Type type = typeof(T); 112 | var instanceProp = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); 113 | var prop = type.GetProperty(propertyName); 114 | 115 | UpdateIndex(); 116 | box.SelectionChanged += SelectionChanged; 117 | 118 | type.GetEvent("Saved", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy) 119 | .AddEventHandler(target: null, new Action(m => 120 | { 121 | box.SelectionChanged -= SelectionChanged; 122 | UpdateIndex(); 123 | box.SelectionChanged += SelectionChanged; 124 | })); 125 | 126 | void UpdateIndex() => box.SelectedIndex = (int)prop.GetValue(model); 127 | 128 | void SelectionChanged(object sender, RoutedEventArgs args) 129 | { 130 | var instance = (T)instanceProp.GetValue(obj: null); 131 | prop.SetValue(instance, box.SelectedIndex); 132 | instance.Save(); 133 | } 134 | } 135 | 136 | /// 137 | /// Binds 's to the specified on . 138 | /// 139 | /// The type of . 140 | /// A to bind. 141 | /// The name of the property to bind to. 142 | /// A to use as the binding source. 143 | public static void InitializeCheckBox(CheckBox box, string propertyName, T model) 144 | where T : BaseOptionModel, new() 145 | { 146 | if (propertyName is null) 147 | return; 148 | 149 | Type type = typeof(T); 150 | var instanceProp = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); 151 | var prop = type.GetProperty(propertyName); 152 | 153 | box.IsChecked = (bool)prop.GetValue(model); 154 | box.Click += (s, e) => 155 | { 156 | var instance = (T)instanceProp.GetValue(null); 157 | prop.SetValue(instance, box.IsChecked); 158 | instance.Save(); 159 | }; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /MicaVisualStudio/Interop/WinEventHook.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio.Interop; 2 | 3 | /// 4 | /// Represents an event hook for Windows events. 5 | /// 6 | public class WinEventHook : IDisposable 7 | { 8 | #region PInvoke 9 | 10 | [DllImport("user32.dll")] 11 | private static extern IntPtr SetWinEventHook( 12 | uint eventMin, 13 | uint eventMax, 14 | IntPtr hmodWinEventProc, 15 | WinEventDelegate pfnWinEventProc, 16 | uint idProcess, 17 | uint idThread, 18 | uint dwFlags); 19 | 20 | [DllImport("user32.dll")] 21 | private static extern bool UnhookWinEvent(IntPtr hWinEventHook); 22 | 23 | [DllImport("user32.dll")] 24 | private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 25 | 26 | public const uint WINEVENT_OUTOFCONTEXT = 0; 27 | 28 | public const uint EVENT_OBJECT_SHOW = 0x8002, 29 | EVENT_OBJECT_CREATE = 0x8000, 30 | EVENT_OBJECT_DESTROY = 0x8001; 31 | 32 | private delegate void WinEventDelegate( 33 | IntPtr hWinEventHook, 34 | int eventConst, 35 | IntPtr hWnd, 36 | int idObject, 37 | int idChild, 38 | int idEventThread, 39 | int dwmsEventTime); 40 | 41 | #endregion 42 | 43 | /// 44 | /// Occurs when the specified occurs. 45 | /// 46 | public event EventOccuredEventHandler EventOccurred; 47 | 48 | private readonly IntPtr hookId; 49 | private readonly WinEventDelegate hook; 50 | 51 | /// 52 | /// Initializes a new instance of . 53 | /// 54 | /// The to look out for. 55 | /// The location of this and the sources to be skipped. 56 | /// The ID of the process to watch. Set to 0 to watch all processes. 57 | public WinEventHook(Event winEvent, EventFlags flags, int pid) => 58 | hookId = SetWinEventHook((uint)winEvent, (uint)winEvent, IntPtr.Zero, hook = Procedure, (uint)pid, idThread: 0, (uint)flags); 59 | 60 | private void Procedure(IntPtr hWinEventHook, int eventConst, IntPtr hWnd, int idObject, int idChild, int idEventThread, int dwmsEventTime) => 61 | EventOccurred?.Invoke(this, new(eventConst, hWnd, idObject, idChild, dwmsEventTime)); 62 | 63 | #region Dispose 64 | 65 | private bool disposed; 66 | 67 | ~WinEventHook() => Dispose(disposing: false); 68 | 69 | /// 70 | /// Disposes this instance of , unregistering the associated hook. 71 | /// 72 | public void Dispose() 73 | { 74 | Dispose(disposing: true); 75 | GC.SuppressFinalize(this); 76 | } 77 | 78 | /// 79 | /// Disposes this instance of . 80 | /// 81 | /// Whether to release managed resources. 82 | protected virtual void Dispose(bool disposing) 83 | { 84 | if (!disposed) 85 | { 86 | UnhookWinEvent(hookId); 87 | disposed = true; 88 | } 89 | } 90 | 91 | #endregion 92 | } 93 | 94 | /// 95 | /// Specifies the event used by . 96 | /// 97 | public enum Event 98 | { 99 | /// 100 | /// Specifies to listen to foreground window changes. 101 | /// 102 | /// Equivalent to EVENT_SYSTEM_FOREGROUND. 103 | Foreground = 0x0003, 104 | 105 | /// 106 | /// Specifies to listen to the creation of objects. 107 | /// 108 | /// Equivalent to EVENT_OBJECT_CREATE. 109 | Create = 0x8000, 110 | 111 | /// 112 | /// Specifies to listen to the destruction of objects. 113 | /// 114 | /// Equivalent to EVENT_OBJECT_DESTROY. 115 | Destroy = 0x8001, 116 | 117 | /// 118 | /// Specifies to listen to hidden objects being shown. 119 | /// 120 | /// Equivalent to EVENT_OBJECT_SHOW. 121 | Show = 0x8002 122 | } 123 | 124 | /// 125 | /// Specifies the location of a hook and the sources to be skipped. 126 | /// 127 | [Flags] 128 | public enum EventFlags 129 | { 130 | /// 131 | /// Specifies to place the hook outside of the process of the hookee. This is the default. 132 | /// 133 | /// Equivalent to WINEVENT_INCONTEXT. 134 | OutOfContext = 0x0000, 135 | 136 | /// 137 | /// Specifies to skip events that occur in the same thread as the thread registering the hook. 138 | /// 139 | /// Equivalent to WINEVENT_SKIPOWNTHREAD. 140 | SkipOwnThread = 0x0001, 141 | 142 | /// 143 | /// Specifies to skip events that occur in the same process as the process registering the hook. 144 | /// 145 | /// Equivalent to WINEVENT_SKIPOWNPROCESS. 146 | SkipOwnProcess = 0x0002, 147 | 148 | /// 149 | /// Specifies to place the hook inside the process of the hookee. 150 | /// 151 | /// Equivalent to WINEVENT_OUTOFCONTEXT. 152 | InContext = 0x0004, 153 | } 154 | 155 | /// 156 | /// Represents the for the event. 157 | /// 158 | /// An representation of the that occured. 159 | /// The handle to the window that generated the . 160 | /// The ID of the object that generated the . 161 | /// The ID of the child that generated the . 162 | /// The amount of time, in milliseconds, that the was generated in. 163 | public class EventOccuredEventArgs(int eventConst, IntPtr hWnd, int idObject, int idChild, int dwmsEventTime) : EventArgs 164 | { 165 | private const int CHILDID_SELF = 0; 166 | 167 | /// 168 | /// The that occured. 169 | /// 170 | public Event Event { get; } = (Event)eventConst; 171 | 172 | /// 173 | /// The handle to the window associated with the event. 174 | /// 175 | public IntPtr WindowHandle { get; } = hWnd; 176 | 177 | /// 178 | /// The ID of the object associated with the event. 179 | /// 180 | public int ObjectId { get; } = idObject; 181 | 182 | /// 183 | /// Whether the event was triggered by a child. 184 | /// 185 | public bool TriggeredByChild { get; } = idChild != CHILDID_SELF; 186 | 187 | /// 188 | /// The amount of time, in milliseconds, which it took for the to be generated. 189 | /// 190 | public int EventTime { get; } = dwmsEventTime; 191 | } 192 | 193 | /// 194 | /// Represents the handler for the event. 195 | /// 196 | /// The that generated the event. 197 | /// The to go along with the event. 198 | public delegate void EventOccuredEventHandler(WinEventHook sender, EventOccuredEventArgs args); 199 | -------------------------------------------------------------------------------- /MicaVisualStudio/MicaVisualStudio.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | 2.0 12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 13 | {D4F84E24-0BE1-41E1-A510-68535792520D} 14 | Library 15 | Properties 16 | MicaVisualStudio 17 | MicaVisualStudio 18 | v4.8 19 | true 20 | true 21 | true 22 | false 23 | false 24 | true 25 | true 26 | Program 27 | $(DevEnvDir)devenv.exe 28 | /rootsuffix Exp 29 | 14 30 | 31 | 32 | true 33 | full 34 | false 35 | bin\Debug\ 36 | DEBUG;TRACE 37 | prompt 38 | 4 39 | 40 | 41 | pdbonly 42 | true 43 | bin\Release\ 44 | TRACE 45 | prompt 46 | 4 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | DialogPage.xaml 62 | 63 | 64 | 65 | GeneralPage.xaml 66 | 67 | 68 | ToolPage.xaml 69 | 70 | 71 | WindowOptions.xaml 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Menus.ctmenu 81 | 82 | 83 | Always 84 | true 85 | 86 | 87 | Always 88 | true 89 | 90 | 91 | Always 92 | true 93 | 94 | 95 | Designer 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 0.11.6 115 | 116 | 117 | 17.0.545 118 | 119 | 120 | compile; build; native; contentfiles; analyzers; buildtransitive 121 | 122 | 123 | runtime; build; native; contentfiles; analyzers; buildtransitive 124 | all 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Designer 134 | MSBuild:Compile 135 | 136 | 137 | Designer 138 | MSBuild:Compile 139 | 140 | 141 | Designer 142 | MSBuild:Compile 143 | 144 | 145 | Designer 146 | MSBuild:Compile 147 | 148 | 149 | 150 | 151 | 158 | -------------------------------------------------------------------------------- /MicaVisualStudio/Extensions/WeakEventExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Data; 2 | 3 | namespace MicaVisualStudio.Extensions; 4 | 5 | /// 6 | /// Represents a notifier that observes a for changes from a specificed source. 7 | /// 8 | public class PropertyChangeNotifier : DependencyObject, IDisposable 9 | { 10 | private readonly WeakReference propertySource; 11 | 12 | /// 13 | /// Initializes a new instance of . 14 | /// 15 | /// The source from which property changes occur. 16 | /// A path to the property to observe. 17 | public PropertyChangeNotifier(DependencyObject source, string path) : 18 | this(source, new PropertyPath(path)) { } 19 | 20 | /// 21 | /// Initializes a new instance of . 22 | /// 23 | /// The source from which property changes occur. 24 | /// The to observe. 25 | public PropertyChangeNotifier(DependencyObject source, DependencyProperty property) : 26 | this(source, new PropertyPath(property)) { } 27 | 28 | /// 29 | /// Initializes a new instance of . 30 | /// 31 | /// The source from which property changes occur. 32 | /// A path to the property to observe. 33 | public PropertyChangeNotifier(DependencyObject source, PropertyPath path) 34 | { 35 | propertySource = new(source); 36 | 37 | BindingOperations.SetBinding(this, ValueProperty, new Binding 38 | { 39 | Path = path, 40 | Mode = BindingMode.OneWay, 41 | Source = source 42 | }); 43 | } 44 | 45 | /// 46 | /// Gets the source from which property changes occur. 47 | /// 48 | /// 49 | /// If the source is unavailable, this returns . 50 | /// 51 | public DependencyObject PropertySource => 52 | propertySource.TryGetTarget(out DependencyObject source) ? source : null; 53 | 54 | /// 55 | /// Gets or sets the value of the property. 56 | /// 57 | public object Value 58 | { 59 | get => GetValue(ValueProperty); 60 | set => SetValue(ValueProperty, value); 61 | } 62 | 63 | /// 64 | /// Identifies the dependency property. 65 | /// 66 | public static readonly DependencyProperty ValueProperty = 67 | DependencyProperty.Register("Value", typeof(object), typeof(PropertyChangeNotifier), new(null, new(OnValueChanged))); 68 | 69 | /// 70 | /// Occurs when the value of the specified has changed. 71 | /// 72 | public event EventHandler ValueChanged; 73 | 74 | private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => 75 | (d as PropertyChangeNotifier)?.ValueChanged?.Invoke(d, EventArgs.Empty); 76 | 77 | /// 78 | /// Clears the internal binding used by this . 79 | /// 80 | public void Dispose() => 81 | BindingOperations.ClearBinding(this, ValueProperty); 82 | } 83 | 84 | /// 85 | /// Contains extensions for adding weak handlers to s and property changes. 86 | /// 87 | public static class WeakEventExtensions 88 | { 89 | #region Events 90 | 91 | /// 92 | /// Adds the specified to the specified using . 93 | /// 94 | /// The -derived type of . 95 | /// The source to attach the to. 96 | /// The to attach the to. 97 | /// The that recieves events. 98 | public static void AddWeakHandler(this T source, RoutedEvent routedEvent, RoutedEventHandler handler) where T : FrameworkElement => 99 | WeakEventManager.AddHandler(source, routedEvent.Name, (s, e) => handler(s, e)); 100 | 101 | 102 | /// 103 | /// Adds the specified to the specified using . 104 | /// 105 | /// Once the is invoked, it is removed from the specified . 106 | /// The -derived type of . 107 | /// The source to attach the to. 108 | /// The to attach the to. 109 | /// The that recieves events. 110 | public static void AddWeakOneTimeHandler(this T source, RoutedEvent routedEvent, RoutedEventHandler handler) where T : FrameworkElement 111 | { 112 | WeakEventManager.AddHandler(source, routedEvent.Name, Handler); 113 | 114 | void Handler(object sender, RoutedEventArgs args) 115 | { 116 | handler(sender, args); 117 | WeakEventManager.RemoveHandler(source, routedEvent.Name, Handler); 118 | } 119 | } 120 | 121 | #endregion 122 | 123 | #region Properties 124 | 125 | /// 126 | /// Adds a to the specified . 127 | /// 128 | /// The from which property changes occur. 129 | /// The observed by . 130 | /// The to invoke once a property change occurs. 131 | public static void AddWeakPropertyChangeHandler(this DependencyObject source, DependencyProperty property, EventHandler handler) 132 | { 133 | PropertyChangeNotifier notifier = new(source, property); 134 | notifier.ValueChanged += (s, e) => handler((s as PropertyChangeNotifier).PropertySource, EventArgs.Empty); 135 | } 136 | 137 | /// 138 | /// Adds a to the specified . 139 | /// 140 | /// Once the is invoked, the is disposed. 141 | /// The from which property changes occur. 142 | /// The observed by . 143 | /// The to invoke once a property change occurs. 144 | public static void AddWeakOneTimePropertyChangeHandler(this DependencyObject source, DependencyProperty property, EventHandler handler) 145 | { 146 | PropertyChangeNotifier notifier = new(source, property); 147 | notifier.ValueChanged += ValueChanged; 148 | 149 | void ValueChanged(object sender, EventArgs args) 150 | { 151 | if (sender is PropertyChangeNotifier notifier) 152 | { 153 | handler(notifier.PropertySource, EventArgs.Empty); 154 | notifier.Dispose(); 155 | } 156 | } 157 | } 158 | 159 | #endregion 160 | } 161 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | /desktop.ini 365 | -------------------------------------------------------------------------------- /MicaVisualStudio/VisualStudio/VsColorManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using Microsoft.Internal.VisualStudio.PlatformUI; 3 | 4 | namespace MicaVisualStudio.VisualStudio; 5 | 6 | /// 7 | /// Represent a manager for the various s and es used by Visual Studio. 8 | /// 9 | public sealed class VsColorManager 10 | { 11 | /// 12 | /// Gets the singleton instance of . 13 | /// 14 | public static VsColorManager Instance => field ??= new(); 15 | 16 | private static Color TransparentWhite = Colors.Transparent, 17 | TranslucentBlack = Color.FromArgb(0x01, 0x00, 0x00, 0x00); //Slight alpha to change icon foreground (basically invisible) 18 | 19 | /// 20 | /// Gets a containing all the s currently in effect. 21 | /// 22 | public ReadOnlyDictionary Configs => new(configs); 23 | private readonly Dictionary configs = []; 24 | 25 | #region Visual Studio Theme 26 | 27 | /// 28 | /// Gets the current used by Visual Studio. 29 | /// 30 | public Theme VisualStudioTheme => vsTheme; 31 | private Theme vsTheme; 32 | 33 | /// 34 | /// Occurs when has changed. 35 | /// 36 | public event EventHandler VisualStudioThemeChanged; 37 | 38 | private readonly ThemeResourceKey MainWindowActiveCaptionKey = 39 | new(category: new("624ed9c3-bdfd-41fa-96c3-7c824ea32e3d"), name: "MainWindowActiveCaption", ThemeResourceKeyType.BackgroundColor); 40 | 41 | private readonly IVsUIShell5 shell = VS.GetRequiredService(); 42 | 43 | /// 44 | /// Gets the current of Visual Studio. 45 | /// 46 | /// The used by the Visual Studio. 47 | /// Whether or not the value has changed; that is, if is different from . 48 | private bool GetVSTheme(out Theme theme) 49 | { 50 | theme = shell?.GetThemedWPFColor(MainWindowActiveCaptionKey).IsLight() == true ? Theme.Light : Theme.Dark; 51 | return vsTheme != theme; 52 | } 53 | 54 | #endregion 55 | 56 | private VsColorManager() 57 | { 58 | GetVSTheme(out vsTheme); 59 | (Application.Current.Resources.MergedDictionaries as INotifyCollectionChanged).CollectionChanged += (s, e) => 60 | { 61 | UpdateColors(); 62 | 63 | if (GetVSTheme(out Theme theme)) 64 | VisualStudioThemeChanged?.Invoke(this, vsTheme = theme); 65 | }; 66 | } 67 | 68 | /// 69 | /// Updates the s and es used by Visual Studio. 70 | /// 71 | public void UpdateColors() 72 | { 73 | foreach (var dictionary in Application.Current.Resources.MergedDictionaries.OfType()) 74 | foreach (var obj in dictionary) 75 | { 76 | DictionaryEntry entry = (DictionaryEntry)obj; 77 | 78 | if (GetConfig(entry.Key) is not ColorConfig config) 79 | continue; 80 | 81 | if (entry.Value is Brush brush) 82 | { 83 | Brush clone = brush.CloneCurrentValue(); 84 | 85 | if (clone is SolidColorBrush solid) 86 | solid.Color = DetermineColor(solid.Color, config); 87 | else if (clone is GradientBrush gradient) 88 | foreach (var stop in gradient.GradientStops) 89 | stop.Color = DetermineColor(stop.Color, config); 90 | 91 | dictionary[entry.Key] = clone; 92 | } 93 | else if (entry.Value is Color color) 94 | dictionary[entry.Key] = DetermineColor(color, config); 95 | } 96 | } 97 | 98 | #region Configuration 99 | 100 | /// 101 | /// Adds to . 102 | /// 103 | /// A key to configure. 104 | /// A containing configuration information. 105 | public void AddConfig(string key, ColorConfig config) 106 | { 107 | if (configs.ContainsKey(key)) 108 | configs[key] = config; 109 | else 110 | configs.Add(key, config); 111 | } 112 | 113 | /// 114 | /// Adds multiple to . 115 | /// 116 | /// A containing keys and their . 117 | public void AddConfigs(Dictionary configs) 118 | { 119 | foreach (var config in configs) 120 | AddConfig(config.Key, config.Value); 121 | } 122 | 123 | /// 124 | /// Removes a from . 125 | /// 126 | /// The key for which the is to be removed for. 127 | public void RemoveConfig(string key) 128 | { 129 | if (configs.ContainsKey(key)) 130 | configs.Remove(key); 131 | } 132 | 133 | /// 134 | /// Removes multiple s from . 135 | /// 136 | /// The keys for which the s are to be removed for. 137 | public void RemoveConfigs(params string[] keys) 138 | { 139 | foreach (var key in keys) 140 | RemoveConfig(key); 141 | } 142 | 143 | private ColorConfig GetConfig(object key) 144 | { 145 | if (key is ThemeResourceKey theme && 146 | (theme.KeyType == ThemeResourceKeyType.BackgroundColor || theme.KeyType == ThemeResourceKeyType.BackgroundBrush)) 147 | return configs.TryGetValue(theme.Name, out ColorConfig config) ? config : null; 148 | else if (key is string str) 149 | return configs.TryGetValue(str.Replace("VsBrush.", null).Replace("VsColor.", null), out ColorConfig config) ? config : null; 150 | else 151 | return null; 152 | } 153 | 154 | #endregion 155 | 156 | private Color DetermineColor(Color color, ColorConfig config) => 157 | !config.IsTranslucent || (config.TransparentOnGray && color.R == color.G && color.G == color.B) ? 158 | (vsTheme == Theme.Light ? TransparentWhite : TranslucentBlack) : 159 | Color.FromArgb(Math.Min(config.Opacity, color.A), color.R, color.G, color.B); 160 | } 161 | 162 | /// 163 | /// Represents the configuration for a or used by Visual Studio. 164 | /// 165 | /// Whether or not to make the or completely transparent if it is gray. 166 | /// Whether or not to allow . 167 | /// The opacity to make the or . 168 | public class ColorConfig(bool transparentOnGray = true, bool translucent = false, byte opacity = 0x38) 169 | { 170 | /// 171 | /// Represents the default for a or . 172 | /// 173 | /// Equivalent to a initialized to its default value; that is, with no parameters modified. 174 | public static readonly ColorConfig Default = new(); 175 | /// 176 | /// Represents the for a layered or . 177 | /// 178 | /// 179 | /// Equivalent to a with the following properties: 180 | /// 181 | /// 182 | /// to 183 | /// 184 | /// 185 | /// to . 186 | /// 187 | /// 188 | /// to 0x7F. 189 | /// 190 | /// 191 | /// 192 | public static readonly ColorConfig Layered = new(transparentOnGray: false, translucent: true, opacity: 0x7F); 193 | 194 | public bool TransparentOnGray { get; } = transparentOnGray; 195 | 196 | public bool IsTranslucent { get; } = translucent; 197 | 198 | public byte Opacity { get; } = opacity; 199 | } 200 | -------------------------------------------------------------------------------- /MicaVisualStudio/MicaVisualStudioPackage.cs: -------------------------------------------------------------------------------- 1 | namespace MicaVisualStudio; 2 | 3 | /// 4 | /// This is the class that implements the package exposed by this assembly. 5 | /// 6 | /// 7 | /// 8 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 9 | /// is to implement the IVsPackage interface and register itself with the shell. 10 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 11 | /// to do it: it derives from the Package class that provides the implementation of the 12 | /// IVsPackage interface and uses the registration attributes defined in the framework to 13 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 14 | /// utility what data to put into .pkgdef file. 15 | /// 16 | /// 17 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 18 | /// 19 | /// 20 | [PackageRegistration(AllowsBackgroundLoading = true, UseManagedResourcesOnly = true)] 21 | [Guid(PackageGuidString)] 22 | [ProvideOptionPage(typeof(OptionsProvider.GeneralOptions), "Mica Visual Studio", "General", 0, 0, true, SupportsProfiles = true)] 23 | [ProvideProfile(typeof(OptionsProvider.GeneralOptions), "Mica Visual Studio", "General", 0, 0, true)] 24 | [ProvideOptionPage(typeof(OptionsProvider.ToolOptions), "Mica Visual Studio", "\u200BTool Windows", 0, 0, true, SupportsProfiles = true)] 25 | [ProvideProfile(typeof(OptionsProvider.ToolOptions), "Mica Visual Studio", "Tool Windows", 0, 0, true)] 26 | [ProvideOptionPage(typeof(OptionsProvider.DialogOptions), "Mica Visual Studio", "Dialog Windows", 0, 0, true, SupportsProfiles = true)] 27 | [ProvideProfile(typeof(OptionsProvider.DialogOptions), "Mica Visual Studio", "Dialog Windows", 0, 0, true)] 28 | [ProvideAutoLoad(UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)] 29 | [ProvideAutoLoad(UIContextGuids.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] 30 | [ProvideAutoLoad(UIContextGuids.EmptySolution, PackageAutoLoadFlags.BackgroundLoad)] 31 | [ProvideMenuResource("Menus.ctmenu", 1)] 32 | public sealed class MicaVisualStudioPackage : AsyncPackage 33 | { 34 | /// 35 | /// MicaVisualStudioPackage GUID string. 36 | /// 37 | public const string PackageGuidString = "1a10bdf6-6cb0-415e-8ddd-f16d897f1e4a"; 38 | 39 | #region Package Members 40 | 41 | private ThemeHelper theme; 42 | private WindowObserver observer; 43 | 44 | private VsColorManager colors; 45 | private VsWindowStyler styler; 46 | 47 | private (string Content, ImageMoniker Image) queuedInfo; 48 | 49 | /// 50 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 51 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 52 | /// 53 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 54 | /// A provider for progress updates. 55 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 56 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 57 | { 58 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 59 | 60 | try 61 | { 62 | WindowObserver.MainWindow.Loaded += Window_Loaded; 63 | 64 | if (Environment.OSVersion.Version.Build < 22000) //Allow Windows 11 or later 65 | { 66 | queuedInfo = ("Mica Visual Studio is not compatible with Windows 10 and earlier.", KnownMonikers.StatusWarning); 67 | return; 68 | } 69 | 70 | colors = VsColorManager.Instance; 71 | 72 | #region Resource Keys 73 | 74 | colors.AddConfigs(new() 75 | { 76 | { "Background", new(translucent: true) }, 77 | 78 | { "SolidBackgroundFillQuaternary", new(translucent: true) }, 79 | 80 | //{ "SolidBackgroundFillTertiary", ColorConfig.Default }, 81 | //{ "EnvironmentLayeredBackground", new(transparentOnGray: true, translucent: true, opacity: 0x7F) }, 82 | 83 | { "EnvironmentBackground", new(translucent: true) }, 84 | { "EnvironmentBackgroundGradient", ColorConfig.Default }, 85 | 86 | { "ActiveCaption", ColorConfig.Layered }, 87 | { "InactiveCaption", ColorConfig.Layered }, 88 | 89 | { "MainWindowActiveCaption", ColorConfig.Default }, 90 | { "MainWindowInactiveCaption", ColorConfig.Default }, 91 | 92 | { "ToolWindow", ColorConfig.Default }, 93 | { "ToolWindowGroup", ColorConfig.Default }, 94 | { "ToolWindowBackground", ColorConfig.Default }, 95 | { "ToolWindowFloatingFrame", ColorConfig.Default }, 96 | { "ToolWindowFloatingFrameInactive", ColorConfig.Default }, 97 | 98 | { "Default", ColorConfig.Default }, 99 | 100 | { "Window", ColorConfig.Default }, 101 | { "WindowPanel", new(translucent: true) }, 102 | 103 | { "CommandBarGradient", ColorConfig.Default }, 104 | { "CommandBarGradientBegin", ColorConfig.Default }, 105 | 106 | { "ListBox", ColorConfig.Layered }, 107 | { "ListItemBackgroundHover", new(transparentOnGray: false, translucent: true) }, 108 | 109 | { "SelectedItemActive", ColorConfig.Layered }, 110 | { "SelectedItemInactive", ColorConfig.Layered }, 111 | 112 | { "Unfocused", ColorConfig.Layered }, 113 | 114 | { "Caption", ColorConfig.Layered }, 115 | 116 | { "TextBoxBackground", ColorConfig.Layered }, 117 | { "SearchBoxBackground", ColorConfig.Layered }, 118 | 119 | { "Button", ColorConfig.Layered }, 120 | { "ButtonFocused", ColorConfig.Default }, 121 | 122 | { "ComboBoxBackground", ColorConfig.Layered }, 123 | 124 | { "InfoBarBorder", ColorConfig.Default }, 125 | 126 | { "ToolWindowTabMouseOverBackgroundGradient", ColorConfig.Layered }, 127 | 128 | { "Page", ColorConfig.Default }, 129 | { "PageBackground", ColorConfig.Default }, 130 | 131 | { "BrandedUIBackground", ColorConfig.Default }, 132 | 133 | { "ScrollBarBackground", ColorConfig.Layered }, 134 | { "ScrollBarArrowBackground", ColorConfig.Default }, 135 | { "ScrollBarArrowDisabledBackground", ColorConfig.Default }, 136 | 137 | { "AutoHideResizeGrip", ColorConfig.Default }, 138 | { "AutoHideResizeGripDisabled", ColorConfig.Default }, 139 | 140 | { "Content", ColorConfig.Default }, 141 | { "ContentSelected", ColorConfig.Layered }, 142 | { "ContentMouseOver", ColorConfig.Layered }, 143 | { "ContentInactiveSelected", ColorConfig.Layered }, 144 | 145 | { "Wonderbar", ColorConfig.Default }, 146 | { "WonderbarMouseOver", ColorConfig.Layered }, 147 | { "WonderbarTreeInactiveSelected", ColorConfig.Default }, 148 | 149 | { "Details", ColorConfig.Layered }, 150 | 151 | { "BackgroundLowerRegion", ColorConfig.Default }, 152 | { "WizardBackgroundLowerRegion", ColorConfig.Default } 153 | }); 154 | colors.UpdateColors(); 155 | 156 | #endregion 157 | 158 | if (General.Instance.ForceTransparency) 159 | (styler = VsWindowStyler.Instance).Listen(); 160 | 161 | theme = ThemeHelper.Instance; 162 | observer = WindowObserver.Instance; 163 | 164 | await BackdropCommands.InitializeAsync(package: this); 165 | 166 | RefreshPreferences(); //Set app theme 167 | 168 | if (WindowObserver.MainWindow.Visibility == Visibility.Visible) //We're late, so add all windows 169 | { 170 | WindowObserver.AllWindows.ForEach(AddWindow); 171 | WindowObserver.MainWindow.Loaded -= Window_Loaded; 172 | } 173 | else if (WindowObserver.CurrentWindow is Window window) //Apply to start window 174 | AddWindow(window); 175 | 176 | observer.WindowOpened += WindowOpened; 177 | //windows.WindowClosed += WindowClosed; 178 | 179 | colors.VisualStudioThemeChanged += ThemeChanged; 180 | theme.SystemThemeChanged += ThemeChanged; 181 | 182 | General.Saved += GeneralSaved; 183 | } 184 | catch (Exception ex) 185 | { 186 | #if DEBUG 187 | Debug.WriteLine($"Error initializing Mica Visual Studio: {ex.Message}"); 188 | #endif 189 | 190 | progress.Report(new("Mica Visual Studio", $"Error while initializing Mica Visual Studio:\n{ex.Message}")); 191 | queuedInfo = ($"Error while initializing Mica Visual Studio: {ex.Message} ({ex.GetType().Name})\n{ex.StackTrace}", KnownMonikers.StatusError); 192 | } 193 | 194 | void Window_Loaded(object sender, RoutedEventArgs args) 195 | { 196 | if (queuedInfo.Content is not null) 197 | VS.InfoBar.CreateAsync(new(queuedInfo.Content, queuedInfo.Image)).Result.TryShowInfoBarUIAsync().Forget(); 198 | 199 | WindowObserver.MainWindow.Loaded -= Window_Loaded; 200 | } 201 | 202 | void AddWindow(Window window) 203 | { 204 | observer.AppendWindow(window); 205 | ApplyWindowPreferences( 206 | window.GetHandle(), 207 | window, 208 | WindowHelper.GetWindowType(window)); 209 | } 210 | } 211 | 212 | #region Event Handlers 213 | 214 | private void WindowOpened(Window sender, WindowActionEventArgs args) => ApplyWindowPreferences(args.WindowHandle, sender, args.WindowType); 215 | 216 | //private void WindowClosed(Window sender, WindowActionEventArgs args) { } 217 | 218 | private void ThemeChanged(object sender, Theme args) => RefreshPreferences(); 219 | 220 | private void GeneralSaved(General sender) => RefreshPreferences(); 221 | 222 | private void RefreshPreferences() 223 | { 224 | General general = General.Instance; 225 | theme.SetAppTheme(EvaluateTheme(general.AppTheme)); 226 | 227 | foreach (var entry in observer.Windows) 228 | ApplyWindowPreferences(entry.Key, entry.Value.Window, entry.Value.Type, firstTime: false, general); 229 | } 230 | 231 | #endregion 232 | 233 | private void ApplyWindowPreferences( 234 | IntPtr handle, 235 | Window window, 236 | WindowType type, 237 | bool firstTime = true, 238 | General general = null) 239 | { 240 | general ??= General.Instance; 241 | 242 | if (firstTime && //Remove caption buttons once 243 | window is not null && 244 | HwndSource.FromHwnd(handle) is HwndSource source) 245 | { 246 | WindowHelper.ExtendFrameIntoClientArea(handle); 247 | source.CompositionTarget.BackgroundColor = Colors.Transparent; 248 | 249 | //Don't remove caption buttons from windows that need them 250 | if (window.WindowStyle == WindowStyle.None || window is not DialogWindowBase) 251 | WindowHelper.RemoveCaptionButtons(source); 252 | } 253 | 254 | switch (type) 255 | { 256 | default: 257 | case WindowType.Main: 258 | ApplyWindowAttributes( 259 | general.Theme, 260 | (CornerPreference)general.CornerPreference, 261 | (BackdropType)general.Backdrop); 262 | break; 263 | 264 | case WindowType.Tool when general.ToolWindows: 265 | ApplyWindowAttributes( 266 | general.ToolTheme, 267 | (CornerPreference)general.ToolCornerPreference, 268 | (BackdropType)general.ToolBackdrop); 269 | break; 270 | 271 | case WindowType.Dialog when general.DialogWindows: 272 | ApplyWindowAttributes( 273 | general.DialogTheme, 274 | (CornerPreference)general.DialogCornerPreference, 275 | (BackdropType)general.DialogBackdrop); 276 | break; 277 | } 278 | 279 | void ApplyWindowAttributes(int theme, CornerPreference corner, BackdropType backdrop) 280 | { 281 | WindowHelper.EnableDarkMode(handle, EvaluateTheme(theme) == Theme.Dark); 282 | WindowHelper.SetCornerPreference(handle, corner); 283 | WindowHelper.SetBackdropType(handle, window is not null || backdrop != BackdropType.Glass ? backdrop : BackdropType.None); 284 | } 285 | } 286 | 287 | private Theme EvaluateTheme(int theme) => (Theme)theme switch 288 | { 289 | Theme.VisualStudio => colors.VisualStudioTheme, 290 | Theme.System => this.theme.SystemTheme, 291 | _ => (Theme)theme 292 | }; 293 | 294 | protected override void Dispose(bool disposing) 295 | { 296 | theme?.Dispose(); 297 | observer?.Dispose(); 298 | 299 | styler?.Dispose(); 300 | 301 | if (disposing) 302 | { 303 | observer?.WindowOpened -= WindowOpened; 304 | //observer?.WindowClosed -= WindowClosed; 305 | 306 | colors?.VisualStudioThemeChanged -= ThemeChanged; 307 | theme?.SystemThemeChanged -= ThemeChanged; 308 | 309 | General.Saved -= GeneralSaved; 310 | } 311 | 312 | base.Dispose(disposing); 313 | } 314 | 315 | #endregion 316 | } 317 | -------------------------------------------------------------------------------- /MicaVisualStudio/Interop/WindowHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Microsoft.VisualStudio.OLE.Interop; 3 | 4 | namespace MicaVisualStudio.Interop; 5 | 6 | /// 7 | /// Represents a static wrapper for various P/Invoke functions involving windows. 8 | /// 9 | public static class WindowHelper 10 | { 11 | #region DWM 12 | 13 | [DllImport("dwmapi.dll", EntryPoint = "DwmExtendFrameIntoClientArea")] 14 | private static extern int ExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset); 15 | 16 | [DllImport("dwmapi.dll", EntryPoint = "DwmSetWindowAttribute")] 17 | private static extern int SetWindowAttribute(IntPtr hwnd, int dwAttribute, ref int pvAttribute, int cbAttribute); 18 | 19 | [DllImport("dwmapi.dll", EntryPoint = "DwmEnableBlurBehindWindow")] 20 | private static extern int EnableBlurBehindWindow(IntPtr hWnd, ref DWM_BLURBEHIND pBlurBehind); 21 | 22 | [DllImport("gdi32.dll")] 23 | private static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); 24 | 25 | [DllImport("user32.dll")] 26 | private static extern bool SetWindowCompositionAttribute(IntPtr hwnd, ref WINDOWCOMPOSITIONATTRIBDATA pwcad); 27 | 28 | private const int DWMWA_SYSTEMBACKDROP_TYPE = 38, 29 | DWMWA_USE_IMMERSIVE_DARK_MODE = 20, 30 | DWMWA_WINDOW_CORNER_PREFERENCE = 33; 31 | 32 | private const uint DWM_BB_ENABLE = 0x1, 33 | DWM_BB_BLURREGION = 0x2, 34 | DWM_BB_TRANSITIONONMAXIMIZED = 0x4; 35 | 36 | private const int WCA_ACCENT_POLICY = 19; 37 | 38 | private const int ACCENT_DISABLED = 0, 39 | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4; 40 | 41 | private struct MARGINS 42 | { 43 | public int cxLeftWidth; 44 | public int cxRightWidth; 45 | public int cyTopHeight; 46 | public int cyBottomHeight; 47 | } 48 | 49 | private struct DWM_BLURBEHIND 50 | { 51 | public uint dwFlags; 52 | public bool fEnable; 53 | public IntPtr hRgnBlur; 54 | public bool fTransitionOnMaximized; 55 | } 56 | 57 | private struct WINDOWCOMPOSITIONATTRIBDATA 58 | { 59 | public int Attrib; 60 | public IntPtr pvData; 61 | public uint cbData; 62 | } 63 | 64 | private struct AccentPolicy 65 | { 66 | public int AccentState; 67 | #pragma warning disable 0649 68 | public int AccentFlags; 69 | public int GradientColor; 70 | public int AnimationId; 71 | #pragma warning restore 0649 72 | } 73 | 74 | /// 75 | /// Extends the frame of the specified into its client area. 76 | /// 77 | /// A handle to a window. 78 | public static void ExtendFrameIntoClientArea(IntPtr hWnd) 79 | { 80 | MARGINS margins = new() { cxLeftWidth = -1, cxRightWidth = -1, cyTopHeight = -1, cyBottomHeight = -1 }; 81 | ExtendFrameIntoClientArea(hWnd, ref margins); 82 | } 83 | 84 | /// 85 | /// Enables or disables dark mode for the specified . 86 | /// 87 | /// A handle to a window. 88 | /// Whether or not to enable dark mode. 89 | public static void EnableDarkMode(IntPtr hWnd, bool enable) 90 | { 91 | int mode = enable ? 1 : 0; 92 | SetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ref mode, sizeof(int)); 93 | } 94 | 95 | /// 96 | /// Sets the of the specified . 97 | /// 98 | /// A handle to a window. 99 | /// The to set. 100 | public static void SetCornerPreference(IntPtr hWnd, CornerPreference preference) 101 | { 102 | int corner = (int)preference; 103 | SetWindowAttribute(hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, ref corner, sizeof(int)); 104 | } 105 | 106 | /// 107 | /// Sets the of the specified . 108 | /// 109 | /// A handle to a window. 110 | /// The to set. 111 | public static void SetBackdropType(IntPtr hWnd, BackdropType backdrop) 112 | { 113 | int type = (int)(backdrop == BackdropType.Glass ? BackdropType.None : backdrop); 114 | SetWindowAttribute(hWnd, DWMWA_SYSTEMBACKDROP_TYPE, ref type, sizeof(int)); 115 | 116 | EnableWindowTransparency(hWnd, enable: backdrop == BackdropType.Glass); 117 | } 118 | 119 | /// 120 | /// Enables or disables transparency for the specified . 121 | /// 122 | /// A handle to a window. 123 | /// Whether or not to enable transparency. 124 | public static void EnableWindowTransparency(IntPtr hWnd, bool enable) 125 | { 126 | DWM_BLURBEHIND bb = new() 127 | { 128 | fEnable = enable, 129 | hRgnBlur = enable ? CreateRectRgn(-2, -2, -1, -1) : IntPtr.Zero, 130 | fTransitionOnMaximized = true, 131 | dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION | DWM_BB_TRANSITIONONMAXIMIZED 132 | }; 133 | _ = EnableBlurBehindWindow(hWnd, ref bb); 134 | } 135 | 136 | /// 137 | /// Enables or disables a blur effect used as the specified 's background. 138 | /// 139 | /// A handle to a window. 140 | /// Whether or not to enable blurring. 141 | public static void EnableWindowBlur(IntPtr hWnd, bool enable) 142 | { 143 | AccentPolicy policy = new() 144 | { 145 | AccentState = enable ? ACCENT_ENABLE_ACRYLICBLURBEHIND : ACCENT_DISABLED 146 | }; 147 | 148 | var size = Marshal.SizeOf(); 149 | var ptr = Marshal.AllocHGlobal(size); 150 | Marshal.StructureToPtr(policy, ptr, fDeleteOld: false); 151 | 152 | WINDOWCOMPOSITIONATTRIBDATA data = new() 153 | { 154 | Attrib = WCA_ACCENT_POLICY, 155 | pvData = ptr, 156 | cbData = (uint)size 157 | }; 158 | 159 | SetWindowCompositionAttribute(hWnd, ref data); 160 | Marshal.FreeHGlobal(ptr); 161 | } 162 | 163 | #endregion 164 | 165 | #region Caption Buttons 166 | 167 | [DllImport("user32.dll")] 168 | private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); 169 | 170 | [DllImport("user32.dll")] 171 | private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); 172 | 173 | [DllImport("user32.dll")] 174 | private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl); 175 | 176 | [DllImport("user32.dll")] 177 | private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); 178 | 179 | [DllImport("user32.dll")] 180 | private static extern IntPtr EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); 181 | 182 | [DllImport("user32.dll")] 183 | private static extern int TrackPopupMenuEx(IntPtr hMenu, uint uFlags, int x, int y, IntPtr hwnd, IntPtr lptpm); 184 | 185 | [DllImport("dwmapi.dll", EntryPoint = "DwmGetWindowAttribute")] 186 | private static extern bool GetWindowAttribute(IntPtr hwnd, uint dwAttribute, out RECT pvAttribute, uint cbAttribute); 187 | 188 | private const int WM_NULL = 0x0, 189 | WM_DESTROY = 0x2, 190 | WM_STYLECHANGING = 0x7C, 191 | WM_NCRBUTTONUP = 0xA5, 192 | WM_SYSKEYDOWN = 0x104, 193 | WM_SYSCOMMAND = 0x112; 194 | 195 | private const uint SC_RESTORE = 0xF120, 196 | SC_MOVE = 0xF010, 197 | SC_SIZE = 0xF000, 198 | SC_MAXIMIZE = 0xF030, 199 | SC_MINIMIZE = 0xF020, 200 | SC_CLOSE = 0xF060; 201 | 202 | private const uint TPM_LEFTBUTTON = 0x0, 203 | TPM_RIGHTBUTTON = 0x2, 204 | TPM_RIGHTALIGN = 0x8, 205 | TPM_NONOTIFY = 0x80, 206 | TPM_RETURNCMD = 0x100, 207 | TPM_NOANIMATION = 0x4000; 208 | 209 | private const uint SW_NORMAL = 1, 210 | SW_MAXIMIZE = 3; 211 | 212 | private const uint MF_ENABLED = 0x0, 213 | MF_GRAYED = 0x1; 214 | 215 | private const int HTCAPTION = 2; 216 | 217 | private const int VK_SPACE = 0x20; 218 | 219 | private const int DWMWA_CAPTION_BUTTON_BOUNDS = 5; 220 | 221 | private struct WINDOWPLACEMENT 222 | { 223 | public uint length; 224 | public uint flags; 225 | public uint showCmd; 226 | public POINT ptMinPosition; 227 | public POINT ptMaxPosition; 228 | public RECT rcNormalPosition; 229 | public RECT rcDevice; 230 | } 231 | 232 | private struct STYLESTRUCT 233 | { 234 | #pragma warning disable 0649 235 | public uint styleOld; 236 | public uint styleNew; 237 | #pragma warning restore 0649 238 | } 239 | 240 | /// 241 | /// Gets the height, in pixels, of the specified 's title bar. 242 | /// 243 | /// A handle to a window. 244 | /// The height of the specified 's title bar. 245 | public static int GetTitleBarHeight(IntPtr hWnd) 246 | { 247 | GetWindowAttribute(hWnd, DWMWA_CAPTION_BUTTON_BOUNDS, out RECT bounds, (uint)Marshal.SizeOf()); 248 | return bounds.bottom - bounds.top; 249 | } 250 | 251 | /// 252 | /// Patches the specified to remove its caption buttons but retain system menu functionality. 253 | /// 254 | /// An to patch. 255 | public static void RemoveCaptionButtons(HwndSource source) 256 | { 257 | const int MenuSpacing = 2; 258 | WindowType type = GetWindowType(source.RootVisual as Window); 259 | 260 | GetSystemMenu(source.Handle, bRevert: false); //Make sure window menu is created 261 | 262 | source.AddHook(Hook); 263 | SetWindowStyles(source.Handle, GetWindowStyles(source.Handle)); //Refresh styles 264 | 265 | IntPtr Hook(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 266 | { 267 | switch (msg) 268 | { 269 | case WM_DESTROY: 270 | HwndSource.FromHwnd(hWnd)?.RemoveHook(Hook); //Avoid memory leaks 271 | break; 272 | 273 | case WM_STYLECHANGING when (int)wParam == GWL_STYLE: 274 | STYLESTRUCT structure = Marshal.PtrToStructure(lParam); 275 | 276 | if (type == WindowType.Main || //Apply WS_OVERLAPPEDWINDOW style to main window 277 | ((WindowStyles)structure.styleNew).HasFlag(WindowStyles.ThickFrame)) //or any sizable window 278 | structure.styleNew |= (uint)WindowStyles.OverlappedWindow; 279 | 280 | structure.styleNew &= (uint)~WindowStyles.SystemMenu; //Remove the WS_SYSMENU style 281 | 282 | Marshal.StructureToPtr(structure, lParam, fDeleteOld: true); 283 | handled = true; 284 | break; 285 | 286 | case WM_NCRBUTTONUP when (int)wParam == HTCAPTION: 287 | ShowMenu( 288 | hWnd, 289 | (short)lParam, 290 | (short)((int)lParam >> 16 /*Y position shift*/), 291 | keyboard: false); 292 | handled = true; 293 | break; 294 | 295 | case WM_SYSKEYDOWN when (int)wParam == VK_SPACE && IsAltPressed(lParam): 296 | int height = GetTitleBarHeight(hWnd); 297 | 298 | POINT point = new() { x = MenuSpacing, y = (height > 0 ? height : System.Windows.Forms.SystemInformation.CaptionHeight) + MenuSpacing }; 299 | ClientToScreen(hWnd, ref point); 300 | 301 | ShowMenu(hWnd, point.x, point.y, keyboard: true); 302 | break; 303 | } 304 | 305 | return IntPtr.Zero; 306 | } 307 | 308 | void ShowMenu(IntPtr hWnd, int x, int y, bool keyboard) 309 | { 310 | IntPtr menu = GetSystemMenu(hWnd, bRevert: false); 311 | 312 | uint minimize = type == WindowType.Dialog ? MF_GRAYED : MF_ENABLED; 313 | uint maximize = GetWindowStyles(source.Handle).HasFlag(WindowStyles.MaximizeBox) ? MF_ENABLED : MF_GRAYED; 314 | uint size = GetWindowStyles(source.Handle).HasFlag(WindowStyles.ThickFrame) ? MF_ENABLED : MF_GRAYED; 315 | 316 | if (GetWindowPlacement(hWnd, out WINDOWPLACEMENT placement)) 317 | if (placement.showCmd == SW_NORMAL) 318 | { 319 | EnableMenuItem(menu, SC_RESTORE, MF_GRAYED); 320 | EnableMenuItem(menu, SC_MOVE, MF_ENABLED); 321 | EnableMenuItem(menu, SC_SIZE, size); 322 | EnableMenuItem(menu, SC_MINIMIZE, minimize); 323 | EnableMenuItem(menu, SC_MAXIMIZE, maximize); 324 | EnableMenuItem(menu, SC_CLOSE, MF_ENABLED); 325 | } 326 | else if (placement.showCmd == SW_MAXIMIZE) 327 | { 328 | EnableMenuItem(menu, SC_RESTORE, MF_ENABLED); 329 | EnableMenuItem(menu, SC_MOVE, MF_GRAYED); 330 | EnableMenuItem(menu, SC_SIZE, MF_GRAYED); 331 | EnableMenuItem(menu, SC_MINIMIZE, minimize); 332 | EnableMenuItem(menu, SC_MAXIMIZE, MF_GRAYED); 333 | EnableMenuItem(menu, SC_CLOSE, MF_ENABLED); 334 | } 335 | 336 | int cmd = TrackPopupMenuEx( 337 | menu, 338 | TPM_RETURNCMD | TPM_NONOTIFY | //Don't notify as we'll send a message later 339 | ((uint)System.Windows.Forms.SystemInformation.PopupMenuAlignment * TPM_RIGHTALIGN) | 340 | (keyboard ? TPM_LEFTBUTTON : TPM_RIGHTBUTTON) | 341 | (keyboard ? TPM_NOANIMATION : 0 /*Default fade animation*/), 342 | keyboard && placement.showCmd == SW_MAXIMIZE ? x - MenuSpacing : x, 343 | y, 344 | hWnd, 345 | IntPtr.Zero); 346 | 347 | if (cmd != WM_NULL) 348 | SendMessage(hWnd, WM_SYSCOMMAND, (IntPtr)cmd, IntPtr.Zero); 349 | } 350 | 351 | bool IsAltPressed(IntPtr lParam) => 352 | (((int)lParam >> 29) //Context code shift 353 | & 1) //First bit mask 354 | == 1; //TRUE 355 | } 356 | 357 | #endregion 358 | 359 | #region Window Styles 360 | 361 | [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")] 362 | private static extern uint GetWindowLong(IntPtr hWnd, int nIndex); 363 | 364 | [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] 365 | private static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); 366 | 367 | private const int GWL_STYLE = -16, 368 | GWL_EXSTYLE = -20; 369 | 370 | /// 371 | /// Determines the of the specified . 372 | /// 373 | /// A to critique. 374 | /// The of the specified . 375 | public static WindowType GetWindowType(Window window) 376 | { 377 | if (window == WindowObserver.MainWindow) 378 | return WindowType.Main; 379 | else if (window is not null && //Check if window is WPF 380 | (window.WindowStyle == WindowStyle.None || //and has no style 381 | window.Owner is null)) //or no owner 382 | return WindowType.Tool; 383 | else 384 | return WindowType.Dialog; 385 | } 386 | 387 | /// 388 | /// Gets the of the specified . 389 | /// 390 | /// A handle to a window. 391 | /// The of the specified . 392 | public static WindowStyles GetWindowStyles(IntPtr hWnd) => (WindowStyles)GetWindowLong(hWnd, GWL_STYLE); 393 | /// 394 | /// Sets the of the specified . 395 | /// 396 | /// A handle to a window. 397 | /// The to set. 398 | public static void SetWindowStyles(IntPtr hWnd, WindowStyles styles) => SetWindowLong(hWnd, GWL_STYLE, (uint)styles); 399 | 400 | /// 401 | /// Gets the of the specified . 402 | /// 403 | /// A handle to a window. 404 | /// The of the specified . 405 | public static WindowStylesEx GetExtendedWindowStyles(IntPtr hWnd) => (WindowStylesEx)GetWindowLong(hWnd, GWL_EXSTYLE); 406 | /// 407 | /// Sets the of the specified . 408 | /// 409 | /// A handle to a window. 410 | /// The to set. 411 | public static void SetExtendedWindowStyles(IntPtr hWnd, WindowStylesEx styles) => SetWindowLong(hWnd, GWL_EXSTYLE, (uint)styles); 412 | 413 | #endregion 414 | 415 | #region Windowing 416 | 417 | [DllImport("user32.dll")] 418 | private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect); 419 | 420 | [DllImport("user32.dll")] 421 | private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); 422 | 423 | private const uint SWP_NOSIZE = 0x0001, 424 | SWP_NOZORDER = 0x0004, 425 | SWP_NOACTIVATE = 0x0010; 426 | 427 | /// 428 | /// Gets the current position of the specified . 429 | /// 430 | /// A handle to a window. 431 | /// A representing the position in screen coordinates. 432 | public static System.Drawing.Point GetWindowPosition(IntPtr hWnd) 433 | { 434 | RECT rect = new(); 435 | GetWindowRect(hWnd, ref rect); 436 | return new(rect.left, rect.top); 437 | } 438 | 439 | /// 440 | /// Moves the specified the specified . 441 | /// 442 | /// A handle to a window. 443 | /// A in screen coordinates. 444 | public static void MoveWindow(IntPtr hWnd, System.Drawing.Point location) => SetWindowPos( 445 | hWnd, IntPtr.Zero, 446 | location.X, location.Y, 447 | 0, 0, 448 | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); 449 | 450 | /// 451 | /// Offsets the specified by the specified . 452 | /// 453 | /// A handle to a window. 454 | /// A in screen coordinates. 455 | public static void OffsetWindow(IntPtr hWnd, System.Drawing.Point offset) 456 | { 457 | var pos = GetWindowPosition(hWnd); 458 | MoveWindow(hWnd, location: new(pos.X + offset.X, pos.Y + offset.Y)); 459 | } 460 | 461 | #endregion 462 | 463 | #region Utilities 464 | 465 | [DllImport("user32.dll")] 466 | private static extern bool IsWindow(IntPtr hWnd); 467 | 468 | [DllImport("user32.dll")] 469 | private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 470 | 471 | [DllImport("user32.dll")] 472 | private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags); 473 | 474 | [DllImport("user32.dll")] 475 | private static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildProc lpEnumFunc, IntPtr lParam); 476 | 477 | private const uint LWA_ALPHA = 0x00000002; 478 | 479 | private delegate bool EnumChildProc(IntPtr hwnd, IntPtr lParam); 480 | 481 | /// 482 | /// Determines whether the specified is alive; that is, whether it is still valid. 483 | /// 484 | /// A handle to a window. 485 | /// if the specified is still alive. Otherwise, . 486 | public static bool IsAlive(IntPtr hWnd) => IsWindow(hWnd); 487 | 488 | /// 489 | /// Gets the process ID associated with the specified . 490 | /// 491 | /// A handle to a window. 492 | /// The ID of the process that owns the specified . 493 | public static int GetProcessId(IntPtr hWnd) 494 | { 495 | GetWindowThreadProcessId(hWnd, out uint procId); 496 | return (int)procId; 497 | } 498 | 499 | /// 500 | /// Makes the specified layered by adding the style. 501 | /// 502 | /// A handle to a window. 503 | public static void MakeLayered(IntPtr hWnd) 504 | { 505 | SetExtendedWindowStyles(hWnd, GetExtendedWindowStyles(hWnd) | WindowStylesEx.Layered); 506 | SetLayeredWindowAttributes( 507 | hWnd, 508 | (uint)ColorTranslator.ToWin32(System.Drawing.Color.Black), 509 | 0xFF, //Set opactiy to 100% 510 | LWA_ALPHA); 511 | } 512 | 513 | /// 514 | /// Enumerates the children of the specified . 515 | /// 516 | /// A handle to a window. 517 | /// An of window handles representing the children of the specified . 518 | public static IEnumerable GetChildren(IntPtr hWnd) 519 | { 520 | List handles = []; 521 | EnumChildWindows(hWnd, Proc, IntPtr.Zero); 522 | return handles; 523 | 524 | bool Proc(IntPtr hwnd, IntPtr lParam) 525 | { 526 | handles.Add(hwnd); 527 | return true; 528 | } 529 | } 530 | 531 | /// 532 | /// Gets the handle of the specified . 533 | /// 534 | /// A to retrieve the handle of. 535 | /// The handle of the specified . 536 | public static IntPtr GetHandle(this Window window) 537 | { 538 | WindowInteropHelper interop = new(window); 539 | interop.EnsureHandle(); 540 | return interop.Handle; 541 | } 542 | 543 | #endregion 544 | } 545 | 546 | #region Enums 547 | 548 | /// 549 | /// Specifies the type of backdrop to use in . 550 | /// 551 | public enum BackdropType 552 | { 553 | Auto, 554 | None, 555 | Mica, 556 | Acrylic, 557 | Tabbed, 558 | Glass 559 | } 560 | 561 | /// 562 | /// Specifies the corner preference to use in . 563 | /// 564 | public enum CornerPreference 565 | { 566 | Default, 567 | Square, 568 | Round, 569 | RoundSmall 570 | } 571 | 572 | /// 573 | /// Specifies the type of a . 574 | /// 575 | public enum WindowType 576 | { 577 | /// 578 | /// Specifies that the is the main window of the process. 579 | /// 580 | Main, 581 | 582 | /// 583 | /// Specifies that the is an additional top-level window, meant for tooling and options. 584 | /// 585 | Tool, 586 | 587 | /// 588 | /// Specifies that the is a child window, meant for responding to requests and displaying information. 589 | /// 590 | Dialog 591 | } 592 | 593 | /// 594 | /// Specifies the style(s) of a window. 595 | /// 596 | /// 597 | /// Used in: 598 | /// 599 | /// 600 | /// 601 | /// 602 | /// 603 | /// 604 | /// 605 | /// 606 | /// 607 | [Flags] 608 | public enum WindowStyles : uint 609 | { 610 | Border = 0x00800000, 611 | Caption = 0x00C00000, 612 | Child = 0x40000000, 613 | ClipChildren = 0x02000000, 614 | ClipSiblings = 0x04000000, 615 | Disabled = 0x08000000, 616 | DLGFrame = 0x00400000, 617 | Group = 0x00020000, 618 | HScroll = 0x00100000, 619 | Iconic = 0x20000000, 620 | Maximize = 0x01000000, 621 | MaximizeBox = 0x00010000, 622 | Minimize = 0x20000000, 623 | MinimizeBox = 0x00020000, 624 | Overlapped = 0x00000000, 625 | OverlappedWindow = Overlapped | Caption | SystemMenu | ThickFrame | MinimizeBox | MaximizeBox, 626 | Popup = 0x80000000, 627 | PopupWindow = Popup | Border | SystemMenu, 628 | SystemMenu = 0x00080000, 629 | TabStop = 0x00010000, 630 | ThickFrame = 0x00040000, 631 | Visible = 0x10000000, 632 | VScroll = 0x00200000 633 | } 634 | 635 | /// 636 | /// Specifies the extended style(s) of a window. 637 | /// 638 | /// 639 | /// Used in: 640 | /// 641 | /// 642 | /// 643 | /// 644 | /// 645 | /// 646 | /// 647 | /// 648 | /// 649 | public enum WindowStylesEx : uint 650 | { 651 | AcceptFiles = 0x00000010, 652 | AppWindow = 0x00040000, 653 | ClientEdge = 0x00000200, 654 | Composited = 0x02000000, 655 | ContextHelp = 0x00000400, 656 | ControlParent = 0x00010000, 657 | DLGModalFrame = 0x00000001, 658 | Layered = 0x00080000, 659 | LayoutRTL = 0x00400000, 660 | Left = 0x00000000, 661 | LeftScrollBar = 0x00004000, 662 | LTRReading = 0x00000000, 663 | MDIChild = 0x00000040, 664 | NoActivate = 0x08000000, 665 | NoInheritLayout = 0x00100000, 666 | NoParentNotify = 0x00000004, 667 | NoRedirectionBitmap = 0x00200000, 668 | OverlappedWindow = WindowEdge | ClientEdge, 669 | PaletteWindow = WindowEdge | ToolWindow | TopMost, 670 | Right = 0x00001000, 671 | RightScrollBar = 0x00000000, 672 | RTLReading = 0x00002000, 673 | StaticEdge = 0x00020000, 674 | ToolWindow = 0x00000080, 675 | TopMost = 0x00000008, 676 | Transparent = 0x00000020, 677 | WindowEdge = 0x00000100 678 | } 679 | 680 | #endregion 681 | -------------------------------------------------------------------------------- /MicaVisualStudio/VisualStudio/VsWindowStyler.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Windows.Shapes; 3 | using Microsoft.Internal.VisualStudio.PlatformUI; 4 | using Mono.Cecil.Cil; 5 | using MonoMod.Cil; 6 | using MonoMod.RuntimeDetour; 7 | using Expression = System.Linq.Expressions.Expression; 8 | 9 | namespace MicaVisualStudio.VisualStudio; 10 | 11 | /// 12 | /// Represents an observer that listens and styles Visual Studio windows. 13 | /// 14 | //This code is bad, but it works, so... 15 | public sealed class VsWindowStyler : IVsWindowFrameEvents, IDisposable 16 | { 17 | /// 18 | /// Gets the singleton instance of . 19 | /// 20 | public static VsWindowStyler Instance => field ??= new(); 21 | 22 | #region Keys 23 | 24 | private const string MultiViewHostTypeName = "Microsoft.VisualStudio.Editor.Implementation.WpfMultiViewHost"; 25 | 26 | private const string SolidBackgroundFillTertiaryLayeredKey = "VsBrush.SolidBackgroundFillTertiaryLayered", 27 | PopupBackgroundLayeredKey = "VsBrush.PopupBackgroundLayered"; 28 | 29 | private readonly ThemeResourceKey SolidBackgroundFillTertiaryKey = 30 | new(category: new("73708ded-2d56-4aad-b8eb-73b20d3f4bff"), name: "SolidBackgroundFillTertiary", ThemeResourceKeyType.BackgroundColor); 31 | 32 | private readonly ThemeResourceKey TextFillPrimaryKey = 33 | new(category: new("73708ded-2d56-4aad-b8eb-73b20d3f4bff"), name: "TextFillPrimary", ThemeResourceKeyType.BackgroundBrush); 34 | 35 | private readonly ThemeResourceKey TextOnAccentFillPrimaryKey = 36 | new(category: new("73708ded-2d56-4aad-b8eb-73b20d3f4bff"), name: "TextOnAccentFillPrimary", ThemeResourceKeyType.BackgroundBrush); 37 | 38 | private readonly ThemeResourceKey ScrollBarBackgroundKey = 39 | new(category: new("{624ed9c3-bdfd-41fa-96c3-7c824ea32e3d}"), name: "ScrollBarBackground", ThemeResourceKeyType.BackgroundBrush); 40 | 41 | #endregion 42 | 43 | #region Shells 44 | 45 | private readonly IVsUIShell shell = VS.GetRequiredService(); 46 | private readonly IVsUIShell5 shell5 = VS.GetRequiredService(); 47 | private readonly IVsUIShell7 shell7 = VS.GetRequiredService(); 48 | 49 | private uint cookie; 50 | 51 | #endregion 52 | 53 | #region Functions 54 | 55 | private Func get_WindowFrame_FrameView; 56 | private Func get_View_Content; 57 | private Func get_View_IsActive; 58 | private Func IsDockTarget; 59 | 60 | private DependencyProperty View_ContentProperty, 61 | View_IsActiveProperty; 62 | 63 | #endregion 64 | 65 | private ILHook visualHook, sourceHook; 66 | 67 | private bool makeLayered = true; 68 | 69 | private VsWindowStyler() { } 70 | 71 | /// 72 | /// Tells the to start listening to and styling Visual Studio windows. 73 | /// 74 | public void Listen() 75 | { 76 | if (disposed) 77 | return; 78 | 79 | #region Function Initialization 80 | 81 | var frameViewProp = Type.GetType("Microsoft.VisualStudio.Platform.WindowManagement.WindowFrame, Microsoft.VisualStudio.Platform.WindowManagement") 82 | .GetProperty("FrameView"); 83 | 84 | var frameParam = Expression.Parameter(typeof(IVsWindowFrame)); 85 | get_WindowFrame_FrameView = frameParam.Convert(frameViewProp.DeclaringType) 86 | .Property(frameViewProp) 87 | .Convert() 88 | .Compile(frameParam); 89 | 90 | var viewType = Type.GetType("Microsoft.VisualStudio.PlatformUI.Shell.View, Microsoft.VisualStudio.Shell.ViewManager"); 91 | var contentProp = viewType.GetProperty("Content"); 92 | 93 | var viewParam = Expression.Parameter(typeof(DependencyObject)); 94 | get_View_Content = viewParam.Convert(contentProp.DeclaringType) 95 | .Property(contentProp) 96 | .Compile(viewParam); 97 | 98 | View_ContentProperty = viewType.GetField("ContentProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy) 99 | .GetValue(null) as DependencyProperty; 100 | 101 | var isActiveProp = viewType.GetProperty("IsActive"); 102 | get_View_IsActive = viewParam.Convert(isActiveProp.DeclaringType) 103 | .Property(isActiveProp) 104 | .Compile(viewParam); 105 | 106 | View_IsActiveProperty = viewType.GetField("IsActiveProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy) 107 | .GetValue(null) as DependencyProperty; 108 | 109 | var dockType = Type.GetType("Microsoft.VisualStudio.PlatformUI.Shell.Controls.DockTarget, Microsoft.VisualStudio.Shell.ViewManager"); 110 | var objectParam = Expression.Parameter(typeof(object)); 111 | IsDockTarget = objectParam.TypeIs(dockType) 112 | .Compile(objectParam); 113 | 114 | #endregion 115 | 116 | #region Layered Brushes 117 | 118 | AddLayeredBrushes(); 119 | (Application.Current.Resources.MergedDictionaries as INotifyCollectionChanged).CollectionChanged += (s, e) => AddLayeredBrushes(); 120 | 121 | void AddLayeredBrushes() 122 | { 123 | var color = shell5?.GetThemedWPFColor(SolidBackgroundFillTertiaryKey) ?? default; 124 | 125 | SolidColorBrush halfBrush = new(color with { A = 0xFF / 2 }); //50% opacity 126 | SolidColorBrush quarterBrush = new(color with { A = 0xFF / 4 }); //25% opacity 127 | 128 | foreach (var dictionary in Application.Current.Resources.MergedDictionaries.OfType()) 129 | { 130 | if (!dictionary.Contains(SolidBackgroundFillTertiaryLayeredKey)) 131 | dictionary.Add(SolidBackgroundFillTertiaryLayeredKey, halfBrush); 132 | 133 | if (!dictionary.Contains(PopupBackgroundLayeredKey)) 134 | if (VsColorManager.Instance.VisualStudioTheme == Theme.Light || 135 | (quarterBrush.Color.R != quarterBrush.Color.G && quarterBrush.Color.G != quarterBrush.Color.B)) 136 | dictionary.Add(PopupBackgroundLayeredKey, quarterBrush); 137 | else //Full acrylic experience for those who can handle it 138 | dictionary.Add(PopupBackgroundLayeredKey, new SolidColorBrush(Colors.Transparent with { A = 0x01 })); 139 | } 140 | } 141 | 142 | #endregion 143 | 144 | #region Listeners 145 | 146 | #pragma warning disable VSTHRD010 //Invoke single-threaded types on Main thread 147 | cookie = shell7.AdviseWindowFrameEvents(this); 148 | #pragma warning restore VSTHRD010 //Invoke single-threaded types on Main thread 149 | 150 | EventManager.RegisterClassHandler( 151 | dockType, 152 | FrameworkElement.LoadedEvent, 153 | new RoutedEventHandler((s, e) => ApplyToDockTarget(s as Border))); 154 | 155 | if (AppDomain.CurrentDomain.GetAssemblies() 156 | .FirstOrDefault(i => i.GetName().Name == "Microsoft.VisualStudio.Editor.Implementation")? 157 | .GetTypes() 158 | .FirstOrDefault(i => i.FullName == MultiViewHostTypeName) is Type hostType) 159 | EventManager.RegisterClassHandler( 160 | hostType, 161 | FrameworkElement.LoadedEvent, 162 | new RoutedEventHandler((s, e) => ApplyToContent(s as DockPanel, applyToDock: false))); 163 | 164 | WindowObserver.Instance.WindowOpened += (s, e) => 165 | { 166 | if (s is not null) 167 | ApplyToWindow(s); 168 | }; 169 | 170 | visualHook = CreatePostfix( 171 | typeof(Visual).GetMethod("AddVisualChild", BindingFlags.Instance | BindingFlags.NonPublic), 172 | AddVisualChild); 173 | 174 | sourceHook = CreatePostfix( 175 | typeof(HwndSource).GetProperty("RootVisual").SetMethod, 176 | RootVisualChanged); 177 | 178 | static void AddVisualChild(Visual instance, Visual child) 179 | { 180 | if (instance is ContentControl or ContentPresenter or Decorator or Panel && //Avoid unnecessary work 181 | instance is FrameworkElement content && 182 | GetIsTracked(content) && Instance is VsWindowStyler styler) 183 | styler.ApplyToContent(content, applyToDock: false); 184 | } 185 | 186 | static void RootVisualChanged(HwndSource instance, Visual value) 187 | { 188 | if (value is null || instance.CompositionTarget is null) 189 | return; 190 | 191 | //Visual Studio popup 192 | if (value is FrameworkElement element && 193 | element.Parent is Popup && //Check if root of popup 194 | element.FindDescendant(i => i.Name == "DropShadowBorder") is Border drop) 195 | { 196 | //Remove current border and update background to be translucent 197 | drop.BorderBrush = Brushes.Transparent; 198 | drop.SetResourceReference(Border.BackgroundProperty, PopupBackgroundLayeredKey); 199 | 200 | //Get left-side offset to move window by 201 | var offset = GetPopupOffset(element); 202 | 203 | if (offset == default) //Get if any offset already cached 204 | if (element.FindDescendant() is ToolTip tip) 205 | { 206 | SetPopupOffset(element, offset = new(tip.Margin.Left, tip.Margin.Top)); 207 | tip.Margin = default; 208 | } 209 | else 210 | { 211 | SetPopupOffset(element, offset = new(drop.Margin.Left, drop.Margin.Top)); 212 | drop.Margin = default; 213 | } 214 | 215 | //Get and update window position 216 | var deviceOffset = instance.CompositionTarget.TransformToDevice.Transform(offset); 217 | Interop.WindowHelper.OffsetWindow(instance.Handle, offset: new((int)deviceOffset.X, (int)deviceOffset.Y)); 218 | 219 | //Add acrylic, shadow, and border 220 | Interop.WindowHelper.EnableWindowBlur(instance.Handle, enable: true); 221 | Interop.WindowHelper.SetCornerPreference(instance.Handle, CornerPreference.Round); 222 | } 223 | else if (value is not Window) //Avoid already handled values 224 | instance.CompositionTarget.BackgroundColor = Colors.Transparent; 225 | } 226 | 227 | #endregion 228 | 229 | makeLayered = General.Instance.LayeredWindows; 230 | 231 | ApplyToAllWindows(); 232 | ApplyToAllWindowPanesAsync().Forget(); 233 | } 234 | 235 | #region Apply To All 236 | 237 | private void ApplyToAllWindows() => WindowObserver.AllWindows.ForEach(ApplyToWindow); 238 | 239 | private async Task ApplyToAllWindowPanesAsync() 240 | { 241 | if (shell is null) 242 | return; 243 | 244 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 245 | 246 | shell.GetToolWindowEnum(out IEnumWindowFrames toolEnum); 247 | shell.GetDocumentWindowEnum(out IEnumWindowFrames docEnum); 248 | 249 | foreach (var frame in toolEnum.ToEnumerable().Concat(docEnum.ToEnumerable())) 250 | ApplyToWindowFrame(frame); 251 | } 252 | 253 | #endregion 254 | 255 | private void ApplyToWindowFrame(IVsWindowFrame frame) 256 | { 257 | if (get_WindowFrame_FrameView(frame) is not DependencyObject view) 258 | return; 259 | 260 | if (get_View_Content(view) is not Grid host) 261 | { 262 | WeakReference weakFrame = new(frame); 263 | 264 | view.AddWeakOneTimePropertyChangeHandler(View_ContentProperty, (s, e) => 265 | { 266 | if (weakFrame.TryGetTarget(out IVsWindowFrame frame)) 267 | ApplyToWindowFrame(frame); 268 | }); 269 | return; 270 | } 271 | 272 | ApplyToContent(host); 273 | } 274 | 275 | private void ApplyToWindow(Window window) 276 | { 277 | //Wizards (e.g. packaged app creation wizard) 278 | if (window.GetType().FullName == "Microsoft.VisualStudio.WizardFrameworkWpf.WizardBase") 279 | { 280 | ApplyToContent(window, applyToDock: false); 281 | return; 282 | } 283 | 284 | foreach (var element in window.FindDescendants()) 285 | 286 | //Warning dialog, footer 287 | if (window is DialogWindowBase && element is Button { Name: "OKButton" } button && 288 | button.FindAncestor()?.FindAncestor() is Border buttonFooter) 289 | buttonFooter.SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey); 290 | 291 | else if (element is Border border) 292 | { 293 | //Footer 294 | if (border.Name == "FooterBorder") 295 | border.SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey); 296 | 297 | //Dock target 298 | else if (IsDockTarget(border)) 299 | ApplyToDockTarget(border); 300 | } 301 | } 302 | 303 | private void ApplyToDockTarget(Border dock, bool applyToContent = true) 304 | { 305 | if (dock.Name == "ViewFrameTarget") 306 | dock.SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey); 307 | else 308 | dock.Background = Brushes.Transparent; //Smoke layer underneath tabs 309 | 310 | var descendants = dock.FindDescendants(); 311 | if (!descendants.Any()) 312 | return; 313 | 314 | //Content area 315 | descendants.FindElement("PART_ContentPanel")? 316 | .SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey); 317 | 318 | //Title bar 319 | descendants.FindElement("PART_Header")?.Background = Brushes.Transparent; 320 | 321 | if (descendants.FindElement("ToolWindowBorder") is Border border) //Body 322 | { 323 | border.Background = Brushes.Transparent; 324 | 325 | if (applyToContent && border.FindDescendant() is Grid host) 326 | ApplyToContent(host, applyToDock: false); 327 | } 328 | 329 | if (descendants.FindElement("PART_TabPanel") is Panel tabs) //Tab strip 330 | foreach (var tab in tabs.Children.OfType()) //Tab items 331 | { 332 | tab.Background = Brushes.Transparent; 333 | 334 | if (tab.DataContext is not DependencyObject view) 335 | continue; 336 | 337 | ApplyTabForeground(tab, view); 338 | 339 | if (GetIsTracked(tab)) 340 | continue; 341 | 342 | SetIsTracked(tab, value: true); 343 | WeakReference weakTab = new(tab); 344 | 345 | tab.AddWeakPropertyChangeHandler(TabItem.IsSelectedProperty, (s, e) => 346 | { 347 | if (s is TabItem tab && tab.DataContext is DependencyObject view) 348 | ApplyTabForeground(tab, view); 349 | }); 350 | view.AddPropertyChangeHandler(View_IsActiveProperty, (s, e) => 351 | { 352 | if (weakTab.TryGetTarget(out TabItem tab) && s is DependencyObject view) 353 | ApplyTabForeground(tab, view); 354 | }); 355 | 356 | void ApplyTabForeground(TabItem item, DependencyObject view) => tab.SetResourceReference( 357 | Control.ForegroundProperty, 358 | tab.IsSelected && get_View_IsActive(view) ? TextOnAccentFillPrimaryKey : TextFillPrimaryKey); 359 | } 360 | } 361 | 362 | private void ApplyToContent(FrameworkElement content, bool applyToDock = true) 363 | { 364 | if (!content.IsLoaded) 365 | content.AddWeakOneTimeHandler(FrameworkElement.LoadedEvent, (s, e) => ApplyToContent(s as FrameworkElement, applyToDock)); 366 | 367 | if (applyToDock && content.FindAncestor(i => i.GetVisualOrLogicalParent(), IsDockTarget) is Border dock) 368 | ApplyToDockTarget(dock, applyToContent: false); 369 | 370 | foreach (var element in content.FindDescendants().Append(content)) 371 | { 372 | if (element is ContentControl or ContentPresenter or Decorator or Panel && !GetIsTracked(element)) 373 | SetIsTracked(element, value: true); //Track visual children 374 | 375 | if (element is ToolBar bar) 376 | { 377 | bar.Background = bar.BorderBrush = Brushes.Transparent; 378 | (bar.Parent as ToolBarTray)?.Background = Brushes.Transparent; 379 | } 380 | 381 | else if (makeLayered && element is HwndHost { IsLoaded: true } host) 382 | { 383 | var sources = PresentationSource.CurrentSources.OfType().ToArray(); 384 | if (sources.Any(i => i.Handle == host.Handle)) 385 | continue; 386 | 387 | var children = Interop.WindowHelper.GetChildren(host.Handle); 388 | 389 | if (sources.FirstOrDefault(i => children.Contains(i.Handle)) is not HwndSource source) 390 | Interop.WindowHelper.MakeLayered(host.Handle); 391 | } 392 | 393 | else if (element is Control control) 394 | switch (control.Name) 395 | { 396 | case "gitWindowView" or //Git changes window 397 | "focusedWindowView" or //Git repository window 398 | "historyView" or //Commit history 399 | "detailsView" or //Git commit details 400 | "focusedDetailsContainer" or //Git commit details container 401 | "teamExplorerFrame" or //Team explorer window 402 | "createPullRequestView": //New PR window 403 | control.Background = Brushes.Transparent; 404 | 405 | foreach (var e in control.LogicalDescendants().Append(control)) 406 | switch (e.Name) 407 | { 408 | //Section header 409 | case "borderHeader" when e is Border { Style: Style bs } b: 410 | b.Style = new(bs.TargetType, bs) 411 | { 412 | Setters = { new Setter(Border.BorderBrushProperty, Brushes.Transparent) } 413 | }; 414 | break; 415 | 416 | //Command buttons 417 | case "gitAction" or 418 | "detailsView" when 419 | e.TryFindResource("TESectionCommandButtonStyle") is Style { Setters.IsSealed: false } ss: 420 | Setter bg = new(Control.BackgroundProperty, Brushes.Transparent), 421 | bb = new(Control.BorderBrushProperty, Brushes.Transparent); 422 | 423 | ss.Setters.Add(bg); 424 | ss.Setters.Add(bb); 425 | 426 | if (ss.Triggers.OfType() 427 | .FirstOrDefault(i => i.Property == System.Windows.UIElement.IsEnabledProperty) is Trigger t) 428 | { 429 | t.Setters.Add(bg); 430 | t.Setters.Add(bb); 431 | } 432 | break; 433 | 434 | //??? 435 | case "thisPageControl" when e is Control c: 436 | c.Background = Brushes.Transparent; 437 | break; 438 | 439 | //Team explorer, project selector 440 | case "navControl" when e is Control c: 441 | c.Background = Brushes.Transparent; 442 | break; 443 | 444 | //Git branch selector 445 | case "branchesList" when e.GetVisualOrLogicalParent() 446 | .GetVisualOrLogicalParent() 447 | .GetVisualOrLogicalParent() 448 | .GetVisualOrLogicalParent() is Control bc: 449 | bc.Background = Brushes.Transparent; 450 | break; 451 | 452 | //Commit history 453 | case "historyView" when e.GetVisualOrLogicalParent() 454 | .GetVisualOrLogicalParent() 455 | .GetVisualOrLogicalParent() is Border hb: 456 | hb.Background = Brushes.Transparent; 457 | break; 458 | 459 | //Commit history list 460 | case "historyListView" when e is ListView { View: GridView g }: 461 | g.ColumnHeaderContainerStyle = new(g.ColumnHeaderContainerStyle.TargetType, g.ColumnHeaderContainerStyle) 462 | { 463 | Setters = 464 | { 465 | new Setter(Control.BackgroundProperty, Brushes.Transparent), 466 | new Setter(Control.BorderBrushProperty, Brushes.Transparent) 467 | } 468 | }; 469 | 470 | foreach (var c in g.Columns.Select(i => i.Header).OfType()) 471 | { 472 | c.ApplyTemplate(); 473 | c.FindDescendant(i => i.Name == "HeaderBorder")?.BorderBrush = Brushes.Transparent; 474 | } 475 | break; 476 | 477 | //Commit diff 478 | case "detailsViewMainGrid" when e is Grid g && g.GetVisualOrLogicalParent() 479 | .GetVisualOrLogicalParent() is Border db: 480 | db.Background = Brushes.Transparent; 481 | break; 482 | 483 | //Commit diff info 484 | case "pageContentViewer" when e.GetVisualOrLogicalParent() 485 | .GetVisualOrLogicalParent() is Border pb: 486 | pb.Background = Brushes.Transparent; 487 | break; 488 | 489 | //Commit diff presenter dock buttons 490 | case "dockToBottomButton" or 491 | "dockToRightButton" or 492 | "undockButton" or 493 | "maximizeMinimizeButton" or 494 | "closeButton" when 495 | e is Button b: 496 | b.Style = new(b.Style.TargetType, b.Style) 497 | { 498 | Setters = 499 | { 500 | new Setter(Control.BackgroundProperty, Brushes.Transparent), 501 | } 502 | }; 503 | break; 504 | 505 | //Git push, pull etc. buttons 506 | case "actionButton" or 507 | "fetchButton" or 508 | "pullButton" or 509 | "pushButton" or 510 | "syncButton" or 511 | "additionalOperationsButton" when 512 | e is Button b: 513 | b.Style = new(b.Style.TargetType, b.Style) 514 | { 515 | Triggers = 516 | { 517 | new Trigger 518 | { 519 | Property = System.Windows.UIElement.IsEnabledProperty, 520 | Value = false, 521 | Setters = 522 | { 523 | new Setter(Control.BackgroundProperty, Brushes.Transparent), 524 | new Setter(Control.BorderBrushProperty, Brushes.Transparent) 525 | } 526 | } 527 | } 528 | }; 529 | break; 530 | 531 | //Git repository window, presenters 532 | case "detailsContent" or 533 | "detailsFullWindowContent" or 534 | "detailsRightContent" or 535 | "detailsBottomContent" when 536 | e is ContentControl c: 537 | SetIsTracked(c, value: true); 538 | break; 539 | 540 | case "statusControl" or //Actions/tool bar 541 | "thisPageControl" or //Changes 542 | "inactiveRepoContent" or //Create repo 543 | "sectionContainer" or //Branches and tags 544 | "amendCheckBox" when //Checkbox... for amending... 545 | e is Control c: 546 | c.Background = Brushes.Transparent; 547 | break; 548 | 549 | default: 550 | if (e is ItemsControl { ItemContainerStyle: Style ics } ic) //Changes 551 | { 552 | ic.Background = Brushes.Transparent; 553 | 554 | if (!ics.IsSealed) 555 | { 556 | ics.Setters.Add(new Setter(Control.BackgroundProperty, Brushes.Transparent)); 557 | ics.Setters.Add(new Setter(Control.BorderBrushProperty, Brushes.Transparent)); 558 | } 559 | } 560 | break; 561 | } 562 | break; 563 | 564 | //Host of WpfTextView I guess 565 | case "WpfTextViewHost": 566 | control.Resources["outlining.chevron.expanded.background"] = 567 | control.Resources["outlining.chevron.collapsed.background"] = Brushes.Transparent; 568 | break; 569 | 570 | //Editor, output, etc. text 571 | case "WpfTextView" when element is ContentControl: 572 | control.Background = Brushes.Transparent; 573 | control.FindDescendant()?.Background = Brushes.Transparent; 574 | break; 575 | 576 | //Packaged app configurations list 577 | case "PackageConfigurationsList" when control is DataGrid grid: 578 | grid.Background = grid.RowBackground = Brushes.Transparent; 579 | 580 | if (grid.CellStyle is Style { Setters.IsSealed: false } cs) 581 | cs.Setters.Add(new Setter(Control.BackgroundProperty, Brushes.Transparent)); 582 | break; 583 | 584 | //VSIX manfiest editor 585 | case "VsixEditorControl": 586 | control.Background = Brushes.Transparent; 587 | 588 | foreach (var tab in control.FindDescendants()) 589 | tab.Background = Brushes.Transparent; 590 | break; 591 | 592 | default: 593 | switch (control.GetType().FullName) 594 | { 595 | //AppxManifest editor 596 | case "Microsoft.VisualStudio.AppxManifestDesigner.Designer.ManifestDesignerUserControlProxy": 597 | case "Microsoft.VisualStudio.AppxManifestDesigner.Designer.ManifestDesignerUserControl": 598 | control.Background = Brushes.Transparent; 599 | break; 600 | 601 | //Resource editor 602 | case "Microsoft.VisualStudio.ResourceExplorer.UI.ResourceGroupEditorControl": 603 | control.Background = Brushes.Transparent; 604 | break; 605 | } 606 | break; 607 | } 608 | 609 | else if (element is Panel panel) 610 | switch (panel.GetType().FullName) 611 | { 612 | //Editor window, root 613 | case MultiViewHostTypeName: 614 | element.Resources[ScrollBarBackgroundKey] = Brushes.Transparent; 615 | break; 616 | 617 | //Editor window, bottom container 618 | case "Microsoft.VisualStudio.Text.Utilities.ContainerMargin" when 619 | !panel.FindDescendants().Any(i => i.GetType().FullName == "Microsoft.VisualStudio.Text.Utilities.ContainerMargin"): 620 | panel.SetResourceReference(Panel.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey); 621 | break; 622 | 623 | //Editor window, left side icon container 624 | case "Microsoft.VisualStudio.Text.Editor.Implementation.GlyphMarginGrid" when 625 | panel.Background is SolidColorBrush solid && solid.Color != Brushes.Transparent.Color: 626 | panel.SetResourceReference(Panel.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey); 627 | break; 628 | 629 | //Scroll bar intersection 630 | case "Microsoft.VisualStudio.Editor.Implementation.BottomRightCornerSpacerMargin": 631 | panel.Background = Brushes.Transparent; 632 | break; 633 | 634 | //Editor window, collapsed item container 635 | case "Microsoft.VisualStudio.Text.Editor.Implementation.AdornmentLayer": 636 | foreach (var rectangle in panel.FindDescendants()) 637 | rectangle.SetResourceReference(Shape.FillProperty, SolidBackgroundFillTertiaryLayeredKey); 638 | break; 639 | 640 | default: 641 | if (panel is DockPanel || (panel is Grid { Background: not null } && panel.FindAncestor() is not Button or TextBox)) 642 | panel.Background = Brushes.Transparent; 643 | break; 644 | } 645 | 646 | else if (element is Border border) 647 | switch (border.GetType().FullName) 648 | { 649 | //Output window, base layer 650 | case "Microsoft.VisualStudio.PlatformUI.OutputWindow": 651 | border.Background = Brushes.Transparent; 652 | break; 653 | 654 | //Editor window, file errors container 655 | case "Microsoft.VisualStudio.UI.Text.Wpf.FileHealthIndicator.Implementation.FileHealthIndicatorMargin": 656 | border.Background = Brushes.Transparent; 657 | break; 658 | } 659 | } 660 | } 661 | 662 | private static ILHook CreatePostfix(MethodInfo info, Action action) => 663 | new(info, context => 664 | { 665 | ILCursor cursor = new(context); 666 | cursor.Index = cursor.Instrs.Count - 1; //Move cursor to end, but before return 667 | 668 | cursor.Emit(OpCodes.Ldarg_0); //this 669 | cursor.Emit(OpCodes.Ldarg_1); //First parameter 670 | 671 | cursor.EmitDelegate(action); 672 | }); 673 | 674 | #region IsTrackedProperty 675 | 676 | /// 677 | /// Gets the value of the attached property from a given . 678 | /// 679 | /// The from which to read the property value. 680 | /// The value of the attached property. 681 | public static bool GetIsTracked(FrameworkElement target) => 682 | (bool)target.GetValue(IsTrackedProperty); 683 | 684 | /// 685 | /// Sets the value of the attached property from a given . 686 | /// 687 | /// The on which to set the attached property. 688 | /// The property value to set. 689 | public static void SetIsTracked(FrameworkElement target, bool value) => 690 | target.SetValue(IsTrackedProperty, value); 691 | 692 | /// 693 | /// Identifies the MicaVisualStudio.VisualStudio.VsWindowStyler.IsTracked dependency property. 694 | /// 695 | public static readonly DependencyProperty IsTrackedProperty = 696 | DependencyProperty.RegisterAttached("IsTracked", typeof(bool), typeof(VsWindowStyler), new(defaultValue: false)); 697 | 698 | #endregion 699 | 700 | #region PopupOffsetProperty 701 | 702 | /// 703 | /// Gets the value of the attached property from a given . 704 | /// 705 | /// The from which to read the property value. 706 | /// The value of the attached property. 707 | public static Point GetPopupOffset(FrameworkElement target) => 708 | (Point)target.GetValue(PopupOffsetProperty); 709 | 710 | /// 711 | /// Sets the value of the attached property from a given . 712 | /// 713 | /// The on which to set the attached property. 714 | /// The property value to set. 715 | public static void SetPopupOffset(FrameworkElement target, Point value) => 716 | target.SetValue(PopupOffsetProperty, value); 717 | 718 | /// 719 | /// Identifies the MicaVisualStudio.VisualStudio.VsWindowStyler.IsTracked dependency property. 720 | /// 721 | public static readonly DependencyProperty PopupOffsetProperty = 722 | DependencyProperty.RegisterAttached("PopupOffset", typeof(Point), typeof(VsWindowStyler), new(defaultValue: default(Point))); 723 | 724 | #endregion 725 | 726 | #region IVsWindowFrameEvents 727 | 728 | public void OnFrameCreated(IVsWindowFrame frame) { } 729 | 730 | public void OnFrameDestroyed(IVsWindowFrame frame) { } 731 | 732 | public void OnFrameIsVisibleChanged(IVsWindowFrame frame, bool newIsVisible) { } 733 | 734 | public void OnFrameIsOnScreenChanged(IVsWindowFrame frame, bool newIsOnScreen) 735 | { 736 | if (newIsOnScreen) 737 | ApplyToWindowFrame(frame); 738 | } 739 | 740 | public void OnActiveFrameChanged(IVsWindowFrame oldFrame, IVsWindowFrame newFrame) 741 | { 742 | if (newFrame is not null) 743 | ApplyToWindowFrame(newFrame); 744 | } 745 | 746 | #endregion 747 | 748 | #region Dispose 749 | 750 | private bool disposed; 751 | 752 | /// 753 | /// Disposes the singleton instance of . 754 | /// 755 | public void Dispose() 756 | { 757 | if (disposed) 758 | return; 759 | 760 | visualHook?.Dispose(); 761 | sourceHook?.Dispose(); 762 | visualHook = sourceHook = null; 763 | 764 | #pragma warning disable VSTHRD010 //Invoke single-threaded types on Main thread 765 | if (cookie > 0) 766 | shell7.UnadviseWindowFrameEvents(cookie); 767 | #pragma warning restore VSTHRD010 //Invoke single-threaded types on Main thread 768 | 769 | disposed = true; 770 | } 771 | 772 | #endregion 773 | } 774 | --------------------------------------------------------------------------------