mirror of
https://github.com/microsoft/PowerToys
synced 2025-09-02 15:35:12 +00:00
[PTRun][System]Fix delay on many net interfaces (#17490)
* code changes * small text fixes * update docs * comment improvements * update tests * fix typo * change * fix typo * fix error msg * fix bug * second fix
This commit is contained in:
@@ -68,6 +68,11 @@ Available commands:
|
||||
### Network results on global queries
|
||||
- The network results (IP and MAC address) are only shown on global queries, if the search term starts with either IP, MAC or Address. (We compare case-insensitive.)
|
||||
|
||||
### Returning results
|
||||
We return the results in two steps:
|
||||
1. All results which we can create very fast like shutdown or logoff via [`Main.Query(Query query)`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs).
|
||||
2. All results which need some time to create like the network results (IP, MAC) via [`Main.Query(Query query, bool delayedExecution)`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs).
|
||||
|
||||
## [Unit Tests](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests)
|
||||
We have a [Unit Test project](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests) that executes various test to ensure that the plugin works as expected.
|
||||
|
||||
|
@@ -28,10 +28,10 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
|
||||
[DataRow("hibernate", "Images\\sleep.dark.png")]
|
||||
[DataRow("empty recycle", "Images\\recyclebin.dark.png")]
|
||||
[DataRow("uefi firmware settings", "Images\\firmwareSettings.dark.png")]
|
||||
[DataRow("ip v4 addr", "Images\\networkAdapter.dark.png")]
|
||||
[DataRow("ip v6 addr", "Images\\networkAdapter.dark.png")]
|
||||
[DataRow("mac addr", "Images\\networkAdapter.dark.png")]
|
||||
public void IconThemeDarkTest(string typedString, string expectedResult)
|
||||
[DataRow("ip v4 addr", "Images\\networkAdapter.dark.png", true)]
|
||||
[DataRow("ip v6 addr", "Images\\networkAdapter.dark.png", true)]
|
||||
[DataRow("mac addr", "Images\\networkAdapter.dark.png", true)]
|
||||
public void IconThemeDarkTest(string typedString, string expectedResult, bool isDelayed = default)
|
||||
{
|
||||
// Setup
|
||||
Mock<Main> main = new Mock<Main>();
|
||||
@@ -40,7 +40,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
|
||||
Query expectedQuery = new Query(typedString);
|
||||
|
||||
// Act
|
||||
var result = main.Object.Query(expectedQuery).FirstOrDefault().IcoPath;
|
||||
var result = !isDelayed ? main.Object.Query(expectedQuery).FirstOrDefault().IcoPath : main.Object.Query(expectedQuery, true).FirstOrDefault().IcoPath;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
@@ -55,10 +55,10 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
|
||||
[DataRow("hibernate", "Images\\sleep.light.png")]
|
||||
[DataRow("empty recycle", "Images\\recyclebin.light.png")]
|
||||
[DataRow("uefi firmware settings", "Images\\firmwareSettings.light.png")]
|
||||
[DataRow("ipv4 addr", "Images\\networkAdapter.light.png")]
|
||||
[DataRow("ipv6 addr", "Images\\networkAdapter.light.png")]
|
||||
[DataRow("mac addr", "Images\\networkAdapter.light.png")]
|
||||
public void IconThemeLightTest(string typedString, string expectedResult)
|
||||
[DataRow("ipv4 addr", "Images\\networkAdapter.light.png", true)]
|
||||
[DataRow("ipv6 addr", "Images\\networkAdapter.light.png", true)]
|
||||
[DataRow("mac addr", "Images\\networkAdapter.light.png", true)]
|
||||
public void IconThemeLightTest(string typedString, string expectedResult, bool isDelayed = default)
|
||||
{
|
||||
// Setup
|
||||
Mock<Main> main = new Mock<Main>();
|
||||
@@ -67,7 +67,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
|
||||
Query expectedQuery = new Query(typedString);
|
||||
|
||||
// Act
|
||||
var result = main.Object.Query(expectedQuery).FirstOrDefault().IcoPath;
|
||||
var result = !isDelayed ? main.Object.Query(expectedQuery).FirstOrDefault().IcoPath : main.Object.Query(expectedQuery, true).FirstOrDefault().IcoPath;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
|
@@ -28,11 +28,6 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
|
||||
[DataRow("sleep", "Put computer to sleep")]
|
||||
[DataRow("hibernate", "Hibernate computer")]
|
||||
[DataRow("empty recycle", "Empty Recycle Bin")]
|
||||
[DataRow("ip", "IPv4 address of")]
|
||||
[DataRow("address", "IPv4 address of")] // searching for address should show ipv4 first
|
||||
[DataRow("ip v4", "IPv4 address of")]
|
||||
[DataRow("ip v6", "IPv6 address of")]
|
||||
[DataRow("mac addr", "MAC address of")]
|
||||
public void EnvironmentIndependentQueryResults(string typedString, string expectedResult)
|
||||
{
|
||||
// Setup
|
||||
@@ -46,6 +41,25 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
|
||||
Assert.IsTrue(result.StartsWith(expectedResult, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("ip", "IPv4 address of")]
|
||||
[DataRow("address", "IPv4 address of")] // searching for address should show ipv4 first
|
||||
[DataRow("ip v4", "IPv4 address of")]
|
||||
[DataRow("ip v6", "IPv6 address of")]
|
||||
[DataRow("mac addr", "MAC address of")]
|
||||
public void DelayedQueryResults(string typedString, string expectedResult)
|
||||
{
|
||||
// Setup
|
||||
Mock<Main> main = new Mock<Main>();
|
||||
Query expectedQuery = new Query(typedString);
|
||||
|
||||
// Act
|
||||
var result = main.Object.Query(expectedQuery, true).FirstOrDefault().SubTitle;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.StartsWith(expectedResult, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UefiCommandIsAvailableOnUefiSystems()
|
||||
{
|
||||
|
@@ -2,10 +2,9 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using Microsoft.PowerToys.Run.Plugin.System.Properties;
|
||||
@@ -28,6 +27,11 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
|
||||
internal const int EWXPOWEROFF = 0x00000008;
|
||||
internal const int EWXFORCEIFHUNG = 0x00000010;
|
||||
|
||||
// Cache for network interface information to save query time
|
||||
private const int UpdateCacheIntervalSeconds = 5;
|
||||
private static List<NetworkConnectionProperties> networkPropertiesCache = new List<NetworkConnectionProperties>();
|
||||
private static DateTime timeOfLastNetworkQuery;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list with all system command results
|
||||
/// </summary>
|
||||
@@ -154,11 +158,16 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
var interfaces = NetworkInterface.GetAllNetworkInterfaces().Where(x => x.NetworkInterfaceType != NetworkInterfaceType.Loopback && x.GetPhysicalAddress() != null);
|
||||
foreach (NetworkInterface i in interfaces)
|
||||
// We update the cache only if the last query is older than 'updateCacheIntervalSeconds' seconds
|
||||
DateTime timeOfLastNetworkQueryBefore = timeOfLastNetworkQuery;
|
||||
timeOfLastNetworkQuery = DateTime.Now; // Set time of last query to this query
|
||||
if ((timeOfLastNetworkQuery - timeOfLastNetworkQueryBefore).TotalSeconds >= UpdateCacheIntervalSeconds)
|
||||
{
|
||||
NetworkConnectionProperties intInfo = new NetworkConnectionProperties(i);
|
||||
networkPropertiesCache = NetworkConnectionProperties.GetList();
|
||||
}
|
||||
|
||||
foreach (NetworkConnectionProperties intInfo in networkPropertiesCache)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(intInfo.IPv4))
|
||||
{
|
||||
results.Add(new Result()
|
||||
|
@@ -4,7 +4,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.PowerToys.Run.Plugin.System.Properties;
|
||||
@@ -94,28 +97,29 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
|
||||
/// <summary>
|
||||
/// Gets the list of gateway IPs as string
|
||||
/// </summary>
|
||||
internal List<string> Gateways { get; private set; } = new List<string>();
|
||||
internal List<IPAddress> Gateways { get; private set; } = new List<IPAddress>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of DHCP server IPs as string
|
||||
/// </summary>
|
||||
internal List<string> DhcpServers { get; private set; } = new List<string>();
|
||||
internal IPAddressCollection DhcpServers { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of DNS server IPs as string
|
||||
/// </summary>
|
||||
internal List<string> DnsServers { get; private set; } = new List<string>();
|
||||
internal IPAddressCollection DnsServers { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of WINS server IPs as string
|
||||
/// </summary>
|
||||
internal List<string> WinsServers { get; private set; } = new List<string>();
|
||||
internal IPAddressCollection WinsServers { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NetworkConnectionProperties"/> class.
|
||||
/// This private constructor is used when we crete the list of adapter (properties) by calling <see cref="NetworkConnectionProperties.GetList()"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkInterface">Network interface of the connection</param>
|
||||
internal NetworkConnectionProperties(NetworkInterface networkInterface)
|
||||
private NetworkConnectionProperties(NetworkInterface networkInterface)
|
||||
{
|
||||
// Setting adapter properties
|
||||
Adapter = networkInterface.Description;
|
||||
@@ -133,6 +137,23 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list with all network adapters and their properties
|
||||
/// </summary>
|
||||
/// <returns>List containing all network adapters</returns>
|
||||
internal static List<NetworkConnectionProperties> GetList()
|
||||
{
|
||||
List<NetworkConnectionProperties> list = new List<NetworkConnectionProperties>();
|
||||
|
||||
var interfaces = NetworkInterface.GetAllNetworkInterfaces().Where(x => x.NetworkInterfaceType != NetworkInterfaceType.Loopback && x.GetPhysicalAddress() != null);
|
||||
foreach (NetworkInterface i in interfaces)
|
||||
{
|
||||
list.Add(new NetworkConnectionProperties(i));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted string with the adapter details
|
||||
/// </summary>
|
||||
@@ -179,64 +200,59 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
|
||||
/// <param name="properties">Element of the type <see cref="IPInterfaceProperties"/>.</param>
|
||||
private void SetIpProperties(IPInterfaceProperties properties)
|
||||
{
|
||||
var ipList = properties.UnicastAddresses;
|
||||
DateTime t = DateTime.Now;
|
||||
|
||||
foreach (var ip in ipList)
|
||||
UnicastIPAddressInformationCollection ipList = properties.UnicastAddresses;
|
||||
GatewayIPAddressInformationCollection gwList = properties.GatewayAddresses;
|
||||
DhcpServers = properties.DhcpServerAddresses;
|
||||
DnsServers = properties.DnsAddresses;
|
||||
WinsServers = properties.WinsServersAddresses;
|
||||
|
||||
for (int i = 0; i < ipList.Count; i++)
|
||||
{
|
||||
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
IPAddress ip = ipList[i].Address;
|
||||
|
||||
if (ip.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
IPv4 = ip.Address.ToString();
|
||||
IPv4Mask = ip.IPv4Mask.ToString();
|
||||
IPv4 = ip.ToString();
|
||||
IPv4Mask = ipList[i].IPv4Mask.ToString();
|
||||
}
|
||||
else if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
if (string.IsNullOrEmpty(IPv6Primary))
|
||||
{
|
||||
IPv6Primary = ip.Address.ToString();
|
||||
IPv6Primary = ip.ToString();
|
||||
}
|
||||
|
||||
if (ip.Address.IsIPv6LinkLocal)
|
||||
if (ip.IsIPv6LinkLocal)
|
||||
{
|
||||
IPv6LinkLocal = ip.Address.ToString();
|
||||
IPv6LinkLocal = ip.ToString();
|
||||
}
|
||||
else if (ip.Address.IsIPv6SiteLocal)
|
||||
else if (ip.IsIPv6SiteLocal)
|
||||
{
|
||||
IPv6SiteLocal = ip.Address.ToString();
|
||||
IPv6SiteLocal = ip.ToString();
|
||||
}
|
||||
else if (ip.Address.IsIPv6UniqueLocal)
|
||||
else if (ip.IsIPv6UniqueLocal)
|
||||
{
|
||||
IPv6UniqueLocal = ip.Address.ToString();
|
||||
IPv6UniqueLocal = ip.ToString();
|
||||
}
|
||||
else if (ip.SuffixOrigin == SuffixOrigin.Random)
|
||||
else if (ipList[i].SuffixOrigin == SuffixOrigin.Random)
|
||||
{
|
||||
IPv6Temporary = ip.Address.ToString();
|
||||
IPv6Temporary = ip.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
IPv6Global = ip.Address.ToString();
|
||||
IPv6Global = ip.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ip in properties.GatewayAddresses)
|
||||
for (int i = 0; i < gwList.Count; i++)
|
||||
{
|
||||
Gateways.Add(ip.Address.ToString());
|
||||
Gateways.Add(gwList[i].Address);
|
||||
}
|
||||
|
||||
foreach (var ip in properties.DhcpServerAddresses)
|
||||
{
|
||||
DhcpServers.Add(ip.ToString());
|
||||
}
|
||||
|
||||
foreach (var ip in properties.DnsAddresses)
|
||||
{
|
||||
DnsServers.Add(ip.ToString());
|
||||
}
|
||||
|
||||
foreach (var ip in properties.WinsServersAddresses)
|
||||
{
|
||||
WinsServers.Add(ip.ToString());
|
||||
}
|
||||
Debug.Print($"time for getting ips: {DateTime.Now - t}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -284,21 +300,20 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
|
||||
/// <exception cref="ArgumentException">If the parameter <paramref name="property"/> is not of the type <see cref="string"/> or <see cref="List{String}"/>.</exception>
|
||||
private static string CreateIpInfoForDetailsText(string title, dynamic property)
|
||||
{
|
||||
if (property is string)
|
||||
switch (property)
|
||||
{
|
||||
case string:
|
||||
return $"\n{title}{property}";
|
||||
}
|
||||
else if (property is List<string> list)
|
||||
{
|
||||
return list.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
|
||||
}
|
||||
else if (property is null)
|
||||
{
|
||||
case List<string> listString:
|
||||
return listString.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
|
||||
case List<IPAddress> listIP:
|
||||
return listIP.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
|
||||
case IPAddressCollection collectionIP:
|
||||
return collectionIP.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
|
||||
case null:
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{property}' is not of type 'string' or 'List<string>'.", nameof(property));
|
||||
default:
|
||||
throw new ArgumentException($"'{property}' is not of type 'string', 'List<string>', 'List<IPAddress>' or 'IPAddressCollection'.", nameof(property));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ using Wox.Plugin.Common.Win32;
|
||||
|
||||
namespace Microsoft.PowerToys.Run.Plugin.System
|
||||
{
|
||||
public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu
|
||||
public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu, IDelayedExecutionPlugin
|
||||
{
|
||||
private PluginInitContext _context;
|
||||
|
||||
@@ -73,17 +73,16 @@ namespace Microsoft.PowerToys.Run.Plugin.System
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
List<Result> results = new List<Result>();
|
||||
CultureInfo culture = _localizeSystemCommands ? CultureInfo.CurrentUICulture : new CultureInfo("en-US");
|
||||
|
||||
if (query == null)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
CultureInfo culture = _localizeSystemCommands ? CultureInfo.CurrentUICulture : new CultureInfo("en-US");
|
||||
// normal system commands are fast and can be returned immediately
|
||||
var systemCommands = Commands.GetSystemCommands(IsBootedInUefiMode, IconTheme, culture, _confirmSystemCommands);
|
||||
var networkConnectionResults = Commands.GetNetworkConnectionResults(IconTheme, culture);
|
||||
|
||||
foreach (var c in systemCommands)
|
||||
{
|
||||
var resultMatch = StringMatcher.FuzzySearch(query.Search, c.Title);
|
||||
@@ -95,18 +94,39 @@ namespace Microsoft.PowerToys.Run.Plugin.System
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var r in networkConnectionResults)
|
||||
{
|
||||
// On global queries the first word/part has to be 'ip', 'mac' or 'address'
|
||||
if (string.IsNullOrEmpty(query.ActionKeyword))
|
||||
{
|
||||
string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; ");
|
||||
if (!keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// The following information result is not returned because delayed queries doesn't clear output if no results are available.
|
||||
// On global queries the first word/part has to be 'ip', 'mac' or 'address' for network results
|
||||
// string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; ");
|
||||
// if (!string.IsNullOrEmpty(query.ActionKeyword) || keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)))
|
||||
// {
|
||||
// results.Add(new Result()
|
||||
// {
|
||||
// Title = "Getting network informations. Please wait ...",
|
||||
// IcoPath = $"Images\\networkAdapter.{IconTheme}.png",
|
||||
// Score = StringMatcher.FuzzySearch("address", "ip address").Score,
|
||||
// });
|
||||
// }
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query, bool delayedExecution)
|
||||
{
|
||||
List<Result> results = new List<Result>();
|
||||
CultureInfo culture = _localizeSystemCommands ? CultureInfo.CurrentUICulture : new CultureInfo("en-US");
|
||||
|
||||
if (query == null)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
// Network (ip and mac) results are slow with many network cards and returned delayed.
|
||||
// On global queries the first word/part has to be 'ip', 'mac' or 'address' for network results
|
||||
string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; ");
|
||||
if (!string.IsNullOrEmpty(query.ActionKeyword) || keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)))
|
||||
{
|
||||
var networkConnectionResults = Commands.GetNetworkConnectionResults(IconTheme, culture);
|
||||
foreach (var r in networkConnectionResults)
|
||||
{
|
||||
var resultMatch = StringMatcher.FuzzySearch(query.Search, r.SubTitle);
|
||||
if (resultMatch.Score > 0)
|
||||
{
|
||||
@@ -115,6 +135,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System
|
||||
results.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
Reference in New Issue
Block a user