microsoft/TypeAgent

Public

mirrored fromhttps://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cleanup/test-consolelogs

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

dotnet/autoShell/AutoShell_Themes.cs

397lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System;
5using System.Collections.Generic;
6using System.Diagnostics;
7using System.IO;
8using System.Linq;
9using System.Text;
10using System.Threading.Tasks;
11using Microsoft.Win32;
12
13namespace autoShell
14{
15 /// <summary>
16 /// AutoShell class partial for managing Windows themes.
17 /// </summary>
18 /// <remarks>This code was mostly generated by AI under the guidance of a human.</remarks>
19 internal partial class AutoShell
20 {
21 private static string s_previousTheme = null;
22 private static Dictionary<string, string> s_themeDictionary = null;
23 private static Dictionary<string, string> s_themeDisplayNameDictionary = null;
24
25 private static void LoadThemes()
26 {
27 s_themeDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
28 s_themeDisplayNameDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
29
30 string[] themePaths = new string[]
31 {
32 Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Resources", "Themes"),
33 Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Resources", "Ease of Access Themes"),
34 Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "Windows", "Themes")
35 };
36
37 foreach (string themesFolder in themePaths)
38 {
39 if (Directory.Exists(themesFolder))
40 {
41 foreach (string themeFile in Directory.GetFiles(themesFolder, "*.theme"))
42 {
43 string themeName = Path.GetFileNameWithoutExtension(themeFile);
44 if (!s_themeDictionary.ContainsKey(themeName))
45 {
46 s_themeDictionary[themeName] = themeFile;
47
48 // Parse display name from theme file
49 string displayName = GetThemeDisplayName(themeFile);
50 if (!string.IsNullOrEmpty(displayName) && !s_themeDisplayNameDictionary.ContainsKey(displayName))
51 {
52 s_themeDisplayNameDictionary[displayName] = themeName;
53 }
54 }
55 }
56 }
57 }
58
59 s_themeDictionary["previous"] = GetCurrentTheme();
60 }
61
62 /// <summary>
63 /// Parses the display name from a .theme file.
64 /// </summary>
65 /// <param name="themeFilePath">The full path to the .theme file.</param>
66 /// <returns>The display name, or null if not found.</returns>
67 private static string GetThemeDisplayName(string themeFilePath)
68 {
69 try
70 {
71 foreach (string line in File.ReadLines(themeFilePath))
72 {
73 if (line.StartsWith("DisplayName=", StringComparison.OrdinalIgnoreCase))
74 {
75 string displayName = line.Substring("DisplayName=".Length).Trim();
76 // Handle localized strings (e.g., @%SystemRoot%\System32\themeui.dll,-2013)
77 if (displayName.StartsWith("@"))
78 {
79 displayName = ResolveLocalizedString(displayName);
80 }
81 return displayName;
82 }
83 }
84 }
85 catch
86 {
87 // Ignore errors reading theme file
88 }
89 return null;
90 }
91
92 /// <summary>
93 /// Resolves a localized string resource reference.
94 /// </summary>
95 /// <param name="localizedString">The localized string reference (e.g., @%SystemRoot%\System32\themeui.dll,-2013).</param>
96 /// <returns>The resolved string, or the original string if resolution fails.</returns>
97 private static string ResolveLocalizedString(string localizedString)
98 {
99 try
100 {
101 // Remove the @ prefix
102 string resourcePath = localizedString.Substring(1);
103 // Expand environment variables
104 int commaIndex = resourcePath.LastIndexOf(',');
105 if (commaIndex > 0)
106 {
107 string dllPath = Environment.ExpandEnvironmentVariables(resourcePath.Substring(0, commaIndex));
108 string resourceIdStr = resourcePath.Substring(commaIndex + 1);
109 if (int.TryParse(resourceIdStr, out int resourceId))
110 {
111 StringBuilder buffer = new StringBuilder(256);
112 IntPtr hModule = LoadLibraryEx(dllPath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
113 if (hModule != IntPtr.Zero)
114 {
115 try
116 {
117 int result = LoadString(hModule, (uint)Math.Abs(resourceId), buffer, buffer.Capacity);
118 if (result > 0)
119 {
120 return buffer.ToString();
121 }
122 }
123 finally
124 {
125 FreeLibrary(hModule);
126 }
127 }
128 }
129 }
130 }
131 catch
132 {
133 // Ignore errors resolving localized string
134 }
135 return localizedString;
136 }
137
138 /// <summary>
139 /// Returns a list of all installed Windows themes.
140 /// </summary>
141 /// <returns>A list of theme names (without the .theme extension).</returns>
142 public static List<string> GetInstalledThemes()
143 {
144 HashSet<string> themes = new HashSet<string>();
145
146 themes.UnionWith(s_themeDictionary.Keys);
147 themes.UnionWith(s_themeDisplayNameDictionary.Keys);
148
149 return themes.ToList();
150 }
151
152 /// <summary>
153 /// Gets the current Windows theme name.
154 /// </summary>
155 /// <returns>The current theme name, or null if it cannot be determined.</returns>
156 public static string GetCurrentTheme()
157 {
158 try
159 {
160 using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes"))
161 {
162 if (key != null)
163 {
164 string currentThemePath = key.GetValue("CurrentTheme") as string;
165 if (!string.IsNullOrEmpty(currentThemePath))
166 {
167 return Path.GetFileNameWithoutExtension(currentThemePath);
168 }
169 }
170 }
171 }
172 catch
173 {
174 // Ignore errors reading registry
175 }
176 return null;
177 }
178
179 /// <summary>
180 /// Applies a Windows theme by name.
181 /// </summary>
182 /// <param name="themeName">The name of the theme to apply (without .theme extension).</param>
183 /// <returns>True if the theme was applied successfully, false otherwise.</returns>
184 public static bool ApplyTheme(string themeName)
185 {
186 string themePath = FindThemePath(themeName);
187 if (string.IsNullOrEmpty(themePath))
188 {
189 return false;
190 }
191
192 try
193 {
194 string previous = GetCurrentTheme();
195
196 if (themeName.ToLowerInvariant() != "previous")
197 {
198 // Apply theme by opening the .theme file
199 Process p = Process.Start(themePath);
200 s_previousTheme = previous;
201 p.Exited += P_Exited;
202
203 return true;
204 }
205 else
206 {
207 bool success = RevertToPreviousTheme();
208
209 if (success)
210 {
211 s_previousTheme = previous;
212 }
213
214 return success;
215 }
216 }
217 catch
218 {
219 return false;
220 }
221 }
222
223 private static void P_Exited(object sender, EventArgs e)
224 {
225 Debug.WriteLine(((Process)sender).ExitCode);
226 }
227
228 /// <summary>
229 /// Reverts to the previous Windows theme.
230 /// </summary>
231 /// <returns>True if the previous theme was applied successfully, false otherwise.</returns>
232 public static bool RevertToPreviousTheme()
233 {
234 if (string.IsNullOrEmpty(s_previousTheme))
235 {
236 return false;
237 }
238
239 string themePath = FindThemePath(s_previousTheme);
240 if (string.IsNullOrEmpty(themePath))
241 {
242 return false;
243 }
244
245 try
246 {
247 Process.Start(themePath);
248 return true;
249 }
250 catch
251 {
252 return false;
253 }
254 }
255
256 /// <summary>
257 /// Gets the name of the previous theme.
258 /// </summary>
259 /// <returns>The previous theme name, or null if no theme change has been made.</returns>
260 public static string GetPreviousTheme()
261 {
262 return s_previousTheme;
263 }
264
265 /// <summary>
266 /// Finds the full path to a theme file by name or display name.
267 /// </summary>
268 /// <param name="themeName">The name of the theme (file name without extension or display name).</param>
269 /// <returns>The full path to the theme file, or null if not found.</returns>
270 private static string FindThemePath(string themeName)
271 {
272 // First check by file name
273 if (s_themeDictionary.TryGetValue(themeName, out string themePath))
274 {
275 return themePath;
276 }
277
278 // Then check by display name
279 if (s_themeDisplayNameDictionary.TryGetValue(themeName, out string fileNameFromDisplay))
280 {
281 if (s_themeDictionary.TryGetValue(fileNameFromDisplay, out string themePathFromDisplay))
282 {
283 return themePathFromDisplay;
284 }
285 }
286
287 return null;
288 }
289
290 /// <summary>
291 /// Sets the Windows light or dark mode by modifying registry keys.
292 /// </summary>
293 /// <param name="useLightMode">True for light mode, false for dark mode.</param>
294 /// <returns>True if the mode was set successfully, false otherwise.</returns>
295 [System.Runtime.Versioning.SupportedOSPlatform("windows")]
296 public static bool SetLightDarkMode(bool useLightMode)
297 {
298 try
299 {
300 const string personalizePath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
301 int value = useLightMode ? 1 : 0;
302
303 using (RegistryKey key = Registry.CurrentUser.OpenSubKey(personalizePath, true))
304 {
305 if (key == null)
306 {
307 return false;
308 }
309
310 // Set apps theme
311 key.SetValue("AppsUseLightTheme", value, RegistryValueKind.DWord);
312
313 // Set system theme (taskbar, Start menu, etc.)
314 key.SetValue("SystemUsesLightTheme", value, RegistryValueKind.DWord);
315 }
316
317 // Broadcast settings change notification to update UI
318 BroadcastSettingsChange();
319
320 return true;
321 }
322 catch
323 {
324 return false;
325 }
326 }
327
328 /// <summary>
329 /// Toggles between light and dark mode.
330 /// </summary>
331 /// <returns>True if the toggle was successful, false otherwise.</returns>
332 [System.Runtime.Versioning.SupportedOSPlatform("windows")]
333 public static bool ToggleLightDarkMode()
334 {
335 bool? currentMode = GetCurrentLightMode();
336 if (currentMode.HasValue)
337 {
338 return SetLightDarkMode(!currentMode.Value);
339 }
340 return false;
341 }
342
343 /// <summary>
344 /// Broadcasts a WM_SETTINGCHANGE message to notify the system of theme changes.
345 /// </summary>
346 private static void BroadcastSettingsChange()
347 {
348 const int HWND_BROADCAST = 0xffff;
349 const int WM_SETTINGCHANGE = 0x001A;
350 const uint SMTO_ABORTIFHUNG = 0x0002;
351 IntPtr result;
352 SendMessageTimeout(
353 (IntPtr)HWND_BROADCAST,
354 WM_SETTINGCHANGE,
355 IntPtr.Zero,
356 "ImmersiveColorSet",
357 SMTO_ABORTIFHUNG,
358 1000,
359 out result);
360 }
361
362 /// <summary>
363 /// Gets the current light/dark mode setting from the registry.
364 /// </summary>
365 /// <returns>True if light mode, false if dark mode, null if unable to determine.</returns>
366 [System.Runtime.Versioning.SupportedOSPlatform("windows")]
367 private static bool? GetCurrentLightMode()
368 {
369 try
370 {
371 const string personalizePath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
372
373 using (RegistryKey key = Registry.CurrentUser.OpenSubKey(personalizePath, false))
374 {
375 if (key == null)
376 {
377 return null;
378 }
379
380 // Read AppsUseLightTheme value (0 = dark, 1 = light)
381 object value = key.GetValue("AppsUseLightTheme");
382 if (value is int intValue)
383 {
384 return intValue == 1;
385 }
386
387 return null;
388 }
389 }
390 catch
391 {
392 return null;
393 }
394 }
395
396 }
397}
398