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
{
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)

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>
<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">

View File

@@ -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>