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:
bao-qian
2016-08-18 01:16:40 +01:00
parent e690bae7d1
commit 01e812aebf
7 changed files with 405 additions and 34 deletions

View File

@@ -15,6 +15,7 @@ namespace Wox.Plugin.Program
public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable
{ {
private static List<Program> _programs = new List<Program>(); private static List<Program> _programs = new List<Program>();
private static List<UWPApp> _uwpApps = new List<UWPApp>();
private PluginInitContext _context; private PluginInitContext _context;
@@ -46,12 +47,16 @@ namespace Wox.Plugin.Program
public List<Result> Query(Query query) public List<Result> Query(Query query)
{ {
var results = _programs.AsParallel() var results1 = _programs.AsParallel()
.Where(p => Score(p, query.Search) > 0) .Where(p => Score(p, query.Search) > 0)
.OrderByDescending(p => p.Score) .Select(ResultFromProgram);
.Select(ResultFromProgram)
.ToList(); var results2 = _uwpApps.AsParallel()
return results; .Where(u => Score(u, query.Search) > 0)
.Select(ResultFromUWPApp);
var result = results1.Concat(results2).ToList();
return result;
} }
public Result ResultFromProgram(Program p) public Result ResultFromProgram(Program p)
@@ -76,6 +81,25 @@ namespace Wox.Plugin.Program
}; };
return result; 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) private int Score(Program program, string query)
{ {
@@ -87,6 +111,16 @@ namespace Wox.Plugin.Program
return score; 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) public void Init(PluginInitContext context)
{ {
_context = context; _context = context;
@@ -105,6 +139,13 @@ namespace Wox.Plugin.Program
_programs = programs.ToList(); _programs = programs.ToList();
_cache.Programs = _programs; _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() private static List<ProgramSource> ProgramSources()
@@ -180,36 +221,43 @@ namespace Wox.Plugin.Program
public List<Result> LoadContextMenus(Result selectedResult) public List<Result> LoadContextMenus(Result selectedResult)
{ {
Program p = selectedResult.ContextData as Program; 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"), new Result
Action = _ =>
{ {
var info = new ProcessStartInfo Title = _context.API.GetTranslation("wox_plugin_program_run_as_administrator"),
Action = _ =>
{ {
FileName = p.Path, var info = new ProcessStartInfo
WorkingDirectory = p.Directory, {
Verb = "runas" FileName = p.Path,
}; WorkingDirectory = p.Directory,
var hide = StartProcess(info); Verb = "runas"
return hide; };
var hide = StartProcess(info);
return hide;
},
IcoPath = "Images/cmd.png"
}, },
IcoPath = "Images/cmd.png" new Result
},
new Result
{
Title = _context.API.GetTranslation("wox_plugin_program_open_containing_folder"),
Action = _ =>
{ {
var hide = StartProcess(new ProcessStartInfo(p.Directory)); Title = _context.API.GetTranslation("wox_plugin_program_open_containing_folder"),
return hide; Action = _ =>
}, {
IcoPath = "Images/folder.png" var hide = StartProcess(new ProcessStartInfo(p.Directory));
} return hide;
}; },
return contextMenus; IcoPath = "Images/folder.png"
}
};
return contextMenus;
}
else
{
return new List<Result>();
}
} }
private bool StartProcess(ProcessStartInfo info) private bool StartProcess(ProcessStartInfo info)

View 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);
}
}

View File

@@ -13,6 +13,8 @@
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir> <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@@ -34,6 +36,10 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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"> <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> <HintPath>..\..\packages\JetBrains.Annotations.10.1.4\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@@ -45,6 +51,10 @@
</Reference> </Reference>
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" /> <Reference Include="PresentationFramework" />
<Reference Include="ShObjIdlTlb">
<HintPath>.\ShObjIdlTlb.dll</HintPath>
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
@@ -61,6 +71,7 @@
<DependentUpon>AddProgramSource.xaml</DependentUpon> <DependentUpon>AddProgramSource.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="FileChangeWatcher.cs" /> <Compile Include="FileChangeWatcher.cs" />
<Compile Include="ProgramSources\UWP.cs" />
<Compile Include="SuffixesConverter.cs" /> <Compile Include="SuffixesConverter.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="ProgramIndexCache.cs" /> <Compile Include="ProgramIndexCache.cs" />
@@ -142,7 +153,17 @@
<Name>Wox.Plugin</Name> <Name>Wox.Plugin</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\packages\UwpDesktop.10.0.10586.2\analyzers\dotnet\UwpDesktopAnalyzer.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <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. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">

View File

@@ -3,4 +3,5 @@
<package id="JetBrains.Annotations" version="10.1.4" targetFramework="net452" /> <package id="JetBrains.Annotations" version="10.1.4" targetFramework="net452" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net452" /> <package id="Newtonsoft.Json" version="8.0.3" targetFramework="net452" />
<package id="System.Runtime" version="4.0.0" targetFramework="net452" /> <package id="System.Runtime" version="4.0.0" targetFramework="net452" />
<package id="UwpDesktop" version="10.0.10586.2" targetFramework="net452" />
</packages> </packages>

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows.Media;
namespace Wox.Plugin namespace Wox.Plugin
{ {
@@ -29,6 +30,11 @@ namespace Wox.Plugin
} }
} }
public delegate ImageSource IconDelegate();
public IconDelegate Icon;
/// <summary> /// <summary>
/// return true to hide wox after select result /// return true to hide wox after select result
/// </summary> /// </summary>

View File

@@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25123.0 VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Test", "Wox.Test\Wox.Test.csproj", "{FF742965-9A80-41A5-B042-D6C7D3A21708}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Test", "Wox.Test\Wox.Test.csproj", "{FF742965-9A80-41A5-B042-D6C7D3A21708}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject

View File

@@ -1,5 +1,8 @@
using System.Windows.Media; using System;
using System.Windows.Media;
using System.Windows.Threading;
using Wox.Infrastructure.Image; using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Plugin; using Wox.Plugin;
@@ -15,7 +18,29 @@ namespace Wox.ViewModel
} }
} }
public ImageSource Image => ImageLoader.Load(Result.IcoPath); public ImageSource Image
{
get
{
if (string.IsNullOrEmpty(Result.IcoPath))
{
ImageSource icon = null;
try
{
return Result.Icon();
}
catch (Exception e)
{
Log.Exception(e);
return ImageLoader.Load(Result.IcoPath);
}
}
else
{
return ImageLoader.Load(Result.IcoPath);
}
}
}
public Result Result { get; } public Result Result { get; }