diff --git a/Plugins/Wox.Plugin.Program/Main.cs b/Plugins/Wox.Plugin.Program/Main.cs index 851907cfb7..45516c85f9 100644 --- a/Plugins/Wox.Plugin.Program/Main.cs +++ b/Plugins/Wox.Plugin.Program/Main.cs @@ -15,6 +15,7 @@ namespace Wox.Plugin.Program public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable { private static List _programs = new List(); + private static List _uwpApps = new List(); private PluginInitContext _context; @@ -46,12 +47,16 @@ namespace Wox.Plugin.Program public List 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 ProgramSources() @@ -180,36 +221,43 @@ namespace Wox.Plugin.Program public List LoadContextMenus(Result selectedResult) { Program p = selectedResult.ContextData as Program; - List contextMenus = new List + if (p != null) { - new Result + List contextMenus = new List { - 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(); + } } private bool StartProcess(ProcessStartInfo info) diff --git a/Plugins/Wox.Plugin.Program/ProgramSources/UWP.cs b/Plugins/Wox.Plugin.Program/ProgramSources/UWP.cs new file mode 100644 index 0000000000..6cc6eef022 --- /dev/null +++ b/Plugins/Wox.Plugin.Program/ProgramSources/UWP.cs @@ -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 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 All() + { + var packages = CurrentUserPackages(); + var uwps = new List(); + 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 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); + } +} diff --git a/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj b/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj index 874d0b5b48..187948deaa 100644 --- a/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj +++ b/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj @@ -13,6 +13,8 @@ 512 ..\..\ + + true @@ -34,6 +36,10 @@ false + + .\AppxPackagingTlb.dll + True + ..\..\packages\JetBrains.Annotations.10.1.4\lib\net20\JetBrains.Annotations.dll True @@ -45,6 +51,10 @@ + + .\ShObjIdlTlb.dll + True + @@ -61,6 +71,7 @@ AddProgramSource.xaml + @@ -142,7 +153,17 @@ Wox.Plugin + + + + + + + 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}. + + +