microsoft/TypeAgent

Public

mirrored from https://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c326cf3d9080c0754c0729816278cf435416bdb8

Branches

Tags

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

Clone

HTTPS

Download ZIP

dotnet/autoShell/AutoShell.cs

1328lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System;
5using System.Collections;
6using System.Collections.Generic;
7using System.Diagnostics;
8using System.IO;
9using System.Linq;
10using System.Reflection;
11using System.Runtime.InteropServices;
12using System.Text;
13using System.Threading.Tasks;
14using System.Windows.Controls;
15using Microsoft.VisualBasic;
16using Microsoft.WindowsAPICodePack.Shell;
17using Newtonsoft.Json;
18using Newtonsoft.Json.Linq;
19using static autoShell.AutoShell;
20
21
22namespace autoShell;
23
24internal partial class AutoShell
25{
26 // create a map of friendly names to executable paths
27 static Hashtable s_friendlyNameToPath = [];
28 static Hashtable s_friendlyNameToId = [];
29 static double s_savedVolumePct = 0.0;
30
31 static IServiceProvider10 s_shell;
32 static IVirtualDesktopManager s_virtualDesktopManager;
33 static IVirtualDesktopManagerInternal s_virtualDesktopManagerInternal;
34 static IVirtualDesktopManagerInternal_BUGBUG s_virtualDesktopManagerInternal_BUGBUG;
35 static IApplicationViewCollection s_applicationViewCollection;
36 static IVirtualDesktopPinnedApps s_virtualDesktopPinnedApps;
37
38
39 /// <summary>
40 /// Constructor used to get system wide information required for specific commands.
41 /// </summary>
42 static AutoShell()
43 {
44 // get current user name
45 string userName = Environment.UserName;
46 SortedList<string, string> sortedList = new SortedList<string, string>
47 {
48 { "chrome", "chrome.exe" },
49 { "power point", "C:\\Program Files\\Microsoft Office\\root\\Office16\\POWERPNT.EXE" },
50 { "powerpoint", "C:\\Program Files\\Microsoft Office\\root\\Office16\\POWERPNT.EXE" },
51 { "word", "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE" },
52 { "winword", "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE" },
53 { "excel", "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE" },
54 { "outlook", "C:\\Program Files\\Microsoft Office\\root\\Office16\\OUTLOOK.EXE" },
55 { "visual studio", "devenv.exe" },
56 { "visual studio code", "C:\\Users\\" + userName + "\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe" },
57 { "edge", "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" },
58 { "microsoft edge", "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" },
59 { "notepad", "C:\\Windows\\System32\\notepad.exe" },
60 { "paint", "mspaint.exe" },
61 { "calculator", "calc.exe" },
62 { "file explorer", "C:\\Windows\\explorer.exe" },
63 { "control panel", "C:\\Windows\\System32\\control.exe" },
64 { "task manager", "C:\\Windows\\System32\\Taskmgr.exe" },
65 { "cmd", "C:\\Windows\\System32\\cmd.exe" },
66 { "powershell", "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" },
67 { "snipping tool", "C:\\Windows\\System32\\SnippingTool.exe" },
68 { "magnifier", "C:\\Windows\\System32\\Magnify.exe" },
69 { "paint 3d", "C:\\Program Files\\WindowsApps\\Microsoft.MSPaint_10.1807.18022.0_x64__8wekyb3d8bbwe\\"},
70 { "m365 copilot", "C:\\Program Files\\WindowsApps\\Microsoft.MicrosoftOfficeHub_19.2512.45041.0_x64__8wekyb3d8bbwe\\M365Copilot.exe" },
71 { "copilot", "C:\\Program Files\\WindowsApps\\Microsoft.MicrosoftOfficeHub_19.2512.45041.0_x64__8wekyb3d8bbwe\\M365Copilot.exe" },
72 { "spotify", "C:\\Program Files\\WindowsApps\\SpotifyAB.SpotifyMusic_1.279.427.0_x64__zpdnekdrzrea0\\spotify.exe" },
73 };
74
75 // add the entries to the hashtable
76 foreach (var kvp in sortedList)
77 {
78 s_friendlyNameToPath.Add(kvp.Key, kvp.Value);
79 }
80
81 var installedApps = GetAllInstalledAppsIds();
82 foreach (var kvp in installedApps)
83 {
84 s_friendlyNameToId.Add(kvp.Key, kvp.Value);
85 }
86
87 // Load the installed themes
88 LoadThemes();
89
90 // Desktop management
91 s_shell = (IServiceProvider10)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_ImmersiveShell));
92 s_virtualDesktopManagerInternal = (IVirtualDesktopManagerInternal)s_shell.QueryService(CLSID_VirtualDesktopManagerInternal, typeof(IVirtualDesktopManagerInternal).GUID);
93 s_virtualDesktopManagerInternal_BUGBUG = (IVirtualDesktopManagerInternal_BUGBUG)s_shell.QueryService(CLSID_VirtualDesktopManagerInternal, typeof(IVirtualDesktopManagerInternal).GUID);
94 s_virtualDesktopManager = (IVirtualDesktopManager)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_VirtualDesktopManager));
95 s_applicationViewCollection = (IApplicationViewCollection)s_shell.QueryService(typeof(IApplicationViewCollection).GUID, typeof(IApplicationViewCollection).GUID);
96 s_virtualDesktopPinnedApps = (IVirtualDesktopPinnedApps)s_shell.QueryService(CLSID_VirtualDesktopPinnedApps, typeof(IVirtualDesktopPinnedApps).GUID);
97 }
98
99 /// <summary>
100 /// Program entry point
101 /// </summary>
102 /// <param name="args">Any command line arguments</param>
103 static void Main(string[] args)
104 {
105 string rawCmdLine = Marshal.PtrToStringUni(GetCommandLineW());
106
107 // if there are command line args let's execute those one at a time and then exit
108 // user can specify a single JSON object command or an array of them on the command line
109 if (args.Length > 0)
110 {
111 string exe = $"\"{Environment.ProcessPath}\"";
112 string cmdLine = rawCmdLine.Replace(exe, "");
113
114 if (cmdLine.StartsWith(exe, StringComparison.OrdinalIgnoreCase))
115 {
116 cmdLine = cmdLine[exe.Length..];
117 }
118 else if (cmdLine.StartsWith(Path.GetFileName(Environment.ProcessPath), StringComparison.OrdinalIgnoreCase))
119 {
120 cmdLine = cmdLine[Path.GetFileName(Environment.ProcessPath).Length..];
121 }
122 else if (cmdLine.StartsWith(Path.GetFileNameWithoutExtension(Environment.ProcessPath), StringComparison.OrdinalIgnoreCase))
123 {
124 cmdLine = cmdLine[Path.GetFileNameWithoutExtension(Environment.ProcessPath).Length..];
125 }
126
127 try
128 {
129 JArray commands = JArray.Parse(cmdLine);
130 foreach (JObject jo in commands.Children<JObject>())
131 {
132 execLine(jo);
133 }
134 }
135 catch (JsonReaderException)
136 {
137 execLine(JObject.Parse(cmdLine));
138 }
139
140 // exit
141 return;
142 }
143
144 // run in interactive mode, keep accepting commands until we get the shutdown command
145 bool quit = false;
146 while (!quit)
147 {
148 try
149 {
150 // read a line from the console
151 string line = Console.ReadLine();
152 // parse the line as a json object with one or more command keys (with values as parameters)
153 JObject root = JObject.Parse(line);
154
155 // execute the line
156 quit = execLine(root);
157 }
158 catch (Exception ex)
159 {
160 LogError(ex);
161 }
162 }
163 }
164
165 static void LogError(Exception ex)
166 {
167 Debug.WriteLine(ex);
168 ConsoleColor previousColor = Console.ForegroundColor;
169 Console.ForegroundColor = ConsoleColor.Red;
170 Console.WriteLine("Error: " + ex.Message);
171 Console.ForegroundColor = previousColor;
172 }
173
174 static void LogWarning(string message)
175 {
176 Debug.WriteLine(message);
177 ConsoleColor previousColor = Console.ForegroundColor;
178 Console.ForegroundColor = ConsoleColor.Yellow;
179 Console.WriteLine("Warning: " + message);
180 Console.ForegroundColor = previousColor;
181 }
182
183 static SortedList<string, string> GetAllInstalledAppsIds()
184 {
185 // GUID taken from https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
186 var FOLDERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
187 ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FOLDERID_AppsFolder);
188 var appIds = new SortedList<string, string>();
189
190 foreach (var app in (IKnownFolder)appsFolder)
191 {
192 string appName = app.Name.ToLowerInvariant();
193 if (appIds.ContainsKey(appName))
194 {
195 Debug.WriteLine("Key has multiple values: " + appName);
196 }
197 else
198 {
199 // The ParsingName property is the AppUserModelID
200 appIds.Add(appName, app.ParsingName);
201 }
202 }
203
204 return appIds;
205 }
206
207 static void SetMasterVolume(int pct)
208 {
209 // Using Windows Core Audio API via COM interop
210 try
211 {
212 var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
213 deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice device);
214 var audioEndpointVolumeGuid = typeof(IAudioEndpointVolume).GUID;
215 device.Activate(ref audioEndpointVolumeGuid, 0, IntPtr.Zero, out object obj);
216 var audioEndpointVolume = (IAudioEndpointVolume)obj;
217 audioEndpointVolume.GetMasterVolumeLevelScalar(out float currentVolume);
218 s_savedVolumePct = currentVolume * 100.0;
219 audioEndpointVolume.SetMasterVolumeLevelScalar(pct / 100.0f, Guid.Empty);
220 }
221 catch (Exception ex)
222 {
223 Debug.WriteLine("Failed to set volume: " + ex.Message);
224 }
225 }
226
227 static void RestoreMasterVolume()
228 {
229 // Using Windows Core Audio API via COM interop
230 try
231 {
232 var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
233 deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice device);
234 var audioEndpointVolumeGuid = typeof(IAudioEndpointVolume).GUID;
235 device.Activate(ref audioEndpointVolumeGuid, 0, IntPtr.Zero, out object obj);
236 var audioEndpointVolume = (IAudioEndpointVolume)obj;
237 audioEndpointVolume.SetMasterVolumeLevelScalar((float)(s_savedVolumePct / 100.0), Guid.Empty);
238 }
239 catch (Exception ex)
240 {
241 Debug.WriteLine("Failed to restore volume: " + ex.Message);
242 }
243 }
244
245 static void SetMasterMute(bool mute)
246 {
247 // Using Windows Core Audio API via COM interop
248 try
249 {
250 var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
251 deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice device);
252 var audioEndpointVolumeGuid = typeof(IAudioEndpointVolume).GUID;
253 device.Activate(ref audioEndpointVolumeGuid, 0, IntPtr.Zero, out object obj);
254 var audioEndpointVolume = (IAudioEndpointVolume)obj;
255 audioEndpointVolume.GetMute(out bool currentMute);
256 Debug.WriteLine("Current Mute:" + currentMute);
257 audioEndpointVolume.SetMute(mute, Guid.Empty);
258 }
259 catch (Exception ex)
260 {
261 Debug.WriteLine("Failed to set mute: " + ex.Message);
262 }
263 }
264
265 static string ResolveProcessNameFromFriendlyName(string friendlyName)
266 {
267 string path = (string)s_friendlyNameToPath[friendlyName.ToLowerInvariant()];
268 if (path != null)
269 {
270 return Path.GetFileNameWithoutExtension(path);
271 }
272 else
273 {
274 return friendlyName;
275 }
276 }
277
278 static IntPtr FindProcessWindowHandle(string processName)
279 {
280 processName = ResolveProcessNameFromFriendlyName(processName);
281 Process[] processes = Process.GetProcessesByName(processName);
282 // loop through the processes that match the name; raise the first one that has a main window
283 foreach (Process p in processes)
284 {
285 if (p.MainWindowHandle != IntPtr.Zero)
286 {
287 return p.MainWindowHandle;
288 }
289 }
290
291 // Try to find by window title if we haven't found it and bring it forward
292 return FindWindowByTitle(processName).hWnd;
293 }
294
295 // given part of a process name, raise the window of that process to the top level
296 static void RaiseWindow(string processName)
297 {
298 processName = ResolveProcessNameFromFriendlyName(processName);
299 Process[] processes = Process.GetProcessesByName(processName);
300 // loop through the processes that match the name; raise the first one that has a main window
301 foreach (Process p in processes)
302 {
303 if (p.MainWindowHandle != IntPtr.Zero)
304 {
305 SetForegroundWindow(p.MainWindowHandle);
306 Interaction.AppActivate(p.Id);
307 return;
308 }
309 }
310
311 // this means all the applications processes are running in the background. This happens for edge and chrome browsers.
312 string path = (string)s_friendlyNameToPath[processName];
313 if (path != null)
314 {
315 Process.Start(path);
316 }
317 else
318 {
319 // Try to find by window title if we haven't found it and bring it forward
320 (nint hWnd1, int pid) = FindWindowByTitle(processName);
321
322 if (hWnd1 != nint.Zero)
323 {
324 SetForegroundWindow(hWnd1);
325 Interaction.AppActivate(pid);
326 }
327 }
328 }
329
330 static void MaximizeWindow(string processName)
331 {
332 processName = ResolveProcessNameFromFriendlyName(processName);
333 Process[] processes = Process.GetProcessesByName(processName);
334 // loop through the processes that match the name; raise the first one that has a main window
335 foreach (Process p in processes)
336 {
337 if (p.MainWindowHandle != IntPtr.Zero)
338 {
339 uint WM_SYSCOMMAND = 0x112;
340 uint SC_MAXIMIZE = 0xf030;
341 SendMessage(p.MainWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, IntPtr.Zero);
342 SetForegroundWindow(p.MainWindowHandle);
343 Interaction.AppActivate(p.Id);
344 return;
345 }
346 }
347
348 // if we haven't found what we are looking for let's enumerate the top level windows and try that way
349 (nint hWnd, int pid) = FindWindowByTitle(processName);
350 if (hWnd != nint.Zero)
351 {
352 uint WM_SYSCOMMAND = 0x112;
353 uint SC_MAXIMIZE = 0xf030;
354 SendMessage(hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, IntPtr.Zero);
355 SetForegroundWindow(hWnd);
356 Interaction.AppActivate(pid);
357 }
358 }
359
360 static void MinimizeWindow(string processName)
361 {
362 processName = ResolveProcessNameFromFriendlyName(processName);
363 Process[] processes = Process.GetProcessesByName(processName);
364 // loop through the processes that match the name; raise the first one that has a main window
365 foreach (Process p in processes)
366 {
367 if (p.MainWindowHandle != IntPtr.Zero)
368 {
369 uint WM_SYSCOMMAND = 0x112;
370 uint SC_MINIMIZE = 0xF020;
371 SendMessage(p.MainWindowHandle, WM_SYSCOMMAND, SC_MINIMIZE, IntPtr.Zero);
372 break;
373 }
374 }
375
376 // if we haven't found what we are looking for let's enumerate the top level windows and try that way
377 (nint hWnd, int pid) = FindWindowByTitle(processName);
378 if (hWnd != nint.Zero)
379 {
380 uint WM_SYSCOMMAND = 0x112;
381 uint SC_MINIMIZE = 0xF020;
382 SendMessage(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, IntPtr.Zero);
383 SetForegroundWindow(hWnd);
384 Interaction.AppActivate(pid);
385 }
386 }
387
388 static void TileWindowPair(string processName1, string processName2)
389 {
390 // find both processes
391 // TODO: Update this to account for UWP apps (e.g. calculator). UWPs are hosted by ApplicationFrameHost.exe
392 processName1 = ResolveProcessNameFromFriendlyName(processName1);
393 Process[] processes1 = Process.GetProcessesByName(processName1);
394 IntPtr hWnd1 = IntPtr.Zero;
395 IntPtr hWnd2 = IntPtr.Zero;
396 int pid1 = -1;
397 int pid2 = -1;
398
399 foreach (Process p in processes1)
400 {
401 if (p.MainWindowHandle != IntPtr.Zero)
402 {
403 hWnd1 = p.MainWindowHandle;
404 pid1 = p.Id;
405 break;
406 }
407 }
408
409 // If no process found by name, search by window title
410 if (hWnd1 == IntPtr.Zero)
411 {
412 (hWnd1, pid1) = FindWindowByTitle(processName1);
413 }
414
415 processName2 = ResolveProcessNameFromFriendlyName(processName2);
416 Process[] processes2 = Process.GetProcessesByName(processName2);
417 foreach (Process p in processes2)
418 {
419 if (p.MainWindowHandle != IntPtr.Zero)
420 {
421 hWnd2 = p.MainWindowHandle;
422 pid2 = p.Id;
423 break;
424 }
425 }
426
427 // If no process found by name, search by window title
428 if (hWnd2 == IntPtr.Zero)
429 {
430 (hWnd2, pid2) = FindWindowByTitle(processName2);
431 }
432
433 if (hWnd1 != IntPtr.Zero && hWnd2 != IntPtr.Zero)
434 {
435 // TODO: handle multiple monitors
436 // get the screen size
437 IntPtr desktopHandle = GetDesktopWindow();
438 RECT desktopRect = new RECT();
439 GetWindowRect(desktopHandle, ref desktopRect);
440 // get the dimensions of the taskbar
441 // find the taskbar window
442 IntPtr taskbarHandle = IntPtr.Zero;
443 IntPtr hWnd = IntPtr.Zero;
444 while ((hWnd = FindWindowEx(IntPtr.Zero, hWnd, "Shell_TrayWnd", null)) != IntPtr.Zero)
445 {
446 // find the taskbar window's child
447 taskbarHandle = FindWindowEx(hWnd, IntPtr.Zero, "ReBarWindow32", null);
448 if (taskbarHandle != IntPtr.Zero)
449 {
450 break;
451 }
452 }
453 if (hWnd == IntPtr.Zero)
454 {
455 Debug.WriteLine("Taskbar not found");
456 return;
457 }
458 else
459 {
460 RECT taskbarRect = new RECT();
461 GetWindowRect(hWnd, ref taskbarRect);
462 Debug.WriteLine("Taskbar Rect: " + taskbarRect.Left + ", " + taskbarRect.Top + ", " + taskbarRect.Right + ", " + taskbarRect.Bottom);
463 // TODO: handle left, top, right and nonexistant taskbars
464 // subtract the taskbar height from the screen height
465 desktopRect.Bottom -= (int)((taskbarRect.Bottom - taskbarRect.Top) / 2);
466 }
467 // set the window positions using the shellRect and making sure the windows are visible
468 int halfwidth = (desktopRect.Right - desktopRect.Left) / 2;
469 IntPtr HWND_TOP = IntPtr.Zero;
470 uint showWindow = 0x40;
471 SetWindowPos(hWnd1, HWND_TOP, desktopRect.Left, desktopRect.Top, halfwidth, desktopRect.Bottom, showWindow);
472 SetForegroundWindow(hWnd1);
473 Interaction.AppActivate(pid1);
474 SetWindowPos(hWnd2, HWND_TOP, desktopRect.Left + halfwidth, desktopRect.Top, halfwidth, desktopRect.Bottom, showWindow);
475 SetForegroundWindow(hWnd2);
476 Interaction.AppActivate(pid2);
477 }
478 }
479
480 /// <summary>
481 /// Finds a top-level window by searching for a partial match in the window title.
482 /// </summary>
483 /// <param name="titleSearch">The text to search for in window titles (case-insensitive).</param>
484 /// <returns>A tuple containing the window handle and process ID, or (IntPtr.Zero, -1) if not found.</returns>
485 static (IntPtr hWnd, int pid) FindWindowByTitle(string titleSearch)
486 {
487 IntPtr foundHandle = IntPtr.Zero;
488 int foundPid = -1;
489 StringBuilder windowTitle = new StringBuilder(256);
490
491 EnumWindows((hWnd, lParam) =>
492 {
493 // Only consider visible windows
494 if (!IsWindowVisible(hWnd))
495 {
496 return true; // Continue enumeration
497 }
498
499 // Get window title
500 int length = GetWindowText(hWnd, windowTitle, windowTitle.Capacity);
501 if (length > 0)
502 {
503 string title = windowTitle.ToString();
504 // Case-insensitive partial match
505 if (title.Contains(titleSearch, StringComparison.OrdinalIgnoreCase))
506 {
507 foundHandle = hWnd;
508 GetWindowThreadProcessId(hWnd, out uint pid);
509 foundPid = (int)pid;
510 return false; // Stop enumeration
511 }
512 }
513 return true; // Continue enumeration
514 }, IntPtr.Zero);
515
516 return (foundHandle, foundPid);
517 }
518
519 // given a friendly name, check if it's running and if not, start it; if it's running raise it to the top level
520 static void OpenApplication(string friendlyName)
521 {
522 // check to see if the application is running
523 Process[] processes = Process.GetProcessesByName(friendlyName);
524 if (processes.Length == 0)
525 {
526 // if not, start it
527 Debug.WriteLine("Starting " + friendlyName);
528 string path = (string)s_friendlyNameToPath[friendlyName.ToLowerInvariant()];
529 if (path != null)
530 {
531 try
532 {
533 Process.Start(path);
534 }
535 catch (System.ComponentModel.Win32Exception)
536 {
537 // alternate start method
538 Process.Start(friendlyName);
539 }
540 }
541 else
542 {
543 string appModelUserID = (string)s_friendlyNameToId[friendlyName.ToLowerInvariant()];
544 if (appModelUserID != null)
545 {
546 try
547 {
548 Process.Start("explorer.exe", @" shell:appsFolder\" + appModelUserID);
549 }
550 catch { }
551 }
552 }
553 }
554 else
555 {
556 // if so, raise it to the top level
557 Debug.WriteLine("Raising " + friendlyName);
558 RaiseWindow(friendlyName);
559 }
560 }
561
562 // close application
563 static void CloseApplication(string friendlyName)
564 {
565 // check to see if the application is running
566 string processName = ResolveProcessNameFromFriendlyName(friendlyName);
567 Process[] processes = Process.GetProcessesByName(processName);
568 if (processes.Length != 0)
569 {
570 // if so, close it
571 Debug.WriteLine("Closing " + friendlyName);
572 foreach (Process p in processes)
573 {
574 if (p.MainWindowHandle != IntPtr.Zero)
575 {
576 p.CloseMainWindow();
577 }
578 }
579 }
580 }
581
582 private static void SetDesktopWallpaper(string imagePath)
583 {
584 SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, imagePath, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
585 }
586
587 /// <summary>
588 /// Creates virtual desktops from a JSON array of desktop names.
589 /// </summary>
590 /// <param name="jsonValue">JSON array containing desktop names, e.g., ["Work", "Personal", "Gaming"]</param>
591 static void CreateDesktop(string jsonValue)
592 {
593 try
594 {
595 // Parse the JSON array of desktop names
596 JArray desktopNames = JArray.Parse(jsonValue);
597
598 if (desktopNames == null || desktopNames.Count == 0)
599 {
600 Debug.WriteLine("No desktop names provided");
601 return;
602 }
603
604 if (s_virtualDesktopManagerInternal == null)
605 {
606 Debug.WriteLine($"Failed to get Virtual Desktop Manager Internal");
607 return;
608 }
609
610 foreach (JToken desktopNameToken in desktopNames)
611 {
612 string desktopName = desktopNameToken.ToString();
613
614 if (string.IsNullOrWhiteSpace(desktopName))
615 {
616 continue;
617 }
618
619 try
620 {
621 // Create a new virtual desktop
622 IVirtualDesktop newDesktop = s_virtualDesktopManagerInternal_BUGBUG.CreateDesktop();
623
624 if (newDesktop != null)
625 {
626 // Set the desktop name (Windows 10 build 20231+ / Windows 11)
627 try
628 {
629 // TODO: debug & get working
630 // Works in .NET framework but not .NET
631 //s_virtualDesktopManagerInternal_BUGBUG.SetDesktopName(newDesktop, desktopName);
632 //Debug.WriteLine($"Created virtual desktop: {desktopName}");
633 }
634 catch (Exception ex2)
635 {
636 // Older Windows version - name setting not supported
637 Debug.WriteLine($"Created virtual desktop (naming not supported on this Windows version): {ex2.Message}");
638 }
639 }
640 }
641 catch (Exception ex)
642 {
643 Debug.WriteLine($"Failed to create desktop '{desktopName}': {ex.Message}");
644 }
645 }
646 }
647 catch (JsonException ex)
648 {
649 Debug.WriteLine($"Failed to parse desktop names JSON: {ex.Message}");
650 }
651 catch (Exception ex)
652 {
653 Debug.WriteLine($"Error creating desktops: {ex.Message}");
654 }
655 }
656
657 static void SwitchDesktop(string desktopIdentifier)
658 {
659 if (!int.TryParse(desktopIdentifier, out int index))
660 {
661 // Try to find the desktop by name
662 s_virtualDesktopManagerInternal.SwitchDesktop(FindDesktopByName(desktopIdentifier));
663 }
664 else
665 {
666 SwitchDesktop(index);
667 }
668 }
669
670 static void SwitchDesktop(int index)
671 {
672 s_virtualDesktopManagerInternal.GetDesktops(out IObjectArray desktops);
673 desktops.GetAt(index, typeof(IVirtualDesktop).GUID, out object od);
674
675 // BUGBUG: different windows versions use different COM interfaces
676 // Different Windows versions use different COM interfaces for desktop switching
677 // Windows 11 22H2 (build 22621) and later use the updated interface
678 if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22621))
679 {
680 // Use the BUGBUG interface for Windows 11 22H2+
681 s_virtualDesktopManagerInternal_BUGBUG.SwitchDesktopWithAnimation((IVirtualDesktop)od);
682 }
683 else if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
684 {
685 // Windows 11 21H2 (build 22000)
686 s_virtualDesktopManagerInternal.SwitchDesktopWithAnimation((IVirtualDesktop)od);
687 }
688 else
689 {
690 // Windows 10 - use the original interface
691 s_virtualDesktopManagerInternal.SwitchDesktopAndMoveForegroundView((IVirtualDesktop)od);
692 }
693
694 Marshal.ReleaseComObject(desktops);
695 }
696
697 static void BumpDesktopIndex(int bump)
698 {
699 IVirtualDesktop desktop = s_virtualDesktopManagerInternal.GetCurrentDesktop();
700 int index = GetDesktopIndex(desktop);
701 int count = s_virtualDesktopManagerInternal.GetCount();
702
703 if (index == -1)
704 {
705 Debug.WriteLine("Undable to get the index of the current desktop");
706 return;
707 }
708
709 index += bump;
710
711 if (index > count)
712 {
713 index = 0;
714 }
715 else if (index < 0)
716 {
717 index = count - 1;
718 }
719
720 SwitchDesktop(index);
721 }
722
723 static IVirtualDesktop FindDesktopByName(string name)
724 {
725 int count = s_virtualDesktopManagerInternal.GetCount();
726
727 s_virtualDesktopManagerInternal.GetDesktops(out IObjectArray desktops);
728 for (int i = 0; i < count; i++)
729 {
730 desktops.GetAt(i, typeof(IVirtualDesktop).GUID, out object od);
731
732 if (string.Equals(((IVirtualDesktop)od).GetName(), name, StringComparison.OrdinalIgnoreCase))
733 {
734 Marshal.ReleaseComObject(desktops);
735 return (IVirtualDesktop)od;
736 }
737 }
738
739 Marshal.ReleaseComObject(desktops);
740
741 return null;
742 }
743
744 static int GetDesktopIndex(IVirtualDesktop desktop)
745 {
746 int index = -1;
747 int count = s_virtualDesktopManagerInternal.GetCount();
748
749 s_virtualDesktopManagerInternal.GetDesktops(out IObjectArray desktops);
750 for (int i = 0; i < count; i++)
751 {
752 desktops.GetAt(i, typeof(IVirtualDesktop).GUID, out object od);
753
754 if (desktop.GetId() == ((IVirtualDesktop)od).GetId())
755 {
756 Marshal.ReleaseComObject(desktops);
757 return i;
758 }
759 }
760
761 Marshal.ReleaseComObject(desktops);
762
763 return -1;
764 }
765
766 /// <summary>
767 ///
768 /// </summary>
769 /// <param name="value"></param>
770 /// <remarks>Currently not working correction, returns ACCESS_DENIED // TODO: investigate</remarks>
771 static void MoveWindowToDesktop(JToken value)
772 {
773 string process = value.SelectToken("process").ToString();
774 string desktop = value.SelectToken("desktop").ToString();
775 if (string.IsNullOrEmpty(process))
776 {
777 Debug.WriteLine("No process name supplied");
778 return;
779 }
780
781 if (string.IsNullOrEmpty(desktop))
782 {
783 Debug.WriteLine("No desktop id supplied");
784 return;
785 }
786
787 IntPtr hWnd = FindProcessWindowHandle(process);
788
789 if (int.TryParse(desktop, out int desktopIndex))
790 {
791 s_virtualDesktopManagerInternal.GetDesktops(out IObjectArray desktops);
792 if (desktopIndex < 1 || desktopIndex > s_virtualDesktopManagerInternal.GetCount())
793 {
794 Debug.WriteLine("Desktop index out of range");
795 Marshal.ReleaseComObject(desktops);
796 return;
797 }
798 desktops.GetAt(desktopIndex - 1, typeof(IVirtualDesktop).GUID, out object od);
799 Guid g = ((IVirtualDesktop)od).GetId();
800 s_virtualDesktopManager.MoveWindowToDesktop(hWnd, ref g);
801 Marshal.ReleaseComObject(desktops);
802 return;
803 }
804
805 IVirtualDesktop ivd = FindDesktopByName(desktop);
806 if (ivd is not null)
807 {
808 Guid desktopGuid = ivd.GetId();
809 s_virtualDesktopManager.MoveWindowToDesktop(hWnd, ref desktopGuid);
810 }
811 }
812
813 static void PinWindow(string processName)
814 {
815 IntPtr hWnd = FindProcessWindowHandle(processName);
816
817 if (hWnd != IntPtr.Zero)
818 {
819 s_applicationViewCollection.GetViewForHwnd(hWnd, out IApplicationView view);
820
821 if (view is not null)
822 {
823 s_virtualDesktopPinnedApps.PinView((IApplicationView)view);
824 }
825 }
826 else
827 {
828 Console.WriteLine($"The window handle for '{processName}' could not be found");
829 }
830 }
831
832 static IVirtualDesktopManagerInternal GetVirtualDesktopManagerInternal()
833 {
834 try
835 {
836 IServiceProvider shellServiceProvider = (IServiceProvider)Activator.CreateInstance(
837 Type.GetTypeFromCLSID(CLSID_ImmersiveShell));
838
839 shellServiceProvider.QueryService(
840 CLSID_VirtualDesktopManagerInternal,
841 typeof(IVirtualDesktopManagerInternal).GUID,
842 out object objVirtualDesktopManagerInternal);
843
844 return (IVirtualDesktopManagerInternal)objVirtualDesktopManagerInternal;
845 }
846 catch
847 {
848 return null;
849 }
850 }
851
852 static bool execLine(JObject root)
853 {
854 var quit = false;
855 foreach (var kvp in root)
856 {
857 string key = kvp.Key;
858 string value = kvp.Value.ToString();
859 switch (key)
860 {
861 case "launchProgram":
862 OpenApplication(value);
863 break;
864 case "closeProgram":
865 CloseApplication(value);
866 break;
867 case "maximize":
868 MaximizeWindow(value);
869 break;
870 case "minimize":
871 MinimizeWindow(value);
872 break;
873 case "switchTo":
874 RaiseWindow(value);
875 break;
876 case "quit":
877 quit = true;
878 break;
879 case "tile":
880 string[] apps = value.Split(',');
881 if (apps.Length == 2)
882 {
883 TileWindowPair(apps[0], apps[1]);
884 }
885 break;
886 case "volume":
887 int pct = 0;
888 if (int.TryParse(value, out pct))
889 {
890 SetMasterVolume(pct);
891 }
892 break;
893 case "restoreVolume":
894 RestoreMasterVolume();
895 break;
896 case "mute":
897 bool mute = false;
898 if (bool.TryParse(value, out mute))
899 {
900 SetMasterMute(mute);
901 }
902 break;
903 case "listAppNames":
904 var installedApps = GetAllInstalledAppsIds();
905 Console.WriteLine(JsonConvert.SerializeObject(installedApps.Keys));
906 break;
907 case "setWallpaper":
908 SetDesktopWallpaper(value);
909 break;
910 case "applyTheme":
911 bool result = ApplyTheme(value);
912 break;
913 case "listThemes":
914 var themes = GetInstalledThemes();
915 Console.WriteLine(JsonConvert.SerializeObject(themes));
916 break;
917 case "createDesktop":
918 CreateDesktop(value);
919 break;
920 case "switchDesktop":
921 SwitchDesktop(value);
922 break;
923 case "nextDesktop":
924 BumpDesktopIndex(1);
925 break;
926 case "previousDesktop":
927 BumpDesktopIndex(-1);
928 break;
929 case "moveWindowToDesktop":
930 MoveWindowToDesktop(kvp.Value);
931 break;
932 case "pinWindow":
933 PinWindow(value);
934 break;
935 case "toggleNotifications":
936 ShellExecute(IntPtr.Zero, "open", "ms-actioncenter:", null, null, 1);
937 break;
938 case "debug":
939 Debugger.Launch();
940 break;
941 case "toggleAirplaneMode":
942 SetAirplaneMode(bool.Parse(value));
943 break;
944 case "listWifiNetworks":
945 ListWifiNetworks();
946 break;
947 case "connectWifi":
948 JObject netInfo = JObject.Parse(value);
949 string ssid = netInfo.Value<string>("ssid");
950 string password = netInfo["password"] is not null ? netInfo.Value<string>("password") : "";
951 ConnectToWifi(ssid, password);
952 break;
953 case "disconnectWifi":
954 DisconnectFromWifi();
955 break;
956 default:
957 Debug.WriteLine("Unknown command: " + key);
958 break;
959 }
960 }
961 return quit;
962 }
963
964 /// <summary>
965 /// Sets the airplane mode state using the Radio Management API.
966 /// </summary>
967 /// <param name="enable">True to enable airplane mode, false to disable.</param>
968 static void SetAirplaneMode(bool enable)
969 {
970 IRadioManager radioManager = null;
971 try
972 {
973 // Create the Radio Management API COM object
974 Type radioManagerType = Type.GetTypeFromCLSID(CLSID_RadioManagementAPI);
975 if (radioManagerType == null)
976 {
977 Debug.WriteLine("Failed to get Radio Management API type");
978 return;
979 }
980
981 object obj = Activator.CreateInstance(radioManagerType);
982 radioManager = (IRadioManager)obj;
983
984 if (radioManager == null)
985 {
986 Debug.WriteLine("Failed to create Radio Manager instance");
987 return;
988 }
989
990 // Get current state (for logging)
991 int hr = radioManager.GetSystemRadioState(out int currentState, out int _, out int _);
992 if (hr < 0)
993 {
994 Debug.WriteLine($"Failed to get system radio state: HRESULT 0x{hr:X8}");
995 return;
996 }
997
998 // currentState: 0 = airplane mode ON (radios off), 1 = airplane mode OFF (radios on)
999 bool airplaneModeCurrentlyOn = currentState == 0;
1000 Debug.WriteLine($"Current airplane mode state: {(airplaneModeCurrentlyOn ? "on" : "off")}");
1001
1002 // Set the new state
1003 // bEnabled: 0 = turn airplane mode ON (disable radios), 1 = turn airplane mode OFF (enable radios)
1004 int newState = enable ? 0 : 1;
1005 hr = radioManager.SetSystemRadioState(newState);
1006 if (hr < 0)
1007 {
1008 Debug.WriteLine($"Failed to set system radio state: HRESULT 0x{hr:X8}");
1009 return;
1010 }
1011
1012 Debug.WriteLine($"Airplane mode set to: {(enable ? "on" : "off")}");
1013 }
1014 catch (COMException ex)
1015 {
1016 Debug.WriteLine($"COM Exception setting airplane mode: {ex.Message} (HRESULT: 0x{ex.HResult:X8})");
1017 }
1018 catch (Exception ex)
1019 {
1020 Debug.WriteLine($"Failed to set airplane mode: {ex.Message}");
1021 }
1022 finally
1023 {
1024 if (radioManager != null)
1025 {
1026 Marshal.ReleaseComObject(radioManager);
1027 }
1028 }
1029 }
1030
1031 /// <summary>
1032 /// Lists all WiFi networks currently in range.
1033 /// </summary>
1034 static void ListWifiNetworks()
1035 {
1036 IntPtr clientHandle = IntPtr.Zero;
1037 IntPtr wlanInterfaceList = IntPtr.Zero;
1038 IntPtr networkList = IntPtr.Zero;
1039
1040 try
1041 {
1042 // Open WLAN handle
1043 int result = WlanOpenHandle(2, IntPtr.Zero, out uint negotiatedVersion, out clientHandle);
1044 if (result != 0)
1045 {
1046 Debug.WriteLine($"Failed to open WLAN handle: {result}");
1047 return;
1048 }
1049
1050 // Enumerate wireless interfaces
1051 result = WlanEnumInterfaces(clientHandle, IntPtr.Zero, out wlanInterfaceList);
1052 if (result != 0)
1053 {
1054 Debug.WriteLine($"Failed to enumerate WLAN interfaces: {result}");
1055 return;
1056 }
1057
1058 WLAN_INTERFACE_INFO_LIST interfaceList = Marshal.PtrToStructure<WLAN_INTERFACE_INFO_LIST>(wlanInterfaceList);
1059
1060 if (interfaceList.dwNumberOfItems == 0)
1061 {
1062 Console.WriteLine("[]");
1063 return;
1064 }
1065
1066 var allNetworks = new List<object>();
1067
1068 for (int i = 0; i < interfaceList.dwNumberOfItems; i++)
1069 {
1070 WLAN_INTERFACE_INFO interfaceInfo = interfaceList.InterfaceInfo[i];
1071
1072 // Scan for networks (trigger a refresh)
1073 WlanScan(clientHandle, ref interfaceInfo.InterfaceGuid, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
1074
1075 // Small delay to allow scan to complete
1076 System.Threading.Thread.Sleep(100);
1077
1078 // Get available networks
1079 result = WlanGetAvailableNetworkList(clientHandle, ref interfaceInfo.InterfaceGuid, 0, IntPtr.Zero, out networkList);
1080 if (result != 0)
1081 {
1082 Debug.WriteLine($"Failed to get network list: {result}");
1083 continue;
1084 }
1085
1086 WLAN_AVAILABLE_NETWORK_LIST availableNetworkList = Marshal.PtrToStructure<WLAN_AVAILABLE_NETWORK_LIST>(networkList);
1087
1088 IntPtr networkPtr = networkList + 8; // Skip dwNumberOfItems and dwIndex
1089
1090 for (int j = 0; j < availableNetworkList.dwNumberOfItems; j++)
1091 {
1092 WLAN_AVAILABLE_NETWORK network = Marshal.PtrToStructure<WLAN_AVAILABLE_NETWORK>(networkPtr);
1093
1094 string ssid = Encoding.ASCII.GetString(network.dot11Ssid.SSID, 0, (int)network.dot11Ssid.SSIDLength);
1095
1096 if (!string.IsNullOrEmpty(ssid))
1097 {
1098 allNetworks.Add(new
1099 {
1100 SSID = ssid,
1101 SignalQuality = network.wlanSignalQuality,
1102 Secured = network.bSecurityEnabled,
1103 Connected = (network.dwFlags & 1) != 0 // WLAN_AVAILABLE_NETWORK_CONNECTED
1104 });
1105 }
1106
1107 networkPtr += Marshal.SizeOf<WLAN_AVAILABLE_NETWORK>();
1108 }
1109
1110 if (networkList != IntPtr.Zero)
1111 {
1112 WlanFreeMemory(networkList);
1113 networkList = IntPtr.Zero;
1114 }
1115 }
1116
1117 // Remove duplicates and sort by signal strength
1118 var uniqueNetworks = allNetworks
1119 .GroupBy(n => ((dynamic)n).SSID)
1120 .Select(g => g.OrderByDescending(n => ((dynamic)n).SignalQuality).First())
1121 .OrderByDescending(n => ((dynamic)n).SignalQuality)
1122 .ToList();
1123
1124 Console.WriteLine(JsonConvert.SerializeObject(uniqueNetworks));
1125 }
1126 catch (Exception ex)
1127 {
1128 Debug.WriteLine($"Error listing WiFi networks: {ex.Message}");
1129 Console.WriteLine("[]");
1130 }
1131 finally
1132 {
1133 if (networkList != IntPtr.Zero)
1134 WlanFreeMemory(networkList);
1135 if (wlanInterfaceList != IntPtr.Zero)
1136 WlanFreeMemory(wlanInterfaceList);
1137 if (clientHandle != IntPtr.Zero)
1138 WlanCloseHandle(clientHandle, IntPtr.Zero);
1139 }
1140 }
1141
1142 /// <summary>
1143 /// Connects to a WiFi network by name (SSID). If the network requires a password and one is provided,
1144 /// it will create a temporary profile. For networks with existing profiles, it connects using the profile.
1145 /// </summary>
1146 /// <param name="ssid">The SSID of the network to connect to.</param>
1147 /// <param name="password">Optional password for secured networks.</param>
1148 static void ConnectToWifi(string ssid, string password = null)
1149 {
1150 IntPtr clientHandle = IntPtr.Zero;
1151 IntPtr wlanInterfaceList = IntPtr.Zero;
1152
1153 try
1154 {
1155 // Open WLAN handle
1156 int result = WlanOpenHandle(2, IntPtr.Zero, out uint negotiatedVersion, out clientHandle);
1157 if (result != 0)
1158 {
1159 LogWarning($"Failed to open WLAN handle: {result}");
1160 return;
1161 }
1162
1163 // Enumerate wireless interfaces
1164 result = WlanEnumInterfaces(clientHandle, IntPtr.Zero, out wlanInterfaceList);
1165 if (result != 0)
1166 {
1167 LogWarning($"Failed to enumerate WLAN interfaces: {result}");
1168 return;
1169 }
1170
1171 WLAN_INTERFACE_INFO_LIST interfaceList = Marshal.PtrToStructure<WLAN_INTERFACE_INFO_LIST>(wlanInterfaceList);
1172
1173 if (interfaceList.dwNumberOfItems == 0)
1174 {
1175 LogWarning("No wireless interfaces found.");
1176 return;
1177 }
1178
1179 // Use the first available wireless interface
1180 WLAN_INTERFACE_INFO interfaceInfo = interfaceList.InterfaceInfo[0];
1181
1182 // If password is provided, create a profile and connect
1183 if (!string.IsNullOrEmpty(password))
1184 {
1185 string profileXml = GenerateWifiProfileXml(ssid, password);
1186
1187 result = WlanSetProfile(clientHandle, ref interfaceInfo.InterfaceGuid, 0, profileXml, null, true, IntPtr.Zero, out uint reasonCode);
1188 if (result != 0)
1189 {
1190 LogWarning($"Failed to set WiFi profile: {result}, reason: {reasonCode}");
1191 return;
1192 }
1193 }
1194
1195 // Set up connection parameters
1196 WLAN_CONNECTION_PARAMETERS connectionParams = new WLAN_CONNECTION_PARAMETERS
1197 {
1198 wlanConnectionMode = WLAN_CONNECTION_MODE.wlan_connection_mode_profile,
1199 strProfile = ssid,
1200 pDot11Ssid = IntPtr.Zero,
1201 pDesiredBssidList = IntPtr.Zero,
1202 dot11BssType = DOT11_BSS_TYPE.dot11_BSS_type_any,
1203 dwFlags = 0
1204 };
1205
1206 result = WlanConnect(clientHandle, ref interfaceInfo.InterfaceGuid, ref connectionParams, IntPtr.Zero);
1207 if (result != 0)
1208 {
1209 LogWarning($"Failed to connect to WiFi network '{ssid}': {result}");
1210 return;
1211 }
1212
1213 Debug.WriteLine($"Successfully initiated connection to WiFi network: {ssid}");
1214 Console.WriteLine($"Connecting to WiFi network: {ssid}");
1215 }
1216 catch (Exception ex)
1217 {
1218 LogError(ex);
1219 }
1220 finally
1221 {
1222 if (wlanInterfaceList != IntPtr.Zero)
1223 WlanFreeMemory(wlanInterfaceList);
1224 if (clientHandle != IntPtr.Zero)
1225 WlanCloseHandle(clientHandle, IntPtr.Zero);
1226 }
1227 }
1228
1229 /// <summary>
1230 /// Generates a WiFi profile XML for WPA2-Personal (PSK) networks.
1231 /// </summary>
1232 static string GenerateWifiProfileXml(string ssid, string password)
1233 {
1234 // Convert SSID to hex
1235 string ssidHex = BitConverter.ToString(Encoding.UTF8.GetBytes(ssid)).Replace("-", "");
1236
1237 return $@"<?xml version=""1.0""?>
1238<WLANProfile xmlns=""http://www.microsoft.com/networking/WLAN/profile/v1"">
1239 <name>{ssid}</name>
1240 <SSIDConfig>
1241 <SSID>
1242 <hex>{ssidHex}</hex>
1243 <name>{ssid}</name>
1244 </SSID>
1245 </SSIDConfig>
1246 <connectionType>ESS</connectionType>
1247 <connectionMode>auto</connectionMode>
1248 <MSM>
1249 <security>
1250 <authEncryption>
1251 <authentication>WPA2PSK</authentication>
1252 <encryption>AES</encryption>
1253 <useOneX>false</useOneX>
1254 </authEncryption>
1255 <sharedKey>
1256 <keyType>passPhrase</keyType>
1257 <protected>false</protected>
1258 <keyMaterial>{password}</keyMaterial>
1259 </sharedKey>
1260 </security>
1261 </MSM>
1262</WLANProfile>";
1263 }
1264
1265 /// <summary>
1266 /// Disconnects from the currently connected WiFi network.
1267 /// </summary>
1268 static void DisconnectFromWifi()
1269 {
1270 IntPtr clientHandle = IntPtr.Zero;
1271 IntPtr wlanInterfaceList = IntPtr.Zero;
1272
1273 try
1274 {
1275 // Open WLAN handle
1276 int result = WlanOpenHandle(2, IntPtr.Zero, out uint negotiatedVersion, out clientHandle);
1277 if (result != 0)
1278 {
1279 LogWarning($"Failed to open WLAN handle: {result}");
1280 return;
1281 }
1282
1283 // Enumerate wireless interfaces
1284 result = WlanEnumInterfaces(clientHandle, IntPtr.Zero, out wlanInterfaceList);
1285 if (result != 0)
1286 {
1287 LogWarning($"Failed to enumerate WLAN interfaces: {result}");
1288 return;
1289 }
1290
1291 WLAN_INTERFACE_INFO_LIST interfaceList = Marshal.PtrToStructure<WLAN_INTERFACE_INFO_LIST>(wlanInterfaceList);
1292
1293 if (interfaceList.dwNumberOfItems == 0)
1294 {
1295 LogWarning("No wireless interfaces found.");
1296 return;
1297 }
1298
1299 // Disconnect from all wireless interfaces
1300 for (int i = 0; i < interfaceList.dwNumberOfItems; i++)
1301 {
1302 WLAN_INTERFACE_INFO interfaceInfo = interfaceList.InterfaceInfo[i];
1303
1304 result = WlanDisconnect(clientHandle, ref interfaceInfo.InterfaceGuid, IntPtr.Zero);
1305 if (result != 0)
1306 {
1307 LogWarning($"Failed to disconnect from WiFi on interface {i}: {result}");
1308 }
1309 else
1310 {
1311 Debug.WriteLine($"Successfully disconnected from WiFi on interface: {interfaceInfo.strInterfaceDescription}");
1312 Console.WriteLine("Disconnected from WiFi");
1313 }
1314 }
1315 }
1316 catch (Exception ex)
1317 {
1318 LogError(ex);
1319 }
1320 finally
1321 {
1322 if (wlanInterfaceList != IntPtr.Zero)
1323 WlanFreeMemory(wlanInterfaceList);
1324 if (clientHandle != IntPtr.Zero)
1325 WlanCloseHandle(clientHandle, IntPtr.Zero);
1326 }
1327 }
1328}
1329