diff --git a/Plugins/Wox.Plugin.BrowserBookmark/Main.cs b/Plugins/Wox.Plugin.BrowserBookmark/Main.cs index 11aa3f9f2f..9bf4c885b3 100644 --- a/Plugins/Wox.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Wox.Plugin.BrowserBookmark/Main.cs @@ -8,7 +8,7 @@ namespace Wox.Plugin.BrowserBookmark { private PluginInitContext context; - // TODO: periodically refresh the cache? + // TODO: periodically refresh the Cache? private List cachedBookmarks = new List(); public void Init(PluginInitContext context) diff --git a/Plugins/Wox.Plugin.FindFile/FindFileContextMenuStorage.cs b/Plugins/Wox.Plugin.FindFile/FindFileContextMenuStorage.cs new file mode 100644 index 0000000000..7bb3b61a7f --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/FindFileContextMenuStorage.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Wox.Infrastructure.Storage; + +namespace Wox.Plugin.FindFile +{ + public class FindFileContextMenuStorage : BaseStorage + { + [JsonProperty] + public List ContextMenus = new List(); + + protected override string ConfigName + { + get { return "FindFileContextMenu"; } + } + + protected override FindFileContextMenuStorage LoadDefaultConfig() + { + ContextMenus = new List() + { + new ContextMenu() + { + Name = "Open Containing Folder", + Command = "explorer.exe", + Argument = " /select \"{path}\"" + } + }; + return this; + } + } + + public class ContextMenu + { + [JsonProperty] + public string Name { get; set; } + + [JsonProperty] + public string Command { get; set; } + + [JsonProperty] + public string Argument { get; set; } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/Images/file.png b/Plugins/Wox.Plugin.FindFile/Images/file.png new file mode 100644 index 0000000000..2913d69623 Binary files /dev/null and b/Plugins/Wox.Plugin.FindFile/Images/file.png differ diff --git a/Plugins/Wox.Plugin.FindFile/Images/find.png b/Plugins/Wox.Plugin.FindFile/Images/find.png new file mode 100644 index 0000000000..f4a7b2a000 Binary files /dev/null and b/Plugins/Wox.Plugin.FindFile/Images/find.png differ diff --git a/Plugins/Wox.Plugin.FindFile/Images/folder.png b/Plugins/Wox.Plugin.FindFile/Images/folder.png new file mode 100644 index 0000000000..330cb2e4bf Binary files /dev/null and b/Plugins/Wox.Plugin.FindFile/Images/folder.png differ diff --git a/Plugins/Wox.Plugin.FindFile/Images/list.png b/Plugins/Wox.Plugin.FindFile/Images/list.png new file mode 100644 index 0000000000..ab96e07812 Binary files /dev/null and b/Plugins/Wox.Plugin.FindFile/Images/list.png differ diff --git a/Plugins/Wox.Plugin.FindFile/Images/open.png b/Plugins/Wox.Plugin.FindFile/Images/open.png new file mode 100644 index 0000000000..6006d12104 Binary files /dev/null and b/Plugins/Wox.Plugin.FindFile/Images/open.png differ diff --git a/Plugins/Wox.Plugin.FindFile/Images/warning.png b/Plugins/Wox.Plugin.FindFile/Images/warning.png new file mode 100644 index 0000000000..b1b5f0acd3 Binary files /dev/null and b/Plugins/Wox.Plugin.FindFile/Images/warning.png differ diff --git a/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearchRecord.cs b/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearchRecord.cs new file mode 100644 index 0000000000..cc3a7ba76c --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearchRecord.cs @@ -0,0 +1,22 @@ +namespace Wox.Plugin.FindFile.MFTSearch +{ + public class MFTSearchRecord + { + private USNRecord usn; + + public MFTSearchRecord(USNRecord usn) + { + this.usn = usn; + } + + public string FullPath + { + get { return usn.FullPath; } + } + + public bool IsFolder + { + get { return usn.IsFolder; } + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearcher.cs b/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearcher.cs new file mode 100644 index 0000000000..39198c4d64 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearcher.cs @@ -0,0 +1,258 @@ +/* + * Thanks to the https://github.com/yiwenshengmei/MyEverything, we can bring MFT search to Wox + * + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; + +namespace Wox.Plugin.FindFile.MFTSearch +{ + + public class MFTSearcher + { + private static MFTSearcherCache cache = new MFTSearcherCache(); + private static VolumeMonitor monitor = new VolumeMonitor(); + + private static void IndexVolume(string volume) + { + cache.EnsureVolumeExistInHashTable(volume); + EnumerateVolume(volume, cache.VolumeRecords[volume]); + monitor.Monitor(volume,cache); + } + + public static void IndexAllVolumes() + { + foreach (DriveInfo drive in DriveInfo.GetDrives()) + { + IndexVolume(drive.Name.Replace("\\", "")); + } + } + + public static long IndexedFileCount + { + get { return cache.RecordsCount; } + } + + public static List Search(string item) + { + if (string.IsNullOrEmpty(item)) return new List(); + + List found = cache.FindByName(item, 100); + found.ForEach(x => FillPath(x.VolumeName, x, cache)); + return found.ConvertAll(o => new MFTSearchRecord(o)); + } + + private static void AddVolumeRootRecord(string volumeName, Dictionary files) + { + string rightVolumeName = string.Concat("\\\\.\\", volumeName); + rightVolumeName = string.Concat(rightVolumeName, Path.DirectorySeparatorChar); + IntPtr hRoot = PInvokeWin32.CreateFile(rightVolumeName, + 0, + PInvokeWin32.FILE_SHARE_READ | PInvokeWin32.FILE_SHARE_WRITE, + IntPtr.Zero, + PInvokeWin32.OPEN_EXISTING, + PInvokeWin32.FILE_FLAG_BACKUP_SEMANTICS, + IntPtr.Zero); + + if (hRoot.ToInt32() != PInvokeWin32.INVALID_HANDLE_VALUE) + { + PInvokeWin32.BY_HANDLE_FILE_INFORMATION fi = new PInvokeWin32.BY_HANDLE_FILE_INFORMATION(); + bool bRtn = PInvokeWin32.GetFileInformationByHandle(hRoot, out fi); + if (bRtn) + { + UInt64 fileIndexHigh = (UInt64)fi.FileIndexHigh; + UInt64 indexRoot = (fileIndexHigh << 32) | fi.FileIndexLow; + + files.Add(indexRoot, new USNRecord + { + FRN = indexRoot, + Name = volumeName, + ParentFrn = 0, + IsVolumeRoot = true, + IsFolder = true, + VolumeName = volumeName + }); + } + else + { + throw new IOException("GetFileInformationbyHandle() returned invalid handle", + new Win32Exception(Marshal.GetLastWin32Error())); + } + PInvokeWin32.CloseHandle(hRoot); + } + else + { + throw new IOException("Unable to get root frn entry", new Win32Exception(Marshal.GetLastWin32Error())); + } + } + + private static void EnumerateVolume(string volumeName, Dictionary files) + { + IntPtr medBuffer = IntPtr.Zero; + IntPtr pVolume = IntPtr.Zero; + try + { + AddVolumeRootRecord(volumeName, files); + pVolume = GetVolumeJournalHandle(volumeName); + EnableVomuleJournal(pVolume); + + SetupMFTEnumInBuffer(ref medBuffer, pVolume); + EnumerateFiles(volumeName, pVolume, medBuffer, files); + } + catch (Exception e) + { + Console.WriteLine(e.Message, e); + Exception innerException = e.InnerException; + while (innerException != null) + { + Console.WriteLine(innerException.Message, innerException); + innerException = innerException.InnerException; + } + throw new ApplicationException("Error in EnumerateVolume()", e); + } + finally + { + if (pVolume.ToInt32() != PInvokeWin32.INVALID_HANDLE_VALUE) + { + PInvokeWin32.CloseHandle(pVolume); + if (medBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(medBuffer); + } + } + } + } + + internal static IntPtr GetVolumeJournalHandle(string volumeName) + { + string vol = string.Concat("\\\\.\\", volumeName); + IntPtr pVolume = PInvokeWin32.CreateFile(vol, + PInvokeWin32.GENERIC_READ | PInvokeWin32.GENERIC_WRITE, + PInvokeWin32.FILE_SHARE_READ | PInvokeWin32.FILE_SHARE_WRITE, + IntPtr.Zero, + PInvokeWin32.OPEN_EXISTING, + 0, + IntPtr.Zero); + if (pVolume.ToInt32() == PInvokeWin32.INVALID_HANDLE_VALUE) + { + throw new IOException(string.Format("CreateFile(\"{0}\") returned invalid handle", volumeName), + new Win32Exception(Marshal.GetLastWin32Error())); + } + else + { + return pVolume; + } + } + unsafe private static void EnableVomuleJournal(IntPtr pVolume) + { + UInt64 MaximumSize = 0x800000; + UInt64 AllocationDelta = 0x100000; + UInt32 cb; + PInvokeWin32.CREATE_USN_JOURNAL_DATA cujd; + cujd.MaximumSize = MaximumSize; + cujd.AllocationDelta = AllocationDelta; + + int sizeCujd = Marshal.SizeOf(cujd); + IntPtr cujdBuffer = Marshal.AllocHGlobal(sizeCujd); + PInvokeWin32.ZeroMemory(cujdBuffer, sizeCujd); + Marshal.StructureToPtr(cujd, cujdBuffer, true); + + bool fOk = PInvokeWin32.DeviceIoControl(pVolume, PInvokeWin32.FSCTL_CREATE_USN_JOURNAL, + cujdBuffer, sizeCujd, IntPtr.Zero, 0, out cb, IntPtr.Zero); + if (!fOk) + { + throw new IOException("DeviceIoControl() returned false", new Win32Exception(Marshal.GetLastWin32Error())); + } + } + unsafe internal static bool QueryUSNJournal(IntPtr pVolume, out PInvokeWin32.USN_JOURNAL_DATA ujd, out uint bytesReturned) + { + bool bOK = PInvokeWin32.DeviceIoControl( + pVolume, PInvokeWin32.FSCTL_QUERY_USN_JOURNAL, + IntPtr.Zero, + 0, + out ujd, + sizeof(PInvokeWin32.USN_JOURNAL_DATA), + out bytesReturned, + IntPtr.Zero + ); + return bOK; + } + unsafe private static void SetupMFTEnumInBuffer(ref IntPtr medBuffer, IntPtr pVolume) + { + uint bytesReturned = 0; + PInvokeWin32.USN_JOURNAL_DATA ujd = new PInvokeWin32.USN_JOURNAL_DATA(); + + bool bOk = QueryUSNJournal(pVolume, out ujd, out bytesReturned); + if (bOk) + { + PInvokeWin32.MFT_ENUM_DATA med; + med.StartFileReferenceNumber = 0; + med.LowUsn = 0; + med.HighUsn = ujd.NextUsn; + int sizeMftEnumData = Marshal.SizeOf(med); + medBuffer = Marshal.AllocHGlobal(sizeMftEnumData); + PInvokeWin32.ZeroMemory(medBuffer, sizeMftEnumData); + Marshal.StructureToPtr(med, medBuffer, true); + } + else + { + throw new IOException("DeviceIoControl() returned false", new Win32Exception(Marshal.GetLastWin32Error())); + } + } + + unsafe private static void EnumerateFiles(string volumeName, IntPtr pVolume, IntPtr medBuffer, Dictionary files) + { + IntPtr pData = Marshal.AllocHGlobal(sizeof(UInt64) + 0x10000); + PInvokeWin32.ZeroMemory(pData, sizeof(UInt64) + 0x10000); + uint outBytesReturned = 0; + + while (false != PInvokeWin32.DeviceIoControl(pVolume, PInvokeWin32.FSCTL_ENUM_USN_DATA, medBuffer, + sizeof(PInvokeWin32.MFT_ENUM_DATA), pData, sizeof(UInt64) + 0x10000, out outBytesReturned, + IntPtr.Zero)) + { + IntPtr pUsnRecord = new IntPtr(pData.ToInt32() + sizeof(Int64)); + while (outBytesReturned > 60) + { + PInvokeWin32.USN_RECORD usn = new PInvokeWin32.USN_RECORD(pUsnRecord); + + files.Add(usn.FRN, new USNRecord + { + Name = usn.FileName, + ParentFrn = usn.ParentFRN, + FRN = usn.FRN, + IsFolder = usn.IsFolder, + VolumeName = volumeName + }); + + pUsnRecord = new IntPtr(pUsnRecord.ToInt32() + usn.RecordLength); + outBytesReturned -= usn.RecordLength; + } + Marshal.WriteInt64(medBuffer, Marshal.ReadInt64(pData, 0)); + } + Marshal.FreeHGlobal(pData); + } + + internal static void FillPath(string volume, USNRecord record, MFTSearcherCache db) + { + if (record == null) return; + var fdSource = db.GetVolumeRecords(volume); + string fullpath = record.Name; + FindRecordPath(record, ref fullpath, fdSource); + record.FullPath = fullpath; + } + + private static void FindRecordPath(USNRecord curRecord, ref string fullpath, Dictionary fdSource) + { + if (curRecord.IsVolumeRoot) return; + USNRecord nextRecord = null; + if (!fdSource.TryGetValue(curRecord.ParentFrn, out nextRecord)) + return; + fullpath = string.Format("{0}{1}{2}", nextRecord.Name, Path.DirectorySeparatorChar, fullpath); + FindRecordPath(nextRecord, ref fullpath, fdSource); + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearcherCache.cs b/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearcherCache.cs new file mode 100644 index 0000000000..e60e558d4d --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/MFTSearch/MFTSearcherCache.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Wox.Plugin.FindFile.MFTSearch +{ + internal class MFTSearcherCache + { + public Dictionary> VolumeRecords = new Dictionary>(); + public static object locker = new object(); + + public MFTSearcherCache() { } + + public bool ContainsVolume(string volume) + { + return VolumeRecords.ContainsKey(volume); + } + + public void AddRecord(string volume, List r) + { + EnsureVolumeExistInHashTable(volume); + r.ForEach(x => VolumeRecords[volume].Add(x.FRN, x)); + } + + public void AddRecord(string volume, USNRecord record) + { + EnsureVolumeExistInHashTable(volume); + if (!VolumeRecords[volume].ContainsKey(record.FRN)) + { + lock (locker) + { + VolumeRecords[volume].Add(record.FRN, record); + } + } + } + + public void EnsureVolumeExistInHashTable(string volume) + { + if (!VolumeRecords.ContainsKey(volume)) + VolumeRecords.Add(volume, new Dictionary()); + } + + public bool DeleteRecord(string volume, ulong frn) + { + bool result = false; + result = DeleteRecordHashTableItem(VolumeRecords, volume, frn); + return result; + } + + private bool DeleteRecordHashTableItem(Dictionary> hashtable, string volume, ulong frn) + { + if (hashtable.ContainsKey(volume) && hashtable[volume].ContainsKey(frn)) + { + lock (locker) + { + hashtable[volume].Remove(frn); + } + return true; + } + else + { + return false; + } + } + + public void UpdateRecord(string volume, USNRecord record) + { + RealUpdateRecord(volume, VolumeRecords, record); + } + + private bool RealUpdateRecord(string volume, Dictionary> source, USNRecord record) + { + if (source.ContainsKey(volume) && source[volume].ContainsKey(record.FRN)) + { + lock (locker) + { + source[volume][record.FRN] = record; + } + return true; + } + else + { + return false; + } + } + + public List FindByName(string filename, long maxResult = -1) + { + List result = new List(); + lock (locker) + { + foreach (Dictionary dictionary in VolumeRecords.Values) + { + foreach (var usnRecord in dictionary) + { + if (usnRecord.Value.Name.IndexOf(filename, StringComparison.OrdinalIgnoreCase) >= 0) + { + result.Add(usnRecord.Value); + if (maxResult > 0 && result.Count() >= maxResult) break; + } + if (maxResult > 0 && result.Count() >= maxResult) break; + } + } + } + return result; + } + + public USNRecord FindByFrn(string volume, ulong frn) + { + if ((!VolumeRecords.ContainsKey(volume))) + throw new Exception(string.Format("DB not contain the volume: {0}", volume)); + USNRecord result = null; + VolumeRecords[volume].TryGetValue(frn, out result); + return result; + } + + public long RecordsCount + { + get { return VolumeRecords.Sum(x => x.Value.Count); } + } + + public Dictionary GetVolumeRecords(string volume) + { + Dictionary result = null; + VolumeRecords.TryGetValue(volume, out result); + return result; + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/MFTSearch/PInvokeWin32.cs b/Plugins/Wox.Plugin.FindFile/MFTSearch/PInvokeWin32.cs new file mode 100644 index 0000000000..dd5a7735d9 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/MFTSearch/PInvokeWin32.cs @@ -0,0 +1,165 @@ +using System; +using System.Runtime.InteropServices; + +namespace Wox.Plugin.FindFile.MFTSearch { + public class PInvokeWin32 + { + + public const UInt32 GENERIC_READ = 0x80000000; + public const UInt32 GENERIC_WRITE = 0x40000000; + public const UInt32 FILE_SHARE_READ = 0x00000001; + public const UInt32 FILE_SHARE_WRITE = 0x00000002; + public const UInt32 FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + public const UInt32 OPEN_EXISTING = 3; + public const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + public const Int32 INVALID_HANDLE_VALUE = -1; + public const UInt32 FSCTL_QUERY_USN_JOURNAL = 0x000900f4; + public const UInt32 FSCTL_ENUM_USN_DATA = 0x000900b3; + public const UInt32 FSCTL_CREATE_USN_JOURNAL = 0x000900e7; + public const UInt32 FSCTL_READ_USN_JOURNAL = 0x000900bb; + + + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, + uint dwShareMode, IntPtr lpSecurityAttributes, + uint dwCreationDisposition, uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetFileInformationByHandle(IntPtr hFile, + out BY_HANDLE_FILE_INFORMATION lpFileInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeviceIoControl(IntPtr hDevice, + UInt32 dwIoControlCode, + IntPtr lpInBuffer, Int32 nInBufferSize, + out USN_JOURNAL_DATA lpOutBuffer, Int32 nOutBufferSize, + out uint lpBytesReturned, IntPtr lpOverlapped); + + [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeviceIoControl(IntPtr hDevice, + UInt32 dwIoControlCode, + IntPtr lpInBuffer, Int32 nInBufferSize, + IntPtr lpOutBuffer, Int32 nOutBufferSize, + out uint lpBytesReturned, IntPtr lpOverlapped); + + [DllImport("kernel32.dll")] + public static extern void ZeroMemory(IntPtr ptr, Int32 size); + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BY_HANDLE_FILE_INFORMATION { + public uint FileAttributes; + public FILETIME CreationTime; + public FILETIME LastAccessTime; + public FILETIME LastWriteTime; + public uint VolumeSerialNumber; + public uint FileSizeHigh; + public uint FileSizeLow; + public uint NumberOfLinks; + public uint FileIndexHigh; + public uint FileIndexLow; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FILETIME { + public uint DateTimeLow; + public uint DateTimeHigh; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct USN_JOURNAL_DATA { + public UInt64 UsnJournalID; + public Int64 FirstUsn; + public Int64 NextUsn; + public Int64 LowestValidUsn; + public Int64 MaxUsn; + public UInt64 MaximumSize; + public UInt64 AllocationDelta; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MFT_ENUM_DATA { + public UInt64 StartFileReferenceNumber; + public Int64 LowUsn; + public Int64 HighUsn; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CREATE_USN_JOURNAL_DATA { + public UInt64 MaximumSize; + public UInt64 AllocationDelta; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct READ_USN_JOURNAL_DATA { + public Int64 StartUsn; + public UInt32 ReasonMask; + public UInt32 ReturnOnlyOnClose; + public UInt64 Timeout; + public UInt64 BytesToWaitFor; + public UInt64 UsnJournalID; + } + + public class USN_RECORD { + public UInt32 RecordLength; + public UInt16 MajorVersion; + public UInt16 MinorVersion; + public UInt64 FRN; // 8 + public UInt64 ParentFRN; // 16 + public Int64 Usn; // Need be care + public UInt64 TimeStamp; // Need Be care + public UInt32 Reason; + public UInt32 SourceInfo; + public UInt32 SecurityId; + public UInt32 FileAttributes; // 52 + public UInt16 FileNameLength; + public UInt16 FileNameOffset; + public string FileName = string.Empty; + + private const int RecordLength_OFFSET = 0; + private const int MajorVersion_OFFSET = 4; + private const int MinorVersion_OFFSET = 6; + private const int FileReferenceNumber_OFFSET = 8; + private const int ParentFileReferenceNumber_OFFSET = 16; + private const int Usn_OFFSET = 24; + private const int TimeStamp_OFFSET = 32; + private const int Reason_OFFSET = 40; + private const int SourceInfo_OFFSET = 44; + private const int SecurityId_OFFSET = 48; + private const int FileAttributes_OFFSET = 52; + private const int FileNameLength_OFFSET = 56; + private const int FileNameOffset_OFFSET = 58; + private const int FileName_OFFSET = 60; + + public USN_RECORD(IntPtr p) { + this.RecordLength = (UInt32)Marshal.ReadInt32(p, RecordLength_OFFSET); + this.MajorVersion = (UInt16)Marshal.ReadInt16(p, MajorVersion_OFFSET); + this.MinorVersion = (UInt16)Marshal.ReadInt16(p, MinorVersion_OFFSET); + this.FRN = (UInt64)Marshal.ReadInt64(p, FileReferenceNumber_OFFSET); + this.ParentFRN = (UInt64)Marshal.ReadInt64(p, ParentFileReferenceNumber_OFFSET); + this.Usn = Marshal.ReadInt64(p, Usn_OFFSET); + this.TimeStamp = (UInt64)Marshal.ReadInt64(p, TimeStamp_OFFSET); + this.Reason = (UInt32)Marshal.ReadInt32(p, Reason_OFFSET); + this.SourceInfo = (UInt32)Marshal.ReadInt32(p, SourceInfo_OFFSET); + this.SecurityId = (UInt32)Marshal.ReadInt32(p, SecurityId_OFFSET); + this.FileAttributes = (UInt32)Marshal.ReadInt32(p, FileAttributes_OFFSET); + this.FileNameLength = (UInt16)Marshal.ReadInt16(p, FileNameLength_OFFSET); + this.FileNameOffset = (UInt16)Marshal.ReadInt16(p, FileNameOffset_OFFSET); + + this.FileName = Marshal.PtrToStringUni(new IntPtr(p.ToInt32() + this.FileNameOffset), this.FileNameLength / sizeof(char)); + } + + public bool IsFolder { + get { return 0 != (FileAttributes & PInvokeWin32.FILE_ATTRIBUTE_DIRECTORY); } + } + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/MFTSearch/USNChangeReason.cs b/Plugins/Wox.Plugin.FindFile/MFTSearch/USNChangeReason.cs new file mode 100644 index 0000000000..c550208eb0 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/MFTSearch/USNChangeReason.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Wox.Plugin.FindFile.MFTSearch +{ + internal class USNChangeReason + { + public static Dictionary USN_REASONS = new Dictionary { + {"USN_REASON_DATA_OVERWRITE", 0x00000001}, + {"USN_REASON_DATA_EXTEND", 0x00000002}, + {"USN_REASON_DATA_TRUNCATION", 0x00000004}, + {"USN_REASON_NAMED_DATA_OVERWRITE", 0x00000010}, + {"USN_REASON_NAMED_DATA_EXTEND", 0x00000020}, + {"USN_REASON_NAMED_DATA_TRUNCATION", 0x00000040}, + {"USN_REASON_FILE_CREATE", 0x00000100}, + {"USN_REASON_FILE_DELETE", 0x00000200}, + {"USN_REASON_EA_CHANGE", 0x00000400}, + {"USN_REASON_SECURITY_CHANGE", 0x00000800}, + {"USN_REASON_RENAME_OLD_NAME", 0x00001000}, + {"USN_REASON_RENAME_NEW_NAME", 0x00002000}, + {"USN_REASON_INDEXABLE_CHANGE", 0x00004000}, + {"USN_REASON_BASIC_INFO_CHANGE", 0x00008000}, + {"USN_REASON_HARD_LINK_CHANGE", 0x00010000}, + {"USN_REASON_COMPRESSION_CHANGE", 0x00020000}, + {"USN_REASON_ENCRYPTION_CHANGE", 0x00040000}, + {"USN_REASON_OBJECT_ID_CHANGE", 0x00080000}, + {"USN_REASON_REPARSE_POINT_CHANGE", 0x00100000}, + {"USN_REASON_STREAM_CHANGE", 0x00200000}, + {"USN_REASON_TRANSACTED_CHANGE", 0x00400000}, + {"USN_REASON_CLOSE", 0x80000000} + }; + + public static string ReasonPrettyFormat(UInt32 rsn) + { + StringBuilder sb = new StringBuilder(); + sb.Append("["); + foreach (var rsnPair in USN_REASONS) + { + if ((rsnPair.Value & rsn) != 0) + sb.Append(rsnPair.Key + " "); + } + sb.Append("]"); + return sb.ToString(); + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/MFTSearch/USNRecord.cs b/Plugins/Wox.Plugin.FindFile/MFTSearch/USNRecord.cs new file mode 100644 index 0000000000..8fbad7b231 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/MFTSearch/USNRecord.cs @@ -0,0 +1,33 @@ +using System; + +namespace Wox.Plugin.FindFile.MFTSearch +{ + public class USNRecord + { + + public string Name { get; set; } + public ulong FRN { get; set; } + public UInt64 ParentFrn { get; set; } + public string FullPath { get; set; } + public bool IsVolumeRoot { get; set; } + public bool IsFolder { get; set; } + public string VolumeName { get; set; } + + public override string ToString() + { + return string.IsNullOrEmpty(FullPath) ? Name : FullPath; + } + + public static USNRecord ParseUSN(string volume, PInvokeWin32.USN_RECORD usn) + { + return new USNRecord + { + FRN = usn.FRN, + Name = usn.FileName, + ParentFrn = usn.ParentFRN, + IsFolder = usn.IsFolder, + VolumeName = volume + }; + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/MFTSearch/VolumeMonitor.cs b/Plugins/Wox.Plugin.FindFile/MFTSearch/VolumeMonitor.cs new file mode 100644 index 0000000000..6048bacf75 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/MFTSearch/VolumeMonitor.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Wox.Plugin.FindFile.MFTSearch +{ + internal class VolumeMonitor + { + public Action RecordAddedEvent; + public Action RecordDeletedEvent; + public Action RecordRenameEvent; + + public void Monitor(List volumes, MFTSearcherCache db) + { + foreach (var volume in volumes) + { + Monitor(volume, db); + } + } + + public void Monitor(string volume, MFTSearcherCache db) + { + if (string.IsNullOrEmpty(volume)) throw new InvalidOperationException("Volume cant't be null or empty string."); + if (!db.ContainsVolume(volume)) throw new InvalidOperationException(string.Format("Volume {0} must be scaned first.")); + + ThreadPool.QueueUserWorkItem(o => MonitorThread(volume, db)); + } + + private PInvokeWin32.READ_USN_JOURNAL_DATA SetupInputData4JournalRead(string volume, uint reason) + { + IntPtr pMonitorVolume = MFTSearcher.GetVolumeJournalHandle(volume); + uint bytesReturned = 0; + PInvokeWin32.USN_JOURNAL_DATA ujd = new PInvokeWin32.USN_JOURNAL_DATA(); + MFTSearcher.QueryUSNJournal(pMonitorVolume, out ujd, out bytesReturned); + + // 构建输入参数 + PInvokeWin32.READ_USN_JOURNAL_DATA rujd = new PInvokeWin32.READ_USN_JOURNAL_DATA(); + rujd.StartUsn = ujd.NextUsn; + rujd.ReasonMask = reason; + rujd.ReturnOnlyOnClose = 1; + rujd.Timeout = 0; + rujd.BytesToWaitFor = 1; + rujd.UsnJournalID = ujd.UsnJournalID; + + return rujd; + } + + private void MonitorThread(string volume, MFTSearcherCache db) + { + IntPtr pbuffer = Marshal.AllocHGlobal(0x1000); + PInvokeWin32.READ_USN_JOURNAL_DATA rujd = SetupInputData4JournalRead(volume, 0xFFFFFFFF); + UInt32 cbRead; + IntPtr prujd; + + while (true) + { + prujd = Marshal.AllocHGlobal(Marshal.SizeOf(rujd)); + PInvokeWin32.ZeroMemory(prujd, Marshal.SizeOf(rujd)); + Marshal.StructureToPtr(rujd, prujd, true); + + IntPtr pVolume = MFTSearcher.GetVolumeJournalHandle(volume); + + bool fok = PInvokeWin32.DeviceIoControl(pVolume, + PInvokeWin32.FSCTL_READ_USN_JOURNAL, + prujd, Marshal.SizeOf(typeof(PInvokeWin32.READ_USN_JOURNAL_DATA)), + pbuffer, 0x1000, out cbRead, IntPtr.Zero); + + IntPtr pRealData = new IntPtr(pbuffer.ToInt32() + Marshal.SizeOf(typeof(Int64))); + uint offset = 0; + + if (fok) + { + while (offset + Marshal.SizeOf(typeof(Int64)) < cbRead) + { + PInvokeWin32.USN_RECORD usn = new PInvokeWin32.USN_RECORD(new IntPtr(pRealData.ToInt32() + (int)offset)); + ProcessUSN(usn, volume, db); + offset += usn.RecordLength; + } + } + + Marshal.FreeHGlobal(prujd); + rujd.StartUsn = Marshal.ReadInt64(pbuffer); + } + } + + private void ProcessUSN(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + if (MaskEqual(usn.Reason, USNChangeReason.USN_REASONS["USN_REASON_RENAME_NEW_NAME"])) + ProcessRenameNewName(usn, volume, db); + if ((usn.Reason & USNChangeReason.USN_REASONS["USN_REASON_FILE_CREATE"]) != 0) + ProcessFileCreate(usn, volume, db); + if (MaskEqual(usn.Reason, USNChangeReason.USN_REASONS["USN_REASON_FILE_DELETE"])) + ProcessFileDelete(usn, volume, db); + } + + private void ProcessFileDelete(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + var cached = db.FindByFrn(volume, usn.FRN); + if (cached != null) + { + MFTSearcher.FillPath(volume, cached, db); + var deleteok = db.DeleteRecord(volume, usn.FRN); + Debug.WriteLine(string.Format(">>>> DeleteFIle {0} {1}.", cached.FullPath, deleteok ? "successful" : "fail")); + if (RecordDeletedEvent != null) + RecordDeletedEvent(cached); + } + } + + private void ProcessRenameNewName(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + USNRecord newRecord = USNRecord.ParseUSN(volume, usn); + db.UpdateRecord(volume, newRecord); + + var oldRecord = db.FindByFrn(volume, usn.FRN); + if (oldRecord != null) + { + Debug.WriteLine(string.Format(">>>> RenameFile {0} to {1}", oldRecord.FullPath, newRecord.FullPath)); + if (RecordRenameEvent != null) RecordRenameEvent(oldRecord, newRecord); + } + } + + private void ProcessFileCreate(PInvokeWin32.USN_RECORD usn, string volume, MFTSearcherCache db) + { + USNRecord record = USNRecord.ParseUSN(volume, usn); + db.AddRecord(volume, record); + Debug.WriteLine(string.Format(">>>> NewFile: {0}", record.FullPath)); + if (RecordAddedEvent != null) + RecordAddedEvent(record); + } + + + private bool MaskEqual(uint target, uint compare) + { + return (target & compare) != 0; + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/Main.cs b/Plugins/Wox.Plugin.FindFile/Main.cs new file mode 100644 index 0000000000..917712b033 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/Main.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Windows.Controls; +using Wox.Infrastructure; +using Wox.Plugin.FindFile.MFTSearch; + +namespace Wox.Plugin.FindFile +{ + public class Main : IPlugin, ISettingProvider + { + private PluginInitContext context; + private bool initial = false; + + public List Query(Query query) + { + if (!initial) + { + return new List() + { + new Result("Wox is indexing your files, please try later.","Images/warning.png") + }; + } + + string q = query.GetAllRemainingParameter(); + return MFTSearcher.Search(q).Take(100).Select(t => ConvertMFTSearch(t, q)).ToList(); + } + + public void Init(PluginInitContext context) + { + this.context = context; + var searchtimestart = DateTime.Now; + MFTSearcher.IndexAllVolumes(); + initial = true; + var searchtimeend = DateTime.Now; + Debug.WriteLine(string.Format("{0} file, indexed, {1}ms has spent.", MFTSearcher.IndexedFileCount, searchtimeend.Subtract(searchtimestart).TotalMilliseconds)); + } + + private Result ConvertMFTSearch(MFTSearchRecord record, string query) + { + string icoPath = "Images/file.png"; + if (record.IsFolder) + { + icoPath = "Images/folder.png"; + } + + string name = Path.GetFileName(record.FullPath); + FuzzyMatcher matcher = FuzzyMatcher.Create(query); + return new Result() + { + Title = name, + Score = matcher.Evaluate(name).Score, + SubTitle = record.FullPath, + IcoPath = icoPath, + Action = _ => + { + try + { + Process.Start(record.FullPath); + } + catch + { + context.API.ShowMsg("Can't open " + record.FullPath, string.Empty, string.Empty); + return false; + } + return true; + }, + ContextMenu = GetContextMenu(record) + }; + } + + private List GetContextMenu(MFTSearchRecord record) + { + List contextMenus = new List(); + + if (!record.IsFolder) + { + foreach (ContextMenu contextMenu in FindFileContextMenuStorage.Instance.ContextMenus) + { + contextMenus.Add(new Result() + { + Title = contextMenu.Name, + Action = _ => + { + string argument = contextMenu.Argument.Replace("{path}", record.FullPath); + try + { + Process.Start(contextMenu.Command,argument); + } + catch + { + context.API.ShowMsg("Can't start " + record.FullPath, string.Empty, string.Empty); + return false; + } + return true; + }, + IcoPath = "Images/list.png" + }); + } + } + + return contextMenus; + } + + public Control CreateSettingPanel() + { + return new Setting(); + } + } +} diff --git a/Plugins/Wox.Plugin.FindFile/Properties/AssemblyInfo.cs b/Plugins/Wox.Plugin.FindFile/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1484395d76 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("Wox.Plugin.FindFile")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Wox.Plugin.FindFile")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("93b857b4-07a4-4ac1-a3ee-d038ace03ce9")] + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Plugins/Wox.Plugin.FindFile/Wox.Plugin.FindFile.csproj b/Plugins/Wox.Plugin.FindFile/Wox.Plugin.FindFile.csproj new file mode 100644 index 0000000000..0324d2b966 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/Wox.Plugin.FindFile.csproj @@ -0,0 +1,132 @@ + + + + + Debug + AnyCPU + {84EA88B4-71F2-4A3D-9771-D7B0244C0D81} + Library + Properties + Wox.Plugin.FindFile + Wox.Plugin.FindFile + v3.5 + 512 + + ..\..\ + true + + + true + full + false + ..\..\Output\Debug\Plugins\Wox.Plugin.FindFile\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + ..\..\Output\Release\Plugins\Wox.Plugin.FindFile\ + TRACE + prompt + 4 + true + + + + False + ..\..\packages\log4net.2.0.3\lib\net35-full\log4net.dll + + + False + ..\..\packages\Newtonsoft.Json.6.0.5\lib\net35\Newtonsoft.Json.dll + + + + + + + ..\..\packages\WPFToolkit.3.5.50211.1\lib\System.Windows.Controls.Input.Toolkit.dll + + + ..\..\packages\WPFToolkit.3.5.50211.1\lib\System.Windows.Controls.Layout.Toolkit.dll + + + + + + + + + ..\..\packages\WPFToolkit.3.5.50211.1\lib\WPFToolkit.dll + + + + + + + + + + + + + + + Setting.xaml + + + + + {4fd29318-a8ab-4d8f-aa47-60bc241b8da3} + Wox.Infrastructure + + + {8451ecdd-2ea4-4966-bb0a-7bbc40138e80} + Wox.Plugin + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + + PreserveNewest + + + + + Designer + MSBuild:Compile + + + + + + \ No newline at end of file diff --git a/Plugins/Wox.Plugin.FindFile/packages.config b/Plugins/Wox.Plugin.FindFile/packages.config new file mode 100644 index 0000000000..30b7b9cf8c --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Plugins/Wox.Plugin.FindFile/plugin.json b/Plugins/Wox.Plugin.FindFile/plugin.json new file mode 100644 index 0000000000..ed9c9f3930 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/plugin.json @@ -0,0 +1,12 @@ +{ + "ID":"64EDFA42642446E5BBFF2546EE4549BB", + "ActionKeyword":"f", + "Name":"Find Files and Folders", + "Description":"Search all your files and folders in your disk", + "Author":"qianlifeng", + "Version":"1.0", + "Language":"csharp", + "Website":"http://www.getwox.com/plugin", + "ExecuteFileName":"Wox.Plugin.FindFile.dll", + "IcoPath":"Images\\find.png" +} diff --git a/Plugins/Wox.Plugin.FindFile/setting.xaml b/Plugins/Wox.Plugin.FindFile/setting.xaml new file mode 100644 index 0000000000..a0218954d8 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/setting.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/Plugins/Wox.Plugin.FindFile/setting.xaml.cs b/Plugins/Wox.Plugin.FindFile/setting.xaml.cs new file mode 100644 index 0000000000..1df74a4291 --- /dev/null +++ b/Plugins/Wox.Plugin.FindFile/setting.xaml.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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; + +namespace Wox.Plugin.FindFile +{ + public partial class Setting : UserControl + { + public Setting() + { + InitializeComponent(); + menuGrid.ItemsSource = FindFileContextMenuStorage.Instance.ContextMenus; + menuGrid.CurrentCellChanged += menuGrid_CurrentCellChanged; + } + + void menuGrid_CurrentCellChanged(object sender, EventArgs e) + { + FindFileContextMenuStorage.Instance.Save(); + } + } +} diff --git a/README.md b/README.md index e7ea29e58e..7399e8cb35 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,60 @@ -Wox [![Build status](https://ci.appveyor.com/api/projects/status/bfktntbivg32e103/branch/dev)](https://ci.appveyor.com/project/qianlifeng/wox/branch/dev) +Wox [![Build status](https://ci.appveyor.com/api/projects/status/bfktntbivg32e103)](https://ci.appveyor.com/project/qianlifeng/wox) +========= +[Wox](http://www.getwox.com) is an effective launcher for windows, which was inspired by [Alfred](http://www.alfredapp.com/) and [Launchy](http://www.launchy.net/). Wox provide bundles of features let you access infomations quickly. + +Features +========= +1. Search applications, files (via everything plugin) and chrome bookmarks +2. Search web contents with shortcuts (e.g. search google with `g keyword` or `youtube keyword`) +3. Search clipboard history (via clipboard plugin) +4. Themes support, get more themes from [http://www.getwox.com/theme](http://www.getwox.com/theme) +5. Plugin support, get more plugins from [http://www.getwox.com/plugin](http://www.getwox.com/plugin) + +Screenshot ========= -This is Wox Dev branch. We will develop new feature here and merge to master when we want to release a new version. -**Please send pull request to this branch if you want to contribute codes** +More screenshot + + +Download +========= + +Download from [release page](https://github.com/qianlifeng/Wox/releases). + +* **Windows 8 users:** You have to [enable the .NET Framework 3.5 in Control Panel](http://msdn.microsoft.com/library/hh506443.aspx). + + +Contribute +========= + +If you are a developer, please feel free to send a pull request to **Dev** branch. We still have a lot functions and bugs need to do now. Just pick one from [issues page](https://github.com/qianlifeng/Wox/issues) that you think you can fix. + +If you are not a developer, you can also help Wox by contributing the Wox wiki page, for example [Wox Function Guide](https://github.com/qianlifeng/Wox/wiki/Wox-Function-Guide). + + + +Create and Share Plugin +========= + +Currently, Wox support using C# and Python to write your plugins. Please refer to [Create-plugin](https://github.com/qianlifeng/Wox/wiki/Create-plugins) page for more infomation. +You can share your plugin with WoxPlugins. After you upload your plugin, other uses can download or search your plugin through `wpm install ` command (this is a plugin for plugin management, which is one of the default plugins inside Wox). Please refer to [How-to-share-your-plugins](https://github.com/qianlifeng/Wox/wiki/How-to-share-your-plugins-in--getwox.com-plugin-page%3F) for details. + + +Create and Share Theme +========= + +Wox provide an online theme editor [http://www.getwox.com/themebuilder](http://www.getwox.com/themebuilder), you build your own theme for wox. + + + +Most Asked Questions +========= + +1. **How to install plugin?** + + Refer to [https://github.com/qianlifeng/Wox/wiki/How-to-install-Plugins](https://github.com/qianlifeng/Wox/wiki/How-to-install-Plugins) + + +2. **How to open setting dialog?** + + Refer to [https://github.com/qianlifeng/Wox/wiki/How-to-open-setting-dialog](https://github.com/qianlifeng/Wox/wiki/How-to-open-setting-dialog) diff --git a/Wox.Infrastructure/WindowsShellRun.cs b/Wox.Infrastructure/WindowsShellRun.cs index 054802cc52..f8ec456b4a 100644 --- a/Wox.Infrastructure/WindowsShellRun.cs +++ b/Wox.Infrastructure/WindowsShellRun.cs @@ -78,7 +78,7 @@ namespace Wox.Infrastructure [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] static extern bool UrlIs(string pszUrl, int UrlIs); - static void ShellExecCmdLine(IntPtr hInstance, IntPtr hwnd, string command, string startDir, global::System.Diagnostics.ProcessWindowStyle nShow, ShellExecCmdLineFlags dwSeclFlags) + static void ShellExecCmdLine(IntPtr hInstance, IntPtr hwnd, string command, string startDir, global::System.Diagnostics.ProcessWindowStyle nShow, ShellExecCmdLineFlags dwSeclFlags,bool runAsAdministrator = false) { string cmd = command; string args = null; @@ -143,6 +143,7 @@ namespace Wox.Infrastructure startInfo.UseShellExecute = true; startInfo.Arguments = args; startInfo.FileName = cmd; + if (runAsAdministrator) startInfo.Verb = "runas"; startInfo.WindowStyle = global::System.Diagnostics.ProcessWindowStyle.Normal; startInfo.ErrorDialog = (dwSeclFlags | ShellExecCmdLineFlags.SECL_NO_UI) == 0; startInfo.ErrorDialogParentHandle = hwnd; @@ -280,17 +281,12 @@ namespace Wox.Infrastructure hresult); } - public static void Start(string cmd) + public static void Start(string cmd, bool runAsAdministrator = false) { - Start(cmd, false); + Start(cmd, false, IntPtr.Zero,runAsAdministrator); } - public static void Start(string cmd, bool showErrorDialog) - { - Start(cmd, false, IntPtr.Zero); - } - - public static void Start(string cmd, bool showErrorDialog, IntPtr errorDialogHwnd) + public static void Start(string cmd, bool showErrorDialog, IntPtr errorDialogHwnd, bool runAsAdministrator = false) { cmd = cmd.Trim(); // PathRemoveBlanks cmd = Environment.ExpandEnvironmentVariables(cmd); // SHExpandEnvironmentStrings @@ -306,7 +302,8 @@ namespace Wox.Infrastructure cmd, null, // i have no ideas about this field global::System.Diagnostics.ProcessWindowStyle.Normal, - ShellExecCmdLineFlags.SECL__IGNORE_ERROR | ShellExecCmdLineFlags.SECL_USE_IDLIST | ShellExecCmdLineFlags.SECL_LOG_USAGE | (showErrorDialog ? 0 : ShellExecCmdLineFlags.SECL_NO_UI) + ShellExecCmdLineFlags.SECL__IGNORE_ERROR | ShellExecCmdLineFlags.SECL_USE_IDLIST | ShellExecCmdLineFlags.SECL_LOG_USAGE | (showErrorDialog ? 0 : ShellExecCmdLineFlags.SECL_NO_UI), + runAsAdministrator ); if (!string.IsNullOrEmpty(home) && Directory.Exists(home)) Environment.CurrentDirectory = oldCwd; } diff --git a/Wox.Infrastructure/Wox.Infrastructure.csproj b/Wox.Infrastructure/Wox.Infrastructure.csproj index 040b9d2be4..df363b7421 100644 --- a/Wox.Infrastructure/Wox.Infrastructure.csproj +++ b/Wox.Infrastructure/Wox.Infrastructure.csproj @@ -22,6 +22,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -30,6 +31,7 @@ TRACE prompt 4 + false diff --git a/Wox.Plugin.SystemPlugins/Program/Programs.cs b/Wox.Plugin.SystemPlugins/Program/Programs.cs index ff87a84667..649ce9acde 100644 --- a/Wox.Plugin.SystemPlugins/Program/Programs.cs +++ b/Wox.Plugin.SystemPlugins/Program/Programs.cs @@ -42,6 +42,20 @@ namespace Wox.Plugin.SystemPlugins.Program context.API.HideApp(); context.API.ShellRun(c.ExecutePath); return true; + }, + ContextMenu = new List() + { + new Result() + { + Title = "Run As Administrator", + Action = _ => + { + context.API.HideApp(); + context.API.ShellRun(c.ExecutePath,true); + return true; + }, + IcoPath = "Images/cmd.png" + } } }).ToList(); } @@ -84,11 +98,11 @@ namespace Wox.Plugin.SystemPlugins.Program Type sourceClass; if (SourceTypes.TryGetValue(source.Type, out sourceClass)) { - ConstructorInfo constructorInfo = sourceClass.GetConstructor(new[] {typeof (ProgramSource)}); + ConstructorInfo constructorInfo = sourceClass.GetConstructor(new[] { typeof(ProgramSource) }); if (constructorInfo != null) { IProgramSource programSource = - constructorInfo.Invoke(new object[] {source}) as IProgramSource; + constructorInfo.Invoke(new object[] { source }) as IProgramSource; sources.Add(programSource); } } @@ -106,7 +120,7 @@ namespace Wox.Plugin.SystemPlugins.Program } // filter duplicate program - tempPrograms = tempPrograms.GroupBy(x => new {x.ExecutePath, x.ExecuteName}) + tempPrograms = tempPrograms.GroupBy(x => new { x.ExecutePath, x.ExecuteName }) .Select(g => g.First()).ToList(); programs = tempPrograms; diff --git a/Wox.Plugin/IPublicAPI.cs b/Wox.Plugin/IPublicAPI.cs index 85e501b651..64ff13ab08 100644 --- a/Wox.Plugin/IPublicAPI.cs +++ b/Wox.Plugin/IPublicAPI.cs @@ -9,7 +9,7 @@ namespace Wox.Plugin void PushResults(Query query,PluginMetadata plugin, List results); - bool ShellRun(string cmd); + bool ShellRun(string cmd, bool runAsAdministrator = false); void ChangeQuery(string query, bool requery = false); diff --git a/Wox.Plugin/Result.cs b/Wox.Plugin/Result.cs index 3a7904b2e9..db0bd309c0 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -3,71 +3,78 @@ using System.Collections; using System.Collections.Generic; using System.IO; -namespace Wox.Plugin { +namespace Wox.Plugin +{ - public class Result { - public string Title { get; set; } - public string SubTitle { get; set; } - public string IcoPath { get; set; } + public class Result + { - public string FullIcoPath - { - get - { + public string Title { get; set; } + public string SubTitle { get; set; } + public string IcoPath { get; set; } + + public string FullIcoPath + { + get + { if (string.IsNullOrEmpty(IcoPath)) return string.Empty; - if (IcoPath.StartsWith("data:")) - { - return IcoPath; - } + if (IcoPath.StartsWith("data:")) + { + return IcoPath; + } return Path.Combine(PluginDirectory, IcoPath); - } - } + } + } - /// - /// return true to hide wox after select result - /// - public Func Action { get; set; } - public int Score { get; set; } + /// + /// return true to hide wox after select result + /// + public Func Action { get; set; } - /// - /// Auto add scores for MRU items - /// - public bool AutoAjustScore { get; set; } + public int Score { get; set; } - //todo: this should be controlled by system, not visible to users - /// - /// Only resulsts that originQuery match with curren query will be displayed in the panel - /// - public Query OriginQuery { get; set; } + /// + /// Auto add scores for MRU items + /// + public bool AutoAjustScore { get; set; } - /// - /// Don't set this property if you are developing a plugin - /// - public string PluginDirectory { get; set; } + //todo: this should be controlled by system, not visible to users + /// + /// Only resulsts that originQuery match with curren query will be displayed in the panel + /// + public Query OriginQuery { get; set; } - public new bool Equals(object obj) { - if (obj == null || !(obj is Result)) return false; + /// + /// Don't set this property if you are developing a plugin + /// + public string PluginDirectory { get; set; } - Result r = (Result)obj; - return r.Title == Title && r.SubTitle == SubTitle; - } + public new bool Equals(object obj) + { + if (obj == null || !(obj is Result)) return false; + Result r = (Result)obj; + return r.Title == Title && r.SubTitle == SubTitle; + } + public override string ToString() + { + return Title + SubTitle; + } - public override string ToString() { - return Title + SubTitle; - } + public Result() + { - public Result() { + } - } + public Result(string Title = null, string IcoPath = null, string SubTitle = null) + { + this.Title = Title; + this.IcoPath = IcoPath; + this.SubTitle = SubTitle; + } - public Result(string Title = null, string IcoPath = null, string SubTitle = null) { - this.Title = Title; - this.IcoPath = IcoPath; - this.SubTitle = SubTitle; - } - - } + public List ContextMenu { get; set; } + } } \ No newline at end of file diff --git a/Wox.Test/MFTSearcherTest.cs b/Wox.Test/MFTSearcherTest.cs new file mode 100644 index 0000000000..3e4992926b --- /dev/null +++ b/Wox.Test/MFTSearcherTest.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Wox.Plugin.FindFile.MFTSearch; + +namespace Wox.Test +{ + [TestFixture] + public class MFTSearcherTest + { + [Test] + public void MatchTest() + { + var searchtimestart = DateTime.Now; + MFTSearcher.IndexAllVolumes(); + var searchtimeend = DateTime.Now; + Console.WriteLine(string.Format("{0} file indexed, {1}ms has spent.", MFTSearcher.IndexedFileCount, searchtimeend.Subtract(searchtimestart).TotalMilliseconds)); + + searchtimestart = DateTime.Now; + List mftSearchRecords = MFTSearcher.Search("q"); + searchtimeend = DateTime.Now; + Console.WriteLine(string.Format("{0} file searched, {1}ms has spent.", mftSearchRecords.Count, searchtimeend.Subtract(searchtimestart).TotalMilliseconds)); + + searchtimestart = DateTime.Now; + mftSearchRecords = MFTSearcher.Search("ss"); + searchtimeend = DateTime.Now; + Console.WriteLine(string.Format("{0} file searched, {1}ms has spent.", mftSearchRecords.Count, searchtimeend.Subtract(searchtimestart).TotalMilliseconds)); + } + + [Test] + public void MemoryTest() + { + long oldWorkingSet = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64; + MFTSearcher.IndexAllVolumes(); + GC.Collect(); + long newWorkingSet = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64; + Console.WriteLine(string.Format("Index: {0}M", (newWorkingSet - oldWorkingSet)/(1024*1024))); + + oldWorkingSet = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64; + List mftSearchRecords = MFTSearcher.Search("q"); + newWorkingSet = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64; + Console.WriteLine(string.Format("Search: {0}M", (newWorkingSet - oldWorkingSet) / (1024 * 1024))); + Console.WriteLine(string.Format("Search results: {0}",mftSearchRecords.Count)); + } + } +} diff --git a/Wox.Test/Wox.Test.csproj b/Wox.Test/Wox.Test.csproj index a7f47b1002..c34a3ec954 100644 --- a/Wox.Test/Wox.Test.csproj +++ b/Wox.Test/Wox.Test.csproj @@ -44,6 +44,7 @@ + @@ -51,6 +52,10 @@ + + {84EA88B4-71F2-4A3D-9771-D7B0244C0D81} + Wox.Plugin.FindFile + {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3} Wox.Infrastructure diff --git a/Wox.sln b/Wox.sln index 1d805d99c7..5f2923c8bf 100644 --- a/Wox.sln +++ b/Wox.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 +VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Test", "Wox.Test\Wox.Test.csproj", "{FF742965-9A80-41A5-B042-D6C7D3A21708}" EndProject @@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Plugin.PluginManagement EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Plugin.BrowserBookmark", "Plugins\Wox.Plugin.BrowserBookmark\Wox.Plugin.BrowserBookmark.csproj", "{9B130CC5-14FB-41FF-B310-0A95B6894C37}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Plugin.FindFile", "Plugins\Wox.Plugin.FindFile\Wox.Plugin.FindFile.csproj", "{84EA88B4-71F2-4A3D-9771-D7B0244C0D81}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,6 +55,10 @@ Global {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|Any CPU.Build.0 = Release|Any CPU + {84EA88B4-71F2-4A3D-9771-D7B0244C0D81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84EA88B4-71F2-4A3D-9771-D7B0244C0D81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84EA88B4-71F2-4A3D-9771-D7B0244C0D81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84EA88B4-71F2-4A3D-9771-D7B0244C0D81}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -60,5 +66,6 @@ Global GlobalSection(NestedProjects) = preSolution {049490F0-ECD2-4148-9B39-2135EC346EBE} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} + {84EA88B4-71F2-4A3D-9771-D7B0244C0D81} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} EndGlobalSection EndGlobal diff --git a/Wox/Commands/CommandFactory.cs b/Wox/Commands/CommandFactory.cs index b248afa716..fc18439b46 100644 --- a/Wox/Commands/CommandFactory.cs +++ b/Wox/Commands/CommandFactory.cs @@ -10,21 +10,11 @@ namespace Wox.Commands { internal static class CommandFactory { - private static PluginCommand pluginCmd; - private static SystemCommand systemCmd; + private static PluginCommand pluginCmd = new PluginCommand(); + private static SystemCommand systemCmd = new SystemCommand(); public static void DispatchCommand(Query query) { - //lazy init command instance. - if (pluginCmd == null) - { - pluginCmd = new PluginCommand(); - } - if (systemCmd == null) - { - systemCmd = new SystemCommand(); - } - if (Plugins.HitThirdpartyKeyword(query)) { pluginCmd.Dispatch(query); diff --git a/Wox/Commands/SystemCommand.cs b/Wox/Commands/SystemCommand.cs index 50b38959fd..a1fc477a82 100644 --- a/Wox/Commands/SystemCommand.cs +++ b/Wox/Commands/SystemCommand.cs @@ -12,19 +12,21 @@ namespace Wox.Commands { public class SystemCommand : BaseCommand { + private IEnumerable allSytemPlugins = Plugins.AllPlugins.Where(o => o.Metadata.PluginType == PluginType.System); + public override void Dispatch(Query query) { - var allSytemPlugins = Plugins.AllPlugins.Where(o => o.Metadata.PluginType == PluginType.System); + var queryPlugins = allSytemPlugins; if (UserSettingStorage.Instance.WebSearches.Exists(o => o.ActionWord == query.ActionName && o.Enabled)) { //websearch mode - allSytemPlugins = new List() + queryPlugins = new List() { allSytemPlugins.First(o => ((ISystemPlugin)o.Plugin).ID == "565B73353DBF4806919830B9202EE3BF") }; } - foreach (PluginPair pair in allSytemPlugins) + foreach (PluginPair pair in queryPlugins) { PluginPair pair1 = pair; ThreadPool.QueueUserWorkItem(state => diff --git a/Wox/Converters/StringNullOrEmptyToVisibilityConverter.cs b/Wox/Converters/StringNullOrEmptyToVisibilityConverter.cs index d6d01f0714..f536e74735 100644 --- a/Wox/Converters/StringNullOrEmptyToVisibilityConverter.cs +++ b/Wox/Converters/StringNullOrEmptyToVisibilityConverter.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Windows; +using Wox.Plugin; namespace Wox.Converters { @@ -21,4 +23,23 @@ namespace Wox.Converters return this; } } + + public class ContextMenuEmptyToWidthConverter : ConvertorBase + { + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + List results = value as List; + return results == null || results.Count == 0 ? 0 : 17; + } + + public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return null; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } + } } \ No newline at end of file diff --git a/Wox/Images/menu.png b/Wox/Images/menu.png new file mode 100644 index 0000000000..d7395f4d9f Binary files /dev/null and b/Wox/Images/menu.png differ diff --git a/Wox/Images/open.png b/Wox/Images/open.png new file mode 100644 index 0000000000..6006d12104 Binary files /dev/null and b/Wox/Images/open.png differ diff --git a/Wox/MainWindow.xaml b/Wox/MainWindow.xaml index 71ef2bfd54..448680b218 100644 --- a/Wox/MainWindow.xaml +++ b/Wox/MainWindow.xaml @@ -22,7 +22,8 @@ - + + \ No newline at end of file diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index 5c8abba11c..3ed2fbbeb0 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -140,6 +140,13 @@ namespace Wox results.ForEach(o => { o.PluginDirectory = plugin.PluginDirectory; + if (o.ContextMenu != null) + { + o.ContextMenu.ForEach(t => + { + t.PluginDirectory = plugin.PluginDirectory; + }); + } o.OriginQuery = query; }); OnUpdateResultView(results); @@ -147,7 +154,6 @@ namespace Wox #endregion - public MainWindow() { InitializeComponent(); @@ -159,7 +165,9 @@ namespace Wox progressBar.ToolTip = toolTip; InitialTray(); - resultCtrl.OnMouseClickItem += AcceptSelect; + pnlResult.LeftMouseClickEvent += SelectResult; + pnlContextMenu.LeftMouseClickEvent += SelectResult; + pnlResult.RightMouseClickEvent += pnlResult_RightMouseClickEvent; ThreadPool.SetMaxThreads(30, 10); try @@ -176,7 +184,15 @@ namespace Wox globalHotkey.hookedKeyboardCallback += KListener_hookedKeyboardCallback; - this.Closing += MainWindow_Closing; + Closing += MainWindow_Closing; + //since MainWIndow implement IPublicAPI, so we need to finish ctor MainWindow object before + //PublicAPI invoke in plugin init methods. E.g FolderPlugin + ThreadPool.QueueUserWorkItem(o => Plugins.Init()); + } + + void pnlResult_RightMouseClickEvent(Result result) + { + ShowContextMenu(result); } void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) @@ -204,8 +220,6 @@ namespace Wox Top = UserSettingStorage.Instance.WindowTop; } - Plugins.Init(); - InitProgressbarAnimation(); //only works for win7+ @@ -299,21 +313,22 @@ namespace Wox lastQuery = tbQuery.Text; toolTip.IsOpen = false; - resultCtrl.Dirty = true; + pnlResult.Dirty = true; Dispatcher.DelayInvoke("UpdateSearch", o => { Dispatcher.DelayInvoke("ClearResults", i => { - // first try to use clear method inside resultCtrl, which is more closer to the add new results + // first try to use clear method inside pnlResult, which is more closer to the add new results // and this will not bring splash issues.After waiting 30ms, if there still no results added, we // must clear the result. otherwise, it will be confused why the query changed, but the results // didn't. - if (resultCtrl.Dirty) resultCtrl.Clear(); + if (pnlResult.Dirty) pnlResult.Clear(); }, TimeSpan.FromMilliseconds(100), null); queryHasReturn = false; var q = new Query(lastQuery); CommandFactory.DispatchCommand(q); + BackToResultMode(); if (Plugins.HitThirdpartyKeyword(q)) { Dispatcher.DelayInvoke("ShowProgressbar", originQuery => @@ -327,6 +342,12 @@ namespace Wox }, TimeSpan.FromMilliseconds(ShouldNotDelayQuery ? 0 : 150)); } + private void BackToResultMode() + { + pnlResult.Visibility = Visibility.Visible; + pnlContextMenu.Visibility = Visibility.Collapsed; + } + private bool ShouldNotDelayQuery { get @@ -426,7 +447,7 @@ namespace Wox ShowWox(false); if (!tbQuery.Text.StartsWith(">")) { - resultCtrl.Clear(); + pnlResult.Clear(); ChangeQuery(">"); } tbQuery.CaretIndex = tbQuery.Text.Length; @@ -436,7 +457,7 @@ namespace Wox private void updateCmdMode() { - var currentSelectedItem = resultCtrl.GetActiveResult(); + var currentSelectedItem = pnlResult.GetActiveResult(); if (currentSelectedItem != null) { ignoreTextChange = true; @@ -452,7 +473,14 @@ namespace Wox switch (key) { case Key.Escape: - HideWox(); + if (IsInContextMenuMode) + { + BackToResultMode(); + } + else + { + HideWox(); + } e.Handled = true; break; @@ -479,14 +507,14 @@ namespace Wox break; case Key.PageDown: - resultCtrl.SelectNextPage(); + pnlResult.SelectNextPage(); if (IsCMDMode) updateCmdMode(); toolTip.IsOpen = false; e.Handled = true; break; case Key.PageUp: - resultCtrl.SelectPrevPage(); + pnlResult.SelectPrevPage(); if (IsCMDMode) updateCmdMode(); toolTip.IsOpen = false; e.Handled = true; @@ -508,29 +536,68 @@ namespace Wox break; case Key.Enter: - AcceptSelect(resultCtrl.GetActiveResult()); + Result activeResult = GetActiveResult(); + if (globalHotkey.CheckModifiers().ShiftPressed) + { + ShowContextMenu(activeResult); + } + else + { + SelectResult(activeResult); + } e.Handled = true; break; } } + private bool IsInContextMenuMode + { + get { return pnlContextMenu.Visibility == Visibility.Visible; } + } + + private Result GetActiveResult() + { + if (IsInContextMenuMode) + { + return pnlContextMenu.GetActiveResult(); + } + else + { + return pnlResult.GetActiveResult(); + } + } + private void SelectPrevItem() { - resultCtrl.SelectPrev(); - if (IsCMDMode) updateCmdMode(); + if (IsInContextMenuMode) + { + pnlContextMenu.SelectPrev(); + } + else + { + pnlResult.SelectPrev(); + if (IsCMDMode) updateCmdMode(); + } toolTip.IsOpen = false; } private void SelectNextItem() { - resultCtrl.SelectNext(); - if (IsCMDMode) updateCmdMode(); + if (IsInContextMenuMode) + { + pnlContextMenu.SelectNext(); + } + else + { + pnlResult.SelectNext(); + if (IsCMDMode) updateCmdMode(); + } toolTip.IsOpen = false; } - private void AcceptSelect(Result result) + private void SelectResult(Result result) { - if (!resultCtrl.Dirty && result != null) + if (result != null) { if (result.Action != null) { @@ -562,7 +629,20 @@ namespace Wox if (o.AutoAjustScore) o.Score += UserSelectedRecordStorage.Instance.GetSelectedCount(o); }); List l = list.Where(o => o.OriginQuery != null && o.OriginQuery.RawQuery == lastQuery).ToList(); - Dispatcher.Invoke(new Action(() => resultCtrl.AddResults(l))); + Dispatcher.Invoke(new Action(() => + pnlResult.AddResults(l)) + ); + } + } + + private void ShowContextMenu(Result result) + { + if (result.ContextMenu != null && result.ContextMenu.Count > 0) + { + pnlContextMenu.Clear(); + pnlContextMenu.AddResults(result.ContextMenu); + pnlContextMenu.Visibility = Visibility.Visible; + pnlResult.Visibility = Visibility.Collapsed; } } @@ -604,14 +684,14 @@ namespace Wox this.Opacity = this.AllowsTransparency ? UserSettingStorage.Instance.Opacity : 1; } - public bool ShellRun(string cmd) + public bool ShellRun(string cmd, bool runAsAdministrator = false) { try { if (string.IsNullOrEmpty(cmd)) throw new ArgumentNullException(); - Wox.Infrastructure.WindowsShellRun.Start(cmd); + WindowsShellRun.Start(cmd, runAsAdministrator); return true; } catch (Exception ex) diff --git a/Wox/Properties/AssemblyInfo.cs b/Wox/Properties/AssemblyInfo.cs index 85f277ce51..0d694b4f83 100644 --- a/Wox/Properties/AssemblyInfo.cs +++ b/Wox/Properties/AssemblyInfo.cs @@ -1,9 +1,12 @@ -using System.Reflection; +using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 [assembly: AssemblyTitle("Wox")] [assembly: AssemblyDescription("https://github.com/qianlifeng/Wox")] [assembly: AssemblyConfiguration("")] @@ -12,10 +15,41 @@ using System.Windows; [assembly: AssemblyCopyright("The MIT License (MIT)")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 [assembly: ComVisible(false)] + +//若要开始生成可本地化的应用程序,请在 +// 中的 .csproj 文件中 +//设置 CultureYouAreCodingWith。例如,如果您在源文件中 +//使用的是美国英语,请将 设置为 en-US。然后取消 +//对以下 NeutralResourceLanguage 特性的注释。更新 +//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + [assembly: ThemeInfo( - ResourceDictionaryLocation.None, - ResourceDictionaryLocation.SourceAssembly + ResourceDictionaryLocation.None, //主题特定资源词典所处位置 + //(在页面或应用程序资源词典中 + // 未找到某个资源的情况下使用) + ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 + //(在页面、应用程序或任何主题特定资源词典中 + // 未找到某个资源的情况下使用) )] -[assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.*")] + + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Wox/ResultPanel.xaml b/Wox/ResultPanel.xaml index 03927ce29d..a156d6fc18 100644 --- a/Wox/ResultPanel.xaml +++ b/Wox/ResultPanel.xaml @@ -7,7 +7,7 @@ mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100"> - + @@ -17,13 +17,14 @@ - + + @@ -32,14 +33,16 @@ - + + + diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index 690a48de69..736d5d262c 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -14,11 +14,18 @@ namespace Wox { public partial class ResultPanel : UserControl { - public event Action OnMouseClickItem; + public event Action LeftMouseClickEvent; + public event Action RightMouseClickEvent; - protected virtual void OnOnMouseClickItem(Result result) + protected virtual void OnRightMouseClick(Result result) { - Action handler = OnMouseClickItem; + Action handler = RightMouseClickEvent; + if (handler != null) handler(result); + } + + protected virtual void OnLeftMouseClick(Result result) + { + Action handler = LeftMouseClickEvent; if (handler != null) handler(result); } @@ -133,9 +140,13 @@ namespace Wox private void LbResults_OnPreviewMouseDown(object sender, MouseButtonEventArgs e) { var item = ItemsControl.ContainerFromElement(lbResults, e.OriginalSource as DependencyObject) as ListBoxItem; - if (item != null) + if (item != null && e.ChangedButton == MouseButton.Left) { - OnOnMouseClickItem(item.DataContext as Result); + OnLeftMouseClick(item.DataContext as Result); + } + if (item != null && e.ChangedButton == MouseButton.Right) + { + OnRightMouseClick(item.DataContext as Result); } } diff --git a/Wox/app.manifest b/Wox/app.manifest new file mode 100644 index 0000000000..1eb9c6c494 --- /dev/null +++ b/Wox/app.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + +