mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +02:00
Initial support for uwp app #198
1. basic support, better than nothing... 2. thanks great contribution from @talynone 3. #198
This commit is contained in:
@@ -15,6 +15,7 @@ namespace Wox.Plugin.Program
|
||||
public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable
|
||||
{
|
||||
private static List<Program> _programs = new List<Program>();
|
||||
private static List<UWPApp> _uwpApps = new List<UWPApp>();
|
||||
|
||||
private PluginInitContext _context;
|
||||
|
||||
@@ -46,12 +47,16 @@ namespace Wox.Plugin.Program
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var results = _programs.AsParallel()
|
||||
.Where(p => Score(p, query.Search) > 0)
|
||||
.OrderByDescending(p => p.Score)
|
||||
.Select(ResultFromProgram)
|
||||
.ToList();
|
||||
return results;
|
||||
var results1 = _programs.AsParallel()
|
||||
.Where(p => Score(p, query.Search) > 0)
|
||||
.Select(ResultFromProgram);
|
||||
|
||||
var results2 = _uwpApps.AsParallel()
|
||||
.Where(u => Score(u, query.Search) > 0)
|
||||
.Select(ResultFromUWPApp);
|
||||
var result = results1.Concat(results2).ToList();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Result ResultFromProgram(Program p)
|
||||
@@ -76,6 +81,25 @@ namespace Wox.Plugin.Program
|
||||
};
|
||||
return result;
|
||||
}
|
||||
public Result ResultFromUWPApp(UWPApp uwpApp)
|
||||
{
|
||||
var app = uwpApp.Apps[0];
|
||||
var result = new Result
|
||||
{
|
||||
Title = app.DisplayName,
|
||||
SubTitle = $"Windows Store app: {app.Description}",
|
||||
Icon = app.Logo,
|
||||
Score = uwpApp.Score,
|
||||
ContextData = app,
|
||||
Action = e =>
|
||||
{
|
||||
app.Launch();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private int Score(Program program, string query)
|
||||
{
|
||||
@@ -87,6 +111,16 @@ namespace Wox.Plugin.Program
|
||||
return score;
|
||||
}
|
||||
|
||||
private int Score(UWPApp app, string query)
|
||||
{
|
||||
var score1 = StringMatcher.Score(app.Apps[0].DisplayName, query);
|
||||
var score2 = StringMatcher.ScoreForPinyin(app.Apps[0].DisplayName, query);
|
||||
var score3 = StringMatcher.Score(app.Apps[0].Description, query);
|
||||
var score = new[] { score1, score2, score3 }.Max();
|
||||
app.Score = score;
|
||||
return score;
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
@@ -105,6 +139,13 @@ namespace Wox.Plugin.Program
|
||||
|
||||
_programs = programs.ToList();
|
||||
_cache.Programs = _programs;
|
||||
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
if (support)
|
||||
{
|
||||
_uwpApps = UWPApp.All();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ProgramSource> ProgramSources()
|
||||
@@ -180,36 +221,43 @@ namespace Wox.Plugin.Program
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
Program p = selectedResult.ContextData as Program;
|
||||
List<Result> contextMenus = new List<Result>
|
||||
if (p != null)
|
||||
{
|
||||
new Result
|
||||
List<Result> contextMenus = new List<Result>
|
||||
{
|
||||
Title = _context.API.GetTranslation("wox_plugin_program_run_as_administrator"),
|
||||
Action = _ =>
|
||||
new Result
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
Title = _context.API.GetTranslation("wox_plugin_program_run_as_administrator"),
|
||||
Action = _ =>
|
||||
{
|
||||
FileName = p.Path,
|
||||
WorkingDirectory = p.Directory,
|
||||
Verb = "runas"
|
||||
};
|
||||
var hide = StartProcess(info);
|
||||
return hide;
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = p.Path,
|
||||
WorkingDirectory = p.Directory,
|
||||
Verb = "runas"
|
||||
};
|
||||
var hide = StartProcess(info);
|
||||
return hide;
|
||||
},
|
||||
IcoPath = "Images/cmd.png"
|
||||
},
|
||||
IcoPath = "Images/cmd.png"
|
||||
},
|
||||
new Result
|
||||
{
|
||||
Title = _context.API.GetTranslation("wox_plugin_program_open_containing_folder"),
|
||||
Action = _ =>
|
||||
new Result
|
||||
{
|
||||
var hide = StartProcess(new ProcessStartInfo(p.Directory));
|
||||
return hide;
|
||||
},
|
||||
IcoPath = "Images/folder.png"
|
||||
}
|
||||
};
|
||||
return contextMenus;
|
||||
Title = _context.API.GetTranslation("wox_plugin_program_open_containing_folder"),
|
||||
Action = _ =>
|
||||
{
|
||||
var hide = StartProcess(new ProcessStartInfo(p.Directory));
|
||||
return hide;
|
||||
},
|
||||
IcoPath = "Images/folder.png"
|
||||
}
|
||||
};
|
||||
return contextMenus;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
}
|
||||
|
||||
private bool StartProcess(ProcessStartInfo info)
|
||||
|
||||
270
Plugins/Wox.Plugin.Program/ProgramSources/UWP.cs
Normal file
270
Plugins/Wox.Plugin.Program/ProgramSources/UWP.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Management.Deployment;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage.Streams;
|
||||
using AppxPackaing;
|
||||
using Shell;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using IStream = AppxPackaing.IStream;
|
||||
|
||||
namespace Wox.Plugin.Program.ProgramSources
|
||||
{
|
||||
public class UWPApp
|
||||
{
|
||||
public string Name { get; }
|
||||
public string FullName { get; }
|
||||
public string FamilyName { get; }
|
||||
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Logo { get; set; } //todo
|
||||
public string PublisherDisplayName { get; set; }
|
||||
public string Location { get; set; }
|
||||
|
||||
public Application[] Apps { get; set; }
|
||||
|
||||
public Package Package { get; }
|
||||
|
||||
public int Score { get; set; }
|
||||
|
||||
|
||||
|
||||
public UWPApp(Package package)
|
||||
{
|
||||
Package = package;
|
||||
Name = Package.Id.Name;
|
||||
FullName = Package.Id.FullName;
|
||||
FamilyName = Package.Id.FamilyName;
|
||||
Location = Package.InstalledLocation.Path;
|
||||
|
||||
InitializeAppDisplayInfo(package);
|
||||
InitializeAppInfo(package);
|
||||
Apps = Apps.Where(a =>
|
||||
{
|
||||
var valid = !string.IsNullOrEmpty(a.Executable) &&
|
||||
!string.IsNullOrEmpty(a.UserModelId) &&
|
||||
!string.IsNullOrEmpty(a.DisplayName);
|
||||
return valid;
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private void InitializeAppInfo(Package package)
|
||||
{
|
||||
var manifestPath = Path.Combine(Location, "AppxManifest.xml");
|
||||
var appxFactory = new AppxFactory();
|
||||
IStream manifestStream;
|
||||
var result = SHCreateStreamOnFileEx(
|
||||
manifestPath,
|
||||
Stgm.Read | Stgm.ShareExclusive,
|
||||
0,
|
||||
false,
|
||||
null,
|
||||
out manifestStream
|
||||
);
|
||||
|
||||
if (result == Hresult.Ok)
|
||||
{
|
||||
var reader = appxFactory.CreateManifestReader(manifestStream);
|
||||
|
||||
var properties = reader.GetProperties();
|
||||
Logo = properties.GetStringValue("Logo");
|
||||
Logo = Path.Combine(Location, Logo);
|
||||
PublisherDisplayName = properties.GetStringValue("PublisherDisplayName");
|
||||
DisplayName = properties.GetStringValue("DisplayName");
|
||||
Description = properties.GetStringValue("Description");
|
||||
|
||||
var apps = reader.GetApplications();
|
||||
int i = 0;
|
||||
while (apps.GetHasCurrent() != 0 && i <= Apps.Length)
|
||||
{
|
||||
var currentApp = apps.GetCurrent();
|
||||
var userModelId = currentApp.GetAppUserModelId();
|
||||
var executable = currentApp.GetStringValue("Executable");
|
||||
|
||||
Apps[i].Executable = executable;
|
||||
Apps[i].UserModelId = userModelId;
|
||||
|
||||
apps.MoveNext();
|
||||
i++;
|
||||
}
|
||||
if (i != Apps.Length)
|
||||
{
|
||||
var message = $"Wrong application number - {Name}: {i}";
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void InitializeAppDisplayInfo(Package package)
|
||||
{
|
||||
IReadOnlyList<AppListEntry> apps;
|
||||
try
|
||||
{
|
||||
apps = package.GetAppListEntriesAsync().AsTask().Result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"{e.Message} @ {Name}";
|
||||
Console.WriteLine(message);
|
||||
return;
|
||||
}
|
||||
|
||||
Apps = apps.Select(a => new Application
|
||||
{
|
||||
DisplayName = a.DisplayInfo.DisplayName,
|
||||
Description = a.DisplayInfo.Description,
|
||||
LogoStream = a.DisplayInfo.GetLogo(new Size(150, 150))
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public static List<UWPApp> All()
|
||||
{
|
||||
var packages = CurrentUserPackages();
|
||||
var uwps = new List<UWPApp>();
|
||||
Parallel.ForEach(packages, p =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var u = new UWPApp(p);
|
||||
if (u.Apps.Length > 0)
|
||||
{
|
||||
uwps.Add(u);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// if there are errors, just ignore it and continue
|
||||
var message = $"Can't parse {p.Id.Name}: {e.Message}";
|
||||
Log.Error(message);
|
||||
}
|
||||
});
|
||||
return uwps;
|
||||
}
|
||||
|
||||
private static IEnumerable<Package> CurrentUserPackages()
|
||||
{
|
||||
var user = WindowsIdentity.GetCurrent()?.User;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var userSecurityId = user.Value;
|
||||
var packageManager = new PackageManager();
|
||||
var packages = packageManager.FindPackagesForUser(userSecurityId);
|
||||
// cw5n1h2txyewy is PublisherID for unnormal package
|
||||
// e.g. ShellExperienceHost
|
||||
// but WindowsFeedBack is flitered
|
||||
// tested with windows 10 1511
|
||||
const string filteredPublisherID = "cw5n1h2txyewy";
|
||||
packages = packages.Where(
|
||||
p => !p.IsFramework && p.Id.PublisherId != filteredPublisherID
|
||||
);
|
||||
return packages;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Package[] { };
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return FamilyName;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var uwp = obj as UWPApp;
|
||||
if (uwp != null)
|
||||
{
|
||||
return FamilyName.Equals(uwp.FamilyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return FamilyName.GetHashCode();
|
||||
}
|
||||
|
||||
|
||||
public class Application
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public RandomAccessStreamReference LogoStream { get; set; }
|
||||
public string UserModelId { get; set; }
|
||||
public string Executable { get; set; }
|
||||
public string PublisherDisplayName { get; set; }
|
||||
|
||||
// todo: wrap with try exception
|
||||
public void Launch()
|
||||
{
|
||||
var appManager = new ApplicationActivationManager();
|
||||
uint unusedPid;
|
||||
const string noArgs = "";
|
||||
const ACTIVATEOPTIONS noFlags = ACTIVATEOPTIONS.AO_NONE;
|
||||
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out unusedPid);
|
||||
}
|
||||
|
||||
|
||||
public BitmapImage Logo()
|
||||
{
|
||||
IRandomAccessStreamWithContentType stream;
|
||||
try
|
||||
{
|
||||
stream = LogoStream.OpenReadAsync().AsTask().Result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"{e.Message} @ {DisplayName}";
|
||||
Log.Error(message);
|
||||
throw;
|
||||
}
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.StreamSource = stream.AsStream();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.EndInit();
|
||||
// magic! don't know why UI thread can't own it
|
||||
image.Freeze();
|
||||
return image;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName}: {Description}";
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum Stgm : uint
|
||||
{
|
||||
Read = 0x0,
|
||||
ShareExclusive = 0x10,
|
||||
}
|
||||
|
||||
private enum Hresult : uint
|
||||
{
|
||||
Ok = 0x0000,
|
||||
}
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMode, uint attributes, bool create, IStream reserved, out IStream stream);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -34,6 +36,10 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="AppxPackagingTlb">
|
||||
<HintPath>.\AppxPackagingTlb.dll</HintPath>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="JetBrains.Annotations, Version=10.1.4.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\JetBrains.Annotations.10.1.4\lib\net20\JetBrains.Annotations.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@@ -45,6 +51,10 @@
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="ShObjIdlTlb">
|
||||
<HintPath>.\ShObjIdlTlb.dll</HintPath>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
@@ -61,6 +71,7 @@
|
||||
<DependentUpon>AddProgramSource.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="FileChangeWatcher.cs" />
|
||||
<Compile Include="ProgramSources\UWP.cs" />
|
||||
<Compile Include="SuffixesConverter.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="ProgramIndexCache.cs" />
|
||||
@@ -142,7 +153,17 @@
|
||||
<Name>Wox.Plugin</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Analyzer Include="..\..\packages\UwpDesktop.10.0.10586.2\analyzers\dotnet\UwpDesktopAnalyzer.dll" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\..\packages\UwpDesktop.10.0.10586.2\build\portable-net45+uap\UwpDesktop.targets" Condition="Exists('..\..\packages\UwpDesktop.10.0.10586.2\build\portable-net45+uap\UwpDesktop.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\UwpDesktop.10.0.10586.2\build\portable-net45+uap\UwpDesktop.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\UwpDesktop.10.0.10586.2\build\portable-net45+uap\UwpDesktop.targets'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
<package id="JetBrains.Annotations" version="10.1.4" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net452" />
|
||||
<package id="System.Runtime" version="4.0.0" targetFramework="net452" />
|
||||
<package id="UwpDesktop" version="10.0.10586.2" targetFramework="net452" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user