diff --git a/src/modules/launcher/.gitattributes b/src/modules/launcher/.gitattributes
new file mode 100644
index 000000000..4f8109456
--- /dev/null
+++ b/src/modules/launcher/.gitattributes
@@ -0,0 +1,65 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
+
+PythonHome/* linguist-vendored
\ No newline at end of file
diff --git a/src/modules/launcher/.gitignore b/src/modules/launcher/.gitignore
new file mode 100644
index 000000000..91216319f
--- /dev/null
+++ b/src/modules/launcher/.gitignore
@@ -0,0 +1,303 @@
+## https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+## https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore
+## C++
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# New to Visual Studio
+*.VC.db
+
+## Wox specific
+Output/*
+/Python.Runtime.dll
+Thumbs.db
+RELEASES
+
+
+*.sublime-*
+*.dgml
+migrateToAutomaticPackageRestore.ps1
+*.pyd
+*.pyc
+*/pinyindb/*
+*.diagsession
+Output-Performance.txt
+*.diff
\ No newline at end of file
diff --git a/src/modules/launcher/Deploy/local_build.ps1 b/src/modules/launcher/Deploy/local_build.ps1
new file mode 100644
index 000000000..9dd7582b1
--- /dev/null
+++ b/src/modules/launcher/Deploy/local_build.ps1
@@ -0,0 +1,4 @@
+New-Alias nuget.exe ".\packages\NuGet.CommandLine.*\tools\NuGet.exe"
+$env:APPVEYOR_BUILD_FOLDER = Convert-Path .
+$env:APPVEYOR_BUILD_VERSION = "1.2.0"
+& .\Deploy\squirrel_installer.ps1
\ No newline at end of file
diff --git a/src/modules/launcher/Doc/app.ico b/src/modules/launcher/Doc/app.ico
new file mode 100644
index 000000000..38c401c8c
Binary files /dev/null and b/src/modules/launcher/Doc/app.ico differ
diff --git a/src/modules/launcher/Doc/app.png b/src/modules/launcher/Doc/app.png
new file mode 100644
index 000000000..8c9ca7971
Binary files /dev/null and b/src/modules/launcher/Doc/app.png differ
diff --git a/src/modules/launcher/Doc/app.psd b/src/modules/launcher/Doc/app.psd
new file mode 100644
index 000000000..833fd6529
Binary files /dev/null and b/src/modules/launcher/Doc/app.psd differ
diff --git a/src/modules/launcher/Doc/app_error.png b/src/modules/launcher/Doc/app_error.png
new file mode 100644
index 000000000..5106d6e8a
Binary files /dev/null and b/src/modules/launcher/Doc/app_error.png differ
diff --git a/src/modules/launcher/Doc/app_error.psd b/src/modules/launcher/Doc/app_error.psd
new file mode 100644
index 000000000..174e9cb62
Binary files /dev/null and b/src/modules/launcher/Doc/app_error.psd differ
diff --git a/src/modules/launcher/ISSUE_TEMPLATE.md b/src/modules/launcher/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..affc8518e
--- /dev/null
+++ b/src/modules/launcher/ISSUE_TEMPLATE.md
@@ -0,0 +1,102 @@
+
+
+### Are you submitting a bug report?
+
+(write your answer here)
+
+
+
+
+
+### Steps to reproduce
+
+
+
+1.
+2.
+3.
+
+### Wox Error Window text
+
+
+(paste here)
+
+
+### Detailed logs
+
+
+(drop your log file here)
+
+### Screenshots (optional)
+
+
+(drop your screenshot here)
+
diff --git a/src/modules/launcher/JsonRPC/wox.py b/src/modules/launcher/JsonRPC/wox.py
new file mode 100644
index 000000000..2e65a629d
--- /dev/null
+++ b/src/modules/launcher/JsonRPC/wox.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+from __future__ import print_function
+import json
+import sys
+import inspect
+
+class Wox(object):
+ """
+ Wox python plugin base
+ """
+
+ def __init__(self):
+ rpc_request = json.loads(sys.argv[1])
+ # proxy is not working now
+ self.proxy = rpc_request.get("proxy",{})
+ request_method_name = rpc_request.get("method")
+ request_parameters = rpc_request.get("parameters")
+ methods = inspect.getmembers(self, predicate=inspect.ismethod)
+
+ request_method = dict(methods)[request_method_name]
+ results = request_method(*request_parameters)
+
+ if request_method_name == "query" or request_method_name == "context_menu":
+ print(json.dumps({"result": results}))
+
+ def query(self,query):
+ """
+ sub class need to override this method
+ """
+ return []
+
+ def context_menu(self, data):
+ """
+ optional context menu entries for a result
+ """
+ return []
+
+ def debug(self,msg):
+ """
+ alert msg
+ """
+ print("DEBUG:{}".format(msg))
+ sys.exit()
+
+class WoxAPI(object):
+
+ @classmethod
+ def change_query(cls,query,requery = False):
+ """
+ change wox query
+ """
+ print(json.dumps({"method": "Wox.ChangeQuery","parameters":[query,requery]}))
+
+ @classmethod
+ def shell_run(cls,cmd):
+ """
+ run shell commands
+ """
+ print(json.dumps({"method": "Wox.ShellRun","parameters":[cmd]}))
+
+ @classmethod
+ def close_app(cls):
+ """
+ close wox
+ """
+ print(json.dumps({"method": "Wox.CloseApp","parameters":[]}))
+
+ @classmethod
+ def hide_app(cls):
+ """
+ hide wox
+ """
+ print(json.dumps({"method": "Wox.HideApp","parameters":[]}))
+
+ @classmethod
+ def show_app(cls):
+ """
+ show wox
+ """
+ print(json.dumps({"method": "Wox.ShowApp","parameters":[]}))
+
+ @classmethod
+ def show_msg(cls,title,sub_title,ico_path=""):
+ """
+ show messagebox
+ """
+ print(json.dumps({"method": "Wox.ShowMsg","parameters":[title,sub_title,ico_path]}))
+
+ @classmethod
+ def open_setting_dialog(cls):
+ """
+ open setting dialog
+ """
+ print(json.dumps({"method": "Wox.OpenSettingDialog","parameters":[]}))
+
+ @classmethod
+ def start_loadingbar(cls):
+ """
+ start loading animation in wox
+ """
+ print(json.dumps({"method": "Wox.StartLoadingBar","parameters":[]}))
+
+ @classmethod
+ def stop_loadingbar(cls):
+ """
+ stop loading animation in wox
+ """
+ print(json.dumps({"method": "Wox.StopLoadingBar","parameters":[]}))
+
+ @classmethod
+ def reload_plugins(cls):
+ """
+ reload all wox plugins
+ """
+ print(json.dumps({"method": "Wox.ReloadPlugins","parameters":[]}))
diff --git a/src/modules/launcher/LICENSE b/src/modules/launcher/LICENSE
new file mode 100644
index 000000000..cb4b563c0
--- /dev/null
+++ b/src/modules/launcher/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Wox
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/modules/launcher/Plugins/HelloWorldCSharp/App.config b/src/modules/launcher/Plugins/HelloWorldCSharp/App.config
new file mode 100644
index 000000000..88fa4027b
--- /dev/null
+++ b/src/modules/launcher/Plugins/HelloWorldCSharp/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/HelloWorldCSharp/HelloWorldCSharp.csproj b/src/modules/launcher/Plugins/HelloWorldCSharp/HelloWorldCSharp.csproj
new file mode 100644
index 000000000..46006f098
--- /dev/null
+++ b/src/modules/launcher/Plugins/HelloWorldCSharp/HelloWorldCSharp.csproj
@@ -0,0 +1,80 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {03FFA443-5F50-48D5-8869-F3DF316803AA}
+ Library
+ Properties
+ HelloWorldCSharp
+ HelloWorldCSharp
+ v4.5.2
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\HelloWorldCSharp\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ AnyCPU
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\HelloWorldCSharp\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}
+ Wox.Plugin
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/HelloWorldCSharp/Images/app.png b/src/modules/launcher/Plugins/HelloWorldCSharp/Images/app.png
new file mode 100644
index 000000000..8c9ca7971
Binary files /dev/null and b/src/modules/launcher/Plugins/HelloWorldCSharp/Images/app.png differ
diff --git a/src/modules/launcher/Plugins/HelloWorldCSharp/Main.cs b/src/modules/launcher/Plugins/HelloWorldCSharp/Main.cs
new file mode 100644
index 000000000..4f17cd43d
--- /dev/null
+++ b/src/modules/launcher/Plugins/HelloWorldCSharp/Main.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Wox.Plugin;
+
+namespace HelloWorldCSharp
+{
+ class Main : IPlugin
+ {
+ public List Query(Query query)
+ {
+ var result = new Result
+ {
+ Title = "Hello World from CSharp",
+ SubTitle = $"Query: {query.Search}",
+ IcoPath = "app.png"
+ };
+ return new List {result};
+ }
+
+ public void Init(PluginInitContext context)
+ {
+
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/HelloWorldCSharp/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/HelloWorldCSharp/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..39677ff22
--- /dev/null
+++ b/src/modules/launcher/Plugins/HelloWorldCSharp/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("HelloWorldCSharp")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HelloWorldCSharp")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("03ffa443-5f50-48d5-8869-f3df316803aa")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/modules/launcher/Plugins/HelloWorldCSharp/plugin.json b/src/modules/launcher/Plugins/HelloWorldCSharp/plugin.json
new file mode 100644
index 000000000..b7edbd6a9
--- /dev/null
+++ b/src/modules/launcher/Plugins/HelloWorldCSharp/plugin.json
@@ -0,0 +1,13 @@
+{
+ "ID":"CEA0FDFC6D3B4085823D60DC76F28844",
+ "ActionKeyword":"hc",
+ "Name":"Hello World CSharp",
+ "Description":"Hello World CSharp",
+ "Author":"happlebao",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"https://github.com/Wox-launcher/Wox",
+ "ExecuteFileName":"HelloWorldCSharp.dll",
+ "IcoPath":"app.png",
+ "Disabled": true
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/HelloWorldPython/Images/app.png b/src/modules/launcher/Plugins/HelloWorldPython/Images/app.png
new file mode 100644
index 000000000..8c9ca7971
Binary files /dev/null and b/src/modules/launcher/Plugins/HelloWorldPython/Images/app.png differ
diff --git a/src/modules/launcher/Plugins/HelloWorldPython/main.py b/src/modules/launcher/Plugins/HelloWorldPython/main.py
new file mode 100644
index 000000000..e7b7edfc2
--- /dev/null
+++ b/src/modules/launcher/Plugins/HelloWorldPython/main.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from wox import Wox
+
+class HelloWorld(Wox):
+
+ def query(self, query):
+ results = []
+ results.append({
+ "Title": "Hello World",
+ "SubTitle": "Query: {}".format(query),
+ "IcoPath":"Images/app.ico",
+ "ContextData": "ctxData"
+ })
+ return results
+
+ def context_menu(self, data):
+ results = []
+ results.append({
+ "Title": "Context menu entry",
+ "SubTitle": "Data: {}".format(data),
+ "IcoPath":"Images/app.ico"
+ })
+ return results
+
+if __name__ == "__main__":
+ HelloWorld()
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/HelloWorldPython/plugin.json b/src/modules/launcher/Plugins/HelloWorldPython/plugin.json
new file mode 100644
index 000000000..a928a5ae5
--- /dev/null
+++ b/src/modules/launcher/Plugins/HelloWorldPython/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"2f4e384e-76ce-45c3-aea2-b16f5e5c328f",
+ "ActionKeyword":"h",
+ "Name":"Hello World Python",
+ "Description":"Hello World",
+ "Author":"happlebao",
+ "Version":"1.0",
+ "Language":"python",
+ "Website":"https://github.com/Wox-launcher/Wox",
+ "IcoPath":"Images\\app.png",
+ "ExecuteFileName":"main.py"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Bookmark.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Bookmark.cs
new file mode 100644
index 000000000..700f253e8
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Bookmark.cs
@@ -0,0 +1,58 @@
+using BinaryAnalysis.UnidecodeSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+
+namespace Wox.Plugin.BrowserBookmark
+{
+ public class Bookmark : IEquatable, IEqualityComparer
+ {
+ private string m_Name;
+ public string Name
+ {
+ get
+ {
+ return m_Name;
+ }
+ set
+ {
+ m_Name = value;
+ PinyinName = m_Name.Unidecode();
+ }
+ }
+ public string PinyinName { get; private set; }
+ public string Url { get; set; }
+ public string Source { get; set; }
+ public int Score { get; set; }
+
+ /* TODO: since Source maybe unimportant, we just need to compare Name and Url */
+ public bool Equals(Bookmark other)
+ {
+ return Equals(this, other);
+ }
+
+ public bool Equals(Bookmark x, Bookmark y)
+ {
+ if (Object.ReferenceEquals(x, y)) return true;
+ if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
+ return false;
+
+ return x.Name == y.Name && x.Url == y.Url;
+ }
+
+ public int GetHashCode(Bookmark bookmark)
+ {
+ if (Object.ReferenceEquals(bookmark, null)) return 0;
+ int hashName = bookmark.Name == null ? 0 : bookmark.Name.GetHashCode();
+ int hashUrl = bookmark.Url == null ? 0 : bookmark.Url.GetHashCode();
+ return hashName ^ hashUrl;
+ }
+
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/ChromeBookmarks.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/ChromeBookmarks.cs
new file mode 100644
index 000000000..16e5c328c
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/ChromeBookmarks.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Wox.Plugin.BrowserBookmark
+{
+ public class ChromeBookmarks
+ {
+ private List bookmarks = new List();
+
+ public List GetBookmarks()
+ {
+ bookmarks.Clear();
+ LoadChromeBookmarks();
+
+ return bookmarks;
+ }
+
+ private void ParseChromeBookmarks(String path, string source)
+ {
+ if (!File.Exists(path)) return;
+
+ string all = File.ReadAllText(path);
+ Regex nameRegex = new Regex("\"name\": \"(?.*?)\"");
+ MatchCollection nameCollection = nameRegex.Matches(all);
+ Regex typeRegex = new Regex("\"type\": \"(?.*?)\"");
+ MatchCollection typeCollection = typeRegex.Matches(all);
+ Regex urlRegex = new Regex("\"url\": \"(?.*?)\"");
+ MatchCollection urlCollection = urlRegex.Matches(all);
+
+ List names = (from Match match in nameCollection select match.Groups["name"].Value).ToList();
+ List types = (from Match match in typeCollection select match.Groups["type"].Value).ToList();
+ List urls = (from Match match in urlCollection select match.Groups["url"].Value).ToList();
+
+ int urlIndex = 0;
+ for (int i = 0; i < names.Count; i++)
+ {
+ string name = DecodeUnicode(names[i]);
+ string type = types[i];
+ if (type == "url")
+ {
+ string url = urls[urlIndex];
+ urlIndex++;
+
+ if (url == null) continue;
+ if (url.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase)) continue;
+ if (url.StartsWith("vbscript:", StringComparison.OrdinalIgnoreCase)) continue;
+
+ bookmarks.Add(new Bookmark()
+ {
+ Name = name,
+ Url = url,
+ Source = source
+ });
+ }
+ }
+ }
+
+ private void LoadChromeBookmarks(string path, string name)
+ {
+ if (!Directory.Exists(path)) return;
+ var paths = Directory.GetDirectories(path);
+
+ foreach (var profile in paths)
+ {
+ if (File.Exists(Path.Combine(profile, "Bookmarks")))
+ ParseChromeBookmarks(Path.Combine(profile, "Bookmarks"), name + (Path.GetFileName(profile) == "Default" ? "" : (" (" + Path.GetFileName(profile) + ")")));
+ }
+ }
+
+ private void LoadChromeBookmarks()
+ {
+ String platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ LoadChromeBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome");
+ LoadChromeBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary");
+ LoadChromeBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium");
+ }
+
+ private String DecodeUnicode(String dataStr)
+ {
+ Regex reg = new Regex(@"(?i)\\[uU]([0-9a-f]{4})");
+ return reg.Replace(dataStr, m => ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Commands/Bookmarks.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Commands/Bookmarks.cs
new file mode 100644
index 000000000..049b8f166
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Commands/Bookmarks.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Linq;
+using Wox.Infrastructure;
+
+namespace Wox.Plugin.BrowserBookmark.Commands
+{
+ internal static class Bookmarks
+ {
+ internal static bool MatchProgram(Bookmark bookmark, string queryString)
+ {
+ if (StringMatcher.FuzzySearch(queryString, bookmark.Name).IsSearchPrecisionScoreMet()) return true;
+ if (StringMatcher.FuzzySearch(queryString, bookmark.PinyinName).IsSearchPrecisionScoreMet()) return true;
+ if (StringMatcher.FuzzySearch(queryString, bookmark.Url).IsSearchPrecisionScoreMet()) return true;
+
+ return false;
+ }
+
+ internal static List LoadAllBookmarks()
+ {
+ var allbookmarks = new List();
+
+ var chromeBookmarks = new ChromeBookmarks();
+ var mozBookmarks = new FirefoxBookmarks();
+
+ //TODO: Let the user select which browser's bookmarks are displayed
+ // Add Firefox bookmarks
+ mozBookmarks.GetBookmarks().ForEach(x => allbookmarks.Add(x));
+
+ // Add Chrome bookmarks
+ chromeBookmarks.GetBookmarks().ForEach(x => allbookmarks.Add(x));
+
+ return allbookmarks.Distinct().ToList();
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/FirefoxBookmarks.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/FirefoxBookmarks.cs
new file mode 100644
index 000000000..7686501cd
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/FirefoxBookmarks.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SQLite;
+using System.IO;
+using System.Linq;
+
+namespace Wox.Plugin.BrowserBookmark
+{
+ public class FirefoxBookmarks
+ {
+ private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title
+ FROM moz_places
+ INNER JOIN moz_bookmarks ON (
+ moz_bookmarks.fk NOT NULL AND moz_bookmarks.fk = moz_places.id
+ )
+ ORDER BY moz_places.visit_count DESC
+ ";
+
+ private const string dbPathFormat = "Data Source ={0};Version=3;New=False;Compress=True;";
+
+ ///
+ /// Searches the places.sqlite db and returns all bookmarks
+ ///
+ public List GetBookmarks()
+ {
+ // Return empty list if the places.sqlite file cannot be found
+ if (string.IsNullOrEmpty(PlacesPath) || !File.Exists(PlacesPath))
+ return new List();
+
+ var bookmarList = new List();
+
+ // create the connection string and init the connection
+ string dbPath = string.Format(dbPathFormat, PlacesPath);
+ using (var dbConnection = new SQLiteConnection(dbPath))
+ {
+ // Open connection to the database file and execute the query
+ dbConnection.Open();
+ var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader();
+
+ // return results in List format
+ bookmarList = reader.Select(x => new Bookmark()
+ {
+ Name = (x["title"] is DBNull) ? string.Empty : x["title"].ToString(),
+ Url = x["url"].ToString()
+ }).ToList();
+ }
+
+ return bookmarList;
+ }
+
+ ///
+ /// Path to places.sqlite
+ ///
+ private string PlacesPath
+ {
+ get
+ {
+ var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox");
+ var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
+
+ if (!File.Exists(profileIni))
+ return string.Empty;
+
+ // get firefox default profile directory from profiles.ini
+ string ini;
+ using (var sReader = new StreamReader(profileIni)) {
+ ini = sReader.ReadToEnd();
+ }
+
+ /*
+ Current profiles.ini structure example as of Firefox version 69.0.1
+
+ [Install736426B0AF4A39CB]
+ Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
+ Locked=1
+
+ [Profile2]
+ Name=newblahprofile
+ IsRelative=0
+ Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
+
+ [Profile1]
+ Name=default
+ IsRelative=1
+ Path=Profiles/cydum7q4.default
+ Default=1
+
+ [Profile0]
+ Name=default-release
+ IsRelative=1
+ Path=Profiles/7789f565.default-release
+
+ [General]
+ StartWithLastProfile=1
+ Version=2
+ */
+
+ var lines = ini.Split(new string[] { "\r\n" }, StringSplitOptions.None).ToList();
+
+ var defaultProfileFolderNameRaw = lines.Where(x => x.Contains("Default=") && x != "Default=1").FirstOrDefault() ?? string.Empty;
+
+ if (string.IsNullOrEmpty(defaultProfileFolderNameRaw))
+ return string.Empty;
+
+ var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last();
+
+ var indexOfDefaultProfileAtttributePath = lines.IndexOf("Path="+ defaultProfileFolderName);
+
+ // Seen in the example above, the IsRelative attribute is always above the Path attribute
+ var relativeAttribute = lines[indexOfDefaultProfileAtttributePath - 1];
+
+ return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
+ ? defaultProfileFolderName + @"\places.sqlite"
+ : Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
+ }
+ }
+ }
+
+ public static class Extensions
+ {
+ public static IEnumerable Select(this SQLiteDataReader reader, Func projection)
+ {
+ while (reader.Read())
+ {
+ yield return projection(reader);
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Images/bookmark.png b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Images/bookmark.png
new file mode 100644
index 000000000..b8aee3564
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Images/bookmark.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Languages/en.xaml
new file mode 100644
index 000000000..7db78333b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Languages/en.xaml
@@ -0,0 +1,9 @@
+
+
+
+ Browser Bookmarks
+ Search your browser bookmarks
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Languages/tr.xaml
new file mode 100644
index 000000000..847d28c7b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Languages/tr.xaml
@@ -0,0 +1,9 @@
+
+
+
+ Yer İşaretleri
+ Tarayıcılarınızdaki yer işaretlerini arayın.
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Main.cs
new file mode 100644
index 000000000..a616edce0
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Main.cs
@@ -0,0 +1,99 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Controls;
+using Wox.Infrastructure.Storage;
+using Wox.Plugin.BrowserBookmark.Commands;
+using Wox.Plugin.BrowserBookmark.Models;
+using Wox.Plugin.BrowserBookmark.Views;
+using Wox.Plugin.SharedCommands;
+
+namespace Wox.Plugin.BrowserBookmark
+{
+ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable
+ {
+ private PluginInitContext context;
+
+ private List cachedBookmarks = new List();
+
+ private readonly Settings _settings;
+ private readonly PluginJsonStorage _storage;
+
+ public Main()
+ {
+ _storage = new PluginJsonStorage();
+ _settings = _storage.Load();
+
+ cachedBookmarks = Bookmarks.LoadAllBookmarks();
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ this.context = context;
+ }
+
+ public List Query(Query query)
+ {
+ string param = query.GetAllRemainingParameter().TrimStart();
+
+ // Should top results be returned? (true if no search parameters have been passed)
+ var topResults = string.IsNullOrEmpty(param);
+
+ var returnList = cachedBookmarks;
+
+ if (!topResults)
+ {
+ // Since we mixed chrome and firefox bookmarks, we should order them again
+ returnList = cachedBookmarks.Where(o => Bookmarks.MatchProgram(o, param)).ToList();
+ returnList = returnList.OrderByDescending(o => o.Score).ToList();
+ }
+
+ return returnList.Select(c => new Result()
+ {
+ Title = c.Name,
+ SubTitle = c.Url,
+ IcoPath = @"Images\bookmark.png",
+ Score = 5,
+ Action = (e) =>
+ {
+ if (_settings.OpenInNewBrowserWindow)
+ {
+ c.Url.NewBrowserWindow(_settings.BrowserPath);
+ }
+ else
+ {
+ c.Url.NewTabInBrowser(_settings.BrowserPath);
+ }
+
+ return true;
+ }
+ }).ToList();
+ }
+
+ public void ReloadData()
+ {
+ cachedBookmarks.Clear();
+
+ cachedBookmarks = Bookmarks.LoadAllBookmarks();
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return context.API.GetTranslation("wox_plugin_browserbookmark_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return context.API.GetTranslation("wox_plugin_browserbookmark_plugin_description");
+ }
+
+ public Control CreateSettingPanel()
+ {
+ return new SettingsControl(_settings);
+ }
+
+ public void Save()
+ {
+ _storage.Save();
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Models/Settings.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Models/Settings.cs
new file mode 100644
index 000000000..41d742fe6
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Models/Settings.cs
@@ -0,0 +1,9 @@
+namespace Wox.Plugin.BrowserBookmark.Models
+{
+ public class Settings : BaseModel
+ {
+ public bool OpenInNewBrowserWindow { get; set; } = true;
+
+ public string BrowserPath { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..982c54999
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Wox.Plugin.BrowserBookmark")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Oracle Corporation")]
+[assembly: AssemblyProduct("Wox.Plugin.BrowserBookmark")]
+[assembly: AssemblyCopyright("Copyright © Oracle Corporation 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("7dd2e33e-d029-4661-8f1d-594e82cef077")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Views/SettingsControl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Views/SettingsControl.xaml
new file mode 100644
index 000000000..93b272fca
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Views/SettingsControl.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs
new file mode 100644
index 000000000..5c87f2d5a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs
@@ -0,0 +1,47 @@
+using Microsoft.Win32;
+using System.Windows;
+using System.Windows.Controls;
+using Wox.Plugin.BrowserBookmark.Models;
+
+namespace Wox.Plugin.BrowserBookmark.Views
+{
+ ///
+ /// Interaction logic for BrowserBookmark.xaml
+ ///
+ public partial class SettingsControl : UserControl
+ {
+ private readonly Settings _settings;
+
+ public SettingsControl(Settings settings)
+ {
+ InitializeComponent();
+ _settings = settings;
+ browserPathBox.Text = _settings.BrowserPath;
+ NewWindowBrowser.IsChecked = _settings.OpenInNewBrowserWindow;
+ NewTabInBrowser.IsChecked = !_settings.OpenInNewBrowserWindow;
+ }
+
+ private void OnNewBrowserWindowClick(object sender, RoutedEventArgs e)
+ {
+ _settings.OpenInNewBrowserWindow = true;
+ }
+
+ private void OnNewTabClick(object sender, RoutedEventArgs e)
+ {
+ _settings.OpenInNewBrowserWindow = false;
+ }
+
+ private void OnChooseClick(object sender, RoutedEventArgs e)
+ {
+ var fileBrowserDialog = new OpenFileDialog();
+ fileBrowserDialog.Filter = "Application(*.exe)|*.exe|All files|*.*";
+ fileBrowserDialog.CheckFileExists = true;
+ fileBrowserDialog.CheckPathExists = true;
+ if (fileBrowserDialog.ShowDialog() == true)
+ {
+ browserPathBox.Text = fileBrowserDialog.FileName;
+ _settings.BrowserPath = fileBrowserDialog.FileName;
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Wox.Plugin.BrowserBookmark.csproj b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Wox.Plugin.BrowserBookmark.csproj
new file mode 100644
index 000000000..413b0aec7
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/Wox.Plugin.BrowserBookmark.csproj
@@ -0,0 +1,126 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {9B130CC5-14FB-41FF-B310-0A95B6894C37}
+ Library
+ Properties
+ Wox.Plugin.BrowserBookmark
+ Wox.Plugin.BrowserBookmark
+ v4.5.2
+ 512
+ ..\..\
+ true
+
+
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.BrowserBookmark\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.BrowserBookmark\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SettingsControl.xaml
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ Always
+
+
+ Designer
+ MSBuild:Compile
+ PreserveNewest
+
+
+ Always
+
+
+ Always
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ 1.0.111
+
+
+ 1.0.111
+
+
+ 1.0.0
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/app.config b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/app.config
new file mode 100644
index 000000000..376ff2d53
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/app.config
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/plugin.json
new file mode 100644
index 000000000..7d40e4bfc
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"0ECADE17459B49F587BF81DC3A125110",
+ "ActionKeyword":"b",
+ "Name":"Browser Bookmarks",
+ "Description":"Search your browser bookmarks",
+ "Author":"qianlifeng, Ioannis G.",
+ "Version":"1.1",
+ "Language":"csharp",
+ "Website":"http://www.getwox.com/plugin",
+ "ExecuteFileName":"Wox.Plugin.browserBookmark.dll",
+ "IcoPath":"Images\\bookmark.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/x64/SQLite.Interop.dll b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/x64/SQLite.Interop.dll
new file mode 100644
index 000000000..877b4d78e
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/x64/SQLite.Interop.dll differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/x86/SQLite.Interop.dll b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/x86/SQLite.Interop.dll
new file mode 100644
index 000000000..ccb5e03b6
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.BrowserBookmark/x86/SQLite.Interop.dll differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Images/calculator.png b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Images/calculator.png
new file mode 100644
index 000000000..102a86bde
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Images/calculator.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/de.xaml
new file mode 100644
index 000000000..286e4bc9b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/de.xaml
@@ -0,0 +1,10 @@
+
+
+ Rechner
+ Stellt mathematische Berechnungen bereit.(Versuche 5*3-2 in Wox)
+ Keine Zahl (NaN)
+ Ausdruck falsch oder nicht vollständig (Klammern vergessen?)
+ Diese Zahl in die Zwischenablage kopieren
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/en.xaml
new file mode 100644
index 000000000..3b5fa32b3
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/en.xaml
@@ -0,0 +1,10 @@
+
+
+ Calculator
+ Allows to do mathematical calculations.(Try 5*3-2 in Wox)
+ Not a number (NaN)
+ Expression wrong or incomplete (Did you forget some parentheses?)
+ Copy this number to the clipboard
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/pl.xaml
new file mode 100644
index 000000000..c93c5e802
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/pl.xaml
@@ -0,0 +1,8 @@
+
+
+ Kalkulator
+ Szybkie wykonywanie obliczeń matematycznych. (Spróbuj wpisać 5*3-2 w oknie Woxa)
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/tr.xaml
new file mode 100644
index 000000000..51c5b9b2b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/tr.xaml
@@ -0,0 +1,10 @@
+
+
+ Hesap Makinesi
+ Matematiksel hesaplamalar yapmaya yarar. (5*3-2 yazmayı deneyin)
+ Sayı değil (NaN)
+ İfade hatalı ya da eksik. (Parantez koymayı mı unuttunuz?)
+ Bu sayıyı panoya kopyala
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/zh-cn.xaml
new file mode 100644
index 000000000..1385f4565
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/zh-cn.xaml
@@ -0,0 +1,8 @@
+
+
+ 计算器
+ 为Wox提供数学计算能力。(试着在Wox输入 5*3-2)
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/zh-tw.xaml
new file mode 100644
index 000000000..08cda08d9
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Languages/zh-tw.xaml
@@ -0,0 +1,8 @@
+
+
+ 計算機
+ 為 Wox 提供數學計算功能。(試著在 Wox 輸入 5*3-2)
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Main.cs
new file mode 100644
index 000000000..fab0b8e03
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Main.cs
@@ -0,0 +1,115 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+using System.Windows;
+using Mages.Core;
+
+namespace Wox.Plugin.Caculator
+{
+ public class Main : IPlugin, IPluginI18n
+ {
+ private static readonly Regex RegValidExpressChar = new Regex(
+ @"^(" +
+ @"ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|" +
+ @"sin|cos|tan|arcsin|arccos|arctan|" +
+ @"eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|" +
+ @"bin2dec|hex2dec|oct2dec|" +
+ @"==|~=|&&|\|\||" +
+ @"[ei]|[0-9]|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
+ @")+$", RegexOptions.Compiled);
+ private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled);
+ private static readonly Engine MagesEngine;
+ private PluginInitContext Context { get; set; }
+
+ static Main()
+ {
+ MagesEngine = new Engine();
+ }
+
+ public List Query(Query query)
+ {
+ if (query.Search.Length <= 2 // don't affect when user only input "e" or "i" keyword
+ || !RegValidExpressChar.IsMatch(query.Search)
+ || !IsBracketComplete(query.Search)) return new List();
+
+ try
+ {
+ var result = MagesEngine.Interpret(query.Search);
+
+ if (result.ToString() == "NaN")
+ result = Context.API.GetTranslation("wox_plugin_calculator_not_a_number");
+
+ if (result is Function)
+ result = Context.API.GetTranslation("wox_plugin_calculator_expression_not_complete");
+
+
+ if (!string.IsNullOrEmpty(result?.ToString()))
+ {
+ return new List
+ {
+ new Result
+ {
+ Title = result.ToString(),
+ IcoPath = "Images/calculator.png",
+ Score = 300,
+ SubTitle = Context.API.GetTranslation("wox_plugin_calculator_copy_number_to_clipboard"),
+ Action = c =>
+ {
+ try
+ {
+ Clipboard.SetText(result.ToString());
+ return true;
+ }
+ catch (ExternalException)
+ {
+ MessageBox.Show("Copy failed, please try later");
+ return false;
+ }
+ }
+ }
+ };
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+
+ return new List();
+ }
+
+ private bool IsBracketComplete(string query)
+ {
+ var matchs = RegBrackets.Matches(query);
+ var leftBracketCount = 0;
+ foreach (Match match in matchs)
+ {
+ if (match.Value == "(" || match.Value == "[")
+ {
+ leftBracketCount++;
+ }
+ else
+ {
+ leftBracketCount--;
+ }
+ }
+
+ return leftBracketCount == 0;
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ Context = context;
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return Context.API.GetTranslation("wox_plugin_caculator_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return Context.API.GetTranslation("wox_plugin_caculator_plugin_description");
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/NumberTranslator.cs b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/NumberTranslator.cs
new file mode 100644
index 000000000..448301d4f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/NumberTranslator.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace Wox.Plugin.Caculator
+{
+ ///
+ /// Tries to convert all numbers in a text from one culture format to another.
+ ///
+ public class NumberTranslator
+ {
+ private readonly CultureInfo sourceCulture;
+ private readonly CultureInfo targetCulture;
+ private readonly Regex splitRegexForSource;
+ private readonly Regex splitRegexForTarget;
+
+ private NumberTranslator(CultureInfo sourceCulture, CultureInfo targetCulture)
+ {
+ this.sourceCulture = sourceCulture;
+ this.targetCulture = targetCulture;
+
+ this.splitRegexForSource = GetSplitRegex(this.sourceCulture);
+ this.splitRegexForTarget = GetSplitRegex(this.targetCulture);
+ }
+
+ ///
+ /// Create a new - returns null if no number conversion
+ /// is required between the cultures.
+ ///
+ /// source culture
+ /// target culture
+ ///
+ public static NumberTranslator Create(CultureInfo sourceCulture, CultureInfo targetCulture)
+ {
+ bool conversionRequired = sourceCulture.NumberFormat.NumberDecimalSeparator != targetCulture.NumberFormat.NumberDecimalSeparator
+ || sourceCulture.NumberFormat.PercentGroupSeparator != targetCulture.NumberFormat.PercentGroupSeparator
+ || sourceCulture.NumberFormat.NumberGroupSizes != targetCulture.NumberFormat.NumberGroupSizes;
+ return conversionRequired
+ ? new NumberTranslator(sourceCulture, targetCulture)
+ : null;
+ }
+
+ ///
+ /// Translate from source to target culture.
+ ///
+ ///
+ ///
+ public string Translate(string input)
+ {
+ return this.Translate(input, this.sourceCulture, this.targetCulture, this.splitRegexForSource);
+ }
+
+ ///
+ /// Translate from target to source culture.
+ ///
+ ///
+ ///
+ public string TranslateBack(string input)
+ {
+ return this.Translate(input, this.targetCulture, this.sourceCulture, this.splitRegexForTarget);
+ }
+
+ private string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex)
+ {
+ var outputBuilder = new StringBuilder();
+
+ string[] tokens = splitRegex.Split(input);
+ foreach (string token in tokens)
+ {
+ decimal number;
+ outputBuilder.Append(
+ decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number)
+ ? number.ToString(cultureTo)
+ : token);
+ }
+
+ return outputBuilder.ToString();
+ }
+
+ private Regex GetSplitRegex(CultureInfo culture)
+ {
+ var splitPattern = $"((?:\\d|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}";
+ if (!string.IsNullOrEmpty(culture.NumberFormat.NumberGroupSeparator))
+ {
+ splitPattern += $"|{Regex.Escape(culture.NumberFormat.NumberGroupSeparator)}";
+ }
+ splitPattern += ")+)";
+ return new Regex(splitPattern);
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..dd66d50a7
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.Caculator")]
+[assembly: Guid("ba698b90-59ed-4c2e-bce1-497eb2f9e76f")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj
new file mode 100644
index 000000000..b0a091934
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj
@@ -0,0 +1,143 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {59BD9891-3837-438A-958D-ADC7F91F6F7E}
+ Library
+ Properties
+ Wox.Plugin.Caculator
+ Wox.Plugin.Caculator
+ v4.5.2
+ 512
+ ..\..\
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Caculator\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Caculator\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+ ..\..\packages\Mages.1.5.0\lib\net35\Mages.Core.dll
+ True
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 1.5.0
+
+
+ 4.0.0
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Calculator/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/plugin.json
new file mode 100644
index 000000000..925099548
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Calculator/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"CEA0FDFC6D3B4085823D60DC76F28855",
+ "ActionKeyword":"*",
+ "Name":"Calculator",
+ "Description":"Provide mathematical calculations.(Try 5*3-2 in Wox)",
+ "Author":"cxfksword",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.Caculator.dll",
+ "IcoPath":"Images\\calculator.png"
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Images/color.png b/src/modules/launcher/Plugins/Wox.Plugin.Color/Images/color.png
new file mode 100644
index 000000000..da28583b1
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Color/Images/color.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/de.xaml
new file mode 100644
index 000000000..becb06305
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/de.xaml
@@ -0,0 +1,8 @@
+
+
+ Farben
+ Stellt eine HEX-Farben Vorschau bereit. (Versuche #000 in Wox)
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/en.xaml
new file mode 100644
index 000000000..f27231ba5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/en.xaml
@@ -0,0 +1,8 @@
+
+
+ Colors
+ Allows to preview colors using hex values.(Try #000 in Wox)
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/pl.xaml
new file mode 100644
index 000000000..2a8361eb2
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/pl.xaml
@@ -0,0 +1,8 @@
+
+
+ Kolory
+ Podgląd kolorów po wpisaniu ich kodu szesnastkowego. (Spróbuj wpisać #000 w oknie Woxa)
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/tr.xaml
new file mode 100644
index 000000000..c3224f517
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/tr.xaml
@@ -0,0 +1,8 @@
+
+
+ Renkler
+ Hex kodunu girdiğiniz renkleri görüntülemeye yarar.(#000 yazmayı deneyin)
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/zh-cn.xaml
new file mode 100644
index 000000000..634f3d0f7
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/zh-cn.xaml
@@ -0,0 +1,8 @@
+
+
+ 颜色
+ 提供在Wox查询hex颜色。(尝试在Wox中输入#000)
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/zh-tw.xaml
new file mode 100644
index 000000000..2c320cb32
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Languages/zh-tw.xaml
@@ -0,0 +1,7 @@
+
+
+ 顏色
+ 提供在 Wox 查詢 hex 顏色。(試著在 Wox 中輸入 #000)
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Color/Main.cs
new file mode 100644
index 000000000..7ae6f453a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Main.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Windows;
+
+namespace Wox.Plugin.Color
+{
+ public sealed class ColorsPlugin : IPlugin, IPluginI18n
+ {
+ private string DIR_PATH = Path.Combine(Path.GetTempPath(), @"Plugins\Colors\");
+ private PluginInitContext context;
+ private const int IMG_SIZE = 32;
+
+ private DirectoryInfo ColorsDirectory { get; set; }
+
+ public ColorsPlugin()
+ {
+ if (!Directory.Exists(DIR_PATH))
+ {
+ ColorsDirectory = Directory.CreateDirectory(DIR_PATH);
+ }
+ else
+ {
+ ColorsDirectory = new DirectoryInfo(DIR_PATH);
+ }
+ }
+
+ public List Query(Query query)
+ {
+ var raw = query.Search;
+ if (!IsAvailable(raw)) return new List(0);
+ try
+ {
+ var cached = Find(raw);
+ if (cached.Length == 0)
+ {
+ var path = CreateImage(raw);
+ return new List
+ {
+ new Result
+ {
+ Title = raw,
+ IcoPath = path,
+ Action = _ =>
+ {
+ Clipboard.SetText(raw);
+ return true;
+ }
+ }
+ };
+ }
+ return cached.Select(x => new Result
+ {
+ Title = raw,
+ IcoPath = x.FullName,
+ Action = _ =>
+ {
+ Clipboard.SetText(raw);
+ return true;
+ }
+ }).ToList();
+ }
+ catch (Exception exception)
+ {
+ // todo: log
+ return new List(0);
+ }
+ }
+
+ private bool IsAvailable(string query)
+ {
+ // todo: rgb, names
+ var length = query.Length - 1; // minus `#` sign
+ return query.StartsWith("#") && (length == 3 || length == 6);
+ }
+
+ public FileInfo[] Find(string name)
+ {
+ var file = string.Format("{0}.png", name.Substring(1));
+ return ColorsDirectory.GetFiles(file, SearchOption.TopDirectoryOnly);
+ }
+
+ private string CreateImage(string name)
+ {
+ using (var bitmap = new Bitmap(IMG_SIZE, IMG_SIZE))
+ using (var graphics = Graphics.FromImage(bitmap))
+ {
+ var color = ColorTranslator.FromHtml(name);
+ graphics.Clear(color);
+
+ var path = CreateFileName(name);
+ bitmap.Save(path, ImageFormat.Png);
+ return path;
+ }
+ }
+
+ private string CreateFileName(string name)
+ {
+ return string.Format("{0}{1}.png", ColorsDirectory.FullName, name.Substring(1));
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ this.context = context;
+ }
+
+
+ public string GetTranslatedPluginTitle()
+ {
+ return context.API.GetTranslation("wox_plugin_color_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return context.API.GetTranslation("wox_plugin_color_plugin_description");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Color/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..1df9ed6e4
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.Color")]
+[assembly: Guid("46b03f84-5bf7-4ed4-a69b-f0274c8b3776")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/Wox.Plugin.Color.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Color/Wox.Plugin.Color.csproj
new file mode 100644
index 000000000..ca75ed8f3
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/Wox.Plugin.Color.csproj
@@ -0,0 +1,130 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F35190AA-4758-4D9E-A193-E3BDF6AD3567}
+ Library
+ Properties
+ Wox.Plugin.Color
+ Wox.Plugin.Color
+ v4.5.2
+ 512
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Color\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Color\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Color/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Color/plugin.json
new file mode 100644
index 000000000..7acb13451
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Color/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"9B36CE6181FC47FBB597AA2C29CD9B0A",
+ "ActionKeyword":"*",
+ "Name":"Colors",
+ "Description":"Provide hex color preview.(Try #000 in Wox)",
+ "Author":"qianlifeng",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.Color.dll",
+ "IcoPath":"Images\\color.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/ControlPanelItem.cs b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/ControlPanelItem.cs
new file mode 100644
index 000000000..a63c9a19c
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/ControlPanelItem.cs
@@ -0,0 +1,25 @@
+using System.Diagnostics;
+using System.Drawing;
+
+namespace Wox.Plugin.ControlPanel
+{
+ //from:https://raw.githubusercontent.com/CoenraadS/Windows-Control-Panel-Items
+ public class ControlPanelItem
+ {
+ public string LocalizedString { get; private set; }
+ public string InfoTip { get; private set; }
+ public string GUID { get; private set; }
+ public ProcessStartInfo ExecutablePath { get; private set; }
+ public Icon Icon { get; private set; }
+ public int Score { get; set; }
+
+ public ControlPanelItem(string newLocalizedString, string newInfoTip, string newGUID, ProcessStartInfo newExecutablePath, Icon newIcon)
+ {
+ LocalizedString = newLocalizedString;
+ InfoTip = newInfoTip;
+ ExecutablePath = newExecutablePath;
+ Icon = newIcon;
+ GUID = newGUID;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/ControlPanelList.cs b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/ControlPanelList.cs
new file mode 100644
index 000000000..49c4b3831
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/ControlPanelList.cs
@@ -0,0 +1,339 @@
+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 Create(uint iconSize)
+ {
+ int size = (int)iconSize;
+ RegistryKey currentKey;
+ ProcessStartInfo executablePath;
+ List controlPanelItems = new List();
+ 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 iconString;
+ IntPtr dataFilePointer;
+ IntPtr iconIndex;
+ Icon myIcon = null;
+
+ if (currentKey.OpenSubKey("DefaultIcon") != null)
+ {
+ if (currentKey.OpenSubKey("DefaultIcon").GetValue(null) != null)
+ {
+ iconString = new List(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;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Images/ControlPanel.png b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Images/ControlPanel.png
new file mode 100644
index 000000000..1a69112f3
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Images/ControlPanel.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/de.xaml
new file mode 100644
index 000000000..994c4c60d
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/de.xaml
@@ -0,0 +1,8 @@
+
+
+ Systemsteuerung
+ Suche in der Systemsteuerung
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/en.xaml
new file mode 100644
index 000000000..cfab068b4
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/en.xaml
@@ -0,0 +1,8 @@
+
+
+ Control Panel
+ Search within the Control Panel
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/pl.xaml
new file mode 100644
index 000000000..018cf2f1f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/pl.xaml
@@ -0,0 +1,8 @@
+
+
+ Panel sterowania
+ Szybie wyszukiwanie ustawień w panelu sterowania
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/tr.xaml
new file mode 100644
index 000000000..e5bb3827b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/tr.xaml
@@ -0,0 +1,8 @@
+
+
+ Denetim Masası
+ Denetim Masası'nda arama yapın.
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/zh-cn.xaml
new file mode 100644
index 000000000..bef221881
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/zh-cn.xaml
@@ -0,0 +1,8 @@
+
+
+ 控制面板
+ 搜索控制面板
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/zh-tw.xaml
new file mode 100644
index 000000000..58eb9232c
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Languages/zh-tw.xaml
@@ -0,0 +1,8 @@
+
+
+ 控制台
+ 搜尋控制台
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Main.cs
new file mode 100644
index 000000000..77b57fe86
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Main.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Wox.Infrastructure;
+
+namespace Wox.Plugin.ControlPanel
+{
+ public class Main : IPlugin, IPluginI18n
+ {
+ private PluginInitContext context;
+ private List controlPanelItems = new List();
+ private string iconFolder;
+ private string fileType;
+
+ public void Init(PluginInitContext context)
+ {
+ this.context = context;
+ controlPanelItems = ControlPanelList.Create(48);
+ iconFolder = Path.Combine(context.CurrentPluginMetadata.PluginDirectory, @"Images\ControlPanelIcons\");
+ fileType = ".bmp";
+
+ if (!Directory.Exists(iconFolder))
+ {
+ Directory.CreateDirectory(iconFolder);
+ }
+
+ foreach (ControlPanelItem item in controlPanelItems)
+ {
+ if (!File.Exists(iconFolder + item.GUID + fileType) && item.Icon != null)
+ {
+ item.Icon.ToBitmap().Save(iconFolder + item.GUID + fileType);
+ }
+ }
+ }
+
+ public List Query(Query query)
+ {
+ List results = new List();
+
+ foreach (var item in controlPanelItems)
+ {
+ var titleMatch = StringMatcher.FuzzySearch(query.Search, item.LocalizedString);
+ var subTitleMatch = StringMatcher.FuzzySearch(query.Search, item.InfoTip);
+
+ item.Score = Math.Max(titleMatch.Score, subTitleMatch.Score);
+ if (item.Score > 0)
+ {
+ var result = new Result
+ {
+ Title = item.LocalizedString,
+ SubTitle = item.InfoTip,
+ Score = item.Score,
+ IcoPath = Path.Combine(context.CurrentPluginMetadata.PluginDirectory,
+ @"Images\\ControlPanelIcons\\" + item.GUID + fileType),
+ Action = e =>
+ {
+ try
+ {
+ Process.Start(item.ExecutablePath);
+ }
+ catch (Exception)
+ {
+ //Silently Fail for now.. todo
+ }
+ return true;
+ }
+ };
+
+ if (item.Score == titleMatch.Score)
+ {
+ result.TitleHighlightData = titleMatch.MatchData;
+ }
+ else
+ {
+ result.SubTitleHighlightData = subTitleMatch.MatchData;
+ }
+
+ results.Add(result);
+ }
+ }
+
+ List panelItems = results.OrderByDescending(o => o.Score).Take(5).ToList();
+ return panelItems;
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return context.API.GetTranslation("wox_plugin_controlpanel_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return context.API.GetTranslation("wox_plugin_controlpanel_plugin_description");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..10e8283a2
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.ControlPanel")]
+[assembly: Guid("59141b10-8941-4e90-a0a6-bc9385a04cc6")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Wox.Plugin.ControlPanel.csproj b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Wox.Plugin.ControlPanel.csproj
new file mode 100644
index 000000000..711cb8e29
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/Wox.Plugin.ControlPanel.csproj
@@ -0,0 +1,132 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {1EE20B48-82FB-48A2-8086-675D6DDAB4F0}
+ Library
+ Properties
+ Wox.Plugin.ControlPanel
+ Wox.Plugin.ControlPanel
+ v4.5.2
+ 512
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.ControlPanel\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.ControlPanel\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/plugin.json
new file mode 100644
index 000000000..7dc455209
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.ControlPanel/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"209621585B9B4D81813913C507C058C6",
+ "ActionKeyword":"*",
+ "Name":"Control Panel",
+ "Description":"Search within the Control Panel.",
+ "Author":"CoenraadS",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.ControlPanel.dll",
+ "IcoPath":"Images\\ControlPanel.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/.gitattributes b/src/modules/launcher/Plugins/Wox.Plugin.Everything/.gitattributes
new file mode 100644
index 000000000..1ff0c4230
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/.gitignore b/src/modules/launcher/Plugins/Wox.Plugin.Everything/.gitignore
new file mode 100644
index 000000000..2f72fc424
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/.gitignore
@@ -0,0 +1,156 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+build/
+[Bb]in/
+[Oo]bj/
+
+# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
+!packages/*/build/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+packages/
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+*.pfx
+*.publishsettings
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+
+#LightSwitch generated files
+GeneratedArtifacts/
+_Pvt_Extensions/
+ModelManifest.xml
+
+# =========================
+# Windows detritus
+# =========================
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac desktop service store files
+.DS_Store
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/EverythingAPI.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/EverythingAPI.cs
new file mode 100644
index 000000000..b9d2098eb
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/EverythingAPI.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using Wox.Infrastructure.Logger;
+using Wox.Plugin.Everything.Everything.Exceptions;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ public interface IEverythingApi
+ {
+ ///
+ /// Searches the specified key word.
+ ///
+ /// The key word.
+ /// token that allow cancellation
+ /// The offset.
+ /// The max count.
+ ///
+ List Search(string keyWord, CancellationToken token, int offset = 0, int maxCount = 100);
+
+ void Load(string sdkPath);
+ }
+
+ public sealed class EverythingApi : IEverythingApi
+ {
+ private const int BufferSize = 4096;
+
+ private readonly object _syncObject = new object();
+ // cached buffer to remove redundant allocations.
+ private readonly StringBuilder _buffer = new StringBuilder(BufferSize);
+
+ public enum StateCode
+ {
+ OK,
+ MemoryError,
+ IPCError,
+ RegisterClassExError,
+ CreateWindowError,
+ CreateThreadError,
+ InvalidIndexError,
+ InvalidCallError
+ }
+
+ ///
+ /// Gets or sets a value indicating whether [match path].
+ ///
+ /// true if [match path]; otherwise, false.
+ public bool MatchPath
+ {
+ get
+ {
+ return EverythingApiDllImport.Everything_GetMatchPath();
+ }
+ set
+ {
+ EverythingApiDllImport.Everything_SetMatchPath(value);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether [match case].
+ ///
+ /// true if [match case]; otherwise, false.
+ public bool MatchCase
+ {
+ get
+ {
+ return EverythingApiDllImport.Everything_GetMatchCase();
+ }
+ set
+ {
+ EverythingApiDllImport.Everything_SetMatchCase(value);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether [match whole word].
+ ///
+ /// true if [match whole word]; otherwise, false.
+ public bool MatchWholeWord
+ {
+ get
+ {
+ return EverythingApiDllImport.Everything_GetMatchWholeWord();
+ }
+ set
+ {
+ EverythingApiDllImport.Everything_SetMatchWholeWord(value);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether [enable regex].
+ ///
+ /// true if [enable regex]; otherwise, false.
+ public bool EnableRegex
+ {
+ get
+ {
+ return EverythingApiDllImport.Everything_GetRegex();
+ }
+ set
+ {
+ EverythingApiDllImport.Everything_SetRegex(value);
+ }
+ }
+
+ ///
+ /// Resets this instance.
+ ///
+ public void Reset()
+ {
+ lock (_syncObject)
+ {
+ EverythingApiDllImport.Everything_Reset();
+ }
+ }
+
+ ///
+ /// Searches the specified key word and reset the everything API afterwards
+ ///
+ /// The key word.
+ /// when cancelled the current search will stop and exit (and would not reset)
+ /// The offset.
+ /// The max count.
+ ///
+ public List Search(string keyWord, CancellationToken token, int offset = 0, int maxCount = 100)
+ {
+ if (string.IsNullOrEmpty(keyWord))
+ throw new ArgumentNullException(nameof(keyWord));
+
+ if (offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(offset));
+
+ if (maxCount < 0)
+ throw new ArgumentOutOfRangeException(nameof(maxCount));
+
+ lock (_syncObject)
+ {
+ if (keyWord.StartsWith("@"))
+ {
+ EverythingApiDllImport.Everything_SetRegex(true);
+ keyWord = keyWord.Substring(1);
+ }
+
+ EverythingApiDllImport.Everything_SetSearchW(keyWord);
+ EverythingApiDllImport.Everything_SetOffset(offset);
+ EverythingApiDllImport.Everything_SetMax(maxCount);
+
+ if (token.IsCancellationRequested)
+ {
+ return null;
+ }
+
+
+ if (!EverythingApiDllImport.Everything_QueryW(true))
+ {
+ CheckAndThrowExceptionOnError();
+ return null;
+ }
+
+ var results = new List();
+ for (int idx = 0; idx < EverythingApiDllImport.Everything_GetNumResults(); ++idx)
+ {
+ if (token.IsCancellationRequested)
+ {
+ return null;
+ }
+
+ EverythingApiDllImport.Everything_GetResultFullPathNameW(idx, _buffer, BufferSize);
+
+ var result = new SearchResult { FullPath = _buffer.ToString() };
+ if (EverythingApiDllImport.Everything_IsFolderResult(idx))
+ result.Type = ResultType.Folder;
+ else if (EverythingApiDllImport.Everything_IsFileResult(idx))
+ result.Type = ResultType.File;
+
+ results.Add(result);
+ }
+
+ Reset();
+
+ return results;
+ }
+ }
+
+ [DllImport("kernel32.dll")]
+ private static extern int LoadLibrary(string name);
+
+ public void Load(string sdkPath)
+ {
+ LoadLibrary(sdkPath);
+ }
+
+ private static void CheckAndThrowExceptionOnError()
+ {
+ switch (EverythingApiDllImport.Everything_GetLastError())
+ {
+ case StateCode.CreateThreadError:
+ throw new CreateThreadException();
+ case StateCode.CreateWindowError:
+ throw new CreateWindowException();
+ case StateCode.InvalidCallError:
+ throw new InvalidCallException();
+ case StateCode.InvalidIndexError:
+ throw new InvalidIndexException();
+ case StateCode.IPCError:
+ throw new IPCErrorException();
+ case StateCode.MemoryError:
+ throw new MemoryErrorException();
+ case StateCode.RegisterClassExError:
+ throw new RegisterClassExException();
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/EverythingApiDllImport.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/EverythingApiDllImport.cs
new file mode 100644
index 000000000..6c056a0f5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/EverythingApiDllImport.cs
@@ -0,0 +1,92 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ public sealed class EverythingApiDllImport
+ {
+ [DllImport(Main.DLL, CharSet = CharSet.Unicode)]
+ internal static extern int Everything_SetSearchW(string lpSearchString);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_SetMatchPath(bool bEnable);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_SetMatchCase(bool bEnable);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_SetMatchWholeWord(bool bEnable);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_SetRegex(bool bEnable);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_SetMax(int dwMax);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_SetOffset(int dwOffset);
+
+ [DllImport(Main.DLL)]
+ internal static extern bool Everything_GetMatchPath();
+
+ [DllImport(Main.DLL)]
+ internal static extern bool Everything_GetMatchCase();
+
+ [DllImport(Main.DLL)]
+ internal static extern bool Everything_GetMatchWholeWord();
+
+ [DllImport(Main.DLL)]
+ internal static extern bool Everything_GetRegex();
+
+ [DllImport(Main.DLL)]
+ internal static extern uint Everything_GetMax();
+
+ [DllImport(Main.DLL)]
+ internal static extern uint Everything_GetOffset();
+
+ [DllImport(Main.DLL, CharSet = CharSet.Unicode)]
+ internal static extern string Everything_GetSearchW();
+
+ [DllImport(Main.DLL)]
+ internal static extern EverythingApi.StateCode Everything_GetLastError();
+
+ [DllImport(Main.DLL, CharSet = CharSet.Unicode)]
+ internal static extern bool Everything_QueryW(bool bWait);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_SortResultsByPath();
+
+ [DllImport(Main.DLL)]
+ internal static extern int Everything_GetNumFileResults();
+
+ [DllImport(Main.DLL)]
+ internal static extern int Everything_GetNumFolderResults();
+
+ [DllImport(Main.DLL)]
+ internal static extern int Everything_GetNumResults();
+
+ [DllImport(Main.DLL)]
+ internal static extern int Everything_GetTotFileResults();
+
+ [DllImport(Main.DLL)]
+ internal static extern int Everything_GetTotFolderResults();
+
+ [DllImport(Main.DLL)]
+ internal static extern int Everything_GetTotResults();
+
+ [DllImport(Main.DLL)]
+ internal static extern bool Everything_IsVolumeResult(int nIndex);
+
+ [DllImport(Main.DLL)]
+ internal static extern bool Everything_IsFolderResult(int nIndex);
+
+ [DllImport(Main.DLL)]
+ internal static extern bool Everything_IsFileResult(int nIndex);
+
+ [DllImport(Main.DLL, CharSet = CharSet.Unicode)]
+ internal static extern void Everything_GetResultFullPathNameW(int nIndex, StringBuilder lpString, int nMaxCount);
+
+ [DllImport(Main.DLL)]
+ internal static extern void Everything_Reset();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/CreateThreadException.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/CreateThreadException.cs
new file mode 100644
index 000000000..170acde18
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/CreateThreadException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ ///
+ ///
+ ///
+ public class CreateThreadException : ApplicationException
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/CreateWindowException.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/CreateWindowException.cs
new file mode 100644
index 000000000..54fc903f1
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/CreateWindowException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ ///
+ ///
+ ///
+ public class CreateWindowException : ApplicationException
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/IPCErrorException.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/IPCErrorException.cs
new file mode 100644
index 000000000..92ea7fb72
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/IPCErrorException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ ///
+ ///
+ ///
+ public class IPCErrorException : ApplicationException
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/InvalidCallException.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/InvalidCallException.cs
new file mode 100644
index 000000000..b109f4d30
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/InvalidCallException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ ///
+ ///
+ ///
+ public class InvalidCallException : ApplicationException
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/InvalidIndexException.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/InvalidIndexException.cs
new file mode 100644
index 000000000..a6e7bfa44
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/InvalidIndexException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ ///
+ ///
+ ///
+ public class InvalidIndexException : ApplicationException
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/MemoryErrorException.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/MemoryErrorException.cs
new file mode 100644
index 000000000..cd74e8487
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/MemoryErrorException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Wox.Plugin.Everything.Everything.Exceptions
+{
+ ///
+ ///
+ ///
+ public class MemoryErrorException : ApplicationException
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/RegisterClassExException.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/RegisterClassExException.cs
new file mode 100644
index 000000000..e51b7e8aa
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/Exceptions/RegisterClassExException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Wox.Plugin.Everything.Everything
+{
+ ///
+ ///
+ ///
+ public class RegisterClassExException : ApplicationException
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/ResultType.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/ResultType.cs
new file mode 100644
index 000000000..bd9776a13
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/ResultType.cs
@@ -0,0 +1,9 @@
+namespace Wox.Plugin.Everything.Everything
+{
+ public enum ResultType
+ {
+ Volume,
+ Folder,
+ File
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/SearchResult.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/SearchResult.cs
new file mode 100644
index 000000000..2a9af23a2
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Everything/SearchResult.cs
@@ -0,0 +1,8 @@
+namespace Wox.Plugin.Everything.Everything
+{
+ public class SearchResult
+ {
+ public string FullPath { get; set; }
+ public ResultType Type { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSDK/x64/Everything.dll b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSDK/x64/Everything.dll
new file mode 100644
index 000000000..20c2ed86c
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSDK/x64/Everything.dll differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSDK/x86/Everything.dll b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSDK/x86/Everything.dll
new file mode 100644
index 000000000..19761064e
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSDK/x86/Everything.dll differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSettings.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSettings.xaml
new file mode 100644
index 000000000..ba98b17cb
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSettings.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSettings.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSettings.xaml.cs
new file mode 100644
index 000000000..4377df292
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/EverythingSettings.xaml.cs
@@ -0,0 +1,49 @@
+using System.Windows;
+using System.Windows.Controls;
+using Microsoft.Win32;
+
+namespace Wox.Plugin.Everything
+{
+ public partial class EverythingSettings : UserControl
+ {
+ private readonly Settings _settings;
+
+ public EverythingSettings(Settings settings)
+ {
+ InitializeComponent();
+ _settings = settings;
+ }
+
+ private void View_Loaded(object sender, RoutedEventArgs re)
+ {
+ UseLocationAsWorkingDir.IsChecked = _settings.UseLocationAsWorkingDir;
+
+ UseLocationAsWorkingDir.Checked += (o, e) =>
+ {
+ _settings.UseLocationAsWorkingDir = true;
+ };
+
+ UseLocationAsWorkingDir.Unchecked += (o, e) =>
+ {
+ _settings.UseLocationAsWorkingDir = false;
+ };
+
+ EditorPath.Content = _settings.EditorPath;
+ }
+
+ private void EditorPath_Clicked(object sender, RoutedEventArgs e)
+ {
+ OpenFileDialog openFileDialog = new OpenFileDialog();
+ openFileDialog.Filter = "Executable File(*.exe)| *.exe";
+ if (!string.IsNullOrEmpty(_settings.EditorPath))
+ openFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(_settings.EditorPath);
+
+ if (openFileDialog.ShowDialog() == true)
+ {
+ _settings.EditorPath = openFileDialog.FileName;
+ }
+
+ EditorPath.Content = _settings.EditorPath;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/error.png b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/error.png
new file mode 100644
index 000000000..022fbc197
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/error.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/file.png b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/file.png
new file mode 100644
index 000000000..36156767a
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/file.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/find.png b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/find.png
new file mode 100644
index 000000000..a3f0be1f5
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/find.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/folder.png b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/folder.png
new file mode 100644
index 000000000..569fa7049
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/folder.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/image.png b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/image.png
new file mode 100644
index 000000000..7fc14c38e
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/image.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/warning.png b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/warning.png
new file mode 100644
index 000000000..8d29625ee
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Images/warning.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/de.xaml
new file mode 100644
index 000000000..34b1fcae6
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/de.xaml
@@ -0,0 +1,18 @@
+
+
+ Everything Service läuft nicht
+ Everything Plugin hat einen Fehler (drücke Enter zum kopieren der Fehlernachricht)
+ kopiert
+ Kann {0} nicht starten
+ Öffne enthaltenden Ordner
+ Openen met {0}
+ Editor pad
+
+ Everything
+ Suche Dateien mit Everything
+
+ Verwenden Suchergebnis Standort als ausführbare Arbeitsverzeichnis
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/en.xaml
new file mode 100644
index 000000000..4e9414660
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/en.xaml
@@ -0,0 +1,22 @@
+
+
+ Everything Service is not running
+ Error while querying Everything
+ Copied
+ Can’t start {0}
+ Open parent folder
+ Open with {0}
+ Editor Path
+ Copy path
+ Copy
+ Delete
+ Can't delete {0}
+
+ Everything
+ Search on-disk files using Everything
+
+ Use search result's location as executable working directory
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/pl.xaml
new file mode 100644
index 000000000..1fe95777e
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/pl.xaml
@@ -0,0 +1,16 @@
+
+
+ Everything Service nie jest uruchomiony
+ Wystąpił błąd podczas pobierania wyników z Everything
+ Skopiowano
+ Nie udało się uruchomić {0}
+ Otwórz folder nadrzędny.
+ Otwórz za pomocą {0}
+ Ścieżka edytora
+
+ Everything
+ Szukaj w plikach na dysku używając programu Everything
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/tr.xaml
new file mode 100644
index 000000000..12cdc1509
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/tr.xaml
@@ -0,0 +1,22 @@
+
+
+ Everything Servisi çalışmıyor
+ Sorgu Everything üzerinde çalıştırılırken hata oluştu
+ Kopyalandı
+ {0} başlatılamıyor
+ İçeren klasörü aç
+ {0} ile aç
+ Düzenleyici Konumu
+ Konumu Kopyala
+ Kopyala
+ Sil
+ {0} silinemiyor
+
+ Everything
+ Everything programı yardımıyla diskteki dosyalarınızı arayın.
+
+ Programın çalışma klasörü olarak sonuç klasörünü kullan
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/zh-cn.xaml
new file mode 100644
index 000000000..6b57eed28
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/zh-cn.xaml
@@ -0,0 +1,21 @@
+
+
+ Everything Service 没有运行
+ Everything 插件发生了一个错误(回车拷贝具体错误信息)
+ 拷贝成功
+ 不能启动 {0}
+ 打开所属文件夹
+ 使用{0}打开
+ 编辑器路径
+ 拷贝路径
+ 拷贝
+ 删除
+ 无法删除 {0}
+
+ Everything
+ 利用 Everything 搜索磁盘文件
+
+ 使用应用程序的位置为可执行的工作目录
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/zh-tw.xaml
new file mode 100644
index 000000000..9f9c0bd88
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Languages/zh-tw.xaml
@@ -0,0 +1,17 @@
+
+
+ Everything Service 尚未啟動
+ Everything 套件發生錯誤(Enter 複製具體錯誤訊息)
+ 複製成功
+ 無法啟動 {0}
+ 開啟檔案位置
+ 利用{0}啟動
+ 編輯器路径
+
+ Everything
+ 利用 Everything 搜尋磁碟上的檔案
+
+ 使用程式所在目錄作為工作目錄
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Main.cs
new file mode 100644
index 000000000..6c4e3f20d
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Main.cs
@@ -0,0 +1,286 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using Wox.Infrastructure;
+using Wox.Infrastructure.Logger;
+using Wox.Infrastructure.Storage;
+using Wox.Plugin.Everything.Everything;
+
+namespace Wox.Plugin.Everything
+{
+ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, ISavable
+ {
+ public const string DLL = "Everything.dll";
+ private readonly IEverythingApi _api = new EverythingApi();
+
+
+
+ private PluginInitContext _context;
+
+ private Settings _settings;
+ private PluginJsonStorage _storage;
+ private CancellationTokenSource _cancellationTokenSource;
+
+ public void Save()
+ {
+ _storage.Save();
+ }
+
+ public List Query(Query query)
+ {
+ _cancellationTokenSource?.Cancel(); // cancel if already exist
+ var cts = _cancellationTokenSource = new CancellationTokenSource();
+ var results = new List();
+ if (!string.IsNullOrEmpty(query.Search))
+ {
+ var keyword = query.Search;
+
+ try
+ {
+ var searchList = _api.Search(keyword, cts.Token, maxCount: _settings.MaxSearchCount);
+ if (searchList == null)
+ {
+ return results;
+ }
+
+ foreach (var searchResult in searchList)
+ {
+ var r = CreateResult(keyword, searchResult);
+ results.Add(r);
+ }
+ }
+ catch (IPCErrorException)
+ {
+ results.Add(new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_everything_is_not_running"),
+ IcoPath = "Images\\warning.png"
+ });
+ }
+ catch (Exception e)
+ {
+ Log.Exception("EverythingPlugin", "Query Error", e);
+ results.Add(new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_everything_query_error"),
+ SubTitle = e.Message,
+ Action = _ =>
+ {
+ Clipboard.SetText(e.Message + "\r\n" + e.StackTrace);
+ _context.API.ShowMsg(_context.API.GetTranslation("wox_plugin_everything_copied"), null, string.Empty);
+ return false;
+ },
+ IcoPath = "Images\\error.png"
+ });
+ }
+ }
+
+ return results;
+ }
+
+ private Result CreateResult(string keyword, SearchResult searchResult)
+ {
+ var path = searchResult.FullPath;
+
+ string workingDir = null;
+ if (_settings.UseLocationAsWorkingDir)
+ workingDir = Path.GetDirectoryName(path);
+
+ var r = new Result
+ {
+ Title = Path.GetFileName(path),
+ SubTitle = path,
+ IcoPath = path,
+ TitleHighlightData = StringMatcher.FuzzySearch(keyword, Path.GetFileName(path)).MatchData,
+ Action = c =>
+ {
+ bool hide;
+ try
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = path, UseShellExecute = true, WorkingDirectory = workingDir
+ });
+ hide = true;
+ }
+ catch (Win32Exception)
+ {
+ var name = $"Plugin: {_context.CurrentPluginMetadata.Name}";
+ var message = "Can't open this file";
+ _context.API.ShowMsg(name, message, string.Empty);
+ hide = false;
+ }
+
+ return hide;
+ },
+ ContextData = searchResult,
+ SubTitleHighlightData = StringMatcher.FuzzySearch(keyword, path).MatchData
+ };
+ return r;
+ }
+
+
+
+ private List GetDefaultContextMenu()
+ {
+ List defaultContextMenus = new List();
+ ContextMenu openFolderContextMenu = new ContextMenu
+ {
+ Name = _context.API.GetTranslation("wox_plugin_everything_open_containing_folder"),
+ Command = "explorer.exe",
+ Argument = " /select,\"{path}\"",
+ ImagePath = "Images\\folder.png"
+ };
+
+ defaultContextMenus.Add(openFolderContextMenu);
+
+ string editorPath = string.IsNullOrEmpty(_settings.EditorPath) ? "notepad.exe" : _settings.EditorPath;
+
+ ContextMenu openWithEditorContextMenu = new ContextMenu
+ {
+ Name = string.Format(_context.API.GetTranslation("wox_plugin_everything_open_with_editor"), Path.GetFileNameWithoutExtension(editorPath)),
+ Command = editorPath,
+ Argument = " \"{path}\"",
+ ImagePath = editorPath
+ };
+
+ defaultContextMenus.Add(openWithEditorContextMenu);
+
+ return defaultContextMenus;
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ _context = context;
+ _storage = new PluginJsonStorage();
+ _settings = _storage.Load();
+ if (_settings.MaxSearchCount <= 0)
+ {
+ _settings.MaxSearchCount = Settings.DefaultMaxSearchCount;
+ }
+
+ var pluginDirectory = context.CurrentPluginMetadata.PluginDirectory;
+ const string sdk = "EverythingSDK";
+ var bundledSDKDirectory = Path.Combine(pluginDirectory, sdk, CpuType());
+ var sdkDirectory = Path.Combine(_storage.DirectoryPath, sdk, CpuType());
+ Helper.ValidateDataDirectory(bundledSDKDirectory, sdkDirectory);
+
+ var sdkPath = Path.Combine(sdkDirectory, DLL);
+ Constant.EverythingSDKPath = sdkPath;
+ _api.Load(sdkPath);
+ }
+
+ private static string CpuType()
+ {
+ return Environment.Is64BitOperatingSystem ? "x64" : "x86";
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return _context.API.GetTranslation("wox_plugin_everything_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return _context.API.GetTranslation("wox_plugin_everything_plugin_description");
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ SearchResult record = selectedResult.ContextData as SearchResult;
+ List contextMenus = new List();
+ if (record == null) return contextMenus;
+
+ List availableContextMenus = new List();
+ availableContextMenus.AddRange(GetDefaultContextMenu());
+ availableContextMenus.AddRange(_settings.ContextMenus);
+
+ if (record.Type == ResultType.File)
+ {
+ foreach (ContextMenu contextMenu in availableContextMenus)
+ {
+ var menu = contextMenu;
+ contextMenus.Add(new Result
+ {
+ Title = contextMenu.Name,
+ Action = _ =>
+ {
+ string argument = menu.Argument.Replace("{path}", record.FullPath);
+ try
+ {
+ Process.Start(menu.Command, argument);
+ }
+ catch
+ {
+ _context.API.ShowMsg(string.Format(_context.API.GetTranslation("wox_plugin_everything_canot_start"), record.FullPath), string.Empty, string.Empty);
+ return false;
+ }
+ return true;
+ },
+ IcoPath = contextMenu.ImagePath
+ });
+ }
+ }
+
+ var icoPath = (record.Type == ResultType.File) ? "Images\\file.png" : "Images\\folder.png";
+ contextMenus.Add(new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_everything_copy_path"),
+ Action = (context) =>
+ {
+ Clipboard.SetText(record.FullPath);
+ return true;
+ },
+ IcoPath = icoPath
+ });
+
+ contextMenus.Add(new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_everything_copy"),
+ Action = (context) =>
+ {
+ Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath });
+ return true;
+ },
+ IcoPath = icoPath
+ });
+
+ if (record.Type == ResultType.File || record.Type == ResultType.Folder)
+ contextMenus.Add(new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_everything_delete"),
+ Action = (context) =>
+ {
+ try
+ {
+ if (record.Type == ResultType.File)
+ System.IO.File.Delete(record.FullPath);
+ else
+ System.IO.Directory.Delete(record.FullPath);
+ }
+ catch
+ {
+ _context.API.ShowMsg(string.Format(_context.API.GetTranslation("wox_plugin_everything_canot_delete"), record.FullPath), string.Empty, string.Empty);
+ return false;
+ }
+
+ return true;
+ },
+ IcoPath = icoPath
+ });
+
+ return contextMenus;
+ }
+
+ public Control CreateSettingPanel()
+ {
+ return new EverythingSettings(_settings);
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..22f46590a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.Everything")]
+[assembly: Guid("97f6ccd0-e9dc-4aa2-b4ce-6b9f14ea20a7")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/README.md b/src/modules/launcher/Plugins/Wox.Plugin.Everything/README.md
new file mode 100644
index 000000000..bb64b31c7
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/README.md
@@ -0,0 +1,4 @@
+Wox.Plugin.Everything
+=====================
+
+Wox plugin for Everything
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Settings.cs b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Settings.cs
new file mode 100644
index 000000000..9fb5ac89b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Settings.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using Newtonsoft.Json;
+using Wox.Infrastructure.Storage;
+
+namespace Wox.Plugin.Everything
+{
+ public class Settings
+ {
+ public const int DefaultMaxSearchCount = 50;
+
+ public string EditorPath { get; set; } = "";
+
+ public List ContextMenus = new List();
+
+ public int MaxSearchCount { get; set; } = DefaultMaxSearchCount;
+
+ public bool UseLocationAsWorkingDir { get; set; } = false;
+ }
+
+ public class ContextMenu
+ {
+ public string Name { get; set; }
+ public string Command { get; set; }
+ public string Argument { get; set; }
+ public string ImagePath { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/Wox.Plugin.Everything.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Wox.Plugin.Everything.csproj
new file mode 100644
index 000000000..2ec791bf8
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/Wox.Plugin.Everything.csproj
@@ -0,0 +1,189 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {230AE83F-E92E-4E69-8355-426B305DA9C0}
+ Library
+ Properties
+ Wox.Plugin.Everything
+ Wox.Plugin.Everything
+ v4.5.2
+ 512
+ ..\Wox\
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Everything\
+ DEBUG;TRACE
+ prompt
+ 4
+ AnyCPU
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Everything\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+ EverythingSettings.xaml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 9.0.1
+
+
+ 4.0.0
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Everything/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Everything/plugin.json
new file mode 100644
index 000000000..c19d9a80f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Everything/plugin.json
@@ -0,0 +1,13 @@
+{
+ "ID":"D2D2C23B084D411DB66FE0C79D6C2A6E",
+ "ActionKeyword":"*",
+ "Name":"Everything",
+ "Description":"Search Everything",
+ "Author":"qianlifeng,orzfly",
+ "Version":"1.1.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one",
+ "IcoPath":"Images\\find.png",
+ "ExecuteFileName":"Wox.Plugin.Everything.dll",
+ "Disabled": true
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs b/src/modules/launcher/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs
new file mode 100644
index 000000000..130fd7007
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows;
+using Wox.Infrastructure.Logger;
+using Wox.Infrastructure.Image;
+using Wox.Plugin.SharedCommands;
+
+namespace Wox.Plugin.Folder
+{
+ internal class ContextMenuLoader : IContextMenu
+ {
+ private readonly PluginInitContext _context;
+
+ public ContextMenuLoader(PluginInitContext context)
+ {
+ _context = context;
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ var contextMenus = new List();
+ if (selectedResult.ContextData is SearchResult record)
+ {
+ if (record.Type == ResultType.File)
+ {
+ contextMenus.Add(CreateOpenWithEditorResult(record));
+ contextMenus.Add(CreateOpenContainingFolderResult(record));
+ }
+
+ var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath;
+ var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder";
+ contextMenus.Add(new Result
+ {
+ Title = "Copy path",
+ SubTitle = $"Copy the current {fileOrFolder} path to clipboard",
+ Action = (context) =>
+ {
+ try
+ {
+ Clipboard.SetText(record.FullPath);
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = "Fail to set text in clipboard";
+ LogException(message, e);
+ _context.API.ShowMsg(message);
+ return false;
+ }
+ },
+ IcoPath = Main.CopyImagePath
+ });
+
+ contextMenus.Add(new Result
+ {
+ Title = $"Copy {fileOrFolder}",
+ SubTitle = $"Copy the {fileOrFolder} to clipboard",
+ Action = (context) =>
+ {
+ try
+ {
+ Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath });
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = $"Fail to set {fileOrFolder} in clipboard";
+ LogException(message, e);
+ _context.API.ShowMsg(message);
+ return false;
+ }
+
+ },
+ IcoPath = icoPath
+ });
+
+ if (record.Type == ResultType.File || record.Type == ResultType.Folder)
+ contextMenus.Add(new Result
+ {
+ Title = $"Delete {fileOrFolder}",
+ SubTitle = $"Delete the selected {fileOrFolder}",
+ Action = (context) =>
+ {
+ try
+ {
+ if (record.Type == ResultType.File)
+ File.Delete(record.FullPath);
+ else
+ Directory.Delete(record.FullPath);
+ }
+ catch(Exception e)
+ {
+ var message = $"Fail to delete {fileOrFolder} at {record.FullPath}";
+ LogException(message, e);
+ _context.API.ShowMsg(message);
+ return false;
+ }
+
+ return true;
+ },
+ IcoPath = Main.DeleteFileFolderImagePath
+ });
+
+ if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
+ contextMenus.Add(new Result
+ {
+ Title = "Run as different user",
+ Action = (context) =>
+ {
+ try
+ {
+ Task.Run(()=> ShellCommand.RunAsDifferentUser(record.FullPath.SetProcessStartInfo()));
+ }
+ catch (FileNotFoundException e)
+ {
+ var name = "Plugin: Folder";
+ var message = $"File not found: {e.Message}";
+ _context.API.ShowMsg(name, message);
+ }
+
+ return true;
+ },
+ IcoPath = "Images/user.png"
+ });
+ }
+
+ return contextMenus;
+ }
+
+ private Result CreateOpenContainingFolderResult(SearchResult record)
+ {
+ return new Result
+ {
+ Title = "Open containing folder",
+ Action = _ =>
+ {
+ try
+ {
+ Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
+ }
+ catch(Exception e)
+ {
+ var message = $"Fail to open file at {record.FullPath}";
+ LogException(message, e);
+ _context.API.ShowMsg(message);
+ return false;
+ }
+
+ return true;
+ },
+ IcoPath = Main.FolderImagePath
+ };
+ }
+
+
+ private Result CreateOpenWithEditorResult(SearchResult record)
+ {
+ string editorPath = "notepad.exe"; // TODO add the ability to create a custom editor
+
+ var name = "Open With Editor: " + Path.GetFileNameWithoutExtension(editorPath);
+ return new Result
+ {
+ Title = name,
+ Action = _ =>
+ {
+ try
+ {
+ Process.Start(editorPath, record.FullPath);
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = $"Fail to editor for file at {record.FullPath}";
+ LogException(message, e);
+ _context.API.ShowMsg(message);
+ return false;
+ }
+ },
+ IcoPath = editorPath
+ };
+ }
+
+ public void LogException(string message, Exception e)
+ {
+ Log.Exception($"|Wox.Plugin.Folder.ContextMenu|{message}", e);
+ }
+
+ private bool CanRunAsDifferentUser(string path)
+ {
+ switch(Path.GetExtension(path))
+ {
+ case ".exe":
+ case ".bat":
+ return true;
+
+ default:
+ return false;
+
+ }
+ }
+ }
+
+ public class SearchResult
+ {
+ public string FullPath { get; set; }
+ public ResultType Type { get; set; }
+ }
+
+ public enum ResultType
+ {
+ Volume,
+ Folder,
+ File
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderLink.cs b/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderLink.cs
new file mode 100644
index 000000000..8148c45bf
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderLink.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Wox.Plugin.Folder
+{
+ [JsonObject(MemberSerialization.OptIn)]
+ public class FolderLink
+ {
+ [JsonProperty]
+ public string Path { get; set; }
+
+ public string Nickname =>
+ Path.Split(new[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None)
+ .Last()
+ + " (" + System.IO.Path.GetDirectoryName(Path) + ")";
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderPluginSettings.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderPluginSettings.xaml
new file mode 100644
index 000000000..93b9178e0
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderPluginSettings.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderPluginSettings.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderPluginSettings.xaml.cs
new file mode 100644
index 000000000..2b5b979ce
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/FolderPluginSettings.xaml.cs
@@ -0,0 +1,130 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Forms;
+using DataFormats = System.Windows.DataFormats;
+using DragDropEffects = System.Windows.DragDropEffects;
+using DragEventArgs = System.Windows.DragEventArgs;
+using MessageBox = System.Windows.MessageBox;
+
+namespace Wox.Plugin.Folder
+{
+
+ public partial class FileSystemSettings
+ {
+ private IPublicAPI woxAPI;
+ private Settings _settings;
+
+ public FileSystemSettings(IPublicAPI woxAPI, Settings settings)
+ {
+ this.woxAPI = woxAPI;
+ InitializeComponent();
+ _settings = settings;
+ lbxFolders.ItemsSource = _settings.FolderLinks;
+ }
+
+ private void btnDelete_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedFolder = lbxFolders.SelectedItem as FolderLink;
+ if (selectedFolder != null)
+ {
+ string msg = string.Format(woxAPI.GetTranslation("wox_plugin_folder_delete_folder_link"), selectedFolder.Path);
+
+ if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ _settings.FolderLinks.Remove(selectedFolder);
+ lbxFolders.Items.Refresh();
+ }
+ }
+ else
+ {
+ string warning = woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
+ MessageBox.Show(warning);
+ }
+ }
+
+ private void btnEdit_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedFolder = lbxFolders.SelectedItem as FolderLink;
+ if (selectedFolder != null)
+ {
+ var folderBrowserDialog = new FolderBrowserDialog();
+ folderBrowserDialog.SelectedPath = selectedFolder.Path;
+ if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
+ {
+ var link = _settings.FolderLinks.First(x => x.Path == selectedFolder.Path);
+ link.Path = folderBrowserDialog.SelectedPath;
+ }
+
+ lbxFolders.Items.Refresh();
+ }
+ else
+ {
+ string warning = woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
+ MessageBox.Show(warning);
+ }
+ }
+
+ private void btnAdd_Click(object sender, RoutedEventArgs e)
+ {
+ var folderBrowserDialog = new FolderBrowserDialog();
+ if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
+ {
+ var newFolder = new FolderLink
+ {
+ Path = folderBrowserDialog.SelectedPath
+ };
+
+ if (_settings.FolderLinks == null)
+ {
+ _settings.FolderLinks = new List();
+ }
+
+ _settings.FolderLinks.Add(newFolder);
+ }
+
+ lbxFolders.Items.Refresh();
+ }
+
+ private void lbxFolders_Drop(object sender, DragEventArgs e)
+ {
+ string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
+
+ if (files != null && files.Count() > 0)
+ {
+ if (_settings.FolderLinks == null)
+ {
+ _settings.FolderLinks = new List();
+ }
+
+ foreach (string s in files)
+ {
+ if (Directory.Exists(s))
+ {
+ var newFolder = new FolderLink
+ {
+ Path = s
+ };
+
+ _settings.FolderLinks.Add(newFolder);
+ }
+
+ lbxFolders.Items.Refresh();
+ }
+ }
+ }
+
+ private void lbxFolders_DragEnter(object sender, DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.FileDrop))
+ {
+ e.Effects = DragDropEffects.Link;
+ }
+ else
+ {
+ e.Effects = DragDropEffects.None;
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/copy.png b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/copy.png
new file mode 100644
index 000000000..8f1fca752
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/copy.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/deletefilefolder.png b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/deletefilefolder.png
new file mode 100644
index 000000000..024cc9291
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/deletefilefolder.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/file.png b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/file.png
new file mode 100644
index 000000000..36156767a
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/file.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/folder.png b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/folder.png
new file mode 100644
index 000000000..569fa7049
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/folder.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/user.png b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/user.png
new file mode 100644
index 000000000..2d45c1ee9
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Images/user.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/de.xaml
new file mode 100644
index 000000000..d58189b34
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/de.xaml
@@ -0,0 +1,15 @@
+
+
+ Löschen
+ Bearbeiten
+ Hinzufügen
+ Ordnerpfad
+ Bitte wähle eine Ordnerverknüpfung
+ Bist du sicher {0} zu löschen?
+
+ Ordner
+ Öffne deine Lieblingsordner direkt von Wox aus
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/en.xaml
new file mode 100644
index 000000000..78190d1b8
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/en.xaml
@@ -0,0 +1,15 @@
+
+
+ Delete
+ Edit
+ Add
+ Folder Path
+ Please select a folder link
+ Are you sure you want to delete {0}?
+
+ Folder
+ Open favorite folder from Wox directly
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/pl.xaml
new file mode 100644
index 000000000..454b85036
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/pl.xaml
@@ -0,0 +1,15 @@
+
+
+ Usuń
+ Edytuj
+ Dodaj
+ Ścieżka folderu
+ Musisz wybrać któryś folder z listy
+ Czy jesteś pewien że chcesz usunąć {0}?
+
+ Foldery
+ Otwórz ulubione foldery bezpośrednio z poziomu Woxa
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/tr.xaml
new file mode 100644
index 000000000..8cf0c7653
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/tr.xaml
@@ -0,0 +1,15 @@
+
+
+ Sil
+ Düzenle
+ Ekle
+ Klasör Konumu
+ Lütfen bir klasör bağlantısı seçin
+ {0} bağlantısını silmek istediğinize emin misiniz?
+
+ Klasör
+ Favori klasörünüzü doğrudan Wox'tan açın
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/zh-cn.xaml
new file mode 100644
index 000000000..15df3d5c6
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/zh-cn.xaml
@@ -0,0 +1,15 @@
+
+
+ 删除
+ 编辑
+ 添加
+ 文件夹路径
+ 请选择一个文件夹
+ 你确定要删除{0}吗?
+
+ 文件夹
+ 在Wox中直接打开收藏的文件夹
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/zh-tw.xaml
new file mode 100644
index 000000000..f87d8c14d
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Languages/zh-tw.xaml
@@ -0,0 +1,15 @@
+
+
+ 刪除
+ 編輯
+ 新增
+ 資料夾路徑
+ 請選擇一個資料夾
+ 你確認要刪除{0}嗎?
+
+ 資料夾
+ 在 Wox 中直接開啟收藏的資料夾
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Main.cs
new file mode 100644
index 000000000..53e298eb6
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Main.cs
@@ -0,0 +1,309 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using Wox.Infrastructure;
+using Wox.Infrastructure.Storage;
+
+namespace Wox.Plugin.Folder
+{
+ public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IContextMenu
+ {
+ public const string FolderImagePath = "Images\\folder.png";
+ public const string FileImagePath = "Images\\file.png";
+ public const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png";
+ public const string CopyImagePath = "Images\\copy.png";
+
+ private string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory";
+
+ private static List _driverNames;
+ private PluginInitContext _context;
+
+ private readonly Settings _settings;
+ private readonly PluginJsonStorage _storage;
+ private IContextMenu _contextMenuLoader;
+
+ public Main()
+ {
+ _storage = new PluginJsonStorage();
+ _settings = _storage.Load();
+ }
+
+ public void Save()
+ {
+ _storage.Save();
+ }
+
+ public Control CreateSettingPanel()
+ {
+ return new FileSystemSettings(_context.API, _settings);
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ _context = context;
+ _contextMenuLoader = new ContextMenuLoader(context);
+ InitialDriverList();
+ }
+
+ public List Query(Query query)
+ {
+ var results = GetUserFolderResults(query);
+
+ string search = query.Search.ToLower();
+ if (!IsDriveOrSharedFolder(search))
+ return results;
+
+ results.AddRange(QueryInternal_Directory_Exists(query));
+
+ // todo why was this hack here?
+ foreach (var result in results)
+ {
+ result.Score += 10;
+ }
+
+ return results;
+ }
+
+ private static bool IsDriveOrSharedFolder(string search)
+ {
+ if (search.StartsWith(@"\\"))
+ { // share folder
+ return true;
+ }
+
+ if (_driverNames != null && _driverNames.Any(search.StartsWith))
+ { // normal drive letter
+ return true;
+ }
+
+ if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':')
+ { // when we don't have the drive letters we can try...
+ return true; // we don't know so let's give it the possibility
+ }
+
+ return false;
+ }
+
+ private Result CreateFolderResult(string title, string subtitle, string path, Query query)
+ {
+ return new Result
+ {
+ Title = title,
+ IcoPath = path,
+ SubTitle = subtitle,
+ TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
+ Action = c =>
+ {
+ if (c.SpecialKeyState.CtrlPressed)
+ {
+ try
+ {
+ Process.Start(path);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Could not start " + path);
+ return false;
+ }
+ }
+
+ string changeTo = path.EndsWith("\\") ? path : path + "\\";
+ _context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ?
+ changeTo :
+ query.ActionKeyword + " " + changeTo);
+ return false;
+ },
+ ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path }
+ };
+ }
+
+ private List GetUserFolderResults(Query query)
+ {
+ string search = query.Search.ToLower();
+ var userFolderLinks = _settings.FolderLinks.Where(
+ x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
+ var results = userFolderLinks.Select(item =>
+ CreateFolderResult(item.Nickname, DefaultFolderSubtitleString, item.Path, query)).ToList();
+ return results;
+ }
+
+ private void InitialDriverList()
+ {
+ if (_driverNames == null)
+ {
+ _driverNames = new List();
+ var allDrives = DriveInfo.GetDrives();
+ foreach (DriveInfo driver in allDrives)
+ {
+ _driverNames.Add(driver.Name.ToLower().TrimEnd('\\'));
+ }
+ }
+ }
+
+ private static readonly char[] _specialSearchChars = new char[]
+ {
+ '?', '*', '>'
+ };
+
+ private List QueryInternal_Directory_Exists(Query query)
+ {
+ var search = query.Search;
+ var results = new List();
+ var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0;
+ string incompleteName = "";
+ if (hasSpecial || !Directory.Exists(search + "\\"))
+ {
+ // if folder doesn't exist, we want to take the last part and use it afterwards to help the user
+ // find the right folder.
+ int index = search.LastIndexOf('\\');
+ if (index > 0 && index < (search.Length - 1))
+ {
+ incompleteName = search.Substring(index + 1).ToLower();
+ search = search.Substring(0, index + 1);
+ if (!Directory.Exists(search))
+ {
+ return results;
+ }
+ }
+ else
+ {
+ return results;
+ }
+ }
+ else
+ {
+ // folder exist, add \ at the end of doesn't exist
+ if (!search.EndsWith("\\"))
+ {
+ search += "\\";
+ }
+ }
+
+ results.Add(CreateOpenCurrentFolderResult(incompleteName, search));
+
+ var searchOption = SearchOption.TopDirectoryOnly;
+ incompleteName += "*";
+
+ // give the ability to search all folder when starting with >
+ if (incompleteName.StartsWith(">"))
+ {
+ searchOption = SearchOption.AllDirectories;
+
+ // match everything before and after search term using supported wildcard '*', ie. *searchterm*
+ incompleteName = "*" + incompleteName.Substring(1);
+ }
+
+ var folderList = new List();
+ var fileList = new List();
+
+ var folderSubtitleString = DefaultFolderSubtitleString;
+
+ try
+ {
+ // search folder and add results
+ var directoryInfo = new DirectoryInfo(search);
+ var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption);
+
+ foreach (var fileSystemInfo in fileSystemInfos)
+ {
+ if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
+
+ if(fileSystemInfo is DirectoryInfo)
+ {
+ if (searchOption == SearchOption.AllDirectories)
+ folderSubtitleString = fileSystemInfo.FullName;
+
+ folderList.Add(CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query));
+ }
+ else
+ {
+ fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ if (e is UnauthorizedAccessException || e is ArgumentException)
+ {
+ results.Add(new Result { Title = e.Message, Score = 501 });
+
+ return results;
+ }
+
+ throw;
+ }
+
+ // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
+ return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList();
+ }
+
+ private static Result CreateFileResult(string filePath, Query query)
+ {
+ var result = new Result
+ {
+ Title = Path.GetFileName(filePath),
+ SubTitle = filePath,
+ IcoPath = filePath,
+ TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
+ Action = c =>
+ {
+ try
+ {
+ Process.Start(filePath);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Could not start " + filePath);
+ }
+
+ return true;
+ },
+ ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath}
+ };
+ return result;
+ }
+
+ private static Result CreateOpenCurrentFolderResult(string incompleteName, string search)
+ {
+ var firstResult = "Open current directory";
+ if (incompleteName.Length > 0)
+ firstResult = "Open " + search;
+
+ var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last();
+
+ return new Result
+ {
+ Title = firstResult,
+ SubTitle = $"Use > to search files and subfolders within {folderName}, " +
+ $"* to search for file extensions in {folderName} or both >* to combine the search",
+ IcoPath = search,
+ Score = 500,
+ Action = c =>
+ {
+ Process.Start(search);
+ return true;
+ }
+ };
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return _context.API.GetTranslation("wox_plugin_folder_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return _context.API.GetTranslation("wox_plugin_folder_plugin_description");
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ return _contextMenuLoader.LoadContextMenus(selectedResult);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..17473b64d
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.Folder")]
+[assembly: Guid("e047418e-f7b0-4a3a-b855-0bef7178179f")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Settings.cs b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Settings.cs
new file mode 100644
index 000000000..27518258f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Settings.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Wox.Infrastructure.Storage;
+
+namespace Wox.Plugin.Folder
+{
+ public class Settings
+ {
+ [JsonProperty]
+ public List FolderLinks { get; set; } = new List();
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj
new file mode 100644
index 000000000..a75fa0ba9
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj
@@ -0,0 +1,157 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}
+ Library
+ Properties
+ Wox.Plugin.Folder
+ Wox.Plugin.Folder
+ v4.5.2
+ 512
+ ..\..\
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Folder\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Folder\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+ FolderPluginSettings.xaml
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ PreserveNewest
+
+
+ Always
+
+
+ PreserveNewest
+
+
+ Always
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 9.0.1
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Folder/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Folder/plugin.json
new file mode 100644
index 000000000..662a4df6e
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Folder/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"B4D3B69656E14D44865C8D818EAE47C4",
+ "ActionKeyword":"*",
+ "Name":"Folder",
+ "Description":"Open favorite folder from wox directorily",
+ "Author":"qianlifeng",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.Folder.dll",
+ "IcoPath":"Images\\folder.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Images/work.png b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Images/work.png
new file mode 100644
index 000000000..6ff9b8b15
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Images/work.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/de.xaml
new file mode 100644
index 000000000..3ee680a64
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/de.xaml
@@ -0,0 +1,8 @@
+
+
+ Plugin Indikator
+ Stellt Vorschläge für Plugin-Befehle bereit
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/en.xaml
new file mode 100644
index 000000000..e3cf4ace6
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/en.xaml
@@ -0,0 +1,8 @@
+
+
+ Plugin Indicator
+ Provides plugins action words suggestions
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/pl.xaml
new file mode 100644
index 000000000..d8016140a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/pl.xaml
@@ -0,0 +1,8 @@
+
+
+ Plugin Indicator
+ Pokazuje podpowiedzi jakich zainstalowanych wtyczek możesz użyć
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/tr.xaml
new file mode 100644
index 000000000..a4cb364c8
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/tr.xaml
@@ -0,0 +1,8 @@
+
+
+ Eklenti Göstergesi
+ Eklenti eylemleri hakkında kelime önerileri sunar
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/zh-cn.xaml
new file mode 100644
index 000000000..12a49d04e
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/zh-cn.xaml
@@ -0,0 +1,8 @@
+
+
+ 插件关键词提示
+ 提供插件关键词搜索提示
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/zh-tw.xaml
new file mode 100644
index 000000000..87320a317
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Languages/zh-tw.xaml
@@ -0,0 +1,8 @@
+
+
+ 套件關鍵字提示
+ 提供套件關鍵字搜尋提示
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Main.cs
new file mode 100644
index 000000000..54dcf007a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Main.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using System.Linq;
+using Wox.Core.Plugin;
+
+namespace Wox.Plugin.PluginIndicator
+{
+ public class Main : IPlugin, IPluginI18n
+ {
+ private PluginInitContext context;
+
+ public List Query(Query query)
+ {
+ var results = from keyword in PluginManager.NonGlobalPlugins.Keys
+ where keyword.StartsWith(query.Terms[0])
+ let metadata = PluginManager.NonGlobalPlugins[keyword].Metadata
+ where !metadata.Disabled
+ select new Result
+ {
+ Title = keyword,
+ SubTitle = $"Activate {metadata.Name} plugin",
+ Score = 100,
+ IcoPath = metadata.IcoPath,
+ Action = c =>
+ {
+ context.API.ChangeQuery($"{keyword}{Plugin.Query.TermSeperater}");
+ return false;
+ }
+ };
+ return results.ToList();
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ this.context = context;
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return context.API.GetTranslation("wox_plugin_pluginindicator_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return context.API.GetTranslation("wox_plugin_pluginindicator_plugin_description");
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..5323b0bc1
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.PluginIndicator")]
+[assembly: Guid("27f6d9fc-340b-47be-90ea-2a86bfca7bad")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Wox.Plugin.PluginIndicator.csproj b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Wox.Plugin.PluginIndicator.csproj
new file mode 100644
index 000000000..709016b06
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/Wox.Plugin.PluginIndicator.csproj
@@ -0,0 +1,129 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {FDED22C8-B637-42E8-824A-63B5B6E05A3A}
+ Library
+ Properties
+ Wox.Plugin.PluginIndicator
+ Wox.Plugin.PluginIndicator
+ v4.5.2
+ 512
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.PluginIndicator\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.PluginIndicator\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ {b749f0db-8e75-47db-9e5e-265d16d0c0d2}
+ Wox.Core
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/plugin.json
new file mode 100644
index 000000000..a8effc2e7
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginIndicator/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"6A122269676E40EB86EB543B945932B9",
+ "ActionKeyword":"*",
+ "Name":"Plugin Indicator",
+ "Description":"Provide plugin actionword suggestion",
+ "Author":"qianlifeng",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.PluginIndicator.dll",
+ "IcoPath":"Images\\work.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Images/plugin.png b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Images/plugin.png
new file mode 100644
index 000000000..6ff9b8b15
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Images/plugin.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/de.xaml
new file mode 100644
index 000000000..aa3de26f0
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/de.xaml
@@ -0,0 +1,8 @@
+
+
+ Wox Plugin Verwaltung
+ Installiere/Entferne/Aktualisiere Wox Plugins
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/en.xaml
new file mode 100644
index 000000000..46d7025c3
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/en.xaml
@@ -0,0 +1,8 @@
+
+
+ Wox Plugin Management
+ Install, remove or update Wox plugins
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/pl.xaml
new file mode 100644
index 000000000..d26a77190
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/pl.xaml
@@ -0,0 +1,8 @@
+
+
+ Zarządzanie wtyczkami Wox
+ Pozwala na instalacje, usuwanie i aktualizacje wtyczek
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/tr.xaml
new file mode 100644
index 000000000..15e5163a5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/tr.xaml
@@ -0,0 +1,8 @@
+
+
+ Wox Eklenti Yöneticisi
+ Wox eklentilerini kurun, kaldırın ya da güncelleyin
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/zh-cn.xaml
new file mode 100644
index 000000000..00ada8ff3
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/zh-cn.xaml
@@ -0,0 +1,8 @@
+
+
+ Wox插件管理
+ 安装/卸载/更新Wox插件
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/zh-tw.xaml
new file mode 100644
index 000000000..c50b0c06e
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Languages/zh-tw.xaml
@@ -0,0 +1,8 @@
+
+
+ Wox 外掛管理
+ 安裝/解除安裝/更新 Wox 外掛
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Main.cs
new file mode 100644
index 000000000..1ee0fd7ae
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Main.cs
@@ -0,0 +1,259 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using Newtonsoft.Json;
+using Wox.Infrastructure;
+using Wox.Infrastructure.Http;
+using Wox.Infrastructure.Logger;
+
+namespace Wox.Plugin.PluginManagement
+{
+ public class Main : IPlugin, IPluginI18n
+ {
+ private static string APIBASE = "http://api.wox.one";
+ private static string PluginConfigName = "plugin.json";
+ private static string pluginSearchUrl = APIBASE + "/plugin/search/";
+ private const string ListCommand = "list";
+ private const string InstallCommand = "install";
+ private const string UninstallCommand = "uninstall";
+ private PluginInitContext context;
+
+ public List Query(Query query)
+ {
+ List results = new List();
+
+ if (string.IsNullOrEmpty(query.Search))
+ {
+ results.Add(ResultForListCommandAutoComplete(query));
+ results.Add(ResultForInstallCommandAutoComplete(query));
+ results.Add(ResultForUninstallCommandAutoComplete(query));
+ return results;
+ }
+
+ string command = query.FirstSearch.ToLower();
+ if (string.IsNullOrEmpty(command)) return results;
+
+ if (command == ListCommand)
+ {
+ return ResultForListInstalledPlugins();
+ }
+ if (command == UninstallCommand)
+ {
+ return ResultForUnInstallPlugin(query);
+ }
+ if (command == InstallCommand)
+ {
+ return ResultForInstallPlugin(query);
+ }
+
+ if (InstallCommand.Contains(command))
+ {
+ results.Add(ResultForInstallCommandAutoComplete(query));
+ }
+ if (UninstallCommand.Contains(command))
+ {
+ results.Add(ResultForUninstallCommandAutoComplete(query));
+ }
+ if (ListCommand.Contains(command))
+ {
+ results.Add(ResultForListCommandAutoComplete(query));
+ }
+
+ return results;
+ }
+
+ private Result ResultForListCommandAutoComplete(Query query)
+ {
+ string title = ListCommand;
+ string subtitle = "list installed plugins";
+ return ResultForCommand(query, ListCommand, title, subtitle);
+ }
+
+ private Result ResultForInstallCommandAutoComplete(Query query)
+ {
+ string title = $"{InstallCommand} ";
+ string subtitle = "list installed plugins";
+ return ResultForCommand(query, InstallCommand, title, subtitle);
+ }
+
+ private Result ResultForUninstallCommandAutoComplete(Query query)
+ {
+ string title = $"{UninstallCommand} ";
+ string subtitle = "list installed plugins";
+ return ResultForCommand(query, UninstallCommand, title, subtitle);
+ }
+
+ private Result ResultForCommand(Query query, string command, string title, string subtitle)
+ {
+ const string seperater = Plugin.Query.TermSeperater;
+ var result = new Result
+ {
+ Title = title,
+ IcoPath = "Images\\plugin.png",
+ SubTitle = subtitle,
+ Action = e =>
+ {
+ context.API.ChangeQuery($"{query.ActionKeyword}{seperater}{command}{seperater}");
+ return false;
+ }
+ };
+ return result;
+ }
+
+ private List ResultForInstallPlugin(Query query)
+ {
+ List results = new List();
+ string pluginName = query.SecondSearch;
+ if (string.IsNullOrEmpty(pluginName)) return results;
+ string json;
+ try
+ {
+ json = Http.Get(pluginSearchUrl + pluginName).Result;
+ }
+ catch (WebException e)
+ {
+ //todo happlebao add option in log to decide give user prompt or not
+ context.API.ShowMsg("PluginManagement.ResultForInstallPlugin: Can't connect to Wox plugin website, check your conenction");
+ Log.Exception("|PluginManagement.ResultForInstallPlugin|Can't connect to Wox plugin website, check your conenction", e);
+ return new List();
+ }
+ List searchedPlugins;
+ try
+ {
+ searchedPlugins = JsonConvert.DeserializeObject>(json);
+ }
+ catch (JsonSerializationException e)
+ {
+ context.API.ShowMsg("PluginManagement.ResultForInstallPlugin: Coundn't parse api search results, Please update your Wox!");
+ Log.Exception("|PluginManagement.ResultForInstallPlugin|Coundn't parse api search results, Please update your Wox!", e);
+ return results;
+ }
+
+ foreach (WoxPluginResult r in searchedPlugins)
+ {
+ WoxPluginResult r1 = r;
+ results.Add(new Result
+ {
+ Title = r.name,
+ SubTitle = r.description,
+ IcoPath = "Images\\plugin.png",
+ TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.name).MatchData,
+ SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.description).MatchData,
+ Action = c =>
+ {
+ MessageBoxResult result = MessageBox.Show("Are you sure you wish to install the \'" + r.name + "\' plugin",
+ "Install plugin", MessageBoxButton.YesNo);
+
+ if (result == MessageBoxResult.Yes)
+ {
+ string folder = Path.Combine(Path.GetTempPath(), "WoxPluginDownload");
+ if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
+ string filePath = Path.Combine(folder, Guid.NewGuid().ToString() + ".wox");
+
+ string pluginUrl = APIBASE + "/media/" + r1.plugin_file;
+
+ try
+ {
+ Http.Download(pluginUrl, filePath);
+ }
+ catch (WebException e)
+ {
+ context.API.ShowMsg($"PluginManagement.ResultForInstallPlugin: download failed for <{r.name}>");
+ Log.Exception($"|PluginManagement.ResultForInstallPlugin|download failed for <{r.name}>", e);
+ return false;
+ }
+ context.API.InstallPlugin(filePath);
+ }
+ return false;
+ }
+ });
+ }
+ return results;
+ }
+
+ private List ResultForUnInstallPlugin(Query query)
+ {
+ List results = new List();
+ List allInstalledPlugins = context.API.GetAllPlugins().Select(o => o.Metadata).ToList();
+ if (!string.IsNullOrEmpty(query.SecondSearch))
+ {
+ allInstalledPlugins =
+ allInstalledPlugins.Where(o => o.Name.ToLower().Contains(query.SecondSearch.ToLower())).ToList();
+ }
+
+ foreach (PluginMetadata plugin in allInstalledPlugins)
+ {
+ results.Add(new Result
+ {
+ Title = plugin.Name,
+ SubTitle = plugin.Description,
+ IcoPath = plugin.IcoPath,
+ TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Name).MatchData,
+ SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Description).MatchData,
+ Action = e =>
+ {
+ UnInstallPlugin(plugin);
+ return false;
+ }
+ });
+ }
+ return results;
+ }
+
+ private void UnInstallPlugin(PluginMetadata plugin)
+ {
+ string content = $"Do you want to uninstall following plugin?{Environment.NewLine}{Environment.NewLine}" +
+ $"Name: {plugin.Name}{Environment.NewLine}" +
+ $"Version: {plugin.Version}{Environment.NewLine}" +
+ $"Author: {plugin.Author}";
+ if (MessageBox.Show(content, "Wox", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ File.Create(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")).Close();
+ var result = MessageBox.Show($"You have uninstalled plugin {plugin.Name} successfully.{Environment.NewLine}" +
+ "Restart Wox to take effect?",
+ "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ context.API.RestarApp();
+ }
+ }
+ }
+
+ private List ResultForListInstalledPlugins()
+ {
+ List results = new List();
+ foreach (PluginMetadata plugin in context.API.GetAllPlugins().Select(o => o.Metadata))
+ {
+ string actionKeywordString = string.Join(" or ", plugin.ActionKeywords.ToArray());
+ results.Add(new Result
+ {
+ Title = $"{plugin.Name} - Action Keywords: {actionKeywordString}",
+ SubTitle = plugin.Description,
+ IcoPath = plugin.IcoPath
+ });
+ }
+ return results;
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ this.context = context;
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return context.API.GetTranslation("wox_plugin_plugin_management_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return context.API.GetTranslation("wox_plugin_plugin_management_plugin_description");
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..6fa58f5d1
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.PluginManagement")]
+[assembly: Guid("92b59bab-5c8c-414b-a8d7-326c7be3a11d")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Wox.Plugin.PluginManagement.csproj b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Wox.Plugin.PluginManagement.csproj
new file mode 100644
index 000000000..d963ea367
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/Wox.Plugin.PluginManagement.csproj
@@ -0,0 +1,137 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {049490F0-ECD2-4148-9B39-2135EC346EBE}
+ Library
+ Properties
+ Wox.Plugin.PluginManagement
+ Wox.Plugin.PluginManagement
+ v4.5.2
+ 512
+ ..\..\
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.PluginManagement\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.PluginManagement\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+
+
+ {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 9.0.1
+
+
+ 4.0.0
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/WoxPluginResult.cs b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/WoxPluginResult.cs
new file mode 100644
index 000000000..a7fcd513e
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/WoxPluginResult.cs
@@ -0,0 +1,11 @@
+namespace Wox.Plugin.PluginManagement
+{
+ public class WoxPluginResult
+ {
+ public string plugin_file;
+ public string description;
+ public int liked_count;
+ public string name;
+ public string version;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/plugin.json
new file mode 100644
index 000000000..88a134e26
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.PluginManagement/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"D2D2C23B084D422DB66FE0C79D6C2A6A",
+ "ActionKeyword":"wpm",
+ "Name":"Wox Plugin Management",
+ "Description":"Install/Remove/Update wox plugins",
+ "Author":"qianlifeng",
+ "Version":"1.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.PluginManagement.dll",
+ "IcoPath":"Images\\plugin.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/AddProgramSource.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/AddProgramSource.xaml
new file mode 100644
index 000000000..6e3d66ff5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/AddProgramSource.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/AddProgramSource.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/AddProgramSource.xaml.cs
new file mode 100644
index 000000000..c79cb46ce
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/AddProgramSource.xaml.cs
@@ -0,0 +1,76 @@
+using System.Windows;
+using System.Windows.Forms;
+using Wox.Plugin.Program.Views.Models;
+using Wox.Plugin.Program.Views;
+using System.Linq;
+
+namespace Wox.Plugin.Program
+{
+ ///
+ /// Interaction logic for AddProgramSource.xaml
+ ///
+ public partial class AddProgramSource
+ {
+ private PluginInitContext _context;
+ private Settings.ProgramSource _editing;
+ private Settings _settings;
+
+ public AddProgramSource(PluginInitContext context, Settings settings)
+ {
+ InitializeComponent();
+ _context = context;
+ _settings = settings;
+ Directory.Focus();
+ }
+
+ public AddProgramSource(Settings.ProgramSource edit, Settings settings)
+ {
+ _editing = edit;
+ _settings = settings;
+
+ InitializeComponent();
+ Directory.Text = _editing.Location;
+ }
+
+ private void BrowseButton_Click(object sender, RoutedEventArgs e)
+ {
+ var dialog = new FolderBrowserDialog();
+ DialogResult result = dialog.ShowDialog();
+ if (result == System.Windows.Forms.DialogResult.OK)
+ {
+ Directory.Text = dialog.SelectedPath;
+ }
+ }
+
+ private void ButtonAdd_OnClick(object sender, RoutedEventArgs e)
+ {
+ string s = Directory.Text;
+ if (!System.IO.Directory.Exists(s))
+ {
+ System.Windows.MessageBox.Show(_context.API.GetTranslation("wox_plugin_program_invalid_path"));
+ return;
+ }
+ if (_editing == null)
+ {
+ if (!ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == Directory.Text))
+ {
+ var source = new ProgramSource
+ {
+ Location = Directory.Text,
+ UniqueIdentifier = Directory.Text
+ };
+
+ _settings.ProgramSources.Insert(0, source);
+ ProgramSetting.ProgramSettingDisplayList.Add(source);
+ }
+ }
+ else
+ {
+ _editing.Location = Directory.Text;
+ }
+
+ DialogResult = true;
+ Close();
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/AppxPackagingTlb.dll b/src/modules/launcher/Plugins/Wox.Plugin.Program/AppxPackagingTlb.dll
new file mode 100644
index 000000000..183cfc085
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Program/AppxPackagingTlb.dll differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs
new file mode 100644
index 000000000..0df361215
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Wox.Infrastructure.Logger;
+using Wox.Plugin.Program.Programs;
+
+namespace Wox.Plugin.Program
+{
+ //internal static class FileChangeWatcher
+ //{
+ // private static readonly List WatchedPath = new List();
+ // // todo remove previous watcher events
+ // public static void AddAll(List sources, string[] suffixes)
+ // {
+ // foreach (var s in sources)
+ // {
+ // if (Directory.Exists(s.Location))
+ // {
+ // AddWatch(s.Location, suffixes);
+ // }
+ // }
+ // }
+
+ // public static void AddWatch(string path, string[] programSuffixes, bool includingSubDirectory = true)
+ // {
+ // if (WatchedPath.Contains(path)) return;
+ // if (!Directory.Exists(path))
+ // {
+ // Log.Warn($"|FileChangeWatcher|{path} doesn't exist");
+ // return;
+ // }
+
+ // WatchedPath.Add(path);
+ // foreach (string fileType in programSuffixes)
+ // {
+ // FileSystemWatcher watcher = new FileSystemWatcher
+ // {
+ // Path = path,
+ // IncludeSubdirectories = includingSubDirectory,
+ // Filter = $"*.{fileType}",
+ // EnableRaisingEvents = true
+ // };
+ // watcher.Changed += FileChanged;
+ // watcher.Created += FileChanged;
+ // watcher.Deleted += FileChanged;
+ // watcher.Renamed += FileChanged;
+ // }
+ // }
+
+ // private static void FileChanged(object source, FileSystemEventArgs e)
+ // {
+ // Task.Run(() =>
+ // {
+ // Main.IndexPrograms();
+ // });
+ // }
+ //}
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/cmd.png b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/cmd.png
new file mode 100644
index 000000000..686583653
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/cmd.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/disable.png b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/disable.png
new file mode 100644
index 000000000..e9d4976bc
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/disable.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/folder.png b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/folder.png
new file mode 100644
index 000000000..569fa7049
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/folder.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/program.png b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/program.png
new file mode 100644
index 000000000..e4c789689
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/program.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/user.png b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/user.png
new file mode 100644
index 000000000..2d45c1ee9
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Program/Images/user.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/de.xaml
new file mode 100644
index 000000000..cfcb43089
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/de.xaml
@@ -0,0 +1,37 @@
+
+
+
+ Löschen
+ Bearbeiten
+ Hinzufügen
+ Speicherort
+ Indexiere Dateiendungen
+ erneut Indexieren
+ Indexieren
+ Indexierungs Startmenü
+ Indexierungsspeicher
+ Endungen
+ Maximale Tiefe
+
+ Verzeichnis:
+ Durchsuchen
+ Dateiendungen:
+ Maximale Suchtiefe (-1 ist unlimitiert):
+
+ Bitte wähle eine Programmquelle
+
+ Aktualisieren
+ Wox indexiert nur Datien mit folgenden Endungen:
+ (Jede Endung muss durch ein ; getrennt werden)
+ Dateiendungen wurden erfolgreich aktualisiert
+ Dateiendungen dürfen nicht leer sein
+
+ Als Administrator ausführen
+ Enthaltenden Ordner öffnen
+
+ Programm
+ Suche Programme mit Wox
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/en.xaml
new file mode 100644
index 000000000..b7202d590
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/en.xaml
@@ -0,0 +1,48 @@
+
+
+
+ Delete
+ Edit
+ Add
+ Disable
+ Location
+ All Programs
+ File Suffixes
+ Reindex
+ Indexing
+ Index Start Menu
+ Index Registry
+ Suffixes
+ Max Depth
+
+ Directory:
+ Browse
+ File Suffixes:
+ Maximum Search Depth (-1 is unlimited):
+
+ Please select a program source
+ Are you sure you want to delete the selected program sources?
+
+ Update
+ Wox will only index files that end with the following suffixes:
+ (Each suffix should split by ';' )
+ Successfully updated file suffixes
+ File suffixes can't be empty
+
+ Run As Different User
+ Run As Administrator
+ Open containing folder
+ Disable this program from displaying
+
+ Program
+ Search programs in Wox
+
+ Invalid Path
+
+
+ Success
+ Successfully disabled this program from displaying in your query
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/ja.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/ja.xaml
new file mode 100644
index 000000000..07630a1cd
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/ja.xaml
@@ -0,0 +1,38 @@
+
+
+
+ Delete
+ Edit
+ Add
+ Location
+ Index file suffixes
+ Reindex
+ Indexing
+ Index Start Menu
+ Index Registry
+ Suffixes
+ Max Depth
+
+ Directory:
+ Browse
+ File Suffixes:
+ Maximum Search Depth (-1 is unlimited):
+
+ Please select a program source
+ Are your sure to delete {0}?
+
+ Update
+ Wox will only index files that end with following suffixes:
+ (Each suffix should split by ;)
+ Sucessfully update file suffixes
+ File suffixes can't be empty
+
+ Run As Administrator
+ Open containing folder
+
+ Program
+ Search programs in Wox
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/pl.xaml
new file mode 100644
index 000000000..bb82ee8d2
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/pl.xaml
@@ -0,0 +1,37 @@
+
+
+
+ Usuń
+ Edytuj
+ Dodaj
+ Lokalizacja
+ Rozszerzenia indeksowanych plików
+ Re-indeksuj
+ Indeksowanie
+ Indeksuj Menu Start
+ Indeksuj rejestr
+ Rozszerzenia
+ Maksymalna głębokość
+
+ Katalog:
+ Przeglądaj
+ Rozszerzenia plików:
+ Maksymalna głębokość wyszukiwania (-1 to nieograniczona):
+
+ Musisz wybrać katalog programu
+
+ Aktualizuj
+ Wox będzie indeksował tylko te pliki z podanymi rozszerzeniami:
+ (Każde rozszerzenie musi być oddzielone ;)
+ Pomyślnie zaktualizowano rozszerzenia plików
+ Musisz podać rozszerzenia plików
+
+ Uruchom jako administrator
+ Otwórz folder zawierający
+
+ Programy
+ Szukaj i uruchamiaj programy z poziomu Woxa
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/tr.xaml
new file mode 100644
index 000000000..ef01e29fd
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/tr.xaml
@@ -0,0 +1,39 @@
+
+
+
+ Sil
+ Düzenle
+ Ekle
+ Konum
+ İndekslenecek Uzantılar
+ Yeniden İndeksle
+ İndeksleniyor
+ Başlat Menüsünü İndeksle
+ Registry'i İndeksle
+ Uzantılar
+ Derinlik
+
+ Dizin:
+ Gözat
+ Dosya Uzantıları:
+ Maksimum Arama Derinliği (Limitsiz için -1):
+
+ İşlem yapmak istediğiniz klasörü seçin.
+
+ Güncelle
+ Wox yalnızca aşağıdaki uzantılara sahip dosyaları indeksleyecektir:
+ (Her uzantıyı ; işareti ile ayırın)
+ Dosya uzantıları başarıyla güncellendi
+ Dosya uzantıları boş olamaz
+
+ Yönetici Olarak Çalıştır
+ İçeren Klasörü Aç
+
+ Program
+ Programları Wox'tan arayın
+
+ Geçersiz Konum
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/zh-cn.xaml
new file mode 100644
index 000000000..f41193095
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/zh-cn.xaml
@@ -0,0 +1,39 @@
+
+
+
+ 删除
+ 编辑
+ 增加
+ 位置
+ 索引文件后缀
+ 重新索引
+ 索引中
+ 索引开始菜单
+ 索引注册表
+ 后缀
+ 最大深度
+
+ 目录
+ 浏览
+ 文件后缀
+ 最大搜索深度(-1是无限的):
+
+ 请先选择一项
+
+ 更新
+ Wox仅索引下列后缀的文件:
+ (每个后缀以英文状态下的分号分隔)
+ 成功更新索引文件后缀
+ 文件后缀不能为空
+
+ 以管理员身份运行
+ 打开所属文件夹
+
+ 程序
+ 在Wox中搜索程序
+
+ 无效路径
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/zh-tw.xaml
new file mode 100644
index 000000000..d3c0f18cc
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Languages/zh-tw.xaml
@@ -0,0 +1,36 @@
+
+
+
+ 刪除
+ 編輯
+ 新增
+ 路徑
+ 索引副檔名清單
+ 重新建立索引
+ 正在建立索引
+ 替開始選單建立索引
+ 替登錄檔建立索引
+ 副檔名
+ 最大深度
+
+ 目錄
+ 瀏覽
+ 副檔名
+ 最大搜尋深度(-1是無限的):
+
+ 請先選擇一項
+
+ 更新
+ Wox 僅索引下列副檔名的檔案:
+ (每個副檔名以英文的分號分隔)
+ 成功更新索引副檔名清單
+ 副檔名不能為空
+
+ 以系統管理員身分執行
+ 開啟檔案位置
+
+ 程式
+ 在 Wox 中搜尋程式
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/LocationConverter.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/LocationConverter.cs
new file mode 100644
index 000000000..bd28bc944
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/LocationConverter.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+namespace Wox.Plugin.Program
+{
+ public class LocationConverter : MarkupExtension, IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return string.Empty;
+ }
+ else
+ {
+ return text;
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ return this;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Logger/ProgramLogger.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Logger/ProgramLogger.cs
new file mode 100644
index 000000000..587aba685
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Logger/ProgramLogger.cs
@@ -0,0 +1,139 @@
+using NLog;
+using NLog.Config;
+using NLog.Targets;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Security;
+using Wox.Infrastructure;
+
+namespace Wox.Plugin.Program.Logger
+{
+ ///
+ /// The Program plugin has seen many issues recorded in the Wox repo related to various loading of Windows programs.
+ /// This is a dedicated logger for this Program plugin with the aim to output a more friendlier message and clearer
+ /// log that will allow debugging to be quicker and easier.
+ ///
+ internal static class ProgramLogger
+ {
+ public const string DirectoryName = "Logs";
+
+ static ProgramLogger()
+ {
+ var path = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version);
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ var configuration = new LoggingConfiguration();
+ var target = new FileTarget();
+ configuration.AddTarget("file", target);
+ target.FileName = path.Replace(@"\", "/") + "/${shortdate}.txt";
+#if DEBUG
+ var rule = new LoggingRule("*", LogLevel.Debug, target);
+#else
+ var rule = new LoggingRule("*", LogLevel.Error, target);
+#endif
+ configuration.LoggingRules.Add(rule);
+ LogManager.Configuration = configuration;
+ }
+
+ ///
+ /// Logs an exception
+ ///
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ internal static void LogException(string classname, string callingMethodName, string loadingProgramPath,
+ string interpretationMessage, Exception e)
+ {
+ Debug.WriteLine($"ERROR{classname}|{callingMethodName}|{loadingProgramPath}|{interpretationMessage}");
+
+ var logger = LogManager.GetLogger("");
+
+ var innerExceptionNumber = 1;
+
+ var possibleResolution = "Not yet known";
+ var errorStatus = "UNKNOWN";
+
+ logger.Error("------------- BEGIN Wox.Plugin.Program exception -------------");
+
+ do
+ {
+ if (IsKnownWinProgramError(e, callingMethodName) || IsKnownUWPProgramError(e, callingMethodName))
+ {
+ possibleResolution = "Can be ignored and Wox should still continue, however the program may not be loaded";
+ errorStatus = "KNOWN";
+ }
+
+ var calledMethod = e.TargetSite != null ? e.TargetSite.ToString() : e.StackTrace;
+
+ calledMethod = string.IsNullOrEmpty(calledMethod) ? "Not available" : calledMethod;
+
+ logger.Error($"\nException full name: {e.GetType().FullName}"
+ + $"\nError status: {errorStatus}"
+ + $"\nClass name: {classname}"
+ + $"\nCalling method: {callingMethodName}"
+ + $"\nProgram path: {loadingProgramPath}"
+ + $"\nInnerException number: {innerExceptionNumber}"
+ + $"\nException message: {e.Message}"
+ + $"\nException error type: HResult {e.HResult}"
+ + $"\nException thrown in called method: {calledMethod}"
+ + $"\nPossible interpretation of the error: {interpretationMessage}"
+ + $"\nPossible resolution: {possibleResolution}");
+
+ innerExceptionNumber++;
+ e = e.InnerException;
+ } while (e != null);
+
+ logger.Error("------------- END Wox.Plugin.Program exception -------------");
+ }
+
+ ///
+ /// Please follow exception format: |class name|calling method name|loading program path|user friendly message that explains the error
+ /// => Example: |Win32|LnkProgram|c:\..\chrome.exe|Permission denied on directory, but Wox should continue
+ ///
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ internal static void LogException(string message, Exception e)
+ {
+ //Index 0 is always empty.
+ var parts = message.Split('|');
+ if (parts.Length < 4)
+ {
+ var logger = LogManager.GetLogger("");
+ logger.Error(e, $"fail to log exception in program logger, parts length is too small: {parts.Length}, message: {message}");
+ }
+
+ var classname = parts[1];
+ var callingMethodName = parts[2];
+ var loadingProgramPath = parts[3];
+ var interpretationMessage = parts[4];
+
+ LogException(classname, callingMethodName, loadingProgramPath, interpretationMessage, e);
+ }
+
+ private static bool IsKnownWinProgramError(Exception e, string callingMethodName)
+ {
+ if (e.TargetSite?.Name == "GetDescription" && callingMethodName == "LnkProgram")
+ return true;
+
+ if (e is SecurityException || e is UnauthorizedAccessException || e is DirectoryNotFoundException)
+ return true;
+
+ return false;
+ }
+
+ private static bool IsKnownUWPProgramError(Exception e, string callingMethodName)
+ {
+ if (((e.HResult == -2147024774 || e.HResult == -2147009769) && callingMethodName == "ResourceFromPri")
+ || (e.HResult == -2147024894 && (callingMethodName == "LogoPathFromUri" || callingMethodName == "ImageFromPath"))
+ || (e.HResult == -2147024864 && callingMethodName == "InitializeAppInfo"))
+ return true;
+
+ if (callingMethodName == "XmlNamespaces")
+ return true;
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Main.cs
new file mode 100644
index 000000000..e99298c78
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Main.cs
@@ -0,0 +1,214 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using Wox.Infrastructure.Logger;
+using Wox.Infrastructure.Storage;
+using Wox.Plugin.Program.Programs;
+using Wox.Plugin.Program.Views;
+using Stopwatch = Wox.Infrastructure.Stopwatch;
+
+namespace Wox.Plugin.Program
+{
+ public class Main : ISettingProvider, IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable
+ {
+ private static readonly object IndexLock = new object();
+ internal static Win32[] _win32s { get; set; }
+ internal static UWP.Application[] _uwps { get; set; }
+ internal static Settings _settings { get; set; }
+
+ private static bool IsStartupIndexProgramsRequired => _settings.LastIndexTime.AddDays(3) < DateTime.Today;
+
+ private static PluginInitContext _context;
+
+ private static BinaryStorage _win32Storage;
+ private static BinaryStorage _uwpStorage;
+ private readonly PluginJsonStorage _settingsStorage;
+
+ public Main()
+ {
+ _settingsStorage = new PluginJsonStorage();
+ _settings = _settingsStorage.Load();
+
+ Stopwatch.Normal("|Wox.Plugin.Program.Main|Preload programs cost", () =>
+ {
+ _win32Storage = new BinaryStorage("Win32");
+ _win32s = _win32Storage.TryLoad(new Win32[] { });
+ _uwpStorage = new BinaryStorage("UWP");
+ _uwps = _uwpStorage.TryLoad(new UWP.Application[] { });
+ });
+ Log.Info($"|Wox.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
+ Log.Info($"|Wox.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
+
+ var a = Task.Run(() =>
+ {
+ if (IsStartupIndexProgramsRequired || !_win32s.Any())
+ Stopwatch.Normal("|Wox.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
+ });
+
+ var b = Task.Run(() =>
+ {
+ if (IsStartupIndexProgramsRequired || !_uwps.Any())
+ Stopwatch.Normal("|Wox.Plugin.Program.Main|Win32Program index cost", IndexUWPPrograms);
+ });
+
+ Task.WaitAll(a, b);
+
+ _settings.LastIndexTime = DateTime.Today;
+ }
+
+ public void Save()
+ {
+ _settingsStorage.Save();
+ _win32Storage.Save(_win32s);
+ _uwpStorage.Save(_uwps);
+ }
+
+ public List Query(Query query)
+ {
+ Win32[] win32;
+ UWP.Application[] uwps;
+
+ lock (IndexLock)
+ { // just take the reference inside the lock to eliminate query time issues.
+ win32 = _win32s;
+ uwps = _uwps;
+ }
+
+ var results1 = win32.AsParallel()
+ .Where(p => p.Enabled)
+ .Select(p => p.Result(query.Search, _context.API));
+
+ var results2 = uwps.AsParallel()
+ .Where(p => p.Enabled)
+ .Select(p => p.Result(query.Search, _context.API));
+
+ var result = results1.Concat(results2).Where(r => r != null && r.Score > 0).ToList();
+ return result;
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ _context = context;
+ }
+
+ public static void IndexWin32Programs()
+ {
+ var win32S = Win32.All(_settings);
+ lock (IndexLock)
+ {
+ _win32s = win32S;
+ }
+ }
+
+ public static void IndexUWPPrograms()
+ {
+ var windows10 = new Version(10, 0);
+ var support = Environment.OSVersion.Version.Major >= windows10.Major;
+
+ var applications = support ? UWP.All() : new UWP.Application[] { };
+ lock (IndexLock)
+ {
+ _uwps = applications;
+ }
+ }
+
+ public static void IndexPrograms()
+ {
+ var t1 = Task.Run(() => IndexWin32Programs());
+
+ var t2 = Task.Run(() => IndexUWPPrograms());
+
+ Task.WaitAll(t1, t2);
+
+ _settings.LastIndexTime = DateTime.Today;
+ }
+
+ public Control CreateSettingPanel()
+ {
+ return new ProgramSetting(_context, _settings, _win32s, _uwps);
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return _context.API.GetTranslation("wox_plugin_program_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return _context.API.GetTranslation("wox_plugin_program_plugin_description");
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ var menuOptions = new List();
+ var program = selectedResult.ContextData as IProgram;
+ if (program != null)
+ {
+ menuOptions = program.ContextMenus(_context.API);
+ }
+
+ menuOptions.Add(
+ new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_program_disable_program"),
+ Action = c =>
+ {
+ DisableProgram(program);
+ _context.API.ShowMsg(_context.API.GetTranslation("wox_plugin_program_disable_dlgtitle_success"),
+ _context.API.GetTranslation("wox_plugin_program_disable_dlgtitle_success_message"));
+ return false;
+ },
+ IcoPath = "Images/disable.png"
+ }
+ );
+
+ return menuOptions;
+ }
+
+ private void DisableProgram(IProgram programToDelete)
+ {
+ if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
+ return;
+
+ if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
+ _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = false;
+
+ if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
+ _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier).FirstOrDefault().Enabled = false;
+
+ _settings.DisabledProgramSources
+ .Add(
+ new Settings.DisabledProgramSource
+ {
+ Name = programToDelete.Name,
+ Location = programToDelete.Location,
+ UniqueIdentifier = programToDelete.UniqueIdentifier,
+ Enabled = false
+ }
+ );
+ }
+
+ public static void StartProcess(Func runProcess, ProcessStartInfo info)
+ {
+ bool hide;
+ try
+ {
+ runProcess(info);
+ }
+ catch (Exception)
+ {
+ var name = "Plugin: Program";
+ var message = $"Unable to start: {info.FileName}";
+ _context.API.ShowMsg(name, message, string.Empty);
+ }
+ }
+
+ public void ReloadData()
+ {
+ IndexPrograms();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/ProgramSuffixes.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/ProgramSuffixes.xaml
new file mode 100644
index 000000000..03d399a19
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/ProgramSuffixes.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/ProgramSuffixes.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/ProgramSuffixes.xaml.cs
new file mode 100644
index 000000000..ef5783b46
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/ProgramSuffixes.xaml.cs
@@ -0,0 +1,37 @@
+using System.Windows;
+
+namespace Wox.Plugin.Program
+{
+ ///
+ /// ProgramSuffixes.xaml 的交互逻辑
+ ///
+ public partial class ProgramSuffixes
+ {
+ private PluginInitContext context;
+ private Settings _settings;
+
+ public ProgramSuffixes(PluginInitContext context, Settings settings)
+ {
+ this.context = context;
+ InitializeComponent();
+ _settings = settings;
+ tbSuffixes.Text = string.Join(Settings.SuffixSeperator.ToString(), _settings.ProgramSuffixes);
+ }
+
+ private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (string.IsNullOrEmpty(tbSuffixes.Text))
+ {
+ string warning = context.API.GetTranslation("wox_plugin_program_suffixes_cannot_empty");
+ MessageBox.Show(warning);
+ return;
+ }
+
+ _settings.ProgramSuffixes = tbSuffixes.Text.Split(Settings.SuffixSeperator);
+ string msg = context.API.GetTranslation("wox_plugin_program_update_file_suffixes");
+ MessageBox.Show(msg);
+
+ DialogResult = true;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/FileSystemFolderSourceShallow.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/FileSystemFolderSourceShallow.cs
new file mode 100644
index 000000000..fe4a5761b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/FileSystemFolderSourceShallow.cs
@@ -0,0 +1,152 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Wox.Infrastructure.Storage.UserSettings;
+
+namespace Wox.Plugin.SystemPlugins.Program.ProgramSources {
+ //TODO: Consider Removing
+
+ ///
+ ///
+ ///
+ public class FileSystemFolderSourceShallow : FileSystemProgramSource {
+ //private static Dictionary parentDirectories = new Dictionary();
+
+
+ public FileSystemFolderSourceShallow(string baseDirectory)
+ : base(baseDirectory) { }
+
+ public FileSystemFolderSourceShallow(ProgramSource source)
+ : base(source) { }
+
+ public override List LoadPrograms() {
+ List list = new List();
+
+ foreach (var Folder in Directory.GetDirectories(BaseDirectory)) {
+ list.Add(CreateEntry(Folder));
+ }
+
+
+ foreach (string file in Directory.GetFiles(base.BaseDirectory)) {
+ if (Suffixes.Any(o => file.EndsWith("." + o))) {
+ list.Add(CreateEntry(file));
+ }
+ }
+
+ return list;
+ }
+
+
+ public override string ToString() {
+ return typeof(UserStartMenuProgramSource).Name;
+ }
+
+
+ /*
+ public class FolderSource : IProgramSource {
+ private PluginInitContext context;
+ public string Location { get; set; }
+ public int BonusPoints { get; set; }
+
+ public FolderSource(string Location) {
+ this.Location = Location;
+ }
+
+ public List LoadPrograms() {
+ List results = new List();
+
+ if (Directory.Exists(Location)) {
+ // show all child directory
+ if (Location.EndsWith("\\") || Location.EndsWith("/")) {
+ var dirInfo = new DirectoryInfo(Location);
+ var dirs = dirInfo.GetDirectories();
+
+ var parentDirKey = Location.TrimEnd('\\', '/');
+ if (!parentDirectories.ContainsKey(parentDirKey))
+ parentDirectories.Add(parentDirKey, dirs);
+
+ foreach (var dir in dirs) {
+ if ((dir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+ continue;
+
+ var dirPath = dir.FullName;
+ Result result = new Result {
+ Title = dir.Name,
+ IcoPath = "Images/folder.png",
+ Action = (c) => {
+ context.ChangeQuery(dirPath);
+ return false;
+ }
+ };
+ results.Add(result);
+ }
+
+ if (results.Count == 0) {
+ Result result = new Result {
+ Title = "Open this directory",
+ SubTitle = "No files in this directory",
+ IcoPath = "Images/folder.png",
+ Action = (c) => {
+ Process.Start(Location);
+ return true;
+ }
+ };
+ results.Add(result);
+ }
+ }
+ else {
+ Result result = new Result {
+ Title = "Open this directory",
+ SubTitle = string.Format("path: {0}", Location),
+ Score = 50,
+ IcoPath = "Images/folder.png",
+ Action = (c) => {
+ Process.Start(Location);
+ return true;
+ }
+ };
+ results.Add(result);
+ }
+
+ }
+
+ // change to search in current directory
+ var parentDir = Path.GetDirectoryName(Location);
+ if (!string.IsNullOrEmpty(parentDir) && results.Count == 0) {
+ parentDir = parentDir.TrimEnd('\\', '/');
+ if (parentDirectories.ContainsKey(parentDir)) {
+
+ var dirs = parentDirectories[parentDir];
+ var queryFileName = Path.GetFileName(Location).ToLower();
+ var fuzzy = FuzzyMatcher.Create(queryFileName);
+ foreach (var dir in dirs) {
+ if ((dir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+ continue;
+
+ var matchResult = fuzzy.Evaluate(dir.Name);
+ if (!matchResult.Success)
+ continue;
+
+ var dirPath = dir.FullName;
+ Result result = new Result {
+ Title = dir.Name,
+ IcoPath = "Images/folder.png",
+ Score = matchResult.Score,
+ Action = (c) => {
+ context.ChangeQuery(dirPath);
+ return false;
+ }
+ };
+ results.Add(result);
+ }
+ }
+ }
+
+
+ throw new Exception("Debug this!");
+ }
+
+ }
+ */
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/IProgram.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/IProgram.cs
new file mode 100644
index 000000000..f51ab9610
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/IProgram.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace Wox.Plugin.Program.Programs
+{
+ public interface IProgram
+ {
+ List ContextMenus(IPublicAPI api);
+ Result Result(string query, IPublicAPI api);
+ string UniqueIdentifier { get; set; }
+ string Name { get; }
+ string Location { get; }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/UWP.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/UWP.cs
new file mode 100644
index 000000000..35917cb31
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/UWP.cs
@@ -0,0 +1,634 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Xml.Linq;
+using Windows.ApplicationModel;
+using Windows.Management.Deployment;
+using AppxPackaing;
+using Shell;
+using Wox.Infrastructure;
+using Wox.Plugin.Program.Logger;
+using IStream = AppxPackaing.IStream;
+using Rect = System.Windows.Rect;
+
+namespace Wox.Plugin.Program.Programs
+{
+ [Serializable]
+ public class UWP
+ {
+ public string Name { get; }
+ public string FullName { get; }
+ public string FamilyName { get; }
+ public string Location { get; set; }
+
+ public Application[] Apps { get; set; }
+
+ public PackageVersion Version { get; set; }
+
+ public UWP(Package package)
+ {
+ Location = package.InstalledLocation.Path;
+ Name = package.Id.Name;
+ FullName = package.Id.FullName;
+ FamilyName = package.Id.FamilyName;
+ InitializeAppInfo();
+ Apps = Apps.Where(a =>
+ {
+ var valid =
+ !string.IsNullOrEmpty(a.UserModelId) &&
+ !string.IsNullOrEmpty(a.DisplayName);
+ return valid;
+ }).ToArray();
+ }
+
+ private void InitializeAppInfo()
+ {
+ var path = Path.Combine(Location, "AppxManifest.xml");
+
+ var namespaces = XmlNamespaces(path);
+ InitPackageVersion(namespaces);
+
+ var appxFactory = new AppxFactory();
+ IStream stream;
+ const uint noAttribute = 0x80;
+ const Stgm exclusiveRead = Stgm.Read | Stgm.ShareExclusive;
+ var hResult = SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out stream);
+
+ if (hResult == Hresult.Ok)
+ {
+ var reader = appxFactory.CreateManifestReader(stream);
+ var manifestApps = reader.GetApplications();
+ var apps = new List();
+ while (manifestApps.GetHasCurrent() != 0)
+ {
+ var manifestApp = manifestApps.GetCurrent();
+ var appListEntry = manifestApp.GetStringValue("AppListEntry");
+ if (appListEntry != "none")
+ {
+ var app = new Application(manifestApp, this);
+ apps.Add(app);
+ }
+ manifestApps.MoveNext();
+ }
+ Apps = apps.Where(a => a.AppListEntry != "none").ToArray();
+ }
+ else
+ {
+ var e = Marshal.GetExceptionForHR((int)hResult);
+ ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" +
+ "|Error caused while trying to get the details of the UWP program", e);
+
+ Apps = new List().ToArray();
+ }
+ }
+
+
+
+ /// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
+ private string[] XmlNamespaces(string path)
+ {
+ XDocument z = XDocument.Load(path);
+ if (z.Root != null)
+ {
+ var namespaces = z.Root.Attributes().
+ Where(a => a.IsNamespaceDeclaration).
+ GroupBy(
+ a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName,
+ a => XNamespace.Get(a.Value)
+ ).Select(
+ g => g.First().ToString()
+ ).ToArray();
+ return namespaces;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" +
+ $"|Error occured while trying to get the XML from {path}", new ArgumentNullException());
+
+ return new string[] { };
+ }
+ }
+
+ private void InitPackageVersion(string[] namespaces)
+ {
+ var versionFromNamespace = new Dictionary
+ {
+ {"http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10},
+ {"http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81},
+ {"http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8},
+ };
+
+ foreach (var n in versionFromNamespace.Keys)
+ {
+ if (namespaces.Contains(n))
+ {
+ Version = versionFromNamespace[n];
+ return;
+ }
+ }
+
+ ProgramLogger.LogException($"|UWP|XmlNamespaces|{Location}" +
+ "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version "
+ + $"{FullName} from location {Location} is returned.", new FormatException());
+
+ Version = PackageVersion.Unknown;
+ }
+
+ public static Application[] All()
+ {
+ var windows10 = new Version(10, 0);
+ var support = Environment.OSVersion.Version.Major >= windows10.Major;
+ if (support)
+ {
+ var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
+ {
+ UWP u;
+ try
+ {
+ u = new UWP(p);
+ }
+#if !DEBUG
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occured and "
+ + $"unable to convert Package to UWP for {p.Id.FullName}", e);
+ return new Application[] { };
+ }
+#endif
+#if DEBUG //make developer aware and implement handling
+ catch
+ {
+ throw;
+ }
+#endif
+ return u.Apps;
+ }).ToArray();
+
+ var updatedListWithoutDisabledApps = applications
+ .Where(t1 => !Main._settings.DisabledProgramSources
+ .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
+ .Select(x => x);
+
+ return updatedListWithoutDisabledApps.ToArray();
+ }
+ else
+ {
+ return new Application[] { };
+ }
+ }
+
+ private static IEnumerable CurrentUserPackages()
+ {
+ var u = WindowsIdentity.GetCurrent().User;
+
+ if (u != null)
+ {
+ var id = u.Value;
+ var m = new PackageManager();
+ var ps = m.FindPackagesForUser(id);
+ ps = ps.Where(p =>
+ {
+ bool valid;
+ try
+ {
+ var f = p.IsFramework;
+ var d = p.IsDevelopmentMode;
+ var path = p.InstalledLocation.Path;
+ valid = !f && !d && !string.IsNullOrEmpty(path);
+ }
+ catch (Exception e)
+ {
+ ProgramLogger.LogException("UWP" ,"CurrentUserPackages", $"id","An unexpected error occured and "
+ + $"unable to verify if package is valid", e);
+ return false;
+ }
+
+
+ return valid;
+ });
+ return ps;
+ }
+ else
+ {
+ return new Package[] { };
+ }
+ }
+
+ public override string ToString()
+ {
+ return FamilyName;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is UWP uwp)
+ {
+ return FamilyName.Equals(uwp.FamilyName);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return FamilyName.GetHashCode();
+ }
+
+ [Serializable]
+ public class Application : IProgram
+ {
+ public string AppListEntry { get; set; }
+ public string UniqueIdentifier { get; set; }
+ public string DisplayName { get; set; }
+ public string Description { get; set; }
+ public string UserModelId { get; set; }
+ public string BackgroundColor { get; set; }
+
+ public string Name => DisplayName;
+ public string Location => Package.Location;
+
+ public bool Enabled { get; set; }
+
+ public string LogoUri { get; set; }
+ public string LogoPath { get; set; }
+ public UWP Package { get; set; }
+
+ private int Score(string query)
+ {
+ var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName);
+ var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
+ var score = new[] { displayNameMatch.Score, descriptionMatch.Score }.Max();
+ return score;
+ }
+
+ public Result Result(string query, IPublicAPI api)
+ {
+ var score = Score(query);
+ if (score <= 0)
+ { // no need to create result if score is 0
+ return null;
+ }
+
+ var result = new Result
+ {
+ SubTitle = Package.Location,
+ Icon = Logo,
+ Score = score,
+ ContextData = this,
+ Action = e =>
+ {
+ Launch(api);
+ return true;
+ }
+ };
+
+ if (Description.Length >= DisplayName.Length &&
+ Description.Substring(0, DisplayName.Length) == DisplayName)
+ {
+ result.Title = Description;
+ result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData;
+ }
+ else if (!string.IsNullOrEmpty(Description))
+ {
+ var title = $"{DisplayName}: {Description}";
+ result.Title = title;
+ result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData;
+ }
+ else
+ {
+ result.Title = DisplayName;
+ result.TitleHighlightData = StringMatcher.FuzzySearch(query, DisplayName).MatchData;
+ }
+ return result;
+ }
+
+ public List ContextMenus(IPublicAPI api)
+ {
+ var contextMenus = new List
+ {
+ new Result
+ {
+ Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
+
+ Action = _ =>
+ {
+ Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location));
+
+ return true;
+ },
+
+ IcoPath = "Images/folder.png"
+ }
+ };
+ return contextMenus;
+ }
+
+ private async void Launch(IPublicAPI api)
+ {
+ var appManager = new ApplicationActivationManager();
+ uint unusedPid;
+ const string noArgs = "";
+ const ACTIVATEOPTIONS noFlags = ACTIVATEOPTIONS.AO_NONE;
+ await Task.Run(() =>
+ {
+ try
+ {
+ appManager.ActivateApplication(UserModelId, noArgs, noFlags, out unusedPid);
+ }
+ catch (Exception)
+ {
+ var name = "Plugin: Program";
+ var message = $"Can't start UWP: {DisplayName}";
+ api.ShowMsg(name, message, string.Empty);
+ }
+ });
+ }
+
+ public Application(IAppxManifestApplication manifestApp, UWP package)
+ {
+ UserModelId = manifestApp.GetAppUserModelId();
+ UniqueIdentifier = manifestApp.GetAppUserModelId();
+ DisplayName = manifestApp.GetStringValue("DisplayName");
+ Description = manifestApp.GetStringValue("Description");
+ BackgroundColor = manifestApp.GetStringValue("BackgroundColor");
+ Package = package;
+
+ DisplayName = ResourceFromPri(package.FullName, DisplayName);
+ Description = ResourceFromPri(package.FullName, Description);
+ LogoUri = LogoUriFromManifest(manifestApp);
+ LogoPath = LogoPathFromUri(LogoUri);
+
+ Enabled = true;
+ }
+
+ internal string ResourceFromPri(string packageFullName, string resourceReference)
+ {
+ const string prefix = "ms-resource:";
+ if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix))
+ {
+ // magic comes from @talynone
+ // https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
+ string key = resourceReference.Substring(prefix.Length);
+ string parsed;
+ if (key.StartsWith("//"))
+ {
+ parsed = prefix + key;
+ }
+ else if (key.StartsWith("/"))
+ {
+ parsed = prefix + "//" + key;
+ }
+ else
+ {
+ parsed = prefix + "///resources/" + key;
+ }
+
+ var outBuffer = new StringBuilder(128);
+ string source = $"@{{{packageFullName}? {parsed}}}";
+ var capacity = (uint)outBuffer.Capacity;
+ var hResult = SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
+ if (hResult == Hresult.Ok)
+ {
+ var loaded = outBuffer.ToString();
+ if (!string.IsNullOrEmpty(loaded))
+ {
+ return loaded;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result "
+ + $"pri {source} in uwp location {Package.Location}", new NullReferenceException());
+ return string.Empty;
+ }
+ }
+ else
+ {
+ // https://github.com/Wox-launcher/Wox/issues/964
+ // known hresult 2147942522:
+ // 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
+ // for
+ // Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
+ // Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
+ var e = Marshal.GetExceptionForHR((int)hResult);
+ ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Load pri failed {source} with HResult {hResult} and location {Package.Location}", e);
+ return string.Empty;
+ }
+ }
+ else
+ {
+ return resourceReference;
+ }
+ }
+
+
+ internal string LogoUriFromManifest(IAppxManifestApplication app)
+ {
+ var logoKeyFromVersion = new Dictionary
+ {
+ { PackageVersion.Windows10, "Square44x44Logo" },
+ { PackageVersion.Windows81, "Square30x30Logo" },
+ { PackageVersion.Windows8, "SmallLogo" },
+ };
+ if (logoKeyFromVersion.ContainsKey(Package.Version))
+ {
+ var key = logoKeyFromVersion[Package.Version];
+ var logoUri = app.GetStringValue(key);
+ return logoUri;
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
+
+ internal string LogoPathFromUri(string uri)
+ {
+ // all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
+ // windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
+ // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
+ // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
+
+ string path;
+ if (uri.Contains("\\"))
+ {
+ path = Path.Combine(Package.Location, uri);
+ }
+ else
+ {
+ // for C:\Windows\MiracastView etc
+ path = Path.Combine(Package.Location, "Assets", uri);
+ }
+
+ var extension = Path.GetExtension(path);
+ if (extension != null)
+ {
+ var end = path.Length - extension.Length;
+ var prefix = path.Substring(0, end);
+ var paths = new List { path };
+
+ var scaleFactors = new Dictionary>
+ {
+ // scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
+ { PackageVersion.Windows10, new List { 100, 125, 150, 200, 400 } },
+ { PackageVersion.Windows81, new List { 100, 120, 140, 160, 180 } },
+ { PackageVersion.Windows8, new List { 100 } }
+ };
+
+ if (scaleFactors.ContainsKey(Package.Version))
+ {
+ foreach (var factor in scaleFactors[Package.Version])
+ {
+ paths.Add($"{prefix}.scale-{factor}{extension}");
+ }
+ }
+
+ var selected = paths.FirstOrDefault(File.Exists);
+ if (!string.IsNullOrEmpty(selected))
+ {
+ return selected;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
+ $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
+ return string.Empty;
+ }
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
+ $"|Unable to find extension from {uri} for {UserModelId} " +
+ $"in package location {Package.Location}", new FileNotFoundException());
+ return string.Empty;
+ }
+ }
+
+
+ public ImageSource Logo()
+ {
+ var logo = ImageFromPath(LogoPath);
+ var plated = PlatedImage(logo);
+
+ // todo magic! temp fix for cross thread object
+ plated.Freeze();
+ return plated;
+ }
+
+
+ private BitmapImage ImageFromPath(string path)
+ {
+ if (File.Exists(path))
+ {
+ var image = new BitmapImage(new Uri(path));
+ return image;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|ImageFromPath|{path}" +
+ $"|Unable to get logo for {UserModelId} from {path} and" +
+ $" located in {Package.Location}", new FileNotFoundException());
+ return new BitmapImage(new Uri(Constant.ErrorIcon));
+ }
+ }
+
+ private ImageSource PlatedImage(BitmapImage image)
+ {
+ if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent")
+ {
+ var width = image.Width;
+ var height = image.Height;
+ var x = 0;
+ var y = 0;
+
+ var group = new DrawingGroup();
+
+ var converted = ColorConverter.ConvertFromString(BackgroundColor);
+ if (converted != null)
+ {
+ var color = (Color)converted;
+ var brush = new SolidColorBrush(color);
+ var pen = new Pen(brush, 1);
+ var backgroundArea = new Rect(0, 0, width, width);
+ var rectabgle = new RectangleGeometry(backgroundArea);
+ var rectDrawing = new GeometryDrawing(brush, pen, rectabgle);
+ group.Children.Add(rectDrawing);
+
+ var imageArea = new Rect(x, y, image.Width, image.Height);
+ var imageDrawing = new ImageDrawing(image, imageArea);
+ group.Children.Add(imageDrawing);
+
+ // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush
+ var visual = new DrawingVisual();
+ var context = visual.RenderOpen();
+ context.DrawDrawing(group);
+ context.Close();
+ const int dpiScale100 = 96;
+ var bitmap = new RenderTargetBitmap(
+ Convert.ToInt32(width), Convert.ToInt32(height),
+ dpiScale100, dpiScale100,
+ PixelFormats.Pbgra32
+ );
+ bitmap.Render(visual);
+ return bitmap;
+ }
+ else
+ {
+ ProgramLogger.LogException($"|UWP|PlatedImage|{Package.Location}" +
+ $"|Unable to convert background string {BackgroundColor} " +
+ $"to color for {Package.Location}", new InvalidOperationException());
+
+ return new BitmapImage(new Uri(Constant.ErrorIcon));
+ }
+ }
+ else
+ {
+ // todo use windows theme as background
+ return image;
+ }
+ }
+
+ public override string ToString()
+ {
+ return $"{DisplayName}: {Description}";
+ }
+ }
+
+ public enum PackageVersion
+ {
+ Windows10,
+ Windows81,
+ Windows8,
+ Unknown
+ }
+
+ [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);
+
+ [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
+ private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf,
+ IntPtr ppvReserved);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/Win32.cs
new file mode 100644
index 000000000..53ac2065c
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Programs/Win32.cs
@@ -0,0 +1,487 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Win32;
+using Shell;
+using Wox.Infrastructure;
+using Wox.Plugin.Program.Logger;
+using Wox.Plugin.SharedCommands;
+
+namespace Wox.Plugin.Program.Programs
+{
+ [Serializable]
+ public class Win32 : IProgram
+ {
+ public string Name { get; set; }
+ public string UniqueIdentifier { get; set; }
+ public string IcoPath { get; set; }
+ public string FullPath { get; set; }
+ public string ParentDirectory { get; set; }
+ public string ExecutableName { get; set; }
+ public string Description { get; set; }
+ public bool Valid { get; set; }
+ public bool Enabled { get; set; }
+ public string Location => ParentDirectory;
+
+ private const string ShortcutExtension = "lnk";
+ private const string ApplicationReferenceExtension = "appref-ms";
+ private const string ExeExtension = "exe";
+
+ private int Score(string query)
+ {
+ var nameMatch = StringMatcher.FuzzySearch(query, Name);
+ var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
+ var executableNameMatch = StringMatcher.FuzzySearch(query, ExecutableName);
+ var score = new[] { nameMatch.Score, descriptionMatch.Score, executableNameMatch.Score }.Max();
+ return score;
+ }
+
+
+ public Result Result(string query, IPublicAPI api)
+ {
+ var score = Score(query);
+ if (score <= 0)
+ { // no need to create result if this is zero
+ return null;
+ }
+
+ var result = new Result
+ {
+ SubTitle = FullPath,
+ IcoPath = IcoPath,
+ Score = score,
+ ContextData = this,
+ Action = e =>
+ {
+ var info = new ProcessStartInfo
+ {
+ FileName = FullPath,
+ WorkingDirectory = ParentDirectory
+ };
+
+ Main.StartProcess(Process.Start, info);
+
+ return true;
+ }
+ };
+
+ if (Description.Length >= Name.Length &&
+ Description.Substring(0, Name.Length) == Name)
+ {
+ result.Title = Description;
+ result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData;
+ }
+ else if (!string.IsNullOrEmpty(Description))
+ {
+ var title = $"{Name}: {Description}";
+ result.Title = title;
+ result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData;
+ }
+ else
+ {
+ result.Title = Name;
+ result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
+ }
+
+ return result;
+ }
+
+
+ public List ContextMenus(IPublicAPI api)
+ {
+ var contextMenus = new List
+ {
+ new Result
+ {
+ Title = api.GetTranslation("wox_plugin_program_run_as_different_user"),
+ Action = _ =>
+ {
+ var info = FullPath.SetProcessStartInfo(ParentDirectory);
+
+ Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info));
+
+ return true;
+ },
+ IcoPath = "Images/user.png"
+ },
+ new Result
+ {
+ Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
+ Action = _ =>
+ {
+ var info = new ProcessStartInfo
+ {
+ FileName = FullPath,
+ WorkingDirectory = ParentDirectory,
+ Verb = "runas"
+ };
+
+ Task.Run(() => Main.StartProcess(Process.Start, info));
+
+ return true;
+ },
+ IcoPath = "Images/cmd.png"
+ },
+ new Result
+ {
+ Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
+ Action = _ =>
+ {
+ Main.StartProcess(Process.Start, new ProcessStartInfo(ParentDirectory));
+
+ return true;
+ },
+ IcoPath = "Images/folder.png"
+ }
+ };
+ return contextMenus;
+ }
+
+
+
+ public override string ToString()
+ {
+ return ExecutableName;
+ }
+
+ private static Win32 Win32Program(string path)
+ {
+ try
+ {
+ var p = new Win32
+ {
+ Name = Path.GetFileNameWithoutExtension(path),
+ IcoPath = path,
+ FullPath = path,
+ UniqueIdentifier = path,
+ ParentDirectory = Directory.GetParent(path).FullName,
+ Description = string.Empty,
+ Valid = true,
+ Enabled = true
+ };
+ return p;
+ }
+ catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
+ {
+ ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
+ $"|Permission denied when trying to load the program from {path}", e);
+
+ return new Win32() { Valid = false, Enabled = false };
+ }
+ }
+
+ private static Win32 LnkProgram(string path)
+ {
+ var program = Win32Program(path);
+ try
+ {
+ var link = new ShellLink();
+ const uint STGM_READ = 0;
+ ((IPersistFile)link).Load(path, STGM_READ);
+ var hwnd = new _RemotableHandle();
+ link.Resolve(ref hwnd, 0);
+
+ const int MAX_PATH = 260;
+ StringBuilder buffer = new StringBuilder(MAX_PATH);
+
+ var data = new _WIN32_FIND_DATAW();
+ const uint SLGP_SHORTPATH = 1;
+ link.GetPath(buffer, buffer.Capacity, ref data, SLGP_SHORTPATH);
+ var target = buffer.ToString();
+ if (!string.IsNullOrEmpty(target))
+ {
+ var extension = Extension(target);
+ if (extension == ExeExtension && File.Exists(target))
+ {
+ buffer = new StringBuilder(MAX_PATH);
+ link.GetDescription(buffer, MAX_PATH);
+ var description = buffer.ToString();
+ if (!string.IsNullOrEmpty(description))
+ {
+ program.Description = description;
+ }
+ else
+ {
+ var info = FileVersionInfo.GetVersionInfo(target);
+ if (!string.IsNullOrEmpty(info.FileDescription))
+ {
+ program.Description = info.FileDescription;
+ }
+ }
+ }
+ }
+ return program;
+ }
+ catch (COMException e)
+ {
+ // C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception
+ ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
+ "|Error caused likely due to trying to get the description of the program", e);
+
+ program.Valid = false;
+ return program;
+ }
+#if !DEBUG //Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
+ catch (Exception e)
+ {
+ ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
+ "|An unexpected error occurred in the calling method LnkProgram", e);
+
+ program.Valid = false;
+ return program;
+ }
+#endif
+ }
+
+ private static Win32 ExeProgram(string path)
+ {
+ try
+ {
+ var program = Win32Program(path);
+ var info = FileVersionInfo.GetVersionInfo(path);
+ if (!string.IsNullOrEmpty(info.FileDescription))
+ {
+ program.Description = info.FileDescription;
+ }
+ return program;
+ }
+ catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
+ {
+ ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
+ $"|Permission denied when trying to load the program from {path}", e);
+
+ return new Win32() { Valid = false, Enabled = false };
+ }
+ }
+
+ private static IEnumerable ProgramPaths(string directory, string[] suffixes)
+ {
+ if (!Directory.Exists(directory))
+ return new string[] { };
+ var files = new List();
+ var folderQueue = new Queue();
+ folderQueue.Enqueue(directory);
+ do
+ {
+ var currentDirectory = folderQueue.Dequeue();
+ try
+ {
+ foreach (var suffix in suffixes)
+ {
+ try
+ {
+ files.AddRange(Directory.EnumerateFiles(currentDirectory, $"*.{suffix}", SearchOption.TopDirectoryOnly));
+ }
+ catch (DirectoryNotFoundException e)
+ {
+ ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" +
+ "|The directory trying to load the program from does not exist", e);
+ }
+ }
+ }
+ catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
+ {
+ ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" +
+ $"|Permission denied when trying to load programs from {currentDirectory}", e);
+ }
+
+ try
+ {
+ foreach (var childDirectory in Directory.EnumerateDirectories(currentDirectory, "*", SearchOption.TopDirectoryOnly))
+ {
+ folderQueue.Enqueue(childDirectory);
+ }
+ }
+ catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
+ {
+ ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" +
+ $"|Permission denied when trying to load programs from {currentDirectory}", e);
+ }
+ } while (folderQueue.Any());
+ return files;
+ }
+
+ private static string Extension(string path)
+ {
+ var extension = Path.GetExtension(path)?.ToLower();
+ if (!string.IsNullOrEmpty(extension))
+ {
+ return extension.Substring(1);
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
+
+ private static ParallelQuery UnregisteredPrograms(List sources, string[] suffixes)
+ {
+ var listToAdd = new List();
+ sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
+ .SelectMany(s => ProgramPaths(s.Location, suffixes))
+ .ToList()
+ .Where(t1 => !Main._settings.DisabledProgramSources.Any(x => t1 == x.UniqueIdentifier))
+ .ToList()
+ .ForEach(x => listToAdd.Add(x));
+
+ var paths = listToAdd.Distinct().ToArray();
+
+ var programs1 = paths.AsParallel().Where(p => Extension(p) == ExeExtension).Select(ExeProgram);
+ var programs2 = paths.AsParallel().Where(p => Extension(p) == ShortcutExtension).Select(ExeProgram);
+ var programs3 = from p in paths.AsParallel()
+ let e = Extension(p)
+ where e != ShortcutExtension && e != ExeExtension
+ select Win32Program(p);
+ return programs1.Concat(programs2).Concat(programs3);
+ }
+
+ private static ParallelQuery StartMenuPrograms(string[] suffixes)
+ {
+ var disabledProgramsList = Main._settings.DisabledProgramSources;
+
+ var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
+ var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
+ var paths1 = ProgramPaths(directory1, suffixes);
+ var paths2 = ProgramPaths(directory2, suffixes);
+
+ var toFilter = paths1.Concat(paths2);
+ var paths = toFilter
+ .Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1))
+ .Select(t1 => t1)
+ .Distinct()
+ .ToArray();
+
+ var programs1 = paths.AsParallel().Where(p => Extension(p) == ShortcutExtension).Select(LnkProgram);
+ var programs2 = paths.AsParallel().Where(p => Extension(p) == ApplicationReferenceExtension).Select(Win32Program);
+ var programs = programs1.Concat(programs2).Where(p => p.Valid);
+ return programs;
+ }
+
+ private static ParallelQuery AppPathsPrograms(string[] suffixes)
+ {
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
+ const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
+ var programs = new List();
+ using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
+ {
+ if (root != null)
+ {
+ programs.AddRange(GetProgramsFromRegistry(root));
+ }
+ }
+ using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
+ {
+ if (root != null)
+ {
+ programs.AddRange(GetProgramsFromRegistry(root));
+ }
+ }
+
+ var disabledProgramsList = Main._settings.DisabledProgramSources;
+ var toFilter = programs.AsParallel().Where(p => suffixes.Contains(Extension(p.ExecutableName)));
+
+ var filtered = toFilter.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)).Select(t1 => t1);
+
+ return filtered;
+ }
+
+ private static IEnumerable GetProgramsFromRegistry(RegistryKey root)
+ {
+ return root
+ .GetSubKeyNames()
+ .Select(x => GetProgramPathFromRegistrySubKeys(root, x))
+ .Distinct()
+ .Select(x => GetProgramFromPath(x));
+ }
+
+ private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string subkey)
+ {
+ var path = string.Empty;
+ try
+ {
+ using (var key = root.OpenSubKey(subkey))
+ {
+ if (key == null)
+ return string.Empty;
+
+ var defaultValue = string.Empty;
+ path = key.GetValue(defaultValue) as string;
+ }
+
+ if (string.IsNullOrEmpty(path))
+ return string.Empty;
+
+ // fix path like this: ""\"C:\\folder\\executable.exe\""
+ return path = path.Trim('"', ' ');
+ }
+ catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
+ {
+ ProgramLogger.LogException($"|Win32|GetProgramPathFromRegistrySubKeys|{path}" +
+ $"|Permission denied when trying to load the program from {path}", e);
+
+ return string.Empty;
+ }
+ }
+
+ private static Win32 GetProgramFromPath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return new Win32();
+
+ path = Environment.ExpandEnvironmentVariables(path);
+
+ if (!File.Exists(path))
+ return new Win32();
+
+ var entry = Win32Program(path);
+ entry.ExecutableName = Path.GetFileName(path);
+
+ return entry;
+ }
+
+ public static Win32[] All(Settings settings)
+ {
+ try
+ {
+ var programs = new List().AsParallel();
+
+ var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
+ programs = programs.Concat(unregistered);
+ if (settings.EnableRegistrySource)
+ {
+ var appPaths = AppPathsPrograms(settings.ProgramSuffixes);
+ programs = programs.Concat(appPaths);
+ }
+
+ if (settings.EnableStartMenuSource)
+ {
+ var startMenu = StartMenuPrograms(settings.ProgramSuffixes);
+ programs = programs.Concat(startMenu);
+ }
+
+ return programs.ToArray();
+ }
+#if DEBUG //This is to make developer aware of any unhandled exception and add in handling.
+ catch (Exception e)
+ {
+ throw e;
+ }
+#endif
+
+#if !DEBUG //Only do a catch all in production.
+ catch (Exception e)
+ {
+ ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e);
+
+ return new Win32[0];
+ }
+#endif
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..70b369ef5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.Program")]
+[assembly: Guid("82f60d9a-9280-4b6a-8b21-f3c694cb7e1d")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Settings.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Settings.cs
new file mode 100644
index 000000000..1ead3f9c3
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Settings.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Wox.Plugin.Program
+{
+ public class Settings
+ {
+ public DateTime LastIndexTime { get; set; }
+ public List ProgramSources { get; set; } = new List();
+ public List DisabledProgramSources { get; set; } = new List();
+ public string[] ProgramSuffixes { get; set; } = {"bat", "appref-ms", "exe", "lnk"};
+
+ public bool EnableStartMenuSource { get; set; } = true;
+
+ public bool EnableRegistrySource { get; set; } = true;
+
+ internal const char SuffixSeperator = ';';
+
+ ///
+ /// Contains user added folder location contents as well as all user disabled applications
+ ///
+ ///
+ /// Win32 class applications set UniqueIdentifier using their full file path
+ /// UWP class applications set UniqueIdentifier using their Application User Model ID
+ /// Custom user added program sources set UniqueIdentifier using their location
+ ///
+ public class ProgramSource
+ {
+ private string name;
+
+ public string Location { get; set; }
+ public string Name { get => name ?? new DirectoryInfo(Location).Name; set => name = value; }
+ public bool Enabled { get; set; } = true;
+ public string UniqueIdentifier { get; set; }
+ }
+
+ public class DisabledProgramSource : ProgramSource { }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/ShObjIdlTlb.dll b/src/modules/launcher/Plugins/Wox.Plugin.Program/ShObjIdlTlb.dll
new file mode 100644
index 000000000..83815e40a
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Program/ShObjIdlTlb.dll differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/SuffixesConverter.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/SuffixesConverter.cs
new file mode 100644
index 000000000..5db4ec945
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/SuffixesConverter.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+namespace Wox.Plugin.Program
+{
+ public class SuffixesConvert : MarkupExtension, IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var text = value as string[];
+ if (text != null)
+ {
+ return string.Join(";", text);
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ return this;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
new file mode 100644
index 000000000..21bba677a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Wox.Plugin.Program.Views.Models;
+
+namespace Wox.Plugin.Program.Views.Commands
+{
+ internal static class ProgramSettingDisplay
+ {
+ internal static List LoadProgramSources(this List programSources)
+ {
+ var list = new List();
+
+ programSources.ForEach(x => list
+ .Add(
+ new ProgramSource
+ {
+ Enabled = x.Enabled,
+ Location = x.Location,
+ Name = x.Name,
+ UniqueIdentifier = x.UniqueIdentifier
+ }
+ ));
+
+ // Even though these are disabled, we still want to display them so users can enable later on
+ Main._settings
+ .DisabledProgramSources
+ .Where(t1 => !Main._settings
+ .ProgramSources // program sourcces added above already, so exlcude
+ .Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
+ .Select(x => x)
+ .ToList()
+ .ForEach(x => list
+ .Add(
+ new ProgramSource
+ {
+ Enabled = x.Enabled,
+ Location = x.Location,
+ Name = x.Name,
+ UniqueIdentifier = x.UniqueIdentifier
+ }
+ ));
+
+ return list;
+ }
+
+ internal static void LoadAllApplications(this List list)
+ {
+ Main._win32s
+ .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
+ .ToList()
+ .ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
+ .Add(
+ new ProgramSource
+ {
+ Name = t1.Name,
+ Location = t1.ParentDirectory,
+ UniqueIdentifier = t1.UniqueIdentifier,
+ Enabled = t1.Enabled
+ }
+ ));
+
+ Main._uwps
+ .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
+ .ToList()
+ .ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
+ .Add(
+ new ProgramSource
+ {
+ Name = t1.DisplayName,
+ Location = t1.Package.Location,
+ UniqueIdentifier = t1.UniqueIdentifier,
+ Enabled = t1.Enabled
+ }
+ ));
+ }
+
+ internal static void SetProgramSourcesStatus(this List list, List selectedProgramSourcesToDisable, bool status)
+ {
+ ProgramSetting.ProgramSettingDisplayList
+ .Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
+ .ToList()
+ .ForEach(t1 => t1.Enabled = status);
+
+ Main._win32s
+ .Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
+ .ToList()
+ .ForEach(t1 => t1.Enabled = status);
+
+ Main._uwps
+ .Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
+ .ToList()
+ .ForEach(t1 => t1.Enabled = status);
+ }
+
+ internal static void StoreDisabledInSettings(this List list)
+ {
+ Main._settings.ProgramSources
+ .Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && !x.Enabled))
+ .ToList()
+ .ForEach(t1 => t1.Enabled = false);
+
+ ProgramSetting.ProgramSettingDisplayList
+ .Where(t1 => !t1.Enabled
+ && !Main._settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
+ .ToList()
+ .ForEach(x => Main._settings.DisabledProgramSources
+ .Add(
+ new Settings.DisabledProgramSource
+ {
+ Name = x.Name,
+ Location = x.Location,
+ UniqueIdentifier = x.UniqueIdentifier,
+ Enabled = false
+ }
+ ));
+ }
+
+ internal static void RemoveDisabledFromSettings(this List list)
+ {
+ Main._settings.ProgramSources
+ .Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && x.Enabled))
+ .ToList()
+ .ForEach(t1 => t1.Enabled = true);
+
+ Main._settings.DisabledProgramSources
+ .Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && x.Enabled))
+ .ToList()
+ .ForEach(x => Main._settings.DisabledProgramSources.Remove(x));
+ }
+
+ internal static bool IsReindexRequired(this List selectedItems)
+ {
+ if (selectedItems.Where(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0
+ && selectedItems.Where(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0)
+ return true;
+
+ // ProgramSources holds list of user added directories,
+ // so when we enable/disable we need to reindex to show/not show the programs
+ // that are found in those directories.
+ if (selectedItems.Where(t1 => Main._settings.ProgramSources.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0)
+ return true;
+
+ return false;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/Models/ProgramSource.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/Models/ProgramSource.cs
new file mode 100644
index 000000000..c2995f74d
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/Models/ProgramSource.cs
@@ -0,0 +1,5 @@
+
+namespace Wox.Plugin.Program.Views.Models
+{
+ public class ProgramSource : Settings.ProgramSource { }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/ProgramSetting.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/ProgramSetting.xaml
new file mode 100644
index 000000000..0fe8d3bd8
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/ProgramSetting.xaml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/ProgramSetting.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/ProgramSetting.xaml.cs
new file mode 100644
index 000000000..72a4f5746
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Views/ProgramSetting.xaml.cs
@@ -0,0 +1,307 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using Wox.Plugin.Program.Views.Models;
+using Wox.Plugin.Program.Views.Commands;
+using Wox.Plugin.Program.Programs;
+using System.ComponentModel;
+using System.Windows.Data;
+
+namespace Wox.Plugin.Program.Views
+{
+ ///
+ /// Interaction logic for ProgramSetting.xaml
+ ///
+ public partial class ProgramSetting : UserControl
+ {
+ private PluginInitContext context;
+ private Settings _settings;
+ private GridViewColumnHeader _lastHeaderClicked;
+ private ListSortDirection _lastDirection;
+
+ // We do not save all program sources to settings, so using
+ // this as temporary holder for displaying all loaded programs sources.
+ internal static List ProgramSettingDisplayList { get; set; }
+
+ public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWP.Application[] uwps)
+ {
+ this.context = context;
+ InitializeComponent();
+ Loaded += Setting_Loaded;
+ _settings = settings;
+ }
+
+ private void Setting_Loaded(object sender, RoutedEventArgs e)
+ {
+ ProgramSettingDisplayList = _settings.ProgramSources.LoadProgramSources();
+ programSourceView.ItemsSource = ProgramSettingDisplayList;
+
+ StartMenuEnabled.IsChecked = _settings.EnableStartMenuSource;
+ RegistryEnabled.IsChecked = _settings.EnableRegistrySource;
+ }
+
+ private void ReIndexing()
+ {
+ programSourceView.Items.Refresh();
+ Task.Run(() =>
+ {
+ Dispatcher.Invoke(() => { indexingPanel.Visibility = Visibility.Visible; });
+ Main.IndexPrograms();
+ Dispatcher.Invoke(() => { indexingPanel.Visibility = Visibility.Hidden; });
+ });
+ }
+
+ private void btnAddProgramSource_OnClick(object sender, RoutedEventArgs e)
+ {
+ var add = new AddProgramSource(context, _settings);
+ if(add.ShowDialog() ?? false)
+ {
+ ReIndexing();
+ }
+
+ programSourceView.Items.Refresh();
+ }
+
+ private void DeleteProgramSources(List itemsToDelete)
+ {
+ itemsToDelete.ForEach(t1 => _settings.ProgramSources
+ .Remove(_settings.ProgramSources
+ .Where(x => x.UniqueIdentifier == t1.UniqueIdentifier)
+ .FirstOrDefault()));
+ itemsToDelete.ForEach(x => ProgramSettingDisplayList.Remove(x));
+
+ ReIndexing();
+ }
+
+ private void btnEditProgramSource_OnClick(object sender, RoutedEventArgs e)
+ {
+ var selectedProgramSource = programSourceView.SelectedItem as Settings.ProgramSource;
+ if (selectedProgramSource != null)
+ {
+ var add = new AddProgramSource(selectedProgramSource, _settings);
+ if (add.ShowDialog() ?? false)
+ {
+ ReIndexing();
+ }
+ }
+ else
+ {
+ string msg = context.API.GetTranslation("wox_plugin_program_pls_select_program_source");
+ MessageBox.Show(msg);
+ }
+ }
+
+ private void btnReindex_Click(object sender, RoutedEventArgs e)
+ {
+ ReIndexing();
+ }
+
+ private void BtnProgramSuffixes_OnClick(object sender, RoutedEventArgs e)
+ {
+ var p = new ProgramSuffixes(context, _settings);
+ if (p.ShowDialog() ?? false)
+ {
+ ReIndexing();
+ }
+ }
+
+ private void programSourceView_DragEnter(object sender, DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.FileDrop))
+ {
+ e.Effects = DragDropEffects.Link;
+ }
+ else
+ {
+ e.Effects = DragDropEffects.None;
+ }
+ }
+
+ private void programSourceView_Drop(object sender, DragEventArgs e)
+ {
+ var directories = (string[])e.Data.GetData(DataFormats.FileDrop);
+
+ var directoriesToAdd = new List();
+
+ if (directories != null && directories.Length > 0)
+ {
+ foreach (string directory in directories)
+ {
+ if (Directory.Exists(directory) && !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == directory))
+ {
+ var source = new ProgramSource
+ {
+ Location = directory,
+ UniqueIdentifier = directory
+ };
+
+ directoriesToAdd.Add(source);
+ }
+ }
+
+ if (directoriesToAdd.Count() > 0)
+ {
+ directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
+ directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
+
+ programSourceView.Items.Refresh();
+ ReIndexing();
+ }
+ }
+ }
+
+ private void StartMenuEnabled_Click(object sender, RoutedEventArgs e)
+ {
+ _settings.EnableStartMenuSource = StartMenuEnabled.IsChecked ?? false;
+ ReIndexing();
+ }
+
+ private void RegistryEnabled_Click(object sender, RoutedEventArgs e)
+ {
+ _settings.EnableRegistrySource = RegistryEnabled.IsChecked ?? false;
+ ReIndexing();
+ }
+
+ private void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
+ {
+ ProgramSettingDisplayList.LoadAllApplications();
+
+ programSourceView.Items.Refresh();
+ }
+
+ private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
+ {
+ var selectedItems = programSourceView
+ .SelectedItems.Cast()
+ .ToList();
+
+ if (selectedItems.Count() == 0)
+ {
+ string msg = context.API.GetTranslation("wox_plugin_program_pls_select_program_source");
+ MessageBox.Show(msg);
+ return;
+ }
+
+ if (selectedItems
+ .Where(t1 => !_settings
+ .ProgramSources
+ .Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
+ .Count() == 0)
+ {
+ var msg = string.Format(context.API.GetTranslation("wox_plugin_program_delete_program_source"));
+
+ if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
+ {
+ return;
+ }
+
+ DeleteProgramSources(selectedItems);
+ }
+ else if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems))
+ {
+ ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, false);
+
+ ProgramSettingDisplayList.StoreDisabledInSettings();
+ }
+ else
+ {
+ ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, true);
+
+ ProgramSettingDisplayList.RemoveDisabledFromSettings();
+ }
+
+ if (selectedItems.IsReindexRequired())
+ ReIndexing();
+
+ programSourceView.SelectedItems.Clear();
+
+ programSourceView.Items.Refresh();
+ }
+
+ private void ProgramSourceView_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ programSourceView.SelectedItems.Clear();
+ }
+
+ private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
+ {
+ var headerClicked = e.OriginalSource as GridViewColumnHeader;
+ ListSortDirection direction;
+
+ if (headerClicked != null)
+ {
+ if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
+ {
+ if (headerClicked != _lastHeaderClicked)
+ {
+ direction = ListSortDirection.Ascending;
+ }
+ else
+ {
+ if (_lastDirection == ListSortDirection.Ascending)
+ {
+ direction = ListSortDirection.Descending;
+ }
+ else
+ {
+ direction = ListSortDirection.Ascending;
+ }
+ }
+
+ var columnBinding = headerClicked.Column.DisplayMemberBinding as Binding;
+ var sortBy = columnBinding?.Path.Path ?? headerClicked.Column.Header as string;
+
+ Sort(sortBy, direction);
+
+ _lastHeaderClicked = headerClicked;
+ _lastDirection = direction;
+ }
+ }
+ }
+
+ private void Sort(string sortBy, ListSortDirection direction)
+ {
+ var dataView = CollectionViewSource.GetDefaultView(programSourceView.ItemsSource);
+
+ dataView.SortDescriptions.Clear();
+ SortDescription sd = new SortDescription(sortBy, direction);
+ dataView.SortDescriptions.Add(sd);
+ dataView.Refresh();
+ }
+
+ private bool IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(List selectedItems)
+ {
+ return selectedItems.Where(x => x.Enabled).Count() >= selectedItems.Where(x => !x.Enabled).Count();
+ }
+
+ private void Row_OnClick(object sender, RoutedEventArgs e)
+ {
+ var selectedItems = programSourceView
+ .SelectedItems.Cast()
+ .ToList();
+
+ if (selectedItems
+ .Where(t1 => !_settings
+ .ProgramSources
+ .Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
+ .Count() == 0)
+ {
+ btnProgramSourceStatus.Content = context.API.GetTranslation("wox_plugin_program_delete");
+ return;
+ }
+
+ if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems))
+ {
+ btnProgramSourceStatus.Content = "Disable";
+ }
+ else
+ {
+ btnProgramSourceStatus.Content = "Enable";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj
new file mode 100644
index 000000000..17ccffaba
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj
@@ -0,0 +1,182 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
+ Library
+ Properties
+ Wox.Plugin.Program
+ Wox.Plugin.Program
+ v4.5.2
+ 512
+ ..\..\
+
+
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Program\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Program\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ .\AppxPackagingTlb.dll
+ True
+
+
+
+
+
+ .\ShObjIdlTlb.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+ AddProgramSource.xaml
+
+
+
+
+
+
+
+
+
+
+
+ ProgramSetting.xaml
+
+
+
+ ProgramSuffixes.xaml
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ Always
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ 10.3.0
+
+
+ 9.0.1
+
+
+ 4.2.0
+
+
+ 4.0.0
+
+
+ 10.0.14393.3
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Program/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Program/plugin.json
new file mode 100644
index 000000000..86fd8ebd6
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Program/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"791FC278BA414111B8D1886DFE447410",
+ "ActionKeyword":"*",
+ "Name":"Program",
+ "Description":"Search programs in Wox",
+ "Author":"qianlifeng",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.Program.dll",
+ "IcoPath":"Images\\program.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Images/shell.png b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Images/shell.png
new file mode 100644
index 000000000..686583653
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Images/shell.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Images/user.png b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Images/user.png
new file mode 100644
index 000000000..2d45c1ee9
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Images/user.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/de.xaml
new file mode 100644
index 000000000..73eebecdc
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/de.xaml
@@ -0,0 +1,12 @@
+
+
+ Ersetzt Win+R
+ Schließe die Kommandozeilte nicht nachdem der Befehl ausgeführt wurde
+ Kommandozeile
+ Bereitstellung der Kommandozeile in Wox. Befehle müssem mit > starten
+ Dieser Befehl wurde {0} mal ausgeführt
+ Führe Befehle mittels Kommandozeile aus
+ Als Administrator ausführen
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/en.xaml
new file mode 100644
index 000000000..0d0a41b3f
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/en.xaml
@@ -0,0 +1,14 @@
+
+
+ Replace Win+R
+ Do not close Command Prompt after command execution
+ Always run as administrator
+ Run as different user
+ Shell
+ Allows to execute system commands from Wox. Commands should start with >
+ this command has been executed {0} times
+ execute command through command shell
+ Run As Administrator
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/pl.xaml
new file mode 100644
index 000000000..909e5c258
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/pl.xaml
@@ -0,0 +1,12 @@
+
+
+ Zastąp Win+R
+ Nie zamykaj wiersza poleceń po wykonaniu polecenia
+ Wiersz poleceń
+ Pozwala wykonywać komend wiersza polecania z Woxa. Polecania zaczynają się od >
+ to polecenie zostało wykonane {0} razy
+ wykonaj to polecenie w wierszu poleceń
+ Uruchom jako administrator
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/tr.xaml
new file mode 100644
index 000000000..268b88253
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/tr.xaml
@@ -0,0 +1,12 @@
+
+
+ Win+R kısayolunu kullan
+ Çalıştırma sona erdikten sonra komut istemini kapatma
+ Kabuk
+ Wox üzerinden komut istemini kullanmanızı sağlar. Komutlar > işareti ile başlamalıdır.
+ Bu komut {0} kez çalıştırıldı
+ Komut isteminde çalıştır
+ Yönetici Olarak Çalıştır
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/zh-cn.xaml
new file mode 100644
index 000000000..c4870a164
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/zh-cn.xaml
@@ -0,0 +1,12 @@
+
+
+ 替换 Win+R
+ 执行后不关闭命令窗口
+ 命令行
+ 提供从Wox中执行命令行的能力,命令应该以>开头
+ 此命令已经被执行了 {0} 次
+ 执行此命令
+ 以管理员身份运行
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/zh-tw.xaml
new file mode 100644
index 000000000..082de6301
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Languages/zh-tw.xaml
@@ -0,0 +1,12 @@
+
+
+ 取代 Win+R
+ 執行後不關閉命令提示字元視窗
+ 命令提示字元
+ 提供從 Wox 中執行命令提示字元的功能,指令應該以>開頭
+ 此指令已執行了 {0} 次
+ 執行指令
+ 以系統管理員身分執行
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Main.cs
new file mode 100644
index 000000000..1a5174c4e
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Main.cs
@@ -0,0 +1,352 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using WindowsInput;
+using WindowsInput.Native;
+using Wox.Infrastructure.Hotkey;
+using Wox.Infrastructure.Logger;
+using Wox.Infrastructure.Storage;
+using Wox.Plugin.SharedCommands;
+using Application = System.Windows.Application;
+using Control = System.Windows.Controls.Control;
+using Keys = System.Windows.Forms.Keys;
+
+namespace Wox.Plugin.Shell
+{
+ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, ISavable
+ {
+ private const string Image = "Images/shell.png";
+ private PluginInitContext _context;
+ private bool _winRStroked;
+ private readonly KeyboardSimulator _keyboardSimulator = new KeyboardSimulator(new InputSimulator());
+
+ private readonly Settings _settings;
+ private readonly PluginJsonStorage _storage;
+
+ public Main()
+ {
+ _storage = new PluginJsonStorage();
+ _settings = _storage.Load();
+ }
+
+ public void Save()
+ {
+ _storage.Save();
+ }
+
+
+ public List Query(Query query)
+ {
+ List results = new List();
+ string cmd = query.Search;
+ if (string.IsNullOrEmpty(cmd))
+ {
+ return ResultsFromlHistory();
+ }
+ else
+ {
+ var queryCmd = GetCurrentCmd(cmd);
+ results.Add(queryCmd);
+ var history = GetHistoryCmds(cmd, queryCmd);
+ results.AddRange(history);
+
+ try
+ {
+ string basedir = null;
+ string dir = null;
+ string excmd = Environment.ExpandEnvironmentVariables(cmd);
+ if (Directory.Exists(excmd) && (cmd.EndsWith("/") || cmd.EndsWith(@"\")))
+ {
+ basedir = excmd;
+ dir = cmd;
+ }
+ else if (Directory.Exists(Path.GetDirectoryName(excmd) ?? string.Empty))
+ {
+ basedir = Path.GetDirectoryName(excmd);
+ var dirn = Path.GetDirectoryName(cmd);
+ dir = (dirn.EndsWith("/") || dirn.EndsWith(@"\")) ? dirn : cmd.Substring(0, dirn.Length + 1);
+ }
+
+ if (basedir != null)
+ {
+ var autocomplete = Directory.GetFileSystemEntries(basedir).
+ Select(o => dir + Path.GetFileName(o)).
+ Where(o => o.StartsWith(cmd, StringComparison.OrdinalIgnoreCase) &&
+ !results.Any(p => o.Equals(p.Title, StringComparison.OrdinalIgnoreCase)) &&
+ !results.Any(p => o.Equals(p.Title, StringComparison.OrdinalIgnoreCase))).ToList();
+ autocomplete.Sort();
+ results.AddRange(autocomplete.ConvertAll(m => new Result
+ {
+ Title = m,
+ IcoPath = Image,
+ Action = c =>
+ {
+ Execute(Process.Start, PrepareProcessStartInfo(m));
+ return true;
+ }
+ }));
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|Wox.Plugin.Shell.Main.Query|Exception when query for <{query}>", e);
+ }
+ return results;
+ }
+ }
+
+ private List GetHistoryCmds(string cmd, Result result)
+ {
+ IEnumerable history = _settings.Count.Where(o => o.Key.Contains(cmd))
+ .OrderByDescending(o => o.Value)
+ .Select(m =>
+ {
+ if (m.Key == cmd)
+ {
+ result.SubTitle = string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value);
+ return null;
+ }
+
+ var ret = new Result
+ {
+ Title = m.Key,
+ SubTitle = string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
+ IcoPath = Image,
+ Action = c =>
+ {
+ Execute(Process.Start, PrepareProcessStartInfo(m.Key));
+ return true;
+ }
+ };
+ return ret;
+ }).Where(o => o != null).Take(4);
+ return history.ToList();
+ }
+
+ private Result GetCurrentCmd(string cmd)
+ {
+ Result result = new Result
+ {
+ Title = cmd,
+ Score = 5000,
+ SubTitle = _context.API.GetTranslation("wox_plugin_cmd_execute_through_shell"),
+ IcoPath = Image,
+ Action = c =>
+ {
+ Execute(Process.Start, PrepareProcessStartInfo(cmd));
+ return true;
+ }
+ };
+
+ return result;
+ }
+
+ private List ResultsFromlHistory()
+ {
+ IEnumerable history = _settings.Count.OrderByDescending(o => o.Value)
+ .Select(m => new Result
+ {
+ Title = m.Key,
+ SubTitle = string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
+ IcoPath = Image,
+ Action = c =>
+ {
+ Execute(Process.Start, PrepareProcessStartInfo(m.Key));
+ return true;
+ }
+ }).Take(5);
+ return history.ToList();
+ }
+
+ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
+ {
+ command = command.Trim();
+ command = Environment.ExpandEnvironmentVariables(command);
+ var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? "" : "runas";
+
+ ProcessStartInfo info;
+ if (_settings.Shell == Shell.Cmd)
+ {
+ var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause";
+
+ info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsAdministratorArg);
+ }
+ else if (_settings.Shell == Shell.Powershell)
+ {
+ string arguments;
+ if (_settings.LeaveShellOpen)
+ {
+ arguments = $"-NoExit \"{command}\"";
+ }
+ else
+ {
+ arguments = $"\"{command} ; Read-Host -Prompt \\\"Press Enter to continue\\\"\"";
+ }
+
+ info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsAdministratorArg);
+ }
+ else if (_settings.Shell == Shell.RunCommand)
+ {
+ var parts = command.Split(new[] { ' ' }, 2);
+ if (parts.Length == 2)
+ {
+ var filename = parts[0];
+ if (ExistInPath(filename))
+ {
+ var arguments = parts[1];
+ info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsAdministratorArg);
+ }
+ else
+ {
+ info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
+ }
+ }
+ else
+ {
+ info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
+ }
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+
+ info.UseShellExecute = true;
+
+ _settings.AddCmdHistory(command);
+
+ return info;
+ }
+
+ private void Execute(Func startProcess,ProcessStartInfo info)
+ {
+ try
+ {
+ startProcess(info);
+ }
+ catch (FileNotFoundException e)
+ {
+ var name = "Plugin: Shell";
+ var message = $"Command not found: {e.Message}";
+ _context.API.ShowMsg(name, message);
+ }
+ catch(Win32Exception e)
+ {
+ var name = "Plugin: Shell";
+ var message = $"Error running the command: {e.Message}";
+ _context.API.ShowMsg(name, message);
+ }
+ }
+
+ private bool ExistInPath(string filename)
+ {
+ if (File.Exists(filename))
+ {
+ return true;
+ }
+ else
+ {
+ var values = Environment.GetEnvironmentVariable("PATH");
+ if (values != null)
+ {
+ foreach (var path in values.Split(';'))
+ {
+ var path1 = Path.Combine(path, filename);
+ var path2 = Path.Combine(path, filename + ".exe");
+ if (File.Exists(path1) || File.Exists(path2))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ this._context = context;
+ context.API.GlobalKeyboardEvent += API_GlobalKeyboardEvent;
+ }
+
+ bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state)
+ {
+ if (_settings.ReplaceWinR)
+ {
+ if (keyevent == (int)KeyEvent.WM_KEYDOWN && vkcode == (int)Keys.R && state.WinPressed)
+ {
+ _winRStroked = true;
+ OnWinRPressed();
+ return false;
+ }
+ if (keyevent == (int)KeyEvent.WM_KEYUP && _winRStroked && vkcode == (int)Keys.LWin)
+ {
+ _winRStroked = false;
+ _keyboardSimulator.ModifiedKeyStroke(VirtualKeyCode.LWIN, VirtualKeyCode.CONTROL);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void OnWinRPressed()
+ {
+ _context.API.ChangeQuery($"{_context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeperater}");
+ Application.Current.MainWindow.Visibility = Visibility.Visible;
+ }
+
+ public Control CreateSettingPanel()
+ {
+ return new CMDSetting(_settings);
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return _context.API.GetTranslation("wox_plugin_cmd_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return _context.API.GetTranslation("wox_plugin_cmd_plugin_description");
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ var resultlist = new List
+ {
+ new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_different_user"),
+ Action = c =>
+ {
+ Task.Run(() =>Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title)));
+ return true;
+ },
+ IcoPath = "Images/user.png"
+ },
+ new Result
+ {
+ Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"),
+ Action = c =>
+ {
+ Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
+ return true;
+ },
+ IcoPath = Image
+ }
+ };
+
+ return resultlist;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..c1968c12a
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.CMD")]
+[assembly: Guid("9283a32d-5d3c-4231-96e0-2150ed4716b9")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Settings.cs b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Settings.cs
new file mode 100644
index 000000000..af149e829
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Settings.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+namespace Wox.Plugin.Shell
+{
+ public class Settings
+ {
+ public Shell Shell { get; set; } = Shell.Cmd;
+ public bool ReplaceWinR { get; set; } = true;
+ public bool LeaveShellOpen { get; set; }
+ public bool RunAsAdministrator { get; set; } = true;
+
+ public Dictionary Count = new Dictionary();
+
+ public void AddCmdHistory(string cmdName)
+ {
+ if (Count.ContainsKey(cmdName))
+ {
+ Count[cmdName] += 1;
+ }
+ else
+ {
+ Count.Add(cmdName, 1);
+ }
+ }
+ }
+
+ public enum Shell
+ {
+ Cmd = 0,
+ Powershell = 1,
+ RunCommand = 2,
+
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/ShellSetting.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Shell/ShellSetting.xaml
new file mode 100644
index 000000000..dc6de53ba
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/ShellSetting.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CMD
+ PowerShell
+ RunCommand
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs
new file mode 100644
index 000000000..ffa3b5856
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs
@@ -0,0 +1,60 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Wox.Plugin.Shell
+{
+ public partial class CMDSetting : UserControl
+ {
+ private readonly Settings _settings;
+
+ public CMDSetting(Settings settings)
+ {
+ InitializeComponent();
+ _settings = settings;
+ }
+
+ private void CMDSetting_OnLoaded(object sender, RoutedEventArgs re)
+ {
+ ReplaceWinR.IsChecked = _settings.ReplaceWinR;
+ LeaveShellOpen.IsChecked = _settings.LeaveShellOpen;
+ AlwaysRunAsAdministrator.IsChecked = _settings.RunAsAdministrator;
+ LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand;
+
+ LeaveShellOpen.Checked += (o, e) =>
+ {
+ _settings.LeaveShellOpen = true;
+ };
+
+ LeaveShellOpen.Unchecked += (o, e) =>
+ {
+ _settings.LeaveShellOpen = false;
+ };
+
+ AlwaysRunAsAdministrator.Checked += (o, e) =>
+ {
+ _settings.RunAsAdministrator = true;
+ };
+
+ AlwaysRunAsAdministrator.Unchecked += (o, e) =>
+ {
+ _settings.RunAsAdministrator = false;
+ };
+
+ ReplaceWinR.Checked += (o, e) =>
+ {
+ _settings.ReplaceWinR = true;
+ };
+ ReplaceWinR.Unchecked += (o, e) =>
+ {
+ _settings.ReplaceWinR = false;
+ };
+
+ ShellComboBox.SelectedIndex = (int) _settings.Shell;
+ ShellComboBox.SelectionChanged += (o, e) =>
+ {
+ _settings.Shell = (Shell) ShellComboBox.SelectedIndex;
+ LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand;
+ };
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj
new file mode 100644
index 000000000..7508783aa
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj
@@ -0,0 +1,155 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
+ Library
+ Properties
+ Wox.Plugin.Shell
+ Wox.Plugin.Shell
+ v4.5.2
+ 512
+ ..\..\
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Shell\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Shell\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\packages\InputSimulator.1.0.4.0\lib\net20\WindowsInput.dll
+ True
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+ ShellSetting.xaml
+
+
+
+
+ Always
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ 1.0.4
+
+
+ 10.3.0
+
+
+ 9.0.1
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Shell/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Shell/plugin.json
new file mode 100644
index 000000000..a09636e39
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Shell/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"D409510CD0D2481F853690A07E6DC426",
+ "ActionKeyword":">",
+ "Name":"Shell",
+ "Description":"Provide executing commands from Wox. Commands should start with >",
+ "Author":"qianlifeng",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.Shell.dll",
+ "IcoPath":"Images\\shell.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/app.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/app.png
new file mode 100644
index 000000000..8c9ca7971
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/app.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/checkupdate.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/checkupdate.png
new file mode 100644
index 000000000..955f6fdbb
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/checkupdate.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/close.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/close.png
new file mode 100644
index 000000000..17c4363ad
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/close.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/lock.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/lock.png
new file mode 100644
index 000000000..4aef7007b
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/lock.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/logoff.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/logoff.png
new file mode 100644
index 000000000..0d1378830
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/logoff.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/recyclebin.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/recyclebin.png
new file mode 100644
index 000000000..2cc3b0116
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/recyclebin.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/restart.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/restart.png
new file mode 100644
index 000000000..aaa2ee711
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/restart.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/shutdown.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/shutdown.png
new file mode 100644
index 000000000..7da7a528d
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/shutdown.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/sleep.png b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/sleep.png
new file mode 100644
index 000000000..42426286d
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Images/sleep.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/de.xaml
new file mode 100644
index 000000000..f47676b47
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/de.xaml
@@ -0,0 +1,21 @@
+
+
+ Befehl
+ Beschreibung
+
+ Computer herunterfahren
+ Computer neu starten
+ Abmelden
+ Computer sperren
+ Wox schließen
+ Wox neu starten
+ Anwendung beschleunigen
+ Computer in Schlafmodus versetzen
+ Papierkorb leeren
+
+ Systembefehle
+ Stellt Systemrelevante Befehle bereit. z.B. herunterfahren, sperren, Einstellungen, usw.
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/en.xaml
new file mode 100644
index 000000000..f4b8bbfba
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/en.xaml
@@ -0,0 +1,32 @@
+
+
+
+ Command
+ Description
+
+ Shutdown Computer
+ Restart Computer
+ Log off
+ Lock this computer
+ Close Wox
+ Restart Wox
+ Tweak this app
+ Put computer to sleep
+ Empty recycle bin
+ Hibernate computer
+ Save all Wox settings
+ Reloads plugin data with new content added after Wox started. Plugins need to have this feature already added.
+
+
+ Success
+ All Wox settings saved
+ Reloaded all applicable plugin data
+ Are you sure you want to shut the computer down?
+ Are you sure you want to restart the computer?
+
+ System Commands
+ Provides System related commands. e.g. shutdown, lock, settings etc.
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/ja.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/ja.xaml
new file mode 100644
index 000000000..0f9093023
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/ja.xaml
@@ -0,0 +1,21 @@
+
+
+ コマンド
+ 説明
+
+ コンピュータをシャットダウンする
+ コンピュータを再起動する
+ ログオフ
+ このコンピュータをロックする
+ Woxを終了する
+ Woxを再起動する
+ このアプリの設定
+ スリープ
+ ゴミ箱を空にする
+
+ システムコマンド
+ システム関連のコマンドを提供します。例:シャットダウン、ロック、設定など
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/pl.xaml
new file mode 100644
index 000000000..19a9c1a67
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/pl.xaml
@@ -0,0 +1,21 @@
+
+
+ Komenda
+ Opis
+
+ Wyłącz komputer
+ Uruchom ponownie komputer
+ Wyloguj się
+ Zablokuj ten komputer
+ Wyłącz Woxa
+ Uruchom ponownie Woxa
+ Dostosuj ustawienia
+ Przełącz komputer w tryb uśpienia
+ Opróżnij kosz
+
+ Komendy systemowe
+ Wykonywanie komend systemowych, np. wyłącz, zablokuj komputer, otwórz ustawienia itp.
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/tr.xaml
new file mode 100644
index 000000000..5111aa778
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/tr.xaml
@@ -0,0 +1,32 @@
+
+
+
+ Komut
+ Açıklama
+
+ Bilgisayarı Kapat
+ Yeniden Başlat
+ Oturumu Kapat
+ Bilgisayarı Kilitle
+ Wox'u Kapat
+ Wox'u Yeniden Başlat
+ Wox Ayarlarını Aç
+ Bilgisayarı Uyku Moduna Al
+ Geri Dönüşüm Kutusunu Boşalt
+ Bilgisayarı Askıya Al
+ Tüm Wox Ayarlarını Kaydet
+ Eklentilerin verilerini Wox'un açılışından sonra yapılan değişiklikleri için günceller. Eklentilerin bu özelliği zaten eklemiş olması gerekir.
+
+
+ Başarılı
+ Tüm Wox ayarları kaydedildi.
+ Bilgisayarı kapatmak istediğinize emin misiniz?
+ Bilgisayarı yeniden başlatmak istediğinize emin misiniz?
+
+
+ Sistem Komutları
+ Sistem ile ilgili komutlara erişim sağlar. ör. shutdown, lock, settings vb.
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/zh-cn.xaml
new file mode 100644
index 000000000..58e209619
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/zh-cn.xaml
@@ -0,0 +1,21 @@
+
+
+ 命令
+ 描述
+
+ 关闭电脑
+ 重启这台电脑
+ 注销
+ 锁定这台电脑
+ 退出Wox
+ 重启Wox
+ 设置
+ 休眠这台电脑
+ 清空回收站
+
+ 系统命令
+ 系统系统相关的命令。例如,关机,锁定,设置等
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/zh-tw.xaml
new file mode 100644
index 000000000..ac035bdd8
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Languages/zh-tw.xaml
@@ -0,0 +1,21 @@
+
+
+ 命令
+ 描述
+
+ 電腦關機
+ 電腦重新啟動
+ 登出
+ 鎖定電腦
+ 退出Wox
+ 重新啟動 Wox
+ 設定
+ 睡眠
+ 清空資源回收桶
+
+ 系統命令
+ 系統相關的命令。例如,關機,鎖定,設定等
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Main.cs
new file mode 100644
index 000000000..f6be3a435
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Main.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Forms;
+using System.Windows.Interop;
+using Wox.Infrastructure;
+using Application = System.Windows.Application;
+using Control = System.Windows.Controls.Control;
+using FormsApplication = System.Windows.Forms.Application;
+using MessageBox = System.Windows.MessageBox;
+
+namespace Wox.Plugin.Sys
+{
+ public class Main : IPlugin, ISettingProvider, IPluginI18n
+ {
+ private PluginInitContext context;
+
+ #region DllImport
+
+ internal const int EWX_LOGOFF = 0x00000000;
+ internal const int EWX_SHUTDOWN = 0x00000001;
+ internal const int EWX_REBOOT = 0x00000002;
+ internal const int EWX_FORCE = 0x00000004;
+ internal const int EWX_POWEROFF = 0x00000008;
+ internal const int EWX_FORCEIFHUNG = 0x00000010;
+
+ [DllImport("user32")]
+ private static extern bool ExitWindowsEx(uint uFlags, uint dwReason);
+
+ [DllImport("user32")]
+ private static extern void LockWorkStation();
+
+ [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
+ private static extern uint SHEmptyRecycleBin(IntPtr hWnd, uint dwFlags);
+
+ // http://www.pinvoke.net/default.aspx/Enums/HRESULT.html
+ private enum HRESULT : uint
+ {
+ S_FALSE = 0x0001,
+ S_OK = 0x0000
+ }
+
+ #endregion
+
+ public Control CreateSettingPanel()
+ {
+ var results = Commands();
+ return new SysSettings(results);
+ }
+
+ public List Query(Query query)
+ {
+ var commands = Commands();
+ var results = new List();
+ foreach (var c in commands)
+ {
+ var titleMatch = StringMatcher.FuzzySearch(query.Search, c.Title);
+ var subTitleMatch = StringMatcher.FuzzySearch(query.Search, c.SubTitle);
+
+ var score = Math.Max(titleMatch.Score, subTitleMatch.Score);
+ if (score > 0)
+ {
+ c.Score = score;
+ if (score == titleMatch.Score)
+ {
+ c.TitleHighlightData = titleMatch.MatchData;
+ }
+ else
+ {
+ c.SubTitleHighlightData = subTitleMatch.MatchData;
+ }
+ results.Add(c);
+ }
+ }
+ return results;
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ this.context = context;
+ }
+
+ private List Commands()
+ {
+ var results = new List();
+ results.AddRange(new[]
+ {
+ new Result
+ {
+ Title = "Shutdown",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_shutdown_computer"),
+ IcoPath = "Images\\shutdown.png",
+ Action = c =>
+ {
+ var reuslt = MessageBox.Show(context.API.GetTranslation("wox_plugin_sys_dlgtext_shutdown_computer"),
+ context.API.GetTranslation("wox_plugin_sys_shutdown_computer"),
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (reuslt == MessageBoxResult.Yes)
+ {
+ Process.Start("shutdown", "/s /t 0");
+ }
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Restart",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_restart_computer"),
+ IcoPath = "Images\\restart.png",
+ Action = c =>
+ {
+ var result = MessageBox.Show(context.API.GetTranslation("wox_plugin_sys_dlgtext_restart_computer"),
+ context.API.GetTranslation("wox_plugin_sys_restart_computer"),
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ Process.Start("shutdown", "/r /t 0");
+ }
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Log Off",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_log_off"),
+ IcoPath = "Images\\logoff.png",
+ Action = c => ExitWindowsEx(EWX_LOGOFF, 0)
+ },
+ new Result
+ {
+ Title = "Lock",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_lock"),
+ IcoPath = "Images\\lock.png",
+ Action = c =>
+ {
+ LockWorkStation();
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Sleep",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_sleep"),
+ IcoPath = "Images\\sleep.png",
+ Action = c => FormsApplication.SetSuspendState(PowerState.Suspend, false, false)
+ },
+ new Result
+ {
+ Title = "Hibernate",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_hibernate"),
+ IcoPath = "Images\\sleep.png", // Icon change needed
+ Action = c => FormsApplication.SetSuspendState(PowerState.Hibernate, false, false)
+ },
+ new Result
+ {
+ Title = "Empty Recycle Bin",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_emptyrecyclebin"),
+ IcoPath = "Images\\recyclebin.png",
+ Action = c =>
+ {
+ // http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html
+ // FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED))
+ // 0 for nothing
+ var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0);
+ if (result != (uint) HRESULT.S_OK && result != (uint)0x8000FFFF)
+ {
+ MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" +
+ "please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137",
+ "Error",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Exit",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_exit"),
+ IcoPath = "Images\\app.png",
+ Action = c =>
+ {
+ Application.Current.MainWindow.Close();
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Save Settings",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_save_all_settings"),
+ IcoPath = "Images\\app.png",
+ Action = c =>
+ {
+ context.API.SaveAppAllSettings();
+ context.API.ShowMsg(context.API.GetTranslation("wox_plugin_sys_dlgtitle_success"),
+ context.API.GetTranslation("wox_plugin_sys_dlgtext_all_settings_saved"));
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Restart Wox",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_restart"),
+ IcoPath = "Images\\app.png",
+ Action = c =>
+ {
+ context.API.RestarApp();
+ return false;
+ }
+ },
+ new Result
+ {
+ Title = "Settings",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_setting"),
+ IcoPath = "Images\\app.png",
+ Action = c =>
+ {
+ context.API.OpenSettingDialog();
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Reload Plugin Data",
+ SubTitle = context.API.GetTranslation("wox_plugin_sys_reload_plugin_data"),
+ IcoPath = "Images\\app.png",
+ Action = c =>
+ {
+ // Hide the window first then show msg after done because sometimes the reload could take a while, so not to make user think it's frozen.
+ Application.Current.MainWindow.Hide();
+ context.API.ReloadAllPluginData();
+ context.API.ShowMsg(context.API.GetTranslation("wox_plugin_sys_dlgtitle_success"),
+ context.API.GetTranslation("wox_plugin_sys_dlgtext_all_applicableplugins_reloaded"));
+ return true;
+ }
+ },
+ new Result
+ {
+ Title = "Check For Update",
+ SubTitle = "Check for new Wox update",
+ IcoPath = "Images\\checkupdate.png",
+ Action = c =>
+ {
+ Application.Current.MainWindow.Hide();
+ context.API.CheckForNewUpdate();
+ context.API.ShowMsg("Please wait...",
+ "Checking for new update");
+ return true;
+ }
+ }
+ });
+ return results;
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return context.API.GetTranslation("wox_plugin_sys_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return context.API.GetTranslation("wox_plugin_sys_plugin_description");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..db9072038
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.Sys")]
+[assembly: Guid("e1eecff6-3f25-424d-9bbd-cbd7d6e1e11e")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/SysSettings.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Sys/SysSettings.xaml
new file mode 100644
index 000000000..5ef2443a5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/SysSettings.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/SysSettings.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Sys/SysSettings.xaml.cs
new file mode 100644
index 000000000..bb1abc819
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/SysSettings.xaml.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.Windows.Controls;
+
+namespace Wox.Plugin.Sys
+{
+ public partial class SysSettings : UserControl
+ {
+ public SysSettings(List Results)
+ {
+ InitializeComponent();
+
+ foreach (var Result in Results)
+ {
+ lbxCommands.Items.Add(Result);
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj
new file mode 100644
index 000000000..ffdfa5972
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj
@@ -0,0 +1,170 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {0B9DE348-9361-4940-ADB6-F5953BFFCCEC}
+ Library
+ Properties
+ Wox.Plugin.Sys
+ Wox.Plugin.Sys
+ v4.5.2
+ 512
+ ..\..\
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Sys\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Sys\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+ SysSettings.xaml
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+ 10.3.0
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Sys/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Sys/plugin.json
new file mode 100644
index 000000000..20c709a79
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Sys/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"CEA08895D2544B019B2E9C5009600DF4",
+ "ActionKeyword":"*",
+ "Name":"System Commands",
+ "Description":"Provide System related commands. e.g. shutdown,lock,setting etc.",
+ "Author":"qianlifeng",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.Sys.dll",
+ "IcoPath":"Images\\lock.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Images/url.png b/src/modules/launcher/Plugins/Wox.Plugin.Url/Images/url.png
new file mode 100644
index 000000000..619d1ad6a
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.Url/Images/url.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/de.xaml
new file mode 100644
index 000000000..172a8412b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/de.xaml
@@ -0,0 +1,11 @@
+
+
+ Öffne URL:{0}
+ Kann URL nicht öffnen:{0}
+
+ URL
+ Öffne eine eingegebene URL mit Wox
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/en.xaml
new file mode 100644
index 000000000..082f12d2d
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/en.xaml
@@ -0,0 +1,14 @@
+
+
+ Open url:{0}
+ Can't open url:{0}
+
+ URL
+ Open the typed URL from Wox
+
+ Please set your browser path:
+ Choose
+ Application(*.exe)|*.exe|All files|*.*
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/ja.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/ja.xaml
new file mode 100644
index 000000000..b20f1a493
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/ja.xaml
@@ -0,0 +1,11 @@
+
+
+ 次のURLを開く:{0}
+ 次のURLを開くことができません:{0}
+
+ URL
+ 入力したURLをWoxから開くプラグインです。
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/pl.xaml
new file mode 100644
index 000000000..b2be290d1
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/pl.xaml
@@ -0,0 +1,11 @@
+
+
+ Otwórz adres URL: {0}
+ Nie udało się otworzyć adresu: {0}
+
+ URL
+ Otwórz wpisany adres URL z poziomu Woxa
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/tr.xaml
new file mode 100644
index 000000000..05ac1d1a4
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/tr.xaml
@@ -0,0 +1,14 @@
+
+
+ URL'yi Aç: {0}
+ URL Açılamıyor: {0}
+
+ URL
+ Wox'a yazılan URL'leri açar
+
+ Tarayıcınızın konumunu ayarlayın:
+ Seç
+ Programlar (*.exe)|*.exe|Tüm Dosyalar|*.*
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/zh-cn.xaml
new file mode 100644
index 000000000..6a4429b01
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/zh-cn.xaml
@@ -0,0 +1,14 @@
+
+
+ 打开链接:{0}
+ 无法打开链接:{0}
+
+ URL
+ 从Wox打开链接
+
+ 请设置你的浏览器路径:
+ 选择
+ 程序文件(*.exe)|*.exe|所有文件|*.*
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/zh-tw.xaml
new file mode 100644
index 000000000..33e7d1bed
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Languages/zh-tw.xaml
@@ -0,0 +1,11 @@
+
+
+ 開啟連結:{0}
+ 無法開啟連結:{0}
+
+ URL
+ 從 Wox 開啟連結
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.Url/Main.cs
new file mode 100644
index 000000000..ed4e55b86
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Main.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using System.Windows.Controls;
+using Wox.Infrastructure.Storage;
+using Wox.Plugin.SharedCommands;
+
+namespace Wox.Plugin.Url
+{
+ public class Main : ISettingProvider,IPlugin, IPluginI18n, ISavable
+ {
+ //based on https://gist.github.com/dperini/729294
+ private const string urlPattern = "^" +
+ // protocol identifier
+ "(?:(?:https?|ftp)://|)" +
+ // user:pass authentication
+ "(?:\\S+(?::\\S*)?@)?" +
+ "(?:" +
+ // IP address exclusion
+ // private & local networks
+ "(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
+ "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
+ "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" +
+ // IP address dotted notation octets
+ // excludes loopback network 0.0.0.0
+ // excludes reserved space >= 224.0.0.0
+ // excludes network & broacast addresses
+ // (first & last IP address of each class)
+ "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
+ "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
+ "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
+ "|" +
+ // host name
+ "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" +
+ // domain name
+ "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" +
+ // TLD identifier
+ "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" +
+ ")" +
+ // port number
+ "(?::\\d{2,5})?" +
+ // resource path
+ "(?:/\\S*)?" +
+ "$";
+ Regex reg = new Regex(urlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private PluginInitContext context;
+ private readonly Settings _settings;
+ private readonly PluginJsonStorage _storage;
+
+ public Main()
+ {
+ _storage = new PluginJsonStorage();
+ _settings = _storage.Load();
+ }
+
+ public void Save()
+ {
+ _storage.Save();
+ }
+
+ public List Query(Query query)
+ {
+ var raw = query.Search;
+ if (IsURL(raw))
+ {
+ return new List
+ {
+ new Result
+ {
+ Title = raw,
+ SubTitle = string.Format(context.API.GetTranslation("wox_plugin_url_open_url"),raw),
+ IcoPath = "Images/url.png",
+ Score = 8,
+ Action = _ =>
+ {
+ if (!raw.ToLower().StartsWith("http"))
+ {
+ raw = "http://" + raw;
+ }
+ try
+ {
+ if (_settings.OpenInNewBrowserWindow)
+ {
+ raw.NewBrowserWindow(_settings.BrowserPath);
+ }
+ else
+ {
+ raw.NewTabInBrowser(_settings.BrowserPath);
+ }
+
+ return true;
+ }
+ catch(Exception ex)
+ {
+ context.API.ShowMsg(string.Format(context.API.GetTranslation("wox_plugin_url_canot_open_url"), raw));
+ return false;
+ }
+ }
+ }
+ };
+ }
+ return new List(0);
+ }
+
+
+ public Control CreateSettingPanel()
+ {
+ return new SettingsControl(context.API,_settings);
+ }
+
+ public bool IsURL(string raw)
+ {
+ raw = raw.ToLower();
+
+ if (reg.Match(raw).Value == raw) return true;
+
+ if (raw == "localhost" || raw.StartsWith("localhost:") ||
+ raw == "http://localhost" || raw.StartsWith("http://localhost:") ||
+ raw == "https://localhost" || raw.StartsWith("https://localhost:")
+ )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ this.context = context;
+ }
+
+ public string GetTranslatedPluginTitle()
+ {
+ return context.API.GetTranslation("wox_plugin_url_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return context.API.GetTranslation("wox_plugin_url_plugin_description");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.Url/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..83520f2f1
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.Url")]
+[assembly: Guid("ea42b60d-34ff-4656-8ee1-012afa397d3e")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Settings.cs b/src/modules/launcher/Plugins/Wox.Plugin.Url/Settings.cs
new file mode 100644
index 000000000..9ee074afc
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Settings.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Wox.Plugin.Url
+{
+ public class Settings
+ {
+ public string BrowserPath { get; set; }
+
+ public bool OpenInNewBrowserWindow { get; set; } = true;
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/SettingsControl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.Url/SettingsControl.xaml
new file mode 100644
index 000000000..5bbca57f0
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/SettingsControl.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/SettingsControl.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.Url/SettingsControl.xaml.cs
new file mode 100644
index 000000000..50fb4f6ad
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/SettingsControl.xaml.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Microsoft.Win32;
+
+
+namespace Wox.Plugin.Url
+{
+ ///
+ /// SettingsControl.xaml 的交互逻辑
+ ///
+ public partial class SettingsControl : UserControl
+ {
+ private Settings _settings;
+ private IPublicAPI _woxAPI;
+
+ public SettingsControl(IPublicAPI woxAPI,Settings settings)
+ {
+ InitializeComponent();
+ _settings = settings;
+ _woxAPI = woxAPI;
+ browserPathBox.Text = _settings.BrowserPath;
+ NewWindowBrowser.IsChecked = _settings.OpenInNewBrowserWindow;
+ NewTabInBrowser.IsChecked = !_settings.OpenInNewBrowserWindow;
+ }
+
+ private void OnChooseClick(object sender, RoutedEventArgs e)
+ {
+ var fileBrowserDialog = new OpenFileDialog();
+ fileBrowserDialog.Filter = _woxAPI.GetTranslation("wox_plugin_url_plugin_filter"); ;
+ fileBrowserDialog.CheckFileExists = true;
+ fileBrowserDialog.CheckPathExists = true;
+ if (fileBrowserDialog.ShowDialog() == true)
+ {
+ browserPathBox.Text = fileBrowserDialog.FileName;
+ _settings.BrowserPath = fileBrowserDialog.FileName;
+ }
+ }
+
+ private void OnNewBrowserWindowClick(object sender, RoutedEventArgs e)
+ {
+ _settings.OpenInNewBrowserWindow = true;
+ }
+
+ private void OnNewTabClick(object sender, RoutedEventArgs e)
+ {
+ _settings.OpenInNewBrowserWindow = false;
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/Wox.Plugin.Url.csproj b/src/modules/launcher/Plugins/Wox.Plugin.Url/Wox.Plugin.Url.csproj
new file mode 100644
index 000000000..bfdae9200
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/Wox.Plugin.Url.csproj
@@ -0,0 +1,140 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {A3DCCBCA-ACC1-421D-B16E-210896234C26}
+ Library
+ Properties
+ Wox.Plugin.Url
+ Wox.Plugin.Url
+ v4.5.2
+ 512
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.Url\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.Url\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\JetBrains.Annotations.10.3.0\lib\net\JetBrains.Annotations.dll
+ True
+
+
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+ SettingsControl.xaml
+
+
+
+
+ PreserveNewest
+
+
+
+
+ {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ 10.3.0
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.Url/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.Url/plugin.json
new file mode 100644
index 000000000..09c54a7ff
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.Url/plugin.json
@@ -0,0 +1,12 @@
+{
+ "ID":"0308FD86DE0A4DEE8D62B9B535370992",
+ "ActionKeyword":"*",
+ "Name":"URL",
+ "Description":"Open the typed URL from Wox",
+ "Author":"qianlifeng",
+ "Version":"1.0.0",
+ "Language":"csharp",
+ "Website":"http://www.wox.one/plugin",
+ "ExecuteFileName":"Wox.Plugin.Url.dll",
+ "IcoPath":"Images\\url.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/baidu.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/baidu.png
new file mode 100644
index 000000000..2d30b504e
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/baidu.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/bing.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/bing.png
new file mode 100644
index 000000000..0e74ef2d6
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/bing.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/duckduckgo.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/duckduckgo.png
new file mode 100644
index 000000000..7cd1abc61
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/duckduckgo.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/facebook.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/facebook.png
new file mode 100644
index 000000000..f9479fc25
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/facebook.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/gist.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/gist.png
new file mode 100644
index 000000000..dd9fb6f00
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/gist.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/github.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/github.png
new file mode 100644
index 000000000..dd9fb6f00
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/github.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/gmail.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/gmail.png
new file mode 100644
index 000000000..aaeff3020
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/gmail.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google.png
new file mode 100644
index 000000000..897151684
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_drive.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_drive.png
new file mode 100644
index 000000000..ce2346a31
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_drive.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_maps.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_maps.png
new file mode 100644
index 000000000..2413eec58
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_maps.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_translate.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_translate.png
new file mode 100644
index 000000000..63ff5c883
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/google_translate.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/pictures.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/pictures.png
new file mode 100644
index 000000000..7fc14c38e
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/pictures.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/stackoverflow.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/stackoverflow.png
new file mode 100644
index 000000000..ca9027afb
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/stackoverflow.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/twitter.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/twitter.png
new file mode 100644
index 000000000..4003ac7de
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/twitter.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/web_search.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/web_search.png
new file mode 100644
index 000000000..a3f0be1f5
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/web_search.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/wiki.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/wiki.png
new file mode 100644
index 000000000..a58ce1e41
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/wiki.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/wolframalpha.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/wolframalpha.png
new file mode 100644
index 000000000..a62e2c7df
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/wolframalpha.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/yahoo.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/yahoo.png
new file mode 100644
index 000000000..3736ca80a
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/yahoo.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/youtube.png b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/youtube.png
new file mode 100644
index 000000000..3a3f8310a
Binary files /dev/null and b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Images/youtube.png differ
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/de.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/de.xaml
new file mode 100644
index 000000000..9cc70038b
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/de.xaml
@@ -0,0 +1,33 @@
+
+
+ Löschen
+ Bearbeiten
+ Hinzufügen
+ Confirm
+ Aktionsschlüsselwort
+ URL
+ Suche
+ Aktiviere Suchvorschläge
+ Bitte wähle einen Suchdienst
+ Willst du wirklich {0} löschen?
+
+
+
+ Titel
+ Aktivieren
+ Wähle Symbol
+ Symbol
+ Abbrechen
+ Ungültige Internetsuche
+ Bitte Titel eingeben
+ Bitte Aktionsschlüsselwort eingeben
+ Bitte URL eingeben
+ Aktionsschlüsselwort existiert bereits. Bitte gebe ein anderes ein.
+ Erfolgreich
+
+ Internetsuche
+ Stellt die Möglichkeit für Internetsuchen bereit
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/en.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/en.xaml
new file mode 100644
index 000000000..f3d8135ca
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/en.xaml
@@ -0,0 +1,32 @@
+
+
+ Delete
+ Edit
+ Add
+ Confirm
+ Action Keyword
+ URL
+ Search
+ Enable search suggestions
+ Please select a web search
+ Are you sure you want to delete {0}?
+
+
+ Title
+ Enable
+ Select Icon
+ Icon
+ Cancel
+ Invalid web search
+ Please enter a title
+ Please enter an action keyword
+ Please enter a URL
+ Action keyword already exists, please enter a different one
+ Success
+
+ Web Searches
+ Allows to perform web searches
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/ja.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/ja.xaml
new file mode 100644
index 000000000..d82cb1456
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/ja.xaml
@@ -0,0 +1,32 @@
+
+
+ 削除
+ 編集
+ 追加
+ キーワード
+ URL
+ 検索
+ 検索サジェスチョンを有効にする
+ web検索を選択してください
+ 本当に{0}を削除しますか?
+
+
+
+ タイトル
+ 有効
+ アイコンを選択
+ アイコン
+ キャンセル
+ web検索を無効にする
+ タイトルを入力してください
+ キーワードを入力してください
+ URLを入力してください
+ キーワードはすでに存在します。違うキーワードを入力してください
+ 成功
+
+ Web検索
+ Web検索を提供
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/pl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/pl.xaml
new file mode 100644
index 000000000..fb6acf941
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/pl.xaml
@@ -0,0 +1,32 @@
+
+
+ Usuń
+ Edytuj
+ Dodaj
+ Confirm
+ Wyzwalacz
+ Adres URL
+ Szukaj
+ Pokazuj podpowiedzi wyszukiwania
+ Musisz wybrać coś z listy
+ Czy jesteś pewnie że chcesz usunąć {0}?
+
+
+ Tytuł
+ Aktywne
+ Wybierz ikonę
+ Ikona
+ Anuluj
+ Niepoprawne wyszukanie
+ Musisz wpisać tytuł
+ Musisz wpisać wyzwalacz
+ Musisz wpisać adres URL
+ Ten wyzwalacz jest już używany, musisz wybrać inny
+ Sukces
+
+ Wyszukiwarka WWW
+ Szybkie wyszukiwanie na stronach WWW
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/tr.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/tr.xaml
new file mode 100644
index 000000000..1a3951faf
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/tr.xaml
@@ -0,0 +1,32 @@
+
+
+ Sil
+ Düzenle
+ Ekle
+ Onayla
+ Anahtar Kelime
+ URL
+ Ara:
+ Arama önerilerini etkinleştir
+ Lütfen bir web araması seçin
+ {0} aramasını silmek istediğinize emin misiniz?
+
+
+ Başlık
+ Etkin
+ Simge Seç
+ Simge
+ İptal
+ Geçersiz web araması
+ Lütfen bir başlık giriniz
+ Lütfen anahtar kelime giriniz
+ Lütfen bir URL giriniz
+ Anahtar kelime zaten mevcut. Lütfen yeni bir tane seçiniz.
+ Başarılı
+
+ Web Araması
+ Web üzerinde arama yapmanızı sağlar
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/zh-cn.xaml
new file mode 100644
index 000000000..d580fb0f5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/zh-cn.xaml
@@ -0,0 +1,33 @@
+
+
+ 删除
+ 编辑
+ 添加
+ 确认
+ 触发关键字
+ URL
+ 搜索
+ 启用搜索建议
+ 请选择一项
+ 你确定要删除 {0} 吗?
+
+
+
+ 标题
+ 启用
+ 图标
+ 选择图标
+ 取消
+ 非法的网页搜索
+ 请输入标题
+ 请输入触发关键字
+ 请输入URL
+ 触发关键字已经存在,请选择一个新的关键字
+ 操作成功
+
+ 网页搜索
+ 提供网页搜索能力
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/zh-tw.xaml
new file mode 100644
index 000000000..5f1640066
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Languages/zh-tw.xaml
@@ -0,0 +1,33 @@
+
+
+ 刪除
+ 編輯
+ 新增
+ 確定
+ 觸發關鍵字
+ URL
+ 搜尋
+ 啟用搜尋建議
+ 請選擇一項
+ 你確定要刪除 {0} 嗎?
+
+
+
+ 標題
+ 啟用
+ 圖示
+ 選擇圖示
+ 取消
+ 無效的網頁搜尋
+ 請輸入標題
+ 請輸入觸發關鍵字
+ 請輸入 URL
+ 觸發關鍵字已經存在,請選擇一個新的關鍵字
+ 操作成功
+
+ 網頁搜尋
+ 提供網頁搜尋功能
+
+
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Main.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Main.cs
new file mode 100644
index 000000000..429e51cc0
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Main.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using Wox.Infrastructure;
+using Wox.Infrastructure.Storage;
+using Wox.Plugin.SharedCommands;
+
+namespace Wox.Plugin.WebSearch
+{
+ public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IResultUpdated
+ {
+ private PluginInitContext _context;
+
+ private readonly Settings _settings;
+ private readonly SettingsViewModel _viewModel;
+ private CancellationTokenSource _updateSource;
+ private CancellationToken _updateToken;
+
+ public const string Images = "Images";
+ public static string ImagesDirectory;
+
+ private readonly string SearchSourceGlobalPluginWildCardSign = "*";
+
+ public void Save()
+ {
+ _viewModel.Save();
+ }
+
+ public List Query(Query query)
+ {
+ var searchSourceList = new List();
+ var results = new List();
+
+ _updateSource?.Cancel();
+ _updateSource = new CancellationTokenSource();
+ _updateToken = _updateSource.Token;
+
+ _settings.SearchSources.Where(o => (o.ActionKeyword == query.ActionKeyword || o.ActionKeyword == SearchSourceGlobalPluginWildCardSign)
+ && o.Enabled)
+ .ToList()
+ .ForEach(x => searchSourceList.Add(x));
+
+ if (searchSourceList.Any())
+ {
+ foreach (SearchSource searchSource in searchSourceList)
+ {
+ string keyword = string.Empty;
+ keyword = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? query.ToString() : query.Search;
+ var title = keyword;
+ string subtitle = _context.API.GetTranslation("wox_plugin_websearch_search") + " " + searchSource.Title;
+
+ if (string.IsNullOrEmpty(keyword))
+ {
+ var result = new Result
+ {
+ Title = subtitle,
+ SubTitle = string.Empty,
+ IcoPath = searchSource.IconPath
+ };
+ results.Add(result);
+ }
+ else
+ {
+ var result = new Result
+ {
+ Title = title,
+ SubTitle = subtitle,
+ Score = 6,
+ IcoPath = searchSource.IconPath,
+ Action = c =>
+ {
+ if (_settings.OpenInNewBrowser)
+ {
+ searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)).NewBrowserWindow(_settings.BrowserPath);
+ }
+ else
+ {
+ searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)).NewTabInBrowser(_settings.BrowserPath);
+ }
+
+ return true;
+ }
+ };
+
+ results.Add(result);
+ ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs
+ {
+ Results = results,
+ Query = query
+ });
+
+ UpdateResultsFromSuggestion(results, keyword, subtitle, searchSource, query);
+ }
+ }
+ }
+
+ return results;
+ }
+
+ private void UpdateResultsFromSuggestion(List results, string keyword, string subtitle,
+ SearchSource searchSource, Query query)
+ {
+ if (_settings.EnableSuggestion)
+ {
+ const int waittime = 300;
+ var task = Task.Run(async () =>
+ {
+ var suggestions = await Suggestions(keyword, subtitle, searchSource);
+ results.AddRange(suggestions);
+ }, _updateToken);
+
+ if (!task.Wait(waittime))
+ {
+ task.ContinueWith(_ => ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs
+ {
+ Results = results,
+ Query = query
+ }), _updateToken);
+ }
+ }
+ }
+
+ private async Task> Suggestions(string keyword, string subtitle, SearchSource searchSource)
+ {
+ var source = _settings.SelectedSuggestion;
+ if (source != null)
+ {
+ var suggestions = await source.Suggestions(keyword);
+ var resultsFromSuggestion = suggestions.Select(o => new Result
+ {
+ Title = o,
+ SubTitle = subtitle,
+ Score = 5,
+ IcoPath = searchSource.IconPath,
+ Action = c =>
+ {
+ if (_settings.OpenInNewBrowser)
+ {
+ searchSource.Url.Replace("{q}", Uri.EscapeDataString(o)).NewBrowserWindow(_settings.BrowserPath);
+ }
+ else
+ {
+ searchSource.Url.Replace("{q}", Uri.EscapeDataString(o)).NewTabInBrowser(_settings.BrowserPath);
+ }
+
+ return true;
+ }
+ });
+ return resultsFromSuggestion;
+ }
+ return new List();
+ }
+
+ public Main()
+ {
+ _viewModel = new SettingsViewModel();
+ _settings = _viewModel.Settings;
+ }
+
+ public void Init(PluginInitContext context)
+ {
+ _context = context;
+ var pluginDirectory = _context.CurrentPluginMetadata.PluginDirectory;
+ var bundledImagesDirectory = Path.Combine(pluginDirectory, Images);
+ ImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginDirectory, Images);
+ Helper.ValidateDataDirectory(bundledImagesDirectory, ImagesDirectory);
+ }
+
+ #region ISettingProvider Members
+
+ public Control CreateSettingPanel()
+ {
+ return new SettingsControl(_context, _viewModel);
+ }
+
+ #endregion
+
+ public string GetTranslatedPluginTitle()
+ {
+ return _context.API.GetTranslation("wox_plugin_websearch_plugin_name");
+ }
+
+ public string GetTranslatedPluginDescription()
+ {
+ return _context.API.GetTranslation("wox_plugin_websearch_plugin_description");
+ }
+
+ public event ResultUpdatedEventHandler ResultsUpdated;
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Properties/AssemblyInfo.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..26736bdf4
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Wox.Plugin.WebSearch")]
+[assembly: Guid("42c17706-44ba-4549-ab66-7bd994706cd1")]
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSource.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSource.cs
new file mode 100644
index 000000000..5e8b8bdab
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSource.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using System.Windows.Media;
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using Wox.Infrastructure.Image;
+
+namespace Wox.Plugin.WebSearch
+{
+ public class SearchSource : BaseModel
+ {
+ public const string DefaultIcon = "web_search.png";
+ public string Title { get; set; }
+ public string ActionKeyword { get; set; }
+
+ [NotNull]
+ public string Icon { get; set; } = DefaultIcon;
+
+ ///
+ /// All icon should be put under Images directory
+ ///
+ [NotNull]
+ [JsonIgnore]
+ internal string IconPath => Path.Combine(Main.ImagesDirectory, Icon);
+
+ [JsonIgnore]
+ public ImageSource Image => ImageLoader.Load(IconPath);
+
+ public string Url { get; set; }
+ public bool Enabled { get; set; }
+
+ public SearchSource DeepCopy()
+ {
+ var webSearch = new SearchSource
+ {
+ Title = string.Copy(Title),
+ ActionKeyword = string.Copy(ActionKeyword),
+ Url = string.Copy(Url),
+ Icon = string.Copy(Icon),
+ Enabled = Enabled
+ };
+ return webSearch;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceSetting.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceSetting.xaml
new file mode 100644
index 000000000..5a1e5bce5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceSetting.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceSetting.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceSetting.xaml.cs
new file mode 100644
index 000000000..9bc31dd03
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceSetting.xaml.cs
@@ -0,0 +1,148 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Windows;
+using Microsoft.Win32;
+using Wox.Core.Plugin;
+
+namespace Wox.Plugin.WebSearch
+{
+ public partial class SearchSourceSettingWindow
+ {
+ private readonly SearchSource _oldSearchSource;
+ private SearchSource _searchSource;
+ private IList _searchSources;
+ private Action _action;
+ private PluginInitContext _context;
+ private IPublicAPI _api;
+ private SearchSourceViewModel _viewModel;
+
+
+ public SearchSourceSettingWindow(IList sources, PluginInitContext context, SearchSource old)
+ {
+ _oldSearchSource = old;
+ _viewModel = new SearchSourceViewModel {SearchSource = old.DeepCopy()};
+ Initilize(sources, context, Action.Edit);
+ }
+
+ public SearchSourceSettingWindow(IList sources, PluginInitContext context)
+ {
+ _viewModel = new SearchSourceViewModel {SearchSource = new SearchSource()};
+ Initilize(sources, context, Action.Add);
+ }
+
+ private void Initilize(IList sources, PluginInitContext context, Action action)
+ {
+ InitializeComponent();
+ DataContext = _viewModel;
+ _searchSource = _viewModel.SearchSource;
+ _searchSources = sources;
+ _action = action;
+ _context = context;
+ _api = _context.API;
+ }
+
+ private void OnCancelButtonClick(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ private void OnConfirmButtonClick(object sender, RoutedEventArgs e)
+ {
+ if (string.IsNullOrEmpty(_searchSource.Title))
+ {
+ var warning = _api.GetTranslation("wox_plugin_websearch_input_title");
+ MessageBox.Show(warning);
+ }
+ else if (string.IsNullOrEmpty(_searchSource.Url))
+ {
+ var warning = _api.GetTranslation("wox_plugin_websearch_input_url");
+ MessageBox.Show(warning);
+ }
+ else if (string.IsNullOrEmpty(_searchSource.ActionKeyword))
+ {
+ var warning = _api.GetTranslation("wox_plugin_websearch_input_action_keyword");
+ MessageBox.Show(warning);
+ }
+ else if (_action == Action.Add)
+ {
+ AddSearchSource();
+ }
+ else if (_action == Action.Edit)
+ {
+ EditSearchSource();
+ }
+ }
+
+ private void AddSearchSource()
+ {
+ var keyword = _searchSource.ActionKeyword;
+ if (!PluginManager.ActionKeywordRegistered(keyword))
+ {
+ var id = _context.CurrentPluginMetadata.ID;
+ PluginManager.AddActionKeyword(id, keyword);
+
+ _searchSources.Add(_searchSource);
+
+ var info = _api.GetTranslation("success");
+ MessageBox.Show(info);
+ Close();
+ }
+ else
+ {
+ var warning = _api.GetTranslation("newActionKeywordsHasBeenAssigned");
+ MessageBox.Show(warning);
+ }
+ }
+
+ private void EditSearchSource()
+ {
+ var newKeyword = _searchSource.ActionKeyword;
+ var oldKeyword = _oldSearchSource.ActionKeyword;
+ if (!PluginManager.ActionKeywordRegistered(newKeyword) || oldKeyword == newKeyword)
+ {
+ var id = _context.CurrentPluginMetadata.ID;
+ PluginManager.ReplaceActionKeyword(id, oldKeyword, newKeyword);
+
+ var index = _searchSources.IndexOf(_oldSearchSource);
+ _searchSources[index] = _searchSource;
+
+ var info = _api.GetTranslation("success");
+ MessageBox.Show(info);
+ Close();
+ }
+ else
+ {
+ var warning = _api.GetTranslation("newActionKeywordsHasBeenAssigned");
+ MessageBox.Show(warning);
+ }
+ }
+
+ private void OnSelectIconClick(object sender, RoutedEventArgs e)
+ {
+ var directory = Main.ImagesDirectory;
+ const string filter = "Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp";
+ var dialog = new OpenFileDialog {InitialDirectory = directory, Filter = filter};
+
+ var result = dialog.ShowDialog();
+ if (result == true)
+ {
+ var fullpath = dialog.FileName;
+ if (!string.IsNullOrEmpty(fullpath))
+ {
+ _searchSource.Icon = Path.GetFileName(fullpath);
+ if (!File.Exists(_searchSource.IconPath))
+ {
+ _searchSource.Icon = SearchSource.DefaultIcon;
+ MessageBox.Show($"The file should be put under {directory}");
+ }
+ }
+ }
+ }
+ }
+
+ public enum Action
+ {
+ Add,
+ Edit
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceViewModel.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceViewModel.cs
new file mode 100644
index 000000000..d57225c81
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SearchSourceViewModel.cs
@@ -0,0 +1,7 @@
+namespace Wox.Plugin.WebSearch
+{
+ public class SearchSourceViewModel
+ {
+ public SearchSource SearchSource { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Settings.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Settings.cs
new file mode 100644
index 000000000..a5c59dad0
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Settings.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.ObjectModel;
+using Newtonsoft.Json;
+using Wox.Plugin.WebSearch.SuggestionSources;
+
+namespace Wox.Plugin.WebSearch
+{
+ public class Settings : BaseModel
+ {
+ public Settings()
+ {
+ SelectedSuggestion = Suggestions[0];
+ if (SearchSources.Count > 0)
+ {
+ SelectedSearchSource = SearchSources[0];
+ }
+ }
+
+ public ObservableCollection SearchSources { get; set; } = new ObservableCollection
+ {
+ new SearchSource
+ {
+ Title = "Google",
+ ActionKeyword = "g",
+ Icon = "google.png",
+ Url = "https://www.google.com/search?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Google Scholar",
+ ActionKeyword = "sc",
+ Icon = "google.png",
+ Url = "https://scholar.google.com/scholar?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Wikipedia",
+ ActionKeyword = "wiki",
+ Icon = "wiki.png",
+ Url = "https://en.wikipedia.org/wiki/{q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "FindIcon",
+ ActionKeyword = "findicon",
+ Icon = "pictures.png",
+ Url = "http://findicons.com/search/{q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Facebook",
+ ActionKeyword = "facebook",
+ Icon = "facebook.png",
+ Url = "https://www.facebook.com/search/?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Twitter",
+ ActionKeyword = "twitter",
+ Icon = "twitter.png",
+ Url = "https://twitter.com/search?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Google Maps",
+ ActionKeyword = "maps",
+ Icon = "google_maps.png",
+ Url = "https://maps.google.com/maps?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Google Translate",
+ ActionKeyword = "translate",
+ Icon = "google_translate.png",
+ Url = "https://translate.google.com/#auto|en|{q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Duckduckgo",
+ ActionKeyword = "duckduckgo",
+ Icon = "duckduckgo.png",
+ Url = "https://duckduckgo.com/?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Github",
+ ActionKeyword = "github",
+ Icon = "github.png",
+ Url = "https://github.com/search?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Github Gist",
+ ActionKeyword = "gist",
+ Icon = "gist.png",
+ Url = "https://gist.github.com/search?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Gmail",
+ ActionKeyword = "gmail",
+ Icon = "gmail.png",
+ Url = "https://mail.google.com/mail/ca/u/0/#apps/{q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Google Drive",
+ ActionKeyword = "drive",
+ Icon = "google_drive.png",
+ Url = "https://drive.google.com/?hl=en&tab=bo#search/{q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Wolframalpha",
+ ActionKeyword = "wolframalpha",
+ Icon = "wolframalpha.png",
+ Url = "https://www.wolframalpha.com/input/?i={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Stackoverflow",
+ ActionKeyword = "stackoverflow",
+ Icon = "stackoverflow.png",
+ Url = "https://stackoverflow.com/search?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "I'm Feeling Lucky",
+ ActionKeyword = "lucky",
+ Icon = "google.png",
+ Url = "https://google.com/search?q={q}&btnI=I",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Google Image",
+ ActionKeyword = "image",
+ Icon = "google.png",
+ Url = "https://www.google.com/search?q={q}&tbm=isch",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Youtube",
+ ActionKeyword = "youtube",
+ Icon = "youtube.png",
+ Url = "https://www.youtube.com/results?search_query={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Bing",
+ ActionKeyword = "bing",
+ Icon = "bing.png",
+ Url = "https://www.bing.com/search?q={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Yahoo",
+ ActionKeyword = "yahoo",
+ Icon = "yahoo.png",
+ Url = "https://www.search.yahoo.com/search?p={q}",
+ Enabled = true
+ },
+ new SearchSource
+ {
+ Title = "Baidu",
+ ActionKeyword = "bd",
+ Icon = "baidu.png",
+ Url = "https://www.baidu.com/#ie=UTF-8&wd={q}",
+ Enabled = true
+ }
+ };
+
+ [JsonIgnore]
+ public SearchSource SelectedSearchSource { get; set; }
+
+ public bool EnableSuggestion { get; set; }
+
+ [JsonIgnore]
+ public SuggestionSource[] Suggestions { get; set; } = {
+ new Google(),
+ new Baidu()
+ };
+
+ [JsonIgnore]
+ public SuggestionSource SelectedSuggestion { get; set; }
+
+ ///
+ /// used to store Settings.json only
+ ///
+ public string Suggestion
+ {
+ get { return SelectedSuggestion.ToString(); }
+ set
+ {
+ foreach (var s in Suggestions)
+ {
+ if (string.Equals(s.ToString(), value, StringComparison.OrdinalIgnoreCase))
+ {
+ SelectedSuggestion = s;
+ }
+ }
+ }
+ }
+
+ public string BrowserPath { get; set; }
+
+ public bool OpenInNewBrowser { get; set; } = true;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsControl.xaml b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsControl.xaml
new file mode 100644
index 000000000..0cc580da0
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsControl.xaml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsControl.xaml.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsControl.xaml.cs
new file mode 100644
index 000000000..b95ace883
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsControl.xaml.cs
@@ -0,0 +1,87 @@
+using Microsoft.Win32;
+using System.Windows;
+using System.Windows.Controls;
+using Wox.Core.Plugin;
+
+namespace Wox.Plugin.WebSearch
+{
+ ///
+ /// Interaction logic for WebSearchesSetting.xaml
+ ///
+ public partial class SettingsControl : UserControl
+ {
+ private readonly Settings _settings;
+ private readonly PluginInitContext _context;
+
+ public SettingsControl(PluginInitContext context, SettingsViewModel viewModel)
+ {
+ InitializeComponent();
+ _context = context;
+ _settings = viewModel.Settings;
+ DataContext = viewModel;
+ browserPathBox.Text = _settings.BrowserPath;
+ NewWindowBrowser.IsChecked = _settings.OpenInNewBrowser;
+ NewTabInBrowser.IsChecked = !_settings.OpenInNewBrowser;
+ }
+
+ private void OnAddSearchSearchClick(object sender, RoutedEventArgs e)
+ {
+ var setting = new SearchSourceSettingWindow(_settings.SearchSources, _context);
+ setting.ShowDialog();
+ }
+
+ private void OnDeleteSearchSearchClick(object sender, RoutedEventArgs e)
+ {
+ if (_settings.SelectedSearchSource != null)
+ {
+ var selected = _settings.SelectedSearchSource;
+ var warning = _context.API.GetTranslation("wox_plugin_websearch_delete_warning");
+ var formated = string.Format(warning, selected.Title);
+
+ var result = MessageBox.Show(formated, string.Empty, MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ var id = _context.CurrentPluginMetadata.ID;
+ PluginManager.RemoveActionKeyword(id, selected.ActionKeyword);
+ _settings.SearchSources.Remove(selected);
+ }
+ }
+ }
+
+ private void OnEditSearchSourceClick(object sender, RoutedEventArgs e)
+ {
+ if (_settings.SelectedSearchSource != null)
+ {
+ var webSearch = new SearchSourceSettingWindow
+ (
+ _settings.SearchSources, _context, _settings.SelectedSearchSource
+ );
+
+ webSearch.ShowDialog();
+ }
+ }
+
+ private void OnNewBrowserWindowClick(object sender, RoutedEventArgs e)
+ {
+ _settings.OpenInNewBrowser = true;
+ }
+
+ private void OnNewTabClick(object sender, RoutedEventArgs e)
+ {
+ _settings.OpenInNewBrowser = false;
+ }
+
+ private void OnChooseClick(object sender, RoutedEventArgs e)
+ {
+ var fileBrowserDialog = new OpenFileDialog();
+ fileBrowserDialog.Filter = "Application(*.exe)|*.exe|All files|*.*";
+ fileBrowserDialog.CheckFileExists = true;
+ fileBrowserDialog.CheckPathExists = true;
+ if (fileBrowserDialog.ShowDialog() == true)
+ {
+ browserPathBox.Text = fileBrowserDialog.FileName;
+ _settings.BrowserPath = fileBrowserDialog.FileName;
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsViewModel.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsViewModel.cs
new file mode 100644
index 000000000..5d50c3926
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SettingsViewModel.cs
@@ -0,0 +1,22 @@
+using Wox.Infrastructure.Storage;
+
+namespace Wox.Plugin.WebSearch
+{
+ public class SettingsViewModel
+ {
+ private readonly PluginJsonStorage _storage;
+
+ public SettingsViewModel()
+ {
+ _storage = new PluginJsonStorage();
+ Settings = _storage.Load();
+ }
+
+ public Settings Settings { get; set; }
+
+ public void Save()
+ {
+ _storage.Save();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Baidu.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Baidu.cs
new file mode 100644
index 000000000..7d58a57f5
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Baidu.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Wox.Infrastructure.Http;
+using Wox.Infrastructure.Logger;
+
+namespace Wox.Plugin.WebSearch.SuggestionSources
+{
+ public class Baidu : SuggestionSource
+ {
+ private readonly Regex _reg = new Regex("window.baidu.sug\\((.*)\\)");
+
+ public override async Task> Suggestions(string query)
+ {
+ string result;
+
+ try
+ {
+ const string api = "http://suggestion.baidu.com/su?json=1&wd=";
+ result = await Http.Get(api + Uri.EscapeUriString(query), "GB2312");
+ }
+ catch (WebException e)
+ {
+ Log.Exception("|Baidu.Suggestions|Can't get suggestion from baidu", e);
+ return new List();
+ }
+
+ if (string.IsNullOrEmpty(result)) return new List();
+ Match match = _reg.Match(result);
+ if (match.Success)
+ {
+ JContainer json;
+ try
+ {
+ json = JsonConvert.DeserializeObject(match.Groups[1].Value) as JContainer;
+ }
+ catch (JsonSerializationException e)
+ {
+ Log.Exception("|Baidu.Suggestions|can't parse suggestions", e);
+ return new List();
+ }
+
+ if (json != null)
+ {
+ var results = json["s"] as JArray;
+ if (results != null)
+ {
+ return results.OfType().Select(o => o.Value).OfType().ToList();
+ }
+ }
+ }
+
+ return new List();
+ }
+
+ public override string ToString()
+ {
+ return "Baidu";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Google.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Google.cs
new file mode 100644
index 000000000..d7a908969
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/Google.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Wox.Infrastructure.Http;
+using Wox.Infrastructure.Logger;
+
+namespace Wox.Plugin.WebSearch.SuggestionSources
+{
+ public class Google : SuggestionSource
+ {
+ public override async Task> Suggestions(string query)
+ {
+ string result;
+ try
+ {
+ const string api = "https://www.google.com/complete/search?output=chrome&q=";
+ result = await Http.Get(api + Uri.EscapeUriString(query));
+ }
+ catch (WebException e)
+ {
+ Log.Exception("|Google.Suggestions|Can't get suggestion from google", e);
+ return new List();
+ ;
+ }
+ if (string.IsNullOrEmpty(result)) return new List();
+ JContainer json;
+ try
+ {
+ json = JsonConvert.DeserializeObject(result) as JContainer;
+ }
+ catch (JsonSerializationException e)
+ {
+ Log.Exception("|Google.Suggestions|can't parse suggestions", e);
+ return new List();
+ }
+ if (json != null)
+ {
+ var results = json[1] as JContainer;
+ if (results != null)
+ {
+ return results.OfType().Select(o => o.Value).OfType().ToList();
+ }
+ }
+ return new List();
+ }
+
+ public override string ToString()
+ {
+ return "Google";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs
new file mode 100644
index 000000000..173efcfaa
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/SuggestionSources/SuggestionSource.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Wox.Plugin.WebSearch.SuggestionSources
+{
+ public abstract class SuggestionSource
+ {
+ public abstract Task> Suggestions(string query);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj
new file mode 100644
index 000000000..7073487b7
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj
@@ -0,0 +1,206 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {403B57F2-1856-4FC7-8A24-36AB346B763E}
+ Library
+ Properties
+ Wox.Plugin.WebSearch
+ Wox.Plugin.WebSearch
+ v4.5.2
+ 512
+ ..\..\
+
+
+
+ true
+ full
+ false
+ ..\..\Output\Debug\Plugins\Wox.Plugin.WebSearch\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ ..\..\Output\Release\Plugins\Wox.Plugin.WebSearch\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SolutionAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+ SettingsControl.xaml
+
+
+
+ SearchSourceSetting.xaml
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+ PreserveNewest
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ {B749F0DB-8E75-47DB-9E5E-265D16D0C0D2}
+ Wox.Core
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+ {8451ecdd-2ea4-4966-bb0a-7bbc40138e80}
+ Wox.Plugin
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+ 10.3.0
+
+
+ 9.0.1
+
+
+ 4.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/plugin.json b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/plugin.json
new file mode 100644
index 000000000..2fe7fe292
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/plugin.json
@@ -0,0 +1,33 @@
+{
+ "ID": "565B73353DBF4806919830B9202EE3BF",
+ "ActionKeywords": [
+ "g",
+ "wiki",
+ "findicon",
+ "facebook",
+ "twitter",
+ "maps",
+ "translate",
+ "duckduckgo",
+ "github",
+ "gist",
+ "gmail",
+ "drive",
+ "wolframalpha",
+ "stackoverflow",
+ "lucky",
+ "image",
+ "youtube",
+ "bing",
+ "yahoo",
+ "bd"
+ ],
+ "Name": "Web Searches",
+ "Description": "Provide the web search ability",
+ "Author": "qianlifeng",
+ "Version": "1.0.0",
+ "Language": "csharp",
+ "Website": "http://www.wox.one/plugin",
+ "ExecuteFileName": "Wox.Plugin.WebSearch.dll",
+ "IcoPath": "Images\\web_search.png"
+}
diff --git a/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/setting.json b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/setting.json
new file mode 100644
index 000000000..c31d02ae6
--- /dev/null
+++ b/src/modules/launcher/Plugins/Wox.Plugin.WebSearch/setting.json
@@ -0,0 +1,139 @@
+{
+ "WebSearches": [
+ {
+ "Title": "Google",
+ "ActionKeyword": "g",
+ "IconPath": "Images\\google.png",
+ "Url": "https://www.google.com/search?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Wikipedia",
+ "ActionKeyword": "wiki",
+ "IconPath": "Images\\wiki.png",
+ "Url": "http://en.wikipedia.org/wiki/{q}",
+ "Enabled": true
+ },
+ {
+ "Title": "FindIcon",
+ "ActionKeyword": "findicon",
+ "IconPath": "Images\\pictures.png",
+ "Url": "http://findicons.com/search/{q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Facebook",
+ "ActionKeyword": "facebook",
+ "IconPath": "Images\\facebook.png",
+ "Url": "http://www.facebook.com/search/?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Twitter",
+ "ActionKeyword": "twitter",
+ "IconPath": "Images\\twitter.png",
+ "Url": "http://twitter.com/search?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Google Maps",
+ "ActionKeyword": "maps",
+ "IconPath": "Images\\google_maps.png",
+ "Url": "http://maps.google.com/maps?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Google Translate",
+ "ActionKeyword": "translate",
+ "IconPath": "Images\\google_translate.png",
+ "Url": "http://translate.google.com/#auto|en|{q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Duckduckgo",
+ "ActionKeyword": "duckduckgo",
+ "IconPath": "Images\\duckduckgo.png",
+ "Url": "https://duckduckgo.com/?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Github",
+ "ActionKeyword": "github",
+ "IconPath": "Images\\github.png",
+ "Url": "https://github.com/search?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Github Gist",
+ "ActionKeyword": "gist",
+ "IconPath": "Images\\gist.png",
+ "Url": "https://gist.github.com/search?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Gmail",
+ "ActionKeyword": "gmail",
+ "IconPath": "Images\\gmail.png",
+ "Url": "https://mail.google.com/mail/ca/u/0/#apps/{q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Google Drive",
+ "ActionKeyword": "drive",
+ "IconPath": "Images\\google_drive.png",
+ "Url": "http://drive.google.com/?hl=en&tab=bo#search/{q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Wolframalpha",
+ "ActionKeyword": "wolframalpha",
+ "IconPath": "Images\\wolframalpha.png",
+ "Url": "http://www.wolframalpha.com/input/?i={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Stackoverflow",
+ "ActionKeyword": "stackoverflow",
+ "IconPath": "Images\\stackoverflow.png",
+ "Url": "http://stackoverflow.com/search?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "I'm Feeling Lucky",
+ "ActionKeyword": "lucky",
+ "IconPath": "Images\\google.png",
+ "Url": "http://google.com/search?q={q}&btnI=I",
+ "Enabled": true
+ },
+ {
+ "Title": "Google Image",
+ "ActionKeyword": "image",
+ "IconPath": "Images\\google.png",
+ "Url": "https://www.google.com/search?q={q}&tbm=isch",
+ "Enabled": true
+ },
+ {
+ "Title": "Youtube",
+ "ActionKeyword": "youtube",
+ "IconPath": "Images\\youtube.png",
+ "Url": "http://www.youtube.com/results?search_query={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Bing",
+ "ActionKeyword": "bing",
+ "IconPath": "Images\\bing.png",
+ "URL": "https://www.bing.com/search?q={q}",
+ "Enabled": true
+ },
+ {
+ "Title": "Yahoo",
+ "ActionKeyword": "yahoo",
+ "IconPath": "Images\\yahoo.png",
+ "URL": "http://www.search.yahoo.com/search?p={q}",
+ "Enabled": true
+ }
+ ],
+ "EnableWebSearchSuggestion": false,
+ "WebSearchSuggestionSource": "Google"
+}
\ No newline at end of file
diff --git a/src/modules/launcher/README.md b/src/modules/launcher/README.md
new file mode 100644
index 000000000..00069024a
--- /dev/null
+++ b/src/modules/launcher/README.md
@@ -0,0 +1,97 @@
+WoX
+===
+
+![Maintenance](https://img.shields.io/maintenance/yes/2020)
+[![GitHub release (latest by date)](https://img.shields.io/github/v/release/jjw24/wox)](https://github.com/jjw24/Wox/releases/latest)
+![GitHub Release Date](https://img.shields.io/github/release-date/jjw24/wox)
+![GitHub commits since latest release](https://img.shields.io/github/commits-since/jjw24/wox/v1.3.524)
+[![Build Status](https://dev.azure.com/Wox-Launcher/Wox/_apis/build/status/jjw24.Wox?branchName=master)](https://dev.azure.com/Wox-Launcher/Wox/_build/latest?definitionId=1&branchName=master)
+[![Github All Releases](https://img.shields.io/github/downloads/jjw24/Wox/total.svg)](https://github.com/jjw24/Wox/releases)
+[![RamenBless](https://cdn.rawgit.com/LunaGao/BlessYourCodeTag/master/tags/ramen.svg)](https://github.com/LunaGao/BlessYourCodeTag)
+
+**WoX** is a launcher for Windows that simply works. It's an alternative to [Alfred](https://www.alfredapp.com/) and [Launchy](http://www.launchy.net/). You can call it Windows omni-eXecutor if you want a long name.
+
+![demo](http://i.imgur.com/DtxNBJi.gif)
+
+Features
+--------
+
+- Search for everything—applications, **uwp**, folders, files and more.
+- Use *pinyin* to search for programs / 支持用 **拼音** 搜索程序
+ - wyy / wangyiyun → 网易云音乐
+- Keyword plugin search `g search_term`
+- Search youtube, google, twitter and many more
+- Build custom themes at http://www.wox.one/theme/builder
+- Install plugins from http://www.wox.one/plugin
+
+**New from this fork:**
+- Portable mode
+- Drastically improved search experience
+- Search all subfolders and files
+- Option to always run CMD or Powershell as administrator
+- Run CMD, Powershell and programs as a different user
+- Manage what programs should be loaded
+- Highlighting of how results are matched during query search
+- Open web search result as a tab or a new window
+- Automatic update
+- Reload/update plugin data
+
+Installation
+------------
+
+View new features released from this fork since Wox v1.3.524: [new releases](https://github.com/jjw24/Wox/releases)
+
+To install this fork's version of Wox, you can **download** it [here](https://github.com/jjw24/Wox/releases/latest).
+
+To install the upstream version:
+
+Download `Wox-xxx.exe` from [releases](https://github.com/Wox-launcher/Wox/releases). Latest as of now is [`1.3.524`](https://github.com/Wox-launcher/Wox/releases/download/v1.3.524/Wox-1.3.524.exe) ([`1.3.578`](https://github.com/Wox-launcher/Wox/releases/download/v1.3.578/Wox-1.3.578.exe) for preview channel)
+
+Windows may complain about security due to code not being signed. This will be fixed later.
+
+Versions marked as **pre-release** are unstable pre-release versions.
+
+- Requirements:
+ - .net >= 4.5.2
+ - If you want to integrate with [everything](https://www.voidtools.com/): `.exe` installer + use x64 if your windows is x64 + everything service is running. Supported version is 1.3.4.686
+ - If you use python plugins, install [python3](https://www.python.org/downloads/): `.exe` installer + add it to `%PATH%` or set it in WoX settings
+
+Usage
+-----
+
+- Launch: Alt+Space
+- Context Menu: Ctrl+O
+- Cancel/Return: Esc
+- Install/Uninstall plugin: type `wpm install/uninstall`
+- Reset: delete `%APPDATA%\Wox`
+- Log: `%APPDATA%\Wox\Logs`
+
+Contribution
+------------
+
+- First and most importantly, star it!
+- Read [Coding Style](https://github.com/Wox-launcher/Wox/wiki/Coding-Style)
+- Send PR to **dev** branch
+- I'd appreciate if you could solve [help_needed](https://github.com/Wox-launcher/Wox/issues?q=is%3Aopen+is%3Aissue+label%3Ahelp_needed) labeled issue
+- Don't hesitate to ask questions in the [issues](https://github.com/Wox-launcher/Wox/issues)
+
+Build
+-----
+
+Install Visual Studio 2015/2017/2019
+
+This project requires Windows 10 SDK:
+
+ VS 2015:
+ - Tick all Windows 10 sdk options
+
+ VS 2017/2019 and later:
+ - Last Windows 10 SDK which [supported](https://github.com/Wox-launcher/Wox/pull/1827#commitcomment-26475392) UwpDesktop is version 10.0.14393.795. It is needed to compile "Programs" Plugin (UWP.cs), you will see the "References" of Plugin.Programs as broken if you use a later SDK version.
+ - This SDK cannot be installed via VS 2019 installer.
+ - Download and install [Windows 10 SDK version 10.0.14393.795](https://go.microsoft.com/fwlink/p/?LinkId=838916).
+
+Documentation
+-------------
+- [Wiki](https://github.com/Wox-launcher/Wox/wiki)
+- Outdated doc: [WoX doc](http://doc.wox.one).
+- Just ask questions in [issues](https://github.com/Wox-launcher/Wox/issues) for now.
diff --git a/src/modules/launcher/Scripts/post_build.ps1 b/src/modules/launcher/Scripts/post_build.ps1
new file mode 100644
index 000000000..eb302a6c6
--- /dev/null
+++ b/src/modules/launcher/Scripts/post_build.ps1
@@ -0,0 +1,140 @@
+param(
+ [string]$config = "Release",
+ [string]$solution
+)
+Write-Host "Config: $config"
+
+function Build-Version {
+ if ([string]::IsNullOrEmpty($env:APPVEYOR_BUILD_VERSION)) {
+ $v = "1.2.0"
+ } else {
+ $v = $env:APPVEYOR_BUILD_VERSION
+ }
+
+ Write-Host "Build Version: $v"
+ return $v
+}
+
+function Build-Path {
+ if (![string]::IsNullOrEmpty($env:APPVEYOR_BUILD_FOLDER)) {
+ $p = $env:APPVEYOR_BUILD_FOLDER
+ } elseif (![string]::IsNullOrEmpty($solution)) {
+ $p = $solution
+ } else {
+ $p = Get-Location
+ }
+
+ Write-Host "Build Folder: $p"
+ Set-Location $p
+
+ return $p
+}
+
+function Copy-Resources ($path, $config) {
+ $project = "$path\Wox"
+ $output = "$path\Output"
+ $target = "$output\$config"
+ Copy-Item -Recurse -Force $project\Themes\* $target\Themes\
+ Copy-Item -Recurse -Force $project\Images\* $target\Images\
+ Copy-Item -Recurse -Force $path\Plugins\HelloWorldPython $target\Plugins\HelloWorldPython
+ Copy-Item -Recurse -Force $path\JsonRPC $target\JsonRPC
+ Copy-Item -Force $path\packages\squirrel*\tools\Squirrel.exe $output\Update.exe
+}
+
+function Delete-Unused ($path, $config) {
+ $target = "$path\Output\$config"
+ $included = Get-ChildItem $target -Filter "*.dll"
+ foreach ($i in $included){
+ Remove-Item -Path $target\Plugins -Include $i -Recurse
+ Write-Host "Deleting duplicated $i"
+ }
+ Remove-Item -Path $target -Include "*.xml" -Recurse
+}
+
+function Validate-Directory ($output) {
+ New-Item $output -ItemType Directory -Force
+}
+
+function Pack-Nuget ($path, $version, $output) {
+ Write-Host "Begin build nuget library"
+
+ $spec = "$path\Scripts\wox.plugin.nuspec"
+ Write-Host "nuspec path: $spec"
+ Write-Host "Output path: $output"
+
+ Nuget pack $spec -Version $version -OutputDirectory $output
+
+ Write-Host "End build nuget library"
+}
+
+function Zip-Release ($path, $version, $output) {
+ Write-Host "Begin zip release"
+
+ $input = "$path\Output\Release"
+ Write-Host "Input path: $input"
+ $file = "$output\Wox-$version.zip"
+ Write-Host "Filename: $file"
+
+ [Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem")
+ [System.IO.Compression.ZipFile]::CreateFromDirectory($input, $file)
+
+ Write-Host "End zip release"
+}
+
+function Pack-Squirrel-Installer ($path, $version, $output) {
+ # msbuild based installer generation is not working in appveyor, not sure why
+ Write-Host "Begin pack squirrel installer"
+
+ $spec = "$path\Scripts\wox.nuspec"
+ Write-Host "nuspec path: $spec"
+ $input = "$path\Output\Release"
+ Write-Host "Input path: $input"
+ Nuget pack $spec -Version $version -Properties Configuration=Release -BasePath $input -OutputDirectory $output
+
+ $nupkg = "$output\Wox.$version.nupkg"
+ Write-Host "nupkg path: $nupkg"
+ $icon = "$path\Wox\Resources\app.ico"
+ Write-Host "icon: $icon"
+ # Squirrel.com: https://github.com/Squirrel/Squirrel.Windows/issues/369
+ New-Alias Squirrel $path\packages\squirrel*\tools\Squirrel.exe -Force
+ # why we need Write-Output: https://github.com/Squirrel/Squirrel.Windows/issues/489#issuecomment-156039327
+ # directory of releaseDir in fucking squirrel can't be same as directory ($nupkg) in releasify
+ $temp = "$output\Temp"
+
+ Squirrel --releasify $nupkg --releaseDir $temp --setupIcon $icon --no-msi | Write-Output
+ Move-Item $temp\* $output -Force
+ Remove-Item $temp
+
+ $file = "$output\Wox-$version.exe"
+ Write-Host "Filename: $file"
+
+ Move-Item "$output\Setup.exe" $file -Force
+
+ Write-Host "End pack squirrel installer"
+}
+
+function Main {
+ $p = Build-Path
+ $v = Build-Version
+ Copy-Resources $p $config
+
+ if ($config -eq "Release"){
+
+ Delete-Unused $p $config
+ $o = "$p\Output\Packages"
+ Validate-Directory $o
+ New-Alias Nuget $p\packages\NuGet.CommandLine.*\tools\NuGet.exe -Force
+ Pack-Squirrel-Installer $p $v $o
+
+ $isInCI = $env:APPVEYOR
+ if ($isInCI) {
+ Pack-Nuget $p $v $o
+ Zip-Release $p $v $o
+ }
+
+ Write-Host "List output directory"
+ Get-ChildItem $o
+ }
+}
+
+Main
\ No newline at end of file
diff --git a/src/modules/launcher/Scripts/wox.nuspec b/src/modules/launcher/Scripts/wox.nuspec
new file mode 100644
index 000000000..681099663
--- /dev/null
+++ b/src/modules/launcher/Scripts/wox.nuspec
@@ -0,0 +1,16 @@
+
+
+
+ Wox
+ Wox
+ $version$
+ happlebao
+ https://github.com/Wox-launcher/Wox
+ https://raw.githubusercontent.com/Wox-launcher/Wox/master/Wox/Images/app.png
+ false
+ Wox - a launcher for windows
+
+
+
+
+
diff --git a/src/modules/launcher/Scripts/wox.plugin.nuspec b/src/modules/launcher/Scripts/wox.plugin.nuspec
new file mode 100644
index 000000000..d88c91af8
--- /dev/null
+++ b/src/modules/launcher/Scripts/wox.plugin.nuspec
@@ -0,0 +1,16 @@
+
+
+
+ Wox.Plugin
+ $version$
+ qianlifeng
+ https://github.com/Wox-launcher/Wox/blob/master/LICENSE
+ https://github.com/Wox-launcher/Wox
+ false
+ Reference this library if you want to develop a wox plugin
+ wox
+
+
+
+
+
diff --git a/src/modules/launcher/SolutionAssemblyInfo.cs b/src/modules/launcher/SolutionAssemblyInfo.cs
new file mode 100644
index 000000000..17bc04fb3
--- /dev/null
+++ b/src/modules/launcher/SolutionAssemblyInfo.cs
@@ -0,0 +1,21 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+#if DEBUG
+
+[assembly: AssemblyConfiguration("Debug")]
+[assembly: AssemblyDescription("Debug build, https://github.com/Wox-launcher/Wox")]
+#else
+[assembly: AssemblyConfiguration("Release")]
+[assembly: AssemblyDescription("Release build, https://github.com/Wox-launcher/Wox")]
+#endif
+
+[assembly: AssemblyCompany("Wox")]
+[assembly: AssemblyProduct("Wox")]
+[assembly: AssemblyCopyright("The MIT License (MIT)")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+[assembly: AssemblyVersion("1.2.0")]
+[assembly: AssemblyFileVersion("1.2.0.0")]
+[assembly: AssemblyInformationalVersion("1.2.0")]
\ No newline at end of file
diff --git a/src/modules/launcher/Wox.Core/FodyWeavers.xml b/src/modules/launcher/Wox.Core/FodyWeavers.xml
new file mode 100644
index 000000000..bb0f322ee
--- /dev/null
+++ b/src/modules/launcher/Wox.Core/FodyWeavers.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/launcher/Wox.Core/Plugin/ExecutablePlugin.cs b/src/modules/launcher/Wox.Core/Plugin/ExecutablePlugin.cs
new file mode 100644
index 000000000..2753b00b3
--- /dev/null
+++ b/src/modules/launcher/Wox.Core/Plugin/ExecutablePlugin.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Diagnostics;
+using Wox.Plugin;
+
+namespace Wox.Core.Plugin
+{
+ internal class ExecutablePlugin : JsonRPCPlugin
+ {
+ private readonly ProcessStartInfo _startInfo;
+ public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
+
+ public ExecutablePlugin(string filename)
+ {
+ _startInfo = new ProcessStartInfo
+ {
+ FileName = filename,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+ }
+
+ protected override string ExecuteQuery(Query query)
+ {
+ JsonRPCServerRequestModel request = new JsonRPCServerRequestModel
+ {
+ Method = "query",
+ Parameters = new object[] { query.Search },
+ };
+
+ _startInfo.Arguments = $"\"{request}\"";
+
+ return Execute(_startInfo);
+ }
+
+ protected override string ExecuteCallback(JsonRPCRequestModel rpcRequest)
+ {
+ _startInfo.Arguments = $"\"{rpcRequest}\"";
+ return Execute(_startInfo);
+ }
+
+ protected override string ExecuteContextMenu(Result selectedResult) {
+ JsonRPCServerRequestModel request = new JsonRPCServerRequestModel {
+ Method = "contextmenu",
+ Parameters = new object[] { selectedResult.ContextData },
+ };
+
+ _startInfo.Arguments = $"\"{request}\"";
+
+ return Execute(_startInfo);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonPRCModel.cs b/src/modules/launcher/Wox.Core/Plugin/JsonPRCModel.cs
new file mode 100644
index 000000000..3a0253e55
--- /dev/null
+++ b/src/modules/launcher/Wox.Core/Plugin/JsonPRCModel.cs
@@ -0,0 +1,135 @@
+
+/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins,
+ * like python or other self-execute program. But, we added addtional infos (proxy and so on) into rpc request. Also, we didn't use the
+ * "id" and "jsonrpc" in the request, since it's not so useful in our request model.
+ *
+ * When execute a query:
+ * Wox -------JsonRPCServerRequestModel--------> client
+ * Wox <------JsonRPCQueryResponseModel--------- client
+ *
+ * When execute a action (which mean user select an item in reulst item):
+ * Wox -------JsonRPCServerRequestModel--------> client
+ * Wox <------JsonRPCResponseModel-------------- client
+ *
+ */
+
+using System.Collections.Generic;
+using System.Linq;
+using Wox.Plugin;
+
+namespace Wox.Core.Plugin
+{
+ public class JsonRPCErrorModel
+ {
+ public int Code { get; set; }
+
+ public string Message { get; set; }
+
+ public string Data { get; set; }
+ }
+
+ public class JsonRPCModelBase
+ {
+ public int Id { get; set; }
+ }
+
+ public class JsonRPCResponseModel : JsonRPCModelBase
+ {
+ public string Result { get; set; }
+
+ public JsonRPCErrorModel Error { get; set; }
+ }
+
+ public class JsonRPCQueryResponseModel : JsonRPCResponseModel
+ {
+ public new List Result { get; set; }
+ }
+
+ public class JsonRPCRequestModel : JsonRPCModelBase
+ {
+ public string Method { get; set; }
+
+ public object[] Parameters { get; set; }
+
+ public override string ToString()
+ {
+ string rpc = string.Empty;
+ if (Parameters != null && Parameters.Length > 0)
+ {
+ string parameters = Parameters.Aggregate("[", (current, o) => current + (GetParameterByType(o) + ","));
+ parameters = parameters.Substring(0, parameters.Length - 1) + "]";
+ rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":{1}", Method, parameters);
+ }
+ else
+ {
+ rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":[]", Method);
+ }
+
+ return rpc;
+
+ }
+
+ private string GetParameterByType(object parameter)
+ {
+ if (parameter == null) {
+ return "null";
+ }
+ if (parameter is string)
+ {
+ return string.Format(@"\""{0}\""", ReplaceEscapes(parameter.ToString()));
+ }
+ if (parameter is int || parameter is float || parameter is double)
+ {
+ return string.Format(@"{0}", parameter);
+ }
+ if (parameter is bool)
+ {
+ return string.Format(@"{0}", parameter.ToString().ToLower());
+ }
+ return parameter.ToString();
+ }
+
+ private string ReplaceEscapes(string str)
+ {
+ return str.Replace(@"\", @"\\") //Escapes in ProcessStartInfo
+ .Replace(@"\", @"\\") //Escapes itself when passed to client
+ .Replace(@"""", @"\\""""");
+ }
+ }
+
+ ///
+ /// Json RPC Request that Wox sent to client
+ ///
+ public class JsonRPCServerRequestModel : JsonRPCRequestModel
+ {
+ public override string ToString()
+ {
+ string rpc = base.ToString();
+ return rpc + "}";
+ }
+ }
+
+ ///
+ /// Json RPC Request(in query response) that client sent to Wox
+ ///
+ public class JsonRPCClientRequestModel : JsonRPCRequestModel
+ {
+ public bool DontHideAfterAction { get; set; }
+
+ public override string ToString()
+ {
+ string rpc = base.ToString();
+ return rpc + "}";
+ }
+ }
+
+ ///
+ /// Represent the json-rpc result item that client send to Wox
+ /// Typically, we will send back this request model to client after user select the result item
+ /// But if the request method starts with "Wox.", we will invoke the public APIs we expose.
+ ///
+ public class JsonRPCResult : Result
+ {
+ public JsonRPCClientRequestModel JsonRPCAction { get; set; }
+ }
+}
diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCPlugin.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCPlugin.cs
new file mode 100644
index 000000000..5057ae472
--- /dev/null
+++ b/src/modules/launcher/Wox.Core/Plugin/JsonRPCPlugin.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using Newtonsoft.Json;
+using Wox.Infrastructure.Exception;
+using Wox.Infrastructure.Logger;
+using Wox.Plugin;
+
+namespace Wox.Core.Plugin
+{
+ ///
+ /// Represent the plugin that using JsonPRC
+ /// every JsonRPC plugin should has its own plugin instance
+ ///
+ internal abstract class JsonRPCPlugin : IPlugin, IContextMenu
+ {
+ protected PluginInitContext context;
+ public const string JsonRPC = "JsonRPC";
+
+ ///
+ /// The language this JsonRPCPlugin support
+ ///
+ public abstract string SupportedLanguage { get; set; }
+
+ protected abstract string ExecuteQuery(Query query);
+ protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest);
+ protected abstract string ExecuteContextMenu(Result selectedResult);
+
+ public List Query(Query query)
+ {
+ string output = ExecuteQuery(query);
+ try
+ {
+ return DeserializedResult(output);
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e);
+ return null;
+ }
+ }
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ string output = ExecuteContextMenu(selectedResult);
+ try
+ {
+ return DeserializedResult(output);
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|JsonRPCPlugin.LoadContextMenus|Exception on result <{selectedResult}>", e);
+ return null;
+ }
+ }
+
+ private List DeserializedResult(string output)
+ {
+ if (!String.IsNullOrEmpty(output))
+ {
+ List results = new List();
+
+ JsonRPCQueryResponseModel queryResponseModel = JsonConvert.DeserializeObject(output);
+ if (queryResponseModel.Result == null) return null;
+
+ foreach (JsonRPCResult result in queryResponseModel.Result)
+ {
+ JsonRPCResult result1 = result;
+ result.Action = c =>
+ {
+ if (result1.JsonRPCAction == null) return false;
+
+ if (!String.IsNullOrEmpty(result1.JsonRPCAction.Method))
+ {
+ if (result1.JsonRPCAction.Method.StartsWith("Wox."))
+ {
+ ExecuteWoxAPI(result1.JsonRPCAction.Method.Substring(4), result1.JsonRPCAction.Parameters);
+ }
+ else
+ {
+ string actionReponse = ExecuteCallback(result1.JsonRPCAction);
+ JsonRPCRequestModel jsonRpcRequestModel = JsonConvert.DeserializeObject(actionReponse);
+ if (jsonRpcRequestModel != null
+ && !String.IsNullOrEmpty(jsonRpcRequestModel.Method)
+ && jsonRpcRequestModel.Method.StartsWith("Wox."))
+ {
+ ExecuteWoxAPI(jsonRpcRequestModel.Method.Substring(4), jsonRpcRequestModel.Parameters);
+ }
+ }
+ }
+ return !result1.JsonRPCAction.DontHideAfterAction;
+ };
+ results.Add(result);
+ }
+ return results;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private void ExecuteWoxAPI(string method, object[] parameters)
+ {
+ MethodInfo methodInfo = PluginManager.API.GetType().GetMethod(method);
+ if (methodInfo != null)
+ {
+ try
+ {
+ methodInfo.Invoke(PluginManager.API, parameters);
+ }
+ catch (Exception)
+ {
+#if (DEBUG)
+ {
+ throw;
+ }
+#endif
+ }
+ }
+ }
+
+ ///
+ /// Execute external program and return the output
+ ///
+ ///
+ ///
+ ///
+ protected string Execute(string fileName, string arguments)
+ {
+ ProcessStartInfo start = new ProcessStartInfo();
+ start.FileName = fileName;
+ start.Arguments = arguments;
+ start.UseShellExecute = false;
+ start.CreateNoWindow = true;
+ start.RedirectStandardOutput = true;
+ start.RedirectStandardError = true;
+ return Execute(start);
+ }
+
+ protected string Execute(ProcessStartInfo startInfo)
+ {
+ try
+ {
+ using (var process = Process.Start(startInfo))
+ {
+ if (process != null)
+ {
+ using (var standardOutput = process.StandardOutput)
+ {
+ var result = standardOutput.ReadToEnd();
+ if (string.IsNullOrEmpty(result))
+ {
+ using (var standardError = process.StandardError)
+ {
+ var error = standardError.ReadToEnd();
+ if (!string.IsNullOrEmpty(error))
+ {
+ Log.Error($"|JsonRPCPlugin.Execute|{error}");
+ return string.Empty;
+ }
+ else
+ {
+ Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error.");
+ return string.Empty;
+ }
+ }
+ }
+ else if (result.StartsWith("DEBUG:"))
+ {
+ MessageBox.Show(new Form { TopMost = true }, result.Substring(6));
+ return string.Empty;
+ }
+ else
+ {
+ return result;
+ }
+ }
+ }
+ else
+ {
+ Log.Error("|JsonRPCPlugin.Execute|Can't start new process");
+ return string.Empty;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|JsonRPCPlugin.Execute|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>", e);
+ return string.Empty;
+ }
+ }
+
+ public void Init(PluginInitContext ctx)
+ {
+ context = ctx;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Wox.Core/Plugin/PluginConfig.cs b/src/modules/launcher/Wox.Core/Plugin/PluginConfig.cs
new file mode 100644
index 000000000..8721a85a1
--- /dev/null
+++ b/src/modules/launcher/Wox.Core/Plugin/PluginConfig.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Wox.Infrastructure.Exception;
+using Wox.Infrastructure.Logger;
+using Wox.Plugin;
+
+namespace Wox.Core.Plugin
+{
+
+ internal abstract class PluginConfig
+ {
+ private const string PluginConfigName = "plugin.json";
+ private static readonly List PluginMetadatas = new List();
+
+ ///
+ /// Parse plugin metadata in giving directories
+ ///
+ ///
+ ///
+ public static List Parse(string[] pluginDirectories)
+ {
+ PluginMetadatas.Clear();
+ var directories = pluginDirectories.SelectMany(Directory.GetDirectories);
+ ParsePluginConfigs(directories);
+ return PluginMetadatas;
+ }
+
+ private static void ParsePluginConfigs(IEnumerable directories)
+ {
+ // todo use linq when diable plugin is implmented since parallel.foreach + list is not thread saft
+ foreach (var directory in directories)
+ {
+ if (File.Exists(Path.Combine(directory, "NeedDelete.txt")))
+ {
+ try
+ {
+ Directory.Delete(directory, true);
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|PluginConfig.ParsePLuginConfigs|Can't delete <{directory}>", e);
+ }
+ }
+ else
+ {
+ PluginMetadata metadata = GetPluginMetadata(directory);
+ if (metadata != null)
+ {
+ PluginMetadatas.Add(metadata);
+ }
+ }
+ }
+ }
+
+ private static PluginMetadata GetPluginMetadata(string pluginDirectory)
+ {
+ string configPath = Path.Combine(pluginDirectory, PluginConfigName);
+ if (!File.Exists(configPath))
+ {
+ Log.Error($"|PluginConfig.GetPluginMetadata|Didn't find config file <{configPath}>");
+ return null;
+ }
+
+ PluginMetadata metadata;
+ try
+ {
+ metadata = JsonConvert.DeserializeObject(File.ReadAllText(configPath));
+ metadata.PluginDirectory = pluginDirectory;
+ // for plugins which doesn't has ActionKeywords key
+ metadata.ActionKeywords = metadata.ActionKeywords ?? new List { metadata.ActionKeyword };
+ // for plugin still use old ActionKeyword
+ metadata.ActionKeyword = metadata.ActionKeywords?[0];
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|PluginConfig.GetPluginMetadata|invalid json for config <{configPath}>", e);
+ return null;
+ }
+
+
+ if (!AllowedLanguage.IsAllowed(metadata.Language))
+ {
+ Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>");
+ return null;
+ }
+
+ if (!File.Exists(metadata.ExecuteFilePath))
+ {
+ Log.Error($"|PluginConfig.GetPluginMetadata|execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
+ return null;
+ }
+
+ return metadata;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/launcher/Wox.Core/Plugin/PluginInstaller.cs b/src/modules/launcher/Wox.Core/Plugin/PluginInstaller.cs
new file mode 100644
index 000000000..ffdb0aa75
--- /dev/null
+++ b/src/modules/launcher/Wox.Core/Plugin/PluginInstaller.cs
@@ -0,0 +1,202 @@
+using System;
+using System.IO;
+using System.Windows;
+using ICSharpCode.SharpZipLib.Zip;
+using Newtonsoft.Json;
+using Wox.Plugin;
+
+namespace Wox.Core.Plugin
+{
+ internal class PluginInstaller
+ {
+ internal static void Install(string path)
+ {
+ if (File.Exists(path))
+ {
+ string tempFoler = Path.Combine(Path.GetTempPath(), "wox\\plugins");
+ if (Directory.Exists(tempFoler))
+ {
+ Directory.Delete(tempFoler, true);
+ }
+ UnZip(path, tempFoler, true);
+
+ string iniPath = Path.Combine(tempFoler, "plugin.json");
+ if (!File.Exists(iniPath))
+ {
+ MessageBox.Show("Install failed: plugin config is missing");
+ return;
+ }
+
+ PluginMetadata plugin = GetMetadataFromJson(tempFoler);
+ if (plugin == null || plugin.Name == null)
+ {
+ MessageBox.Show("Install failed: plugin config is invalid");
+ return;
+ }
+
+ string pluginFolerPath = Infrastructure.Constant.PluginsDirectory;
+
+ string newPluginName = plugin.Name
+ .Replace("/", "_")
+ .Replace("\\", "_")
+ .Replace(":", "_")
+ .Replace("<", "_")
+ .Replace(">", "_")
+ .Replace("?", "_")
+ .Replace("*", "_")
+ .Replace("|", "_")
+ + "-" + Guid.NewGuid();
+ string newPluginPath = Path.Combine(pluginFolerPath, newPluginName);
+ string content = $"Do you want to install following plugin?{Environment.NewLine}{Environment.NewLine}" +
+ $"Name: {plugin.Name}{Environment.NewLine}" +
+ $"Version: {plugin.Version}{Environment.NewLine}" +
+ $"Author: {plugin.Author}";
+ PluginPair existingPlugin = PluginManager.GetPluginForId(plugin.ID);
+
+ if (existingPlugin != null)
+ {
+ content = $"Do you want to update following plugin?{Environment.NewLine}{Environment.NewLine}" +
+ $"Name: {plugin.Name}{Environment.NewLine}" +
+ $"Old Version: {existingPlugin.Metadata.Version}" +
+ $"{Environment.NewLine}New Version: {plugin.Version}" +
+ $"{Environment.NewLine}Author: {plugin.Author}";
+ }
+
+ var result = MessageBox.Show(content, "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (existingPlugin != null && Directory.Exists(existingPlugin.Metadata.PluginDirectory))
+ {
+ //when plugin is in use, we can't delete them. That's why we need to make plugin folder a random name
+ File.Create(Path.Combine(existingPlugin.Metadata.PluginDirectory, "NeedDelete.txt")).Close();
+ }
+
+ UnZip(path, newPluginPath, true);
+ Directory.Delete(tempFoler, true);
+
+ //exsiting plugins may be has loaded by application,
+ //if we try to delelte those kind of plugins, we will get a error that indicate the
+ //file is been used now.
+ //current solution is to restart wox. Ugly.
+ //if (MainWindow.Initialized)
+ //{
+ // Plugins.Initialize();
+ //}
+ if (MessageBox.Show($"You have installed plugin {plugin.Name} successfully.{Environment.NewLine}" +
+ "Restart Wox to take effect?",
+ "Install plugin", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
+ {
+ PluginManager.API.RestarApp();
+ }
+ }
+ }
+ }
+
+ private static PluginMetadata GetMetadataFromJson(string pluginDirectory)
+ {
+ string configPath = Path.Combine(pluginDirectory, "plugin.json");
+ PluginMetadata metadata;
+
+ if (!File.Exists(configPath))
+ {
+ return null;
+ }
+
+ try
+ {
+ metadata = JsonConvert.DeserializeObject(File.ReadAllText(configPath));
+ metadata.PluginDirectory = pluginDirectory;
+ }
+ catch (Exception)
+ {
+ string error = $"Parse plugin config {configPath} failed: json format is not valid";
+#if (DEBUG)
+ {
+ throw new Exception(error);
+ }
+#endif
+ return null;
+ }
+
+
+ if (!AllowedLanguage.IsAllowed(metadata.Language))
+ {
+ string error = $"Parse plugin config {configPath} failed: invalid language {metadata.Language}";
+#if (DEBUG)
+ {
+ throw new Exception(error);
+ }
+#endif
+ return null;
+ }
+ if (!File.Exists(metadata.ExecuteFilePath))
+ {
+ string error = $"Parse plugin config {configPath} failed: ExecuteFile {metadata.ExecuteFilePath} didn't exist";
+#if (DEBUG)
+ {
+ throw new Exception(error);
+ }
+#endif
+ return null;
+ }
+
+ return metadata;
+ }
+
+ ///
+ /// unzip
+ ///
+ /// The ziped file.
+ /// The STR directory.
+ /// overwirte
+ private static void UnZip(string zipedFile, string strDirectory, bool overWrite)
+ {
+ if (strDirectory == "")
+ strDirectory = Directory.GetCurrentDirectory();
+ if (!strDirectory.EndsWith("\\"))
+ strDirectory = strDirectory + "\\";
+
+ using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipedFile)))
+ {
+ ZipEntry theEntry;
+
+ while ((theEntry = s.GetNextEntry()) != null)
+ {
+ string directoryName = "";
+ string pathToZip = "";
+ pathToZip = theEntry.Name;
+
+ if (pathToZip != "")
+ directoryName = Path.GetDirectoryName(pathToZip) + "\\";
+
+ string fileName = Path.GetFileName(pathToZip);
+
+ Directory.CreateDirectory(strDirectory + directoryName);
+
+ if (fileName != "")
+ {
+ if ((File.Exists(strDirectory + directoryName + fileName) && overWrite) || (!File.Exists(strDirectory + directoryName + fileName)))
+ {
+ using (FileStream streamWriter = File.Create(strDirectory + directoryName + fileName))
+ {
+ byte[] data = new byte[2048];
+ while (true)
+ {
+ int size = s.Read(data, 0, data.Length);
+
+ if (size > 0)
+ streamWriter.Write(data, 0, size);
+ else
+ break;
+ }
+ streamWriter.Close();
+ }
+ }
+ }
+ }
+
+ s.Close();
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs b/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs
new file mode 100644
index 000000000..0166690b7
--- /dev/null
+++ b/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs
@@ -0,0 +1,313 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Wox.Infrastructure;
+using Wox.Infrastructure.Logger;
+using Wox.Infrastructure.Storage;
+using Wox.Infrastructure.UserSettings;
+using Wox.Plugin;
+
+namespace Wox.Core.Plugin
+{
+ ///
+ /// The entry for managing Wox plugins
+ ///
+ public static class PluginManager
+ {
+ private static IEnumerable _contextMenuPlugins;
+
+ ///
+ /// Directories that will hold Wox plugin directory
+ ///
+
+ public static List AllPlugins { get; private set; }
+ public static readonly List GlobalPlugins = new List();
+ public static readonly Dictionary NonGlobalPlugins = new Dictionary();
+
+ public static IPublicAPI API { private set; get; }
+
+ // todo happlebao, this should not be public, the indicator function should be embeded
+ public static PluginsSettings Settings;
+ private static List _metadatas;
+ private static readonly string[] Directories = { Constant.PreinstalledDirectory, Constant.PluginsDirectory };
+
+ private static void ValidateUserDirectory()
+ {
+ if (!Directory.Exists(Constant.PluginsDirectory))
+ {
+ Directory.CreateDirectory(Constant.PluginsDirectory);
+ }
+ }
+
+ private static void DeletePythonBinding()
+ {
+ const string binding = "wox.py";
+ var directory = Constant.PluginsDirectory;
+ foreach (var subDirectory in Directory.GetDirectories(directory))
+ {
+ var path = Path.Combine(subDirectory, binding);
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+ }
+ }
+
+ public static void Save()
+ {
+ foreach (var plugin in AllPlugins)
+ {
+ var savable = plugin.Plugin as ISavable;
+ savable?.Save();
+ }
+ }
+
+ public static void ReloadData()
+ {
+ foreach(var plugin in AllPlugins)
+ {
+ var reloadablePlugin = plugin.Plugin as IReloadable;
+ reloadablePlugin?.ReloadData();
+ }
+ }
+
+ static PluginManager()
+ {
+ ValidateUserDirectory();
+ // force old plugins use new python binding
+ DeletePythonBinding();
+ }
+
+ ///
+ /// because InitializePlugins needs API, so LoadPlugins needs to be called first
+ /// todo happlebao The API should be removed
+ ///
+ ///
+ public static void LoadPlugins(PluginsSettings settings)
+ {
+ _metadatas = PluginConfig.Parse(Directories);
+ Settings = settings;
+ Settings.UpdatePluginSettings(_metadatas);
+ AllPlugins = PluginsLoader.Plugins(_metadatas, Settings);
+ }
+
+ ///
+ /// Call initialize for all plugins
+ ///
+ /// return the list of failed to init plugins or null for none
+ public static void InitializePlugins(IPublicAPI api)
+ {
+ API = api;
+ var failedPlugins = new ConcurrentQueue();
+ Parallel.ForEach(AllPlugins, pair =>
+ {
+ try
+ {
+ var milliseconds = Stopwatch.Debug($"|PluginManager.InitializePlugins|Init method time cost for <{pair.Metadata.Name}>", () =>
+ {
+ pair.Plugin.Init(new PluginInitContext
+ {
+ CurrentPluginMetadata = pair.Metadata,
+ API = API
+ });
+ });
+ pair.Metadata.InitTime += milliseconds;
+ Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
+ }
+ catch (Exception e)
+ {
+ Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e);
+ pair.Metadata.Disabled = true;
+ failedPlugins.Enqueue(pair);
+ }
+ });
+
+ _contextMenuPlugins = GetPluginsForInterface();
+ foreach (var plugin in AllPlugins)
+ {
+ if (IsGlobalPlugin(plugin.Metadata))
+ GlobalPlugins.Add(plugin);
+
+ // Plugins may have multiple ActionKeywords, eg. WebSearch
+ plugin.Metadata.ActionKeywords.Where(x => x != Query.GlobalPluginWildcardSign)
+ .ToList()
+ .ForEach(x => NonGlobalPlugins[x] = plugin);
+ }
+
+ if (failedPlugins.Any())
+ {
+ var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
+ API.ShowMsg($"Fail to Init Plugins", $"Plugins: {failed} - fail to load and would be disabled, please contact plugin creator for help", "", false);
+ }
+ }
+
+ public static void InstallPlugin(string path)
+ {
+ PluginInstaller.Install(path);
+ }
+
+ public static List ValidPluginsForQuery(Query query)
+ {
+ if (NonGlobalPlugins.ContainsKey(query.ActionKeyword))
+ {
+ var plugin = NonGlobalPlugins[query.ActionKeyword];
+ return new List { plugin };
+ }
+ else
+ {
+ return GlobalPlugins;
+ }
+ }
+
+ public static List QueryForPlugin(PluginPair pair, Query query)
+ {
+ try
+ {
+ List results = null;
+ var metadata = pair.Metadata;
+ var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () =>
+ {
+ results = pair.Plugin.Query(query) ?? new List();
+ UpdatePluginMetadata(results, metadata, query);
+ });
+ metadata.QueryCount += 1;
+ metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
+ return results;
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
+ return new List();
+ }
+ }
+
+ public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query)
+ {
+ foreach (var r in results)
+ {
+ r.PluginDirectory = metadata.PluginDirectory;
+ r.PluginID = metadata.ID;
+ r.OriginQuery = query;
+ }
+ }
+
+ private static bool IsGlobalPlugin(PluginMetadata metadata)
+ {
+ return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);
+ }
+
+ ///
+ /// get specified plugin, return null if not found
+ ///
+ ///
+ ///
+ public static PluginPair GetPluginForId(string id)
+ {
+ return AllPlugins.FirstOrDefault(o => o.Metadata.ID == id);
+ }
+
+ public static IEnumerable GetPluginsForInterface() where T : IFeatures
+ {
+ return AllPlugins.Where(p => p.Plugin is T);
+ }
+
+ public static List GetContextMenusForPlugin(Result result)
+ {
+ var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID);
+ if (pluginPair != null)
+ {
+ var metadata = pluginPair.Metadata;
+ var plugin = (IContextMenu)pluginPair.Plugin;
+
+ try
+ {
+ var results = plugin.LoadContextMenus(result);
+ foreach (var r in results)
+ {
+ r.PluginDirectory = metadata.PluginDirectory;
+ r.PluginID = metadata.ID;
+ r.OriginQuery = result.OriginQuery;
+ }
+ return results;
+ }
+ catch (Exception e)
+ {
+ Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{metadata.Name}>", e);
+ return new List();
+ }
+ }
+ else
+ {
+ return new List();
+ }
+
+ }
+
+ public static bool ActionKeywordRegistered(string actionKeyword)
+ {
+ if (actionKeyword != Query.GlobalPluginWildcardSign &&
+ NonGlobalPlugins.ContainsKey(actionKeyword))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///