mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-12-13 02:39:22 +08:00
340 lines
12 KiB
C#
340 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using Microsoft.Win32;
|
|
|
|
namespace Wox.Plugin.ControlPanel
|
|
{
|
|
//from:https://raw.githubusercontent.com/CoenraadS/Windows-Control-Panel-Items
|
|
public static class ControlPanelList
|
|
{
|
|
private const uint GROUP_ICON = 14;
|
|
private const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
|
|
private const string CONTROL = @"%SystemRoot%\System32\control.exe";
|
|
|
|
private delegate bool EnumResNameDelegate(
|
|
IntPtr hModule,
|
|
IntPtr lpszType,
|
|
IntPtr lpszName,
|
|
IntPtr lParam);
|
|
|
|
[DllImport("kernel32.dll", EntryPoint = "EnumResourceNamesW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
static extern bool EnumResourceNamesWithID(IntPtr hModule, uint lpszType, EnumResNameDelegate lpEnumFunc, IntPtr lParam);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
static extern bool FreeLibrary(IntPtr hModule);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax);
|
|
|
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
|
static extern IntPtr LoadImage(IntPtr hinst, IntPtr lpszName, uint uType,
|
|
int cxDesired, int cyDesired, uint fuLoad);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
extern static bool DestroyIcon(IntPtr handle);
|
|
|
|
[DllImport("kernel32.dll")]
|
|
static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
|
|
|
|
static IntPtr defaultIconPtr;
|
|
|
|
|
|
static RegistryKey nameSpace = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ControlPanel\\NameSpace");
|
|
static RegistryKey clsid = Registry.ClassesRoot.OpenSubKey("CLSID");
|
|
|
|
public static List<ControlPanelItem> Create(uint iconSize)
|
|
{
|
|
int size = (int)iconSize;
|
|
RegistryKey currentKey;
|
|
ProcessStartInfo executablePath;
|
|
List<ControlPanelItem> controlPanelItems = new List<ControlPanelItem>();
|
|
string localizedString;
|
|
string infoTip;
|
|
Icon myIcon;
|
|
|
|
foreach (string key in nameSpace.GetSubKeyNames())
|
|
{
|
|
currentKey = clsid.OpenSubKey(key);
|
|
if (currentKey != null)
|
|
{
|
|
executablePath = getExecutablePath(currentKey);
|
|
|
|
if (!(executablePath == null)) //Cannot have item without executable path
|
|
{
|
|
localizedString = getLocalizedString(currentKey);
|
|
|
|
if (!string.IsNullOrEmpty(localizedString))//Cannot have item without Title
|
|
{
|
|
infoTip = getInfoTip(currentKey);
|
|
|
|
myIcon = getIcon(currentKey, size);
|
|
|
|
controlPanelItems.Add(new ControlPanelItem(localizedString, infoTip, key, executablePath, myIcon));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return controlPanelItems;
|
|
}
|
|
|
|
private static ProcessStartInfo getExecutablePath(RegistryKey currentKey)
|
|
{
|
|
ProcessStartInfo executablePath = new ProcessStartInfo();
|
|
string applicationName;
|
|
|
|
if (currentKey.GetValue("System.ApplicationName") != null)
|
|
{
|
|
//CPL Files (usually native MS items)
|
|
applicationName = currentKey.GetValue("System.ApplicationName").ToString();
|
|
executablePath.FileName = Environment.ExpandEnvironmentVariables(CONTROL);
|
|
executablePath.Arguments = "-name " + applicationName;
|
|
}
|
|
else if (currentKey.OpenSubKey("Shell\\Open\\Command") != null && currentKey.OpenSubKey("Shell\\Open\\Command").GetValue(null) != null)
|
|
{
|
|
//Other files (usually third party items)
|
|
string input = "\"" + Environment.ExpandEnvironmentVariables(currentKey.OpenSubKey("Shell\\Open\\Command").GetValue(null).ToString()) + "\"";
|
|
executablePath.FileName = "cmd.exe";
|
|
executablePath.Arguments = "/C " + input;
|
|
executablePath.WindowStyle = ProcessWindowStyle.Hidden;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return executablePath;
|
|
}
|
|
|
|
private static string getLocalizedString(RegistryKey currentKey)
|
|
{
|
|
IntPtr dataFilePointer;
|
|
string[] localizedStringRaw;
|
|
uint stringTableIndex;
|
|
StringBuilder resource;
|
|
string localizedString;
|
|
|
|
if (currentKey.GetValue("LocalizedString") != null)
|
|
{
|
|
localizedStringRaw = currentKey.GetValue("LocalizedString").ToString().Split(new[] { ",-" }, StringSplitOptions.None);
|
|
|
|
if (localizedStringRaw.Length > 1)
|
|
{
|
|
if (localizedStringRaw[0][0] == '@')
|
|
{
|
|
localizedStringRaw[0] = localizedStringRaw[0].Substring(1);
|
|
}
|
|
|
|
localizedStringRaw[0] = Environment.ExpandEnvironmentVariables(localizedStringRaw[0]);
|
|
|
|
dataFilePointer = LoadLibraryEx(localizedStringRaw[0], IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); //Load file with strings
|
|
|
|
stringTableIndex = sanitizeUint(localizedStringRaw[1]);
|
|
|
|
resource = new StringBuilder(255);
|
|
LoadString(dataFilePointer, stringTableIndex, resource, resource.Capacity + 1); //Extract needed string
|
|
FreeLibrary(dataFilePointer);
|
|
|
|
localizedString = resource.ToString();
|
|
|
|
//Some apps don't return a string, although they do have a stringIndex. Use Default value.
|
|
|
|
if (String.IsNullOrEmpty(localizedString))
|
|
{
|
|
if (currentKey.GetValue(null) != null)
|
|
{
|
|
localizedString = currentKey.GetValue(null).ToString();
|
|
}
|
|
else
|
|
{
|
|
return null; //Cannot have item without title.
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
localizedString = localizedStringRaw[0];
|
|
}
|
|
}
|
|
else if (currentKey.GetValue(null) != null)
|
|
{
|
|
localizedString = currentKey.GetValue(null).ToString();
|
|
}
|
|
else
|
|
{
|
|
return null; //Cannot have item without title.
|
|
}
|
|
return localizedString;
|
|
}
|
|
|
|
private static string getInfoTip(RegistryKey currentKey)
|
|
{
|
|
IntPtr dataFilePointer;
|
|
string[] infoTipRaw;
|
|
uint stringTableIndex;
|
|
StringBuilder resource;
|
|
string infoTip = "";
|
|
|
|
if (currentKey.GetValue("InfoTip") != null)
|
|
{
|
|
infoTipRaw = currentKey.GetValue("InfoTip").ToString().Split(new[] { ",-" }, StringSplitOptions.None);
|
|
|
|
if (infoTipRaw.Length == 2)
|
|
{
|
|
if (infoTipRaw[0][0] == '@')
|
|
{
|
|
infoTipRaw[0] = infoTipRaw[0].Substring(1);
|
|
}
|
|
infoTipRaw[0] = Environment.ExpandEnvironmentVariables(infoTipRaw[0]);
|
|
|
|
dataFilePointer = LoadLibraryEx(infoTipRaw[0], IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); //Load file with strings
|
|
|
|
stringTableIndex = sanitizeUint(infoTipRaw[1]);
|
|
|
|
resource = new StringBuilder(255);
|
|
LoadString(dataFilePointer, stringTableIndex, resource, resource.Capacity + 1); //Extract needed string
|
|
FreeLibrary(dataFilePointer);
|
|
|
|
infoTip = resource.ToString();
|
|
}
|
|
else
|
|
{
|
|
infoTip = currentKey.GetValue("InfoTip").ToString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
infoTip = "";
|
|
}
|
|
|
|
return infoTip;
|
|
}
|
|
|
|
private static Icon getIcon(RegistryKey currentKey, int iconSize)
|
|
{
|
|
IntPtr iconPtr = IntPtr.Zero;
|
|
List<string> iconString;
|
|
IntPtr dataFilePointer;
|
|
IntPtr iconIndex;
|
|
Icon myIcon = null;
|
|
|
|
if (currentKey.OpenSubKey("DefaultIcon") != null)
|
|
{
|
|
if (currentKey.OpenSubKey("DefaultIcon").GetValue(null) != null)
|
|
{
|
|
iconString = new List<string>(currentKey.OpenSubKey("DefaultIcon").GetValue(null).ToString().Split(new[] { ',' }, 2));
|
|
if (string.IsNullOrEmpty(iconString[0]))
|
|
{
|
|
// fallback to default icon
|
|
return null;
|
|
}
|
|
|
|
if (iconString[0][0] == '@')
|
|
{
|
|
iconString[0] = iconString[0].Substring(1);
|
|
}
|
|
|
|
dataFilePointer = LoadLibraryEx(iconString[0], IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
|
|
|
|
if (iconString.Count == 2)
|
|
{
|
|
iconIndex = (IntPtr)sanitizeUint(iconString[1]);
|
|
|
|
iconPtr = LoadImage(dataFilePointer, iconIndex, 1, iconSize, iconSize, 0);
|
|
}
|
|
|
|
if (iconPtr == IntPtr.Zero)
|
|
{
|
|
defaultIconPtr = IntPtr.Zero;
|
|
EnumResourceNamesWithID(dataFilePointer, GROUP_ICON, EnumRes, IntPtr.Zero); //Iterate through resources.
|
|
|
|
iconPtr = LoadImage(dataFilePointer, defaultIconPtr, 1, iconSize, iconSize, 0);
|
|
}
|
|
|
|
FreeLibrary(dataFilePointer);
|
|
|
|
if (iconPtr != IntPtr.Zero)
|
|
{
|
|
try
|
|
{
|
|
myIcon = Icon.FromHandle(iconPtr);
|
|
myIcon = (Icon)myIcon.Clone(); //Remove pointer dependancy.
|
|
}
|
|
catch
|
|
{
|
|
//Silently fail for now.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iconPtr != IntPtr.Zero)
|
|
{
|
|
DestroyIcon(iconPtr);
|
|
}
|
|
return myIcon;
|
|
}
|
|
|
|
private static uint sanitizeUint(string args) //Remove all chars before and after first set of digits.
|
|
{
|
|
int x = 0;
|
|
|
|
while (x < args.Length && !Char.IsDigit(args[x]))
|
|
{
|
|
args = args.Substring(1);
|
|
}
|
|
|
|
x = 0;
|
|
|
|
while (x < args.Length && Char.IsDigit(args[x]))
|
|
{
|
|
x++;
|
|
}
|
|
|
|
if (x < args.Length)
|
|
{
|
|
args = args.Remove(x);
|
|
}
|
|
|
|
/*If the logic is correct, this should never through an exception.
|
|
* If there is an exception, then need to analyze what the input is.
|
|
* Returning the wrong number will cause more errors */
|
|
return Convert.ToUInt32(args);
|
|
}
|
|
|
|
private static bool IS_INTRESOURCE(IntPtr value)
|
|
{
|
|
if (((uint)value) > ushort.MaxValue)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
private static uint GET_RESOURCE_ID(IntPtr value)
|
|
{
|
|
if (IS_INTRESOURCE(value))
|
|
return (uint)value;
|
|
throw new NotSupportedException("value is not an ID!");
|
|
}
|
|
|
|
private static string GET_RESOURCE_NAME(IntPtr value)
|
|
{
|
|
if (IS_INTRESOURCE(value))
|
|
return value.ToString();
|
|
return Marshal.PtrToStringUni(value);
|
|
}
|
|
|
|
private static bool EnumRes(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam)
|
|
{
|
|
defaultIconPtr = lpszName;
|
|
return false;
|
|
}
|
|
}
|
|
}
|